fix: harden greenfield + repair paths (2nd-pass review — 2×P1, 2×P2)#45
Conversation
…nd-pass review) [P1] Contract path traversal via model-controlled feature ids: feature ids are now validated kebab-case at parse (plan.ts) AND load (state.ts) via isFeatureId, so '../../README' is dropped; writeContract derives the filename from a basename with a safe fallback as defence-in-depth. [P1] Rollback left created binary/asset files behind: the prompt-facing resolver skips .svg/images/fonts, so tombstoning missed them. Added resolveScopeFilesForRollback (uncapped, ignored-dirs-only, binary-inclusive); file-snapshot uses it for the existed set + tombstone scan, while content is still captured from the text-only resolver. [P2] Greenfield --browser path now resolves against --dir, not the launcher cwd. [P2] Accept-rate undercounted multi-file reverts: the reverted event now carries a batch mutation count (review-repair + quality tally edit/create events); metrics subtract event.count (default 1); count round-trips through the ledger. Full validate green (1421 pass).
There was a problem hiding this comment.
Code Review
This pull request introduces several enhancements and bug fixes: it resolves relative --browser paths against the run directory, tracks the exact mutation count of reverted batches to improve accept-rate metric accuracy, ensures rollback operations can detect and clean up newly created binary assets, and adds strict validation for feature IDs to prevent path traversal vulnerabilities. The reviewer's feedback suggests a valuable performance optimization in file-snapshot.ts to combine the separate text and binary file traversals into a single pass, reducing disk I/O by 50% in large workspaces, which subsequently allows for cleaning up the unused textFiles helper and its associated import.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Collapse the two scope traversals into one binary-inclusive walk, classifying text vs binary in-memory via a new exported isBinaryPath. Removes the unused textFiles/existedSet helpers and the resolveScopeFiles import. Behavior unchanged (tombstone + rewrite tests green).
Why
A second-pass review of the greenfield/repair paths (shipped in 0.20.0 via #44) found four real issues — two security/correctness P1s and two P2s. This is the hardening pass before the feature is settled.
Fixes
[P1] Contract persistence trusted model-controlled feature IDs as paths.
writeContractdidjoin(dir, \${feature.id}.md`), and the planner accepted any string id — so withTSFORGE_CONTRACT=1a feature id like../../../READMEescaped the state dir (reproduced in a temp repo). Now: feature ids are validated kebab-case (isFeatureId) at **both** parse (plan.ts) and load (state.ts), so a path-like id is dropped;writeContractadditionally derives the filename from abasenamewith a safe"feature"` fallback (defence-in-depth).[P1] Rollback left newly-created binary/asset files behind.
restoreFilestombstoned throughresolveScopeFiles, which intentionally filters out.svg/images/fonts for prompt safety — so a failed repair that createdicon.svgreported "reverted" while leaving it on disk (reproduced). AddedresolveScopeFilesForRollback(uncapped, ignored-dirs-only, binary-inclusive); the snapshot's existed-set and the tombstone scan use it, while text content is still captured via the filtered resolver (writing back.text()of a binary would corrupt it).[P2] Greenfield
--browserresolved relative paths from the launcher cwd, not--dir.Normal gate checks run inside
--dir, but greenfield's in-processrenderCheckdid not. Relative--browseris now resolved againstargs.dir.[P2] Accept-rate metrics undercounted multi-file reverts.
One
revertedevent subtracted only 1, but a failed batch may roll back N edits — making/traceoptimistic. Therevertedevent now carries a batch mutationcount(review-repair + quality tallyedit/createevents during the attempt);analyzeEventssubtractsevent.count(default 1), andcountround-trips through the ledger.Verification
bun run validategreen — 1421 pass / 0 fail.