Skip to content

feat(engine): add pull request review command with full-diff parsing - #52

Merged
lbildzinkas merged 5 commits into
masterfrom
fm/second-look-engine-reads-pr
Oct 1, 2026
Merged

lbildzinkas merged 5 commits into
masterfrom
fm/second-look-engine-reads-pr

Conversation

@lbildzinkas

@lbildzinkas lbildzinkas commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Intent

This is the first change of the v1 milestone: it turns the repository from a design-only project into a working TypeScript codebase and gives the engine its first command. The project is a VS Code companion for human pull request review; its glossary is CONTEXT.md and its decisions are recorded in docs/adr/0001-0006. ADR 0005 sets the engine and the extension in TypeScript, with the engine a separate local process behind a language-neutral protocol. This change implements the v1 step-0 ticket: #17.

What to build

Sets up the repository as a TypeScript project with an engine package and an extension package, strict type checks, lint, unit tests, and GitHub Actions CI on every pull request. The engine gets a review command: given a GitHub pull request URL it fetches the metadata, the full description (never truncated) and the full diff (from the diff media type, so a large lockfile keeps its patch), parses the diff into files and hunks, and prints a typed, versioned review result as JSON with one part per file and no importance yet.

How to try it by hand: give the engine a GitHub login (for example a token from the GitHub CLI in the environment) and run the review command on any public pull request. Compare the file list and line counts with the GitHub page; try a pull request whose lockfile is too large for GitHub to show inline.

Acceptance criteria

  • One command installs, type-checks, lints and tests the whole repository; CI runs the same on every pull request and on master.
  • The review command prints a JSON review result for a pull request URL; the result types are versioned and shared by the engine and the extension.
  • The full diff is used even when the REST file list omits a patch; renames, deletions, binary files (added, deleted and modified), copies, quoted non-ASCII paths, mode changes and missing final newlines parse correctly.
  • The description is kept in full, never truncated.
  • A coverage validator proves every changed line belongs to exactly one part.
  • The GitHub token is passed in by the caller and never written to disk or logs.
  • Tests: the diff parser against recorded fixtures (rename, copy, quoted path, binary, no final newline, large file); the GitHub client against recorded responses; no live network in tests.

What Changed

  • Set up the repository as a TypeScript npm workspace with strict type-checking, ESLint, Vitest unit tests, and a GitHub Actions workflow that runs the single npm run check command on every pull request and on master.
  • Add the packages/engine review command: given a pull request URL and a caller-supplied token, it fetches the full PR metadata (description never truncated) and the complete diff via the diff media type (so oversized files like lockfiles keep their patch), parses renames, deletions, binary files, mode changes, and missing final newlines into files and hunks, and prints a typed, versioned ReviewResult JSON with one part per changed file after a coverage validator proves every changed line belongs to exactly one part.
  • Add packages/extension guards that validate and parse the engine's JSON output against the shared, versioned protocol types, with fixture-backed tests for the diff parser, coverage validator, GitHub client, CLI, and extension reader that never touch the network.

Risk Assessment

🚨 High: The core review command crashes on any pull request that adds or deletes a binary file — a very common shape — which directly violates the acceptance criterion that binary files parse correctly, so the change needs that fix (or an explicit decision) before merge; the remaining findings (broken documented npx entry point, beyond-scope diff shapes, type-check and redundancy nits) are bounded follow-ups.

Testing

Drove the real built engine CLI live against four public GitHub PRs with the caller-supplied token, exactly as the intent's manual trial prescribes: file lists, per-file line counts, totals, and full untruncated descriptions match GitHub's REST data in every case, including the lockfile PR whose patch the REST file list omits. Parser acceptance shapes (rename, copy, binary add/delete/modify, mode change, missing final newline, quoted non-ASCII path, multi-digit hunk counts, header-shaped body lines) were generated with real git and verified against git's own numstat accounting on identical diff text. Token secrecy held under three adversarial error paths with no disk writes. Build, full type-check (src + test code), and the 54 unit tests pass; the lint and CI legs of 'npm run check' were not run because this phase forbids running linters and CI requires a push owned by the outer executor. No UI surface exists in this change, so evidence is CLI transcripts and JSON outputs rather than screenshots.

  • Live validation: ✅ go - 9 of 10 scenarios driven live against the product
Scenario Result Live Evidence
A reviewer runs the review command on a real public pull request and the printed file list and line counts match the GitHub page ✅ pass live live-pr16-review.json + live-cli-vs-github-summary.txt (8 parts, +146/-2, per-file counts identical to gh api pulls/16/files)
A pull request whose lockfile is too large for GitHub to show inline still yields the complete lockfile part (diff media type used even when the REST file list omits the patch) ✅ pass live live-lockfile-pr-review.json + rest-omits-lockfile-patch-proof.txt (npm/cli#9734: REST row has NO patch; engine parsed package-lock.json +7405/-8035 in 61 hunks; all 18 files, totals +7422/-8052, matc…
The pull request description is kept in full, never truncated ✅ pass live live-pr16-review.json (981 chars) and live-lockfile-pr-review.json (20850 chars) — lengths identical to the REST body for both PRs
The review command prints a typed, versioned JSON result (version field, one part per file, hunks and change kinds) consumable by the extension's shared types ✅ pass live Every live output carries version:1 with parts[]/hunks[]; packages/extension/test/review-result.test.ts (9 tests) passed in the vitest run
Real git diff shapes parse correctly: renames (pure and with edits), copies, added/deleted/modified binaries, mode changes, missing final newlines, C-quoted non-ASCII paths, multi-digit hunk counts, a… ✅ pass live real-git-shapes-parser-transcript.txt — built engine parseDiff driven on git-generated diffs; every part's counts agree with 'git apply --numstat' on the identical diff text
Live GitHub diff endpoints produce correct change kinds: added binary files parse as binary additions and renamed files carry previousPath ✅ pass live live-binary-pr-review.json (Hotters_hackaton#12: Logo.png addition isBinary:true) and live-rename-pr-review.json (zustand#1305: 30 renames with previousPath, all 35 files match paginated REST)
Adversarial: the GitHub token passed in by the caller never appears in any output or on disk, across error paths (bad credentials via env, bad credentials via --token flag, malformed URL) ✅ pass live token-secrecy-checks.txt — 0 sentinel occurrences in every output; real token absent from all evidence files; git status clean after all runs (no files written)
Adversarial: the coverage validator proves every changed line belongs to exactly one part by rejecting a parse that drops a part ✅ pass live coverage-guard-adversarial.txt — intact parse ok; parse with a dropped part rejected with a named problem
The repository's validation legs pass: build, whole-repository type-check including test code, and the unit test suite ✅ pass live npm run build clean; npx tsc -p tsconfig.test.json clean; npx vitest run 54/54
CI runs the same check on every pull request and on master ⏸️ untested no Driving GitHub Actions requires pushing the branch and waiting on the remote runner, which belongs to the outer executor's push/CI phases; local lint was not run because this phase forbids running lin…
Evidence: Live CLI vs GitHub REST summary (4 PRs)
Second Look engine — live CLI vs GitHub REST comparisons (all runs: node packages/engine/dist/main.js review <url>, token via GITHUB_TOKEN env)

=== PR lbildzinkas/second-look#16 (real public PR, docs) ===
engine: version 1 | parts 8 | totals +146 -2 | description chars 981
rest:   changed_files 8 | totals +146 -2 | body 981 chars   -> identical

=== PR npm/cli#9734 (lockfile too large for GitHub to show inline) ===
engine: version 1 | parts 18 | totals +7422 -8052 | description chars 20850
engine: package-lock.json modification +7405 -8035 hunks 61 (REST files list has NO patch field for this file)
rest:   changed_files 18 | totals +7422 -8052 | body 20850 chars | package-lock.json +7405 -8035, no patch -> identical
per-file cross-check: ALL 18 FILES + COUNTS MATCH REST (run of this session)

=== PR AjAzE-dev/Hotters_hackaton#12 (added binary files) ===
engine: test-app/public/Logo.png addition binary:true +0 -0 hunks:0
engine: test-app/public/matchpoint-mark.png addition binary:true +0 -0 hunks:0
engine: test-app/src/main.ts modification binary:false +4 -2 hunks:1
engine: test-app/src/style.css modification binary:false +8 -4 hunks:1
rest:   test-app/public/Logo.png added 0/0; matchpoint-mark.png added 0/0; main.ts +4 -2; style.css +8 -4 -> identical

=== PR pmndrs/zustand#1305 (30 renames incl. binary renames, deletions) ===
engine: version 1 | parts 35 | renames 30 | example: examples/demo/yarn.lock rename previousPath=examples/yarn.lock +73 -183 (REST: renamed, 73/183)
rest:   35 files, 30 renamed (REST lists previous_filename) -> per-file diff of all 35: ALL FILES, RENAME FLAGS AND COUNTS MATCH REST
Evidence: Full review-result JSON for second-look#16 (typed, versioned, 8 parts)
{
  "version": 1,
  "pullRequest": {
    "url": "https://github.com/lbildzinkas/second-look/pull/16",
    "number": 16,
    "title": "Add design glossary and decision records",
    "author": "lbildzinkas",
    "description": "## What this adds\n\nThis documentation-only change records the project's completed design in the repository.\n\n- **[CONTEXT.md](CONTEXT.md)** — the project glossary: the shared words for the review companion (reviewer, companion, story, part, importance, signal, claim, verdict, and more), each with the terms to avoid in its place.\n- **[docs/adr/](docs/adr/)** — six numbered decision records: any-language support (`0001`), reviewer-sent comments (`0002`), library source only on request (`0003`), driving installed agent CLIs (`0004`), TypeScript for the engine and extension (`0005`), and no prompt without its evaluation (`0006`).\n- **README.md** — extended with a short project description, the current status (design complete, not yet usable), and links to both.\n\nAll relative links were checked to resolve in the repository.\n\nCloses #1\n\n## Follow-ups (out of scope here)\n\n- Implementation code and CI\n- Issue templates\n- Agent instruction files and any further documentation\n",
    "base": "master",
    "head": "fm/second-look-design-docs"
  },
  "parts": [
    {
      "path": "CONTEXT.md",
      "changeKind": "addition",
      "isBinary": false,
      "oldMissingFinalNewline": false,
      "newMissingFinalNewline": false,
      "hunks": [
        {
          "oldStart": 0,
          "oldLines": 0,
          "newStart": 1,
          "newLines": 85,
          "lines": [
            {
              "kind": "addition",
              "newLineNumber": 1,
              "text": "# Second Look glossary"
            },
            {
              "kind": "addition",
              "newLineNumber": 2,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 3,
              "text": "An editor add-on that helps a human review a pull request faster and better: it orders the change by what matters, checks claims against real code, and lets the reviewer comment back to GitHub."
            },
            {
              "kind": "addition",
              "newLineNumber": 4,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 5,
              "text": "## Language"
            },
            {
              "kind": "addition",
              "newLineNumber": 6,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 7,
              "text": "### People and the tool"
            },
            {
              "kind": "addition",
              "newLineNumber": 8,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 9,
              "text": "**Reviewer**:"
            },
            {
              "kind": "addition",
              "newLineNumber": 10,
              "text": "The human reviewing a pull request with the companion's help."
            },
            {
              "kind": "addition",
              "newLineNumber": 11,
              "text": "_Avoid_: User, developer"
            },
            {
              "kind": "addition",
              "newLineNumber": 12,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 13,
              "text": "**Companion**:"
            },
            {
              "kind": "addition",
              "newLineNumber": 14,
              "text": "The review tool itself, which serves the reviewer and never reviews or posts in their place."
            },
            {
              "kind": "addition",
              "newLineNumber": 15,
              "text": "_Avoid_: Bot, review bot, assistant"
            },
            {
              "kind": "addition",
              "newLineNumber": 16,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 17,
              "text": "### Reading the change"
            },
            {
              "kind": "addition",
              "newLineNumber": 18,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 19,
              "text": "**Story**:"
            },
            {
              "kind": "addition",
              "newLineNumber": 20,
              "text": "A few sentences at the top of a review that tell what the change does, in the order the parts should be read."
            },
            {
              "kind": "addition",
              "newLineNumber": 21,
              "text": "_Avoid_: Walkthrough, tour, summary"
            },
            {
              "kind": "addition",
              "newLineNumber": 22,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 23,
              "text": "**Part**:"
            },
            {
              "kind": "addition",
              "newLineNumber": 24,
              "text": "A named group of related edits; every changed line belongs to exactly one part."
            },
            {
              "kind": "addition",
              "newLineNumber": 25,
              "text": "_Avoid_: Chunk, section, group, file"
            },
            {
              "kind": "addition",
              "newLineNumber": 26,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 27,
              "text": "**Importance**:"
            },
            {
              "kind": "addition",
              "newLineNumber": 28,
              "text": "The level a part is given for review: **must review**, **worth reviewing**, or **context**, always with a one-line reason."
            },
            {
              "kind": "addition",
              "newLineNumber": 29,
              "text": "_Avoid_: Priority, severity, critical"
            },
            {
              "kind": "addition",
              "newLineNumber": 30,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 31,
              "text": "**Signal**:"
            },
            {
              "kind": "addition",
              "newLineNumber": 32,
              "text": "A plain, model-free fact about a part (for example new versus changed code, or how much other code depends on it) that ranking must cite."
            },
            {
              "kind": "addition",
              "newLineNumber": 33,
              "text": "_Avoid_: Heuristic, feature"
            },
            {
              "kind": "addition",
              "newLineNumber": 34,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 35,
              "text": "**Noise**:"
            },
            {
              "kind": "addition",
              "newLineNumber": 36,
              "text": "A part whose changes need no careful reading, such as a lockfile or a generated file; it is **confirmed** when a check proved it and **claimed** otherwise."
            },
            {
              "kind": "addition",
              "newLineNumber": 37,
              "text": "_Avoid_: Boilerplate, trivial change"
            },
            {
              "kind": "addition",
              "newLineNumber": 38,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 39,
              "text": "### Checking claims"
            },
            {
              "kind": "addition",
              "newLineNumber": 40,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 41,
              "text": "**Claim**:"
            },
            {
              "kind": "addition",
              "newLineNumber": 42,
              "text": "A statement about how the code or a library behaves, 

... [15882 bytes truncated] ...

     "changeKind": "addition",
      "isBinary": false,
      "oldMissingFinalNewline": false,
      "newMissingFinalNewline": false,
      "hunks": [
        {
          "oldStart": 0,
          "oldLines": 0,
          "newStart": 1,
          "newLines": 13,
          "lines": [
            {
              "kind": "addition",
              "newLineNumber": 1,
              "text": "# Drive the reviewer's installed agent CLIs"
            },
            {
              "kind": "addition",
              "newLineNumber": 2,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 3,
              "text": "The companion does its model work by calling the coding agents the reviewer already has installed (Claude Code, Pi, Codex) non-interactively, with the companion's own prompts and skills, behind one adapter interface that probes each tool's version for what it supports. It does not call model APIs directly or embed an agent SDK, so it reuses agents that are already good, runs on the reviewer's own subscriptions, and never handles tokens."
            },
            {
              "kind": "addition",
              "newLineNumber": 4,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 5,
              "text": "## Considered Options"
            },
            {
              "kind": "addition",
              "newLineNumber": 6,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 7,
              "text": "- Anthropic's Agent SDK: rejected, it steers products towards API keys and ties the companion to one vendor."
            },
            {
              "kind": "addition",
              "newLineNumber": 8,
              "text": "- The Agent Client Protocol: rejected for now, it has no structured final answer."
            },
            {
              "kind": "addition",
              "newLineNumber": 9,
              "text": "- Direct model API calls with a home-grown agent loop: rejected, it rebuilds what the agents already do well and needs keys."
            },
            {
              "kind": "addition",
              "newLineNumber": 10,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 11,
              "text": "## Consequences"
            },
            {
              "kind": "addition",
              "newLineNumber": 12,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 13,
              "text": "Anthropic's terms are unclear on third-party tools driving a user's own Claude Code; this will be disclosed in the README."
            }
          ]
        }
      ],
      "additions": 13,
      "deletions": 0,
      "newMode": "100644"
    },
    {
      "path": "docs/adr/0005-typescript-engine.md",
      "changeKind": "addition",
      "isBinary": false,
      "oldMissingFinalNewline": false,
      "newMissingFinalNewline": false,
      "hunks": [
        {
          "oldStart": 0,
          "oldLines": 0,
          "newStart": 1,
          "newLines": 12,
          "lines": [
            {
              "kind": "addition",
              "newLineNumber": 1,
              "text": "# TypeScript for the engine and the extension"
            },
            {
              "kind": "addition",
              "newLineNumber": 2,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 3,
              "text": "The engine is written in TypeScript and runs as a separate local process behind a language-neutral protocol, with the VS Code extension as a thin TypeScript client. The extension must be TypeScript anyway, so one language gives one toolchain, shared protocol types, one universal package with no per-OS builds, the official GitHub client, tree-sitter grammars for any language as WASM, and the largest pool of contributors for a VS Code tool. Performance is not the constraint: agent calls dominate review time, and the model-free pass over a pull request's diff is small."
            },
            {
              "kind": "addition",
              "newLineNumber": 4,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 5,
              "text": "## Considered Options"
            },
            {
              "kind": "addition",
              "newLineNumber": 6,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 7,
              "text": "- .NET with Native AOT: rejected; its main dividend, deep C# understanding through Roslyn, serves one language when the companion must serve any language, and AOT needs one build per OS and chip."
            },
            {
              "kind": "addition",
              "newLineNumber": 8,
              "text": "- Rust: rejected for now; a single fast binary, but speed is not the bottleneck and it splits the code base into two languages. The process boundary keeps a later Rust rewrite of a hot path possible."
            },
            {
              "kind": "addition",
              "newLineNumber": 9,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 10,
              "text": "## Consequences"
            },
            {
              "kind": "addition",
              "newLineNumber": 11,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 12,
              "text": ".NET's exact-source route needs a small reader for portable PDB files written in TypeScript, since there is no maintained library for it."
            }
          ]
        }
      ],
      "additions": 12,
      "deletions": 0,
      "newMode": "100644"
    },
    {
      "path": "docs/adr/0006-no-prompt-without-its-evaluation.md",
      "changeKind": "addition",
      "isBinary": false,
      "oldMissingFinalNewline": false,
      "newMissingFinalNewline": false,
      "hunks": [
        {
          "oldStart": 0,
          "oldLines": 0,
          "newStart": 1,
          "newLines": 7,
          "lines": [
            {
              "kind": "addition",
              "newLineNumber": 1,
              "text": "# No prompt without its evaluation"
            },
            {
              "kind": "addition",
              "newLineNumber": 2,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 3,
              "text": "The companion is an agent system with many prompts (grouping, story, ranking, finding claims, judging claims, criteria mapping, asks, draft comments), and a prompt behaves differently on each model. So every prompt lands together with its own small test set and score, prompts are versioned like code, any prompt change automatically runs a cheap evaluation subset against the last baseline, and every run is traced locally and stamped with prompt version, agent and model. Plain checks come before model judges, and any judge is itself checked against hand labels. The evaluation starts lean, as a script with a few hand-made cases, and grows one prompt at a time."
            },
            {
              "kind": "addition",
              "newLineNumber": 4,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 5,
              "text": "## Considered Options"
            },
            {
              "kind": "addition",
              "newLineNumber": 6,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 7,
              "text": "- End-to-end evaluation only: rejected, it shows that something got worse but not which prompt or model caused it."
            }
          ]
        }
      ],
      "additions": 7,
      "deletions": 0,
      "newMode": "100644"
    }
  ]
}
Evidence: Proof REST files list omits the lockfile patch for npm/cli#9734
REST files list for npm/cli#9734 (gh api repos/npm/cli/pulls/9734/files): the lockfile row has NO patch field
package-lock.json	modified	7405	8035	NO-patch	-

REST metadata:
{"additions":7422,"body_len":20850,"changed_files":18,"deletions":8052}
Evidence: Review-result JSON for the added-binary PR (Logo.png, matchpoint-mark.png)
{
  "version": 1,
  "pullRequest": {
    "url": "https://github.com/AjAzE-dev/Hotters_hackaton/pull/12",
    "number": 12,
    "title": "add logo",
    "author": "AjAzE-dev",
    "description": "",
    "base": "main",
    "head": "Xander"
  },
  "parts": [
    {
      "path": "test-app/public/Logo.png",
      "changeKind": "addition",
      "isBinary": true,
      "oldMissingFinalNewline": false,
      "newMissingFinalNewline": false,
      "hunks": [],
      "additions": 0,
      "deletions": 0,
      "newMode": "100644"
    },
    {
      "path": "test-app/public/matchpoint-mark.png",
      "changeKind": "addition",
      "isBinary": true,
      "oldMissingFinalNewline": false,
      "newMissingFinalNewline": false,
      "hunks": [],
      "additions": 0,
      "deletions": 0,
      "newMode": "100644"
    },
    {
      "path": "test-app/src/main.ts",
      "changeKind": "modification",
      "isBinary": false,
      "oldMissingFinalNewline": false,
      "newMissingFinalNewline": false,
      "hunks": [
        {
          "oldStart": 649,
          "oldLines": 9,
          "newStart": 649,
          "newLines": 11,
          "heading": "async function initDashboard() {",
          "lines": [
            {
              "kind": "context",
              "oldLineNumber": 649,
              "newLineNumber": 649,
              "text": "    <div class=\"dashboard-shell\">"
            },
            {
              "kind": "context",
              "oldLineNumber": 650,
              "newLineNumber": 650,
              "text": "      <aside class=\"chat-panel\">"
            },
            {
              "kind": "context",
              "oldLineNumber": 651,
              "newLineNumber": 651,
              "text": "        <div class=\"brand-row\">"
            },
            {
              "kind": "deletion",
              "oldLineNumber": 652,
              "text": "          <div class=\"brand-mark\">C</div>"
            },
            {
              "kind": "addition",
              "newLineNumber": 652,
              "text": "          <div class=\"brand-mark\">"
            },
            {
              "kind": "addition",
              "newLineNumber": 653,
              "text": "            <img src=\"/matchpoint-mark.png\" alt=\"Matchpoint\" />"
            },
            {
              "kind": "addition",
              "newLineNumber": 654,
              "text": "          </div>"
            },
            {
              "kind": "context",
              "oldLineNumber": 653,
              "newLineNumber": 655,
              "text": "          <div>"
            },
            {
              "kind": "deletion",
              "oldLineNumber": 654,
              "text": "            <p class=\"brand-title\">Connect</p>"
            },
            {
              "kind": "addition",
              "newLineNumber": 656,
              "text": "            <p class=\"brand-title\">Matchpoint</p>"
            },
            {
              "kind": "context",
              "oldLineNumber": 655,
              "newLineNumber": 657,
              "text": "            <span>Expertise discovery</span>"
            },
            {
              "kind": "context",
              "oldLineNumber": 656,
              "newLineNumber": 658,
              "text": "          </div>"
            },
            {
              "kind": "context",
              "oldLineNumber": 657,
              "newLineNumber": 659,
              "text": "        </div>"
            }
          ]
        }
      ],
      "additions": 4,
      "deletions": 2
    },
    {
      "path": "test-app/src/style.css",
      "changeKind": "modification",
      "isBinary": false,
      "oldMissingFinalNewline": false,
      "newMissingFinalNewline": false,
      "hunks": [
        {
          "oldStart": 84,
          "oldLines": 10,
          "newStart": 84,
          "newLines": 14,
          "heading": "button, input {",
          "lines": [
            {
              "kind": "context",
              "oldLineNumber": 84,
              "newLineNumber": 84,
              "text": "  border-radius: 12px;"
            },
            {
              "kind": "context",
              "oldLineNumber": 85,
              "newLineNumber": 85,
              "text": "  display: grid;"
            },
            {
              "kind": "context",
              "oldLineNumber": 86,
              "newLineNumber": 86,
              "text": "  place-items: center;"
            },
            {
              "kind": "deletion",
              "oldLineNumber": 87,
              "text": "  background: linear-gradient(135deg, var(--primary), #9f73ff);"
            },
            {
              "kind": "deletion",
              "oldLineNumber": 88,
              "text": "  color: white;"
            },
            {
              "kind": "deletion",
              "oldLineNumber": 89,
              "text": "  font-weight: 700;"
            },
            {
              "kind": "deletion",
              "oldLineNumber": 90,
              "text": "  box-shadow: 0 10px 24px rgba(124, 156, 255, 0.4);"
            },
            {
              "kind": "addition",
              "newLineNumber": 87,
              "text": "  flex: 0 0 38px;"
            },
            {
              "kind": "addition",
              "newLineNumber": 88,
              "text": "}"
            },
            {
              "kind": "addition",
              "newLineNumber": 89,
              "text": ""
            },
            {
              "kind": "addition",
              "newLineNumber": 90,
              "text": ".brand-mark img {"
            },
            {
              "kind": "addition",
              "newLineNumber": 91,
              "text": "  display: block;"
            },
            {
              "kind": "addition",
              "newLineNumber": 92,
              "text": "  width: 32px;"
            },
            {
              "kind": "addition",
              "newLineNumber": 93,
              "text": "  height: 32px;"
            },
            {
              "kind": "addition",
              "newLineNumber": 94,
              "text": "  object-fit: contain;"
            },
            {
              "kind": "context",
              "oldLineNumber": 91,
              "newLineNumber": 95,
              "text": "}"
            },
            {
              "kind": "context",
              "oldLineNumber": 92,
              "newLineNumber": 96,
              "text": ""
            },
            {
              "kind": "context",
              "oldLineNumber": 93,
              "newLineNumber": 97,
              "text": ".brand-title {"
            }
          ]
        }
      ],
      "additions": 8,
      "deletions": 4
    }
  ]
}
  • Evidence: Review-result JSON for the rename PR zustand#1305 (30 previousPath renames) (local file: ~/.no-mistakes/evidence/01M3WAQP6ZCNE2HGG2BZJGKVEX/live-rename-pr-review.json)
Evidence: Real-git diff shapes parsed by the built engine, cross-checked against git apply --numstat
$ node driver.mjs ... (engine dist parseDiff vs git apply --numstat on identical diff text)
A: default git diff HEAD (rename, binary add/del/mod, quoted non-ASCII path, mode change, missing final newline): 11 parts, coverage ok
  "src/added.bin" addition +0 -0 [binary]
  "src/copy-of-keep.txt" addition +5 -0
  "src/editmove.txt" addition +5 -0
  "src/existing.bin" modification +0 -0 [binary]
  "src/gone.txt" deletion +0 -1 [mode:100644->undefined]
  "src/moved.txt" rename +0 -0 [from:src/moveme.txt]
  "src/noeol.txt" modification +1 -1 [oldNoEol newNoEol]
  "src/patchlike.txt" addition +3 -0
  "src/plain.txt" modification +0 -0 [mode:100644->100755]
  "src/wide.txt" addition +40 -0
  "src/über.md" addition +1 -0
  NUMSTAT CROSS-CHECK: all 11 files agree with git

B: git diff HEAD -C --find-copies-harder (copy from/copy to headers): 11 parts, coverage ok
  "src/added.bin" addition +0 -0 [binary]
  "src/copy-of-keep.txt" copy +0 -0 [from:src/keep.txt]
  "src/editmove.txt" copy +1 -1 [from:src/keep.txt]
  "src/existing.bin" modification +0 -0 [binary]
  "src/gone.txt" deletion +0 -1 [mode:100644->undefined]
  "src/moved.txt" rename +0 -0 [from:src/moveme.txt]
  "src/noeol.txt" modification +1 -1 [oldNoEol newNoEol]
  "src/patchlike.txt" addition +3 -0
  "src/plain.txt" modification +0 -0 [mode:100644->100755]
  "src/wide.txt" addition +40 -0
  "src/über.md" addition +1 -0
  NUMSTAT CROSS-CHECK: all 11 files agree with git

C: git diff -U20 (multi-digit hunk counts @@ -1,40 +1,40 @@; body lines shaped like --- a/... and +++ b/...): 2 parts, coverage ok
  "src/patchbody.txt" modification +1 -1
  "src/wide.txt" modification +1 -1
  NUMSTAT CROSS-CHECK: all 2 files agree with git
Evidence: Adversarial coverage-validator check (dropped part rejected)
intact parts: coverage ok
after dropping one part: correctly rejected: 1 problems
first problem: {"file":"src/added.bin","description":"changed file belongs to no part"}
Evidence: Adversarial token-secrecy checks (sentinel never echoed; no disk writes)
Token-secrecy adversarial checks: sentinel token = ghp_ + SENTINEL x5 (52 chars, never a real credential)

[1] GITHUB_TOKEN=<sentinel> review <real public PR>  (GitHub rejects with 401)
second-look-engine: Bad credentials - https://docs.github.com/rest
exit=1
grep SENTINEL in that output: 0 occurrences (want 0)

[2] --token <sentinel> passed on the command line (flag path, 401)
second-look-engine: Bad credentials - https://docs.github.com/rest
exit=1
grep SENTINEL: 0 occurrences (want 0)

[3] malformed URL with sentinel in env (error echoes the bad URL; must not echo the token)
second-look-engine: not a GitHub pull request URL: https://github.com/o/r/pull/notanumber
expected the form https://github.com/{owner}/{repo}/pull/{number}
exit=1
grep SENTINEL: 0 occurrences (want 0)

[4] real gh keyring token: grep sweep across every evidence file from the live runs -> 0 occurrences (clean)
[5] engine wrote no files during any run: git status --porcelain stayed empty (nothing persisted to disk)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • 🚨 packages/engine/src/diff.ts:27 - The parser crashes on git's canonical output for ADDED and DELETED binary files. BINARY_FILES (/^Binary files a\/(.*) and b\/(.*) differ$/) requires a/ on the left and b/ on the right, but git emits Binary files /dev/null and b/shot.png differ for any newly added binary file (e.g. a screenshot added to a README) and Binary files a/old.png and /dev/null differ for a deleted one. Verified by running the built parser on both shapes: parseDiff throws unexpected line outside any hunk, so reviewPullRequest and the whole review command fail for such a pull request. This contradicts the required acceptance criterion: 'The full diff is used even when the REST file list omits a patch; renames, deletions, binary files, mode changes and missing final newlines parse correctly' — added/deleted binary files do not parse at all, and the test fixtures only cover the modified-binary and GIT-binary-patch shapes. The fix direction is mechanical and mirrors what OLD_PATH/NEW_PATH already do at lines 28-29 (accept /dev/null on either side): accept /dev/null in BINARY_FILES, set changeKind to addition/deletion accordingly, and make finishFile (diff.ts:289) derive the path from the non-null side; the affected consumer is packages/engine/src/review.ts (the review command fails entirely). Quoting the criterion for the author's decision as the intent-conformance rules require.
  • ⚠️ README.md:35 - The README's primary invocation npx second-look-engine review … does not work on a fresh clone following the README's own order. npm ci runs while the bin target packages/engine/dist/main.js is a gitignored build output that does not yet exist, so npm never creates the node_modules/.bin/second-look-engine link; npm run build afterwards creates dist/main.js but not the link, and npx then falls back to the registry, which 404s (verified: fresh clone → npm ci → build → npx --no-install second-look-engine finds no local bin and the unscoped name does not exist on npm). The documented alternative node packages/engine/dist/main.js review … (README.md:39) does work, as does re-running npm ci after a build. Remedy: make the node packages/engine/dist/main.js form the primary documented command (or commit a small bin wrapper script so the link target exists at install time).
  • ⚠️ packages/engine/src/diff.ts:20 - The parser hard-crashes on two other shapes git really emits, both verified against the built parser: (1) C-quoted paths — any filename with non-ASCII or special bytes is printed as diff --git &#34;a/\303\274ber.md&#34; &#34;b/\303\274ber.md&#34; / --- &#34;a/…&#34; (core.quotePath default), which matches neither DIFF_GIT (line 20) nor OLD_PATH/NEW_PATH (lines 28-29), so the whole review fails with unexpected line before any &#39;diff --git&#39; header; (2) copy from/copy to headers, which no branch handles (crash: unexpected line outside any hunk). The intent's manual trial is 'run the review command on any public pull request', and non-ASCII filenames are realistic, but these forms are not among the acceptance criteria's enumerated cases, so the remedy (quoted-path octal decoding plus copy handling) extends the change beyond its stated intent and needs the author's authorization rather than an automated fix. All callers are affected equally (reviewPullRequest, runCli, the future extension).
  • ℹ️ packages/engine/src/review.ts:44 - The coverage proof is wired as validateCoverage(parsed, parsed.files): parts are exactly the parsed files, so both sides of the comparison come from the same parse and the check can never fail today (it also cannot detect a parse that dropped lines, since diffLines are built from the same parse). At this milestone the invariant 'every changed line belongs to exactly one part' holds by construction because part ≡ file, and the validator itself is real and well-tested against manipulated inputs; it becomes load-bearing when later steps group several files into one part. Informational note of that tradeoff, no action needed now.
  • ℹ️ packages/engine/tsconfig.json:9 - include: [&#34;src&#34;] (same in packages/extension/tsconfig.json:10) means tsc -b never type-checks the ~800 lines of test code, and vitest's esbuild transform does not type-check either, so npm run check — which the acceptance criterion says 'type-checks … the whole repository' — leaves test files untype-checked. Remedy: add a noEmit tsconfig covering test (e.g. tsconfig.test.json wired into the build or a tsc -p step in check).
  • ⚠️ package.json:20 - Simplification: the root devDependency on @second-look/engine is a second, redundant definition of a dependency the repository already declares once — the extension package depends on it (which is what creates the workspace symlink), vitest aliases it to engine source, and tsc resolves it through the extension's own dependency. No intent requirement needs it, and it does not make the npx second-look-engine entry point reliable (see the bin-linking finding). Recommend removing it as the remedy.

🔧 Fix applied.
7 issues (2 errors, 3 warnings, 2 infos) still open:

  • 🚨 packages/engine/src/diff.ts:27 - The parser crashes on git's canonical output for ADDED and DELETED binary files. BINARY_FILES (/^Binary files a\/(.*) and b\/(.*) differ$/) requires a/ on the left and b/ on the right, but git emits Binary files /dev/null and b/shot.png differ for any newly added binary file (e.g. a screenshot added to a README) and Binary files a/old.png and /dev/null differ for a deleted one. Verified by running the built parser on both shapes: parseDiff throws unexpected line outside any hunk, so reviewPullRequest and the whole review command fail for such a pull request. This contradicts the required acceptance criterion: 'The full diff is used even when the REST file list omits a patch; renames, deletions, binary files, mode changes and missing final newlines parse correctly' — added/deleted binary files do not parse at all, and the test fixtures only cover the modified-binary and GIT-binary-patch shapes. The fix direction is mechanical and mirrors what OLD_PATH/NEW_PATH already do at lines 28-29 (accept /dev/null on either side): accept /dev/null in BINARY_FILES, set changeKind to addition/deletion accordingly, and make finishFile (diff.ts:289) derive the path from the non-null side; the affected consumer is packages/engine/src/review.ts (the review command fails entirely). Quoting the criterion for the author's decision as the intent-conformance rules require.
  • ⚠️ README.md:35 - The README's primary invocation npx second-look-engine review … does not work on a fresh clone following the README's own order. npm ci runs while the bin target packages/engine/dist/main.js is a gitignored build output that does not yet exist, so npm never creates the node_modules/.bin/second-look-engine link; npm run build afterwards creates dist/main.js but not the link, and npx then falls back to the registry, which 404s (verified: fresh clone → npm ci → build → npx --no-install second-look-engine finds no local bin and the unscoped name does not exist on npm). The documented alternative node packages/engine/dist/main.js review … (README.md:39) does work, as does re-running npm ci after a build. Remedy: make the node packages/engine/dist/main.js form the primary documented command (or commit a small bin wrapper script so the link target exists at install time).
  • ⚠️ packages/engine/src/diff.ts:20 - The parser hard-crashes on two other shapes git really emits, both verified against the built parser: (1) C-quoted paths — any filename with non-ASCII or special bytes is printed as diff --git &#34;a/\303\274ber.md&#34; &#34;b/\303\274ber.md&#34; / --- &#34;a/…&#34; (core.quotePath default), which matches neither DIFF_GIT (line 20) nor OLD_PATH/NEW_PATH (lines 28-29), so the whole review fails with unexpected line before any &#39;diff --git&#39; header; (2) copy from/copy to headers, which no branch handles (crash: unexpected line outside any hunk). The intent's manual trial is 'run the review command on any public pull request', and non-ASCII filenames are realistic, but these forms are not among the acceptance criteria's enumerated cases, so the remedy (quoted-path octal decoding plus copy handling) extends the change beyond its stated intent and needs the author's authorization rather than an automated fix. All callers are affected equally (reviewPullRequest, runCli, the future extension).
  • ℹ️ packages/engine/src/review.ts:44 - The coverage proof is wired as validateCoverage(parsed, parsed.files): parts are exactly the parsed files, so both sides of the comparison come from the same parse and the check can never fail today (it also cannot detect a parse that dropped lines, since diffLines are built from the same parse). At this milestone the invariant 'every changed line belongs to exactly one part' holds by construction because part ≡ file, and the validator itself is real and well-tested against manipulated inputs; it becomes load-bearing when later steps group several files into one part. Informational note of that tradeoff, no action needed now.
  • ℹ️ packages/engine/tsconfig.json:9 - include: [&#34;src&#34;] (same in packages/extension/tsconfig.json:10) means tsc -b never type-checks the ~800 lines of test code, and vitest's esbuild transform does not type-check either, so npm run check — which the acceptance criterion says 'type-checks … the whole repository' — leaves test files untype-checked. Remedy: add a noEmit tsconfig covering test (e.g. tsconfig.test.json wired into the build or a tsc -p step in check).
  • 🚨 packages/engine/src/diff.ts:33 - The fix round (commit 05197d3) accidentally regressed HUNK_HEADER's old-side count capture from (\d+) to (\d) (exactly one digit), while the new-side count kept (\d+). Any hunk header whose old-side line count is 10 or more — e.g. @@ -1,15 +1,20 @@, which any PR deleting ~10 lines or with a 10-line hunk context produces — fails to match, falls through every branch, and throws unexpected line outside any hunk, so parseDiff, reviewPullRequest and the whole review command fail. Verified against the built parser: @@ -1,9 +1,20 @@ parses, @@ -1,10 +1,20 @@ and @@ -1,99 +1,1 @@ throw. Every fixture's old-side count is a single digit (@@ -4388,9 +4388,2507 @@ in large-lock.diff and pull-42.diff, @@ -12,3 +12,5 @@, etc.), including the four fixtures the fix round itself added, so the test suite cannot catch this — this is collateral damage in code adjacent to the F1/F3 fixes, not part of either finding. Remedy: restore (\d+) on the old-side count (and add a fixture with a multi-digit old count). No sibling sites: OLD_MODE/NEW_MODE/NEW_FILE_MODE/DELETED_FILE_MODE (diff.ts:22-25) all already use (\d+); this is the only single-digit capture. This defect was introduced by the pipeline's fix round.
  • ⚠️ packages/engine/src/diff.ts:36 - ESCAPED_BYTES, added by the fix round for decodePathToken, covers only &#34;, \\, n, r, t, but git's quote_c_style also emits \a (0x07), \b (0x08), \v (0x0b), \f (0x0c) for control bytes in paths. For those, decodePathToken hits the non-octal branch and returns undefined (diff.ts:73-74), so the header carrying the quoted path falls through and parseDiff throws — the review command crashes (fail-loud, not silently wrong) on any PR with a control character in a filename, which is legal on macOS/Linux and realistic for machine-generated paths. Remedy: add the four mappings (a: 0x07, b: 0x08, v: 0x0b, f: 0x0c) to ESCAPED_BYTES. Incompleteness introduced by the fix round inside the decoding machinery the user authorized for F3; the octal-escape path itself reassembles UTF-8 correctly.

🔧 Fix applied.
3 issues (1 warning, 2 infos) still open:

  • ⚠️ README.md:35 - The README's primary invocation npx second-look-engine review … does not work on a fresh clone following the README's own order. npm ci runs while the bin target packages/engine/dist/main.js is a gitignored build output that does not yet exist, so npm never creates the node_modules/.bin/second-look-engine link; npm run build afterwards creates dist/main.js but not the link, and npx then falls back to the registry, which 404s (verified: fresh clone → npm ci → build → npx --no-install second-look-engine finds no local bin and the unscoped name does not exist on npm). The documented alternative node packages/engine/dist/main.js review … (README.md:39) does work, as does re-running npm ci after a build. Remedy: make the node packages/engine/dist/main.js form the primary documented command (or commit a small bin wrapper script so the link target exists at install time).
  • ℹ️ packages/engine/src/review.ts:44 - The coverage proof is wired as validateCoverage(parsed, parsed.files): parts are exactly the parsed files, so both sides of the comparison come from the same parse and the check can never fail today (it also cannot detect a parse that dropped lines, since diffLines are built from the same parse). At this milestone the invariant 'every changed line belongs to exactly one part' holds by construction because part ≡ file, and the validator itself is real and well-tested against manipulated inputs; it becomes load-bearing when later steps group several files into one part. Informational note of that tradeoff, no action needed now.
  • ℹ️ packages/engine/tsconfig.json:9 - include: [&#34;src&#34;] (same in packages/extension/tsconfig.json:10) means tsc -b never type-checks the ~800 lines of test code, and vitest's esbuild transform does not type-check either, so npm run check — which the acceptance criterion says 'type-checks … the whole repository' — leaves test files untype-checked. Remedy: add a noEmit tsconfig covering test (e.g. tsconfig.test.json wired into the build or a tsc -p step in check).

🔧 Fix applied.
2 issues (1 warning, 1 info) still open:

  • ℹ️ packages/engine/src/review.ts:44 - The coverage proof is wired as validateCoverage(parsed, parsed.files): parts are exactly the parsed files, so both sides of the comparison come from the same parse and the check can never fail today (it also cannot detect a parse that dropped lines, since diffLines are built from the same parse). At this milestone the invariant 'every changed line belongs to exactly one part' holds by construction because part ≡ file, and the validator itself is real and well-tested against manipulated inputs; it becomes load-bearing when later steps group several files into one part. Informational note of that tradeoff, no action needed now.
  • ⚠️ packages/engine/src/diff.ts:380 - The ---/+++ header matchers run even while the current hunk still expects body lines, so a deletion whose content begins -- a/ (realistic as a Lua comment or vendored-patch text naming a path) or -- /dev/null is misread as the --- header. Verified against the built parser: mid-hunk it resets the hunk and the next body line throws unexpected line outside any hunk; when it is the final body line the deletion is silently dropped (parse returns deletions=0, the line absent from hunk.lines) and the part's addition/deletion counts are wrong — a wrong result without an error from a tool whose core job is exact line accounting, and the coverage validator cannot catch it (both sides come from the same parse, cf. previously-noted F4). Sibling site with the same collision shape: the NEW_PATH matcher at packages/engine/src/diff.ts:391 (an addition of ++ b/… content parses as +++ b/…). No other matcher can collide: hunk body lines begin with +, -, space or \, and only -+-- …→--- and ++++ …→+++ reproduce header shapes; DIFF_GIT, HUNK_HEADER, mode/rename/copy/index/Binary-files lines all require leading characters a body prefix cannot produce. Remedy: mirror the existing body gate at packages/engine/src/diff.ts:421 — while oldRemaining &gt; 0 || newRemaining &gt; 0, consume the line as hunk body before the OLD_PATH/NEW_PATH matchers; git never emits these headers inside a hunk body, so well-formed diffs (every recorded fixture) parse identically. This defect is in the author's original parser (commit 2fc5e20), not introduced by the fix rounds; no prior round reported it.

🔧 Fix applied.
1 info still open:

  • ℹ️ packages/engine/src/review.ts:44 - The coverage proof is wired as validateCoverage(parsed, parsed.files): parts are exactly the parsed files, so both sides of the comparison come from the same parse and the check can never fail today (it also cannot detect a parse that dropped lines, since diffLines are built from the same parse). At this milestone the invariant 'every changed line belongs to exactly one part' holds by construction because part ≡ file, and the validator itself is real and well-tested against manipulated inputs; it becomes load-bearing when later steps group several files into one part. Informational note of that tradeoff, no action needed now.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 9 of 10 scenarios driven live against the product
Scenario Result Live Evidence
A reviewer runs the review command on a real public pull request and the printed file list and line counts match the GitHub page ✅ pass live live-pr16-review.json + live-cli-vs-github-summary.txt (8 parts, +146/-2, per-file counts identical to gh api pulls/16/files)
A pull request whose lockfile is too large for GitHub to show inline still yields the complete lockfile part (diff media type used even when the REST file list omits the patch) ✅ pass live live-lockfile-pr-review.json + rest-omits-lockfile-patch-proof.txt (npm/cli#9734: REST row has NO patch; engine parsed package-lock.json +7405/-8035 in 61 hunks; all 18 files, totals +7422/-8052, matc…
The pull request description is kept in full, never truncated ✅ pass live live-pr16-review.json (981 chars) and live-lockfile-pr-review.json (20850 chars) — lengths identical to the REST body for both PRs
The review command prints a typed, versioned JSON result (version field, one part per file, hunks and change kinds) consumable by the extension's shared types ✅ pass live Every live output carries version:1 with parts[]/hunks[]; packages/extension/test/review-result.test.ts (9 tests) passed in the vitest run
Real git diff shapes parse correctly: renames (pure and with edits), copies, added/deleted/modified binaries, mode changes, missing final newlines, C-quoted non-ASCII paths, multi-digit hunk counts, a… ✅ pass live real-git-shapes-parser-transcript.txt — built engine parseDiff driven on git-generated diffs; every part's counts agree with 'git apply --numstat' on the identical diff text
Live GitHub diff endpoints produce correct change kinds: added binary files parse as binary additions and renamed files carry previousPath ✅ pass live live-binary-pr-review.json (Hotters_hackaton#12: Logo.png addition isBinary:true) and live-rename-pr-review.json (zustand#1305: 30 renames with previousPath, all 35 files match paginated REST)
Adversarial: the GitHub token passed in by the caller never appears in any output or on disk, across error paths (bad credentials via env, bad credentials via --token flag, malformed URL) ✅ pass live token-secrecy-checks.txt — 0 sentinel occurrences in every output; real token absent from all evidence files; git status clean after all runs (no files written)
Adversarial: the coverage validator proves every changed line belongs to exactly one part by rejecting a parse that drops a part ✅ pass live coverage-guard-adversarial.txt — intact parse ok; parse with a dropped part rejected with a named problem
The repository's validation legs pass: build, whole-repository type-check including test code, and the unit test suite ✅ pass live npm run build clean; npx tsc -p tsconfig.test.json clean; npx vitest run 54/54
CI runs the same check on every pull request and on master ⏸️ untested no Driving GitHub Actions requires pushing the branch and waiting on the remote runner, which belongs to the outer executor's push/CI phases; local lint was not run because this phase forbids running lin…
  • npm run build (tsc -b) — clean
  • npx tsc -p tsconfig.test.json — test code type-checks
  • npx vitest run — 54/54 tests pass (diff parser fixtures, recorded-response GitHub client, no live network)
  • GITHUB_TOKEN="$(gh auth token)" node packages/engine/dist/main.js review https://github.com/lbildzinkas/second-look/pull/16 — 8 parts, +146/-2, description 981 chars, identical to REST metadata and file list
  • GITHUB_TOKEN="$(gh auth token)" node packages/engine/dist/main.js review https://github.com/npm/cli/pull/9734 — 18 parts, +7422/-8052, description 20850 chars; per-file diff against gh api pulls/9734/files (paginated): all files and counts match; package-lock.json parsed +7405/-8035 in 61 hunks despite REST omitting its patch
  • GITHUB_TOKEN="$(gh auth token)" node packages/engine/dist/main.js review https://github.com/AjAzE-dev/Hotters_hackaton/pull/12 — two added binary PNGs parsed as addition/isBinary:true
  • GITHUB_TOKEN="$(gh auth token)" node packages/engine/dist/main.js review https://github.com/pmndrs/zustand/pull/1305 — 35 parts, 30 renames with previousPath, all files/flags/counts match paginated REST
  • Disposable git repo: generated real diffs (pure rename, rename with edits, copy via -C, added/deleted/modified binary, mode change, missing final newline both sides, C-quoted non-ASCII path 'src/über.md', @@ -1,40 +1,40 @@ multi-digit counts, body lines shaped like '--- a/x'/'+++ b/x') and parsed them with packages/engine/dist parseDiff, cross-checking every part against 'git apply --numstat' on the identical diff text
  • Adversarial coverage check: validateCoverage rejects a parse with a dropped part, accepts the intact parse
  • Adversarial token checks: sentinel token via env, via --token flag, and with a malformed URL — 0 occurrences of the sentinel in any output; grep sweep confirms the real gh keyring token is absent from every evidence file; git status stayed clean (nothing written to disk)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Closes #17

Repository as a TypeScript workspace: an engine package and an extension
package, strict type checks, ESLint, Vitest, and GitHub Actions CI running
install, build, lint and tests on every pull request and on master.

The engine gets a review command: given a GitHub pull request URL it
fetches the metadata (description kept in full, never truncated) and the
full diff through the diff media type (so a large lockfile keeps its
patch), parses the diff into files and hunks — renames, deletions, binary
files, mode changes and missing final newlines included — and prints a
typed, versioned review result as JSON with one part per changed file.
A coverage validator proves every changed line belongs to exactly one
part before anything is printed.

The GitHub token is passed in by the caller (flag or environment), is
used only for the request, and is never written to disk or logs. The
extension consumes the shared, versioned protocol types and validates
the engine's JSON before use. Tests run against recorded fixtures and
responses; no test touches the network.

Closes #17
@lbildzinkas
lbildzinkas merged commit 9a84fad into master Oct 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Engine reads a pull request and prints its parts

1 participant