c444051bef8ca147bd3de59411cb98a933c7e8bb
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9341d8b8d3 |
test(#3334): fold the workflow-dispatch & review-lane fix-* cluster — Wave 2 (#3342)
Folds 15 tests/fix-*.test.cjs regression files (191 test() blocks) into their module's main suite, per the wave decomposition of #3315 (H3 of epic #3053). 187 blocks land in 8 existing suites (4 exact-duplicate cases dropped, documented inline); 4 blocks move via git mv into 2 new suite files with no prior coverage to merge into. Zero production behavior change. Also tightens two H1 (#3313) ratchets that the fold's own file-count reduction moved past their grace window, per the ratchets' documented dual failure mode (a stale/too-loose baseline fails exactly like a novel violation): - lint-test-file-count.allowlist.json: removes the stale "audit" entry (folding fix-2766 into tests/uat.test.cjs drops that module back to its 2-file cap). - lint-allow-test-rule-refs.ceiling.json: lowers maxFiles 314 -> 309, the real post-fold high-water mark (gsd-test's own repo-baseline test caught this — CI, not a human, found it). Two orthogonal review passes (Standards+Spec code-review, isolated security-review) found and this commit fixes two issues before push: a genuinely-distinct #2287 test case (file-absent vs. file-present- resolved) that a prior fold pass had wrongly dropped as a duplicate — restored verbatim into tests/uat.test.cjs; and a missing same-line allow-test-rule citation on the #2196 block in tests/debug-session-management.test.cjs, added for consistency with its sibling #2257 block. lint-removed-but-needed also caught two stale doc references to the now-folded-away fix-2285-claude-orchestration-wiring.test.cjs filename (docs/adr/1143-claude-orchestration-capability.md, gsd-core/references/execute-phase-response-language.md) — updated both to point at tests/claude-orchestration.test.cjs, its new home. Co-authored-by: sim <sim@local> |
||
|
|
3fac6e629f |
test(#3145): bound the installer/runtime cluster onto the process seam (#3176)
* test(#3145): bound the installer/runtime cluster onto the process seam Migrates 156 unbounded sync spawn sites across 47 files. Allowlist 120 to 73. Timeouts are sized from evidence already in the tree rather than a house default, because this wave spawns installers rather than git plumbing and an undersized bound does not catch a hang -- it manufactures CI flake, which is worse, since a flake gets re-run instead of investigated. install.test.cjs records a real spawnSync ETIMEDOUT at a 60000ms cap on a loaded bench while another lane passed the same commit in 12.7s, so full installs are bound at 120000ms against that recorded incident. Also adds an auditable escape to the guard's timeout ceiling. The 600000ms cap was set in #3143 from partial evidence, but fragment-single-edit- propagation carries a documented, load-tested 900000ms bound on a run that chains a full build plus eight generators -- the guard would have rejected a correct timeout the moment that file left the allowlist. A value above the ceiling is now permitted only with an inline allow-spawn-timeout-ceiling marker carrying a non-empty reason. It raises the ceiling; it never waives the requirement for a bound, which is asserted directly. install-shared.cjs keeps its hand-rolled assert rather than routing through throwIfFailed: its message embeds both streams, and throwIfFailed carries only a trimmed stderr. The message now also names the outcome, so a bounded timeout reads as such across its 38 importers instead of as expected null to equal 0. * test(#3145): extract class-norm timeouts and correct the build-hooks sizing A pre-PR review found 52 copies of four class-norm timeout constants across this wave. These are not per-suite fixture bindings -- they are shared facts about how long a class of subprocess takes, derived from a recorded bench incident. That norm already moved once (60000 to 120000 after a real ETIMEDOUT), and 52 copies would have drifted the next time it moved. Extracts tests/helpers/timeouts.cjs, where each norm is justified once, and converts the copies. A site that genuinely differs -- a real tsc compile, or regen:derived -- keeps its own local constant with its own justification. Also corrects a misclassification: scripts/build-hooks.js was sized as a build at 120000 in twelve places and 60000 in another, but it compiles and bundles nothing. Its own header says no bundling needed; it copies pre-built files and syntax-checks them with vm. Three different values bounded one script; now there is one. * test(#3145): fix red CI — lint self-match and a Windows chunk overrun Two failures on PR 3176. lint-allow-test-rule-refs read a RuleTester fixture as a real exemption. The fixture exists to prove an unrelated marker does NOT suppress the rule, so it carries that marker's literal text as test data. Split via concatenation, the same idiom no-unbounded-spawn-allowlist.test.cjs already uses for its own self-match problem. The explanatory comment needed the same treatment. The Windows shard 3/3 chunk was killed at its 600000ms budget. Output stopped seven minutes before the kill, so this was an overrun rather than a slow chunk: regenDerivedPropagatesSingleFragmentEditWithNoSecondSourceSurface runs regen:derived bounded at 900000ms, which is larger than the whole chunk budget, so the chunk killer always fires first and it can never complete there. Both the test and that bound predate this change; modifying the file pulled it into the Windows targeted set and exposed it. Skipped on Windows with the reason recorded; the Linux lanes cover it. The 900000 bound and its ceiling marker are unchanged -- they are correct. * test(#3145): refresh the stale test-timings cost table The Windows shard was killed at its 600000ms per-chunk budget. run-tests.cjs packs chunks by measured duration from tests/test-timings.json, and an unknown file falls back to the table's median weight -- advisory by design, but it silently underweights exactly the files that matter. Four of the failing chunk's 22 files were absent from the table, including the two heaviest: fragment-single-edit-propagation.install.test.cjs at 230s (it runs regen:derived) and agent-fragments-emission.install.test.cjs at 79s. Both were weighted as average, so the chunk's total weight read 53.68 against a budget of 60 and the packer produced a single chunk. Regenerated from a passing full-suite run, per the remedy the script itself documents. 700 to 770 entries, 70 added, 0 dropped -- verified, since gen-test-timings.cjs replaces the table wholesale rather than merging. Proven against the real packer: the same 22 files now weigh 103.91 and split into two chunks. No logic, budget, or timeout was changed; raising a budget to make a red gate pass is not a fix. --------- Co-authored-by: sim <sim@local> |
||
|
|
1d208e5af6 |
test(#3144): bound the git/worktree cluster onto the process seam (#3152)
* test(#3144): bound the git/worktree cluster onto the process seam Migrates 180 unbounded sync spawn sites across 19 files. Every previously unbounded call now carries an explicit timeout with a comment giving the number and why. The migration is not a callee swap. execSync and execFileSync throw on a non-zero exit and the seam never does, so each site was classified first: sites that rely on the throw route to gitOrThrow, and sites that already read .status to detect an EXPECTED non-zero -- an intended cherry-pick conflict, a rev-parse outside a repo driving a skip -- route to the never-throwing runGit instead, which would otherwise throw on exactly the exit being probed for. Two same-named git() helpers in worktree-cleanup.test.cjs have different return contracts, one trimmed and one raw; both are preserved rather than unified. Collapses five hand-rolled throw wrappers onto one throwIfFailed in git-fixture.cjs, which gitOrThrow now also uses so the shape cannot drift. Allowlist drops 139 to 120; BASELINE lowered to match. * test(#3144): fix pre-PR review findings Documents throwIfFailed in the CONTEXT.md glossary and CONTRIBUTING.md -- it became the shared throw mechanism without either doc naming it. Routes the sixth and seventh hand-rolled copies of the throw shape through throwIfFailed (worktree-baseref-install, worktree-safety-reap); the first consolidation missed both. Converts ci-rebase-check's 8 fixture-setup calls from unchecked runGit to gitOrThrow so a failed setup step aborts where it fails rather than surfacing later as a confusing failure against the wrong subject. Adds 12 direct unit tests for throwIfFailed, which until now was only exercised transitively. Splits verify.test.cjs's non-git grep/sed bound off GIT_TIMEOUT_MS. --------- Co-authored-by: sim <sim@local> |
||
|
|
28e486faf7 |
fix(#2608): fail closed when git add fails during commit staging (#2693)
* fix(#2608): fail closed when `git add` fails during commit staging `cmdCommit` ignored `git add` failures. #2523 had already stopped a failed path entering the commit pathspec, but skipping it silently left two bad outcomes, both reproduced against the pre-fix build: - SOME paths fail -> `{"committed":true}`. `git commit` still ran and PARTIALLY committed the subset that happened to stage, under a message describing the full requested scope. - EVERY path fails -> `{"reason":"nothing_to_commit"}`, which is not what happened and points the operator nowhere. In both cases git's original `add` stderr was discarded, so the user saw a downstream `commit_failed` / pathspec error naming an innocent file — the symptom reported in the issue from a linked worktree whose git directory was outside the managed writable root. Staging failures are now collected and the command fails closed BEFORE `git commit` runs, returning the issue's specified shape: { committed: false, hash: null, reason: "staging_failed", file: "<first failing path>", error: "<original git add stderr>", failures: [ { file, error, timed_out }, ... ] } A timeout is distinguished as `staging_timeout` (issue AC5) using the projection's SIGTERM+ETIMEDOUT signal — the same idiom worktree-safety.cts uses. The check is placed ahead of the `nothing_to_commit` branch so an all-paths-failed run reports the staging cause rather than an empty changeset. Unchanged: successful staging still commits exactly the declared scope and leaves unrelated staged files alone; an explicitly-named file that does not exist is still skipped rather than staged as a deletion (#2014/#2523), and a request where every named file is missing still reports `nothing_to_commit` — no `git add` ran, so there is no staging failure to report. Regression tests inject the failure by monkeypatching `execGit` on the projection module (per CLAUDE.md, over `chmod 0o000`, which does not fault under root and would make the tests vacuous), driven in a `node -e` child because `output()` writes via `fs.writeSync(1, …)` and cannot be captured in-process. Pre-fix, 6 of the 10 assertions fail; post-fix all pass. Closes #2608 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QT3ibz5qJuDuGqpTGRYVGf * fix(#2608): roll back the index, guard the sibling surfaces, document the new reasons Six findings from the orthogonal review of the first commit, all fixed here. 1. A `staging_failed` return left the index PARTIALLY STAGED. The paths that did stage stayed in the index with no commit made and no cleanup, so the next bare `git commit` would sweep them up — the same silent partial commit this fix exists to prevent, deferred one step. (Pre-fix the partial state at least got consumed by the incorrect commit.) The staging failure path now resets the paths it staged, matching cmdPrSubrepo's established rollback-then-error convention. The reset is scoped to what THIS call staged — paths the caller had already staged are captured up front and excluded, so a caller's own work is never destroyed — and is best-effort, since an unwritable index (the very failure being reported) cannot be reset either. 2. `cmdCommitToSubrepo` still had the identical defect: a failed `git add` was dropped silently and the function committed the subset that happened to stage, discarding git's stderr. It now fails closed per sub-repo with the same staging_failed/staging_timeout reasons and the same scoped rollback. 3. The `git rm --cached --ignore-unmatch` branch (default mode, for a planning file that no longer exists on disk) still discarded its result. It mutates the index exactly like `git add`, and `--ignore-unmatch` already makes "no such path" a success, so a non-zero exit there is a real I/O failure — now routed through the same staging-failure path. 4. `agents/gsd-executor.md` documented the commit envelope as an exhaustive three-shape enum and pattern-matched only `nothing_to_commit | commit_failed`. It is the sole consumer doc for this surface, so the new reasons are added with explicit guidance not to retry (a retry hits the same unwritable index), and the "one of three shapes" framing is corrected. 5. The default (non---files) staging path and `--amend` are now covered by tests. Both were already guarded by the first commit but unexercised. 6. The changeset framed the fix as `--files`-only; it applies to default and sub-repo commits too, and now mentions the rollback. Regenerated the agent size baseline and the 18 golden install-parity fixtures for the gsd-executor.md edit. 16 assertions across both surfaces verified against the built lib. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QT3ibz5qJuDuGqpTGRYVGf * test(#2608): update the #2523 out-of-repo contract to the new staging_failed reason The remote test run surfaced this: `#2523: out-of-repo --files path is rejected by git` asserted `reason: 'nothing_to_commit'`, and now gets `staging_failed`. This is a deliberate contract improvement, not a papered-over failure. The old reason existed only because a failed `git add` was skipped and the resulting empty `stagedPaths` fell through to the empty-changeset branch. But "nothing to commit" is not what happened — the caller named a file and git refused it — and that misreport is exactly the class of defect #2608 closes. The result now carries the offending path and git's own message ("… is outside repository at …"), which is strictly more actionable for the same condition. #2523's two substantive invariants are untouched and still asserted: no commit is created, and the index is left clean. Two assertions are ADDED (the path is named, git's message is preserved) so the richer contract is pinned rather than merely allowed. Per CONTRIBUTING, a stale-test correction rides its own commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QT3ibz5qJuDuGqpTGRYVGf * fix(#2608): compact the executor doc addition to stay under the agent LARGE cap The remote test run failed: `gsd-executor.md is 49217 bytes — exceeds the LARGE hard cap of 49152`. The file was already at 48596 (556 bytes of headroom) and the new commit-envelope documentation pushed it 65 bytes over. The cap is a red line, not a budget to raise, so the addition is compacted rather than the cap moved: four lines instead of eight, keeping the load-bearing facts — the two new reasons, that nothing was committed and the index was rolled back, that `file` + `error` should be surfaced, and that retrying is wrong because a retry hits the same cause. Dropped only the restatement of the linked-worktree example (already in the changeset and PR) and the `failures[]` field (a superset of `file`/`error`, discoverable from the payload). Net addition is now 276 bytes; the file sits at 48872 with 280 bytes of headroom. Extracting the agent's shared boilerplate to references/ would buy much more, but that is a restructuring of the executor agent and does not belong in a commit-staging bugfix. Agent size baseline and the golden install-parity fixtures regenerated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QT3ibz5qJuDuGqpTGRYVGf * chore(#2608): backfill changeset PR number (#2693) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6ee4349272 |
fix(#2537): extract offer_next step to references/ (~3.3KB headroom restored) (#2642)
* fix(#2537): extract offer_next step to references/ (~3.3KB headroom restored) * chore(#2537): backfill changeset pr to 2642 |
||
|
|
e4dd0cbdd5 |
fix(#2523): normalize --files to repo-relative; reject out-of-repo; gate push on git-add exit (#2638)
* test(#2523): absolute + mixed + out-of-repo --files paths * fix(#2523): normalize --files to repo-relative; reject out-of-repo; gate push on git-add exit * chore(#2523): backfill changeset pr to 2638 |
||
|
|
4bb846b67a |
fix(#2112): scope commit to --files pathspec, not entire index (#2148)
* fix(2112): scope commit to --files pathspec, not entire index cmdCommit/cmdCommitToSubrepo/cmdPrSubrepo staged exactly the files named in --files but then ran a bare 'git commit' with no pathspec, absorbing anything else in the index into a commit whose message described only the named files (#2112). Fix: append '-- ...stagedPaths' to the commit args when the caller declared a scope. Three guards are load-bearing: - stagedPaths (not filesToStage) excludes skipped missing files (#2014) - explicitFiles gate keeps the default .planning/ path byte-identical - MERGE_HEAD check via 'git rev-parse' falls back to bare commit during merge - --amend is left without pathspec (different operation) cmdPrSubrepo pathspec uses changedFiles (old+new for renames) so the full rename is captured atomically. Also fixes workflow markdown in spec-phase.md and add-tests.md. All-files-missing now short-circuits to nothing_to_commit instead of absorbing the entire index under a message describing files that were not committed. * docs(changeset): backfill PR number (#2148) * test: update golden-install-parity fixtures for workflow markdown changes (#2112) * test: update golden fixtures + workflow baselines for #2112 changes - claude-local.json golden fixture (now generated via gen script) - workflow-size-baseline.json (add-tests.md +16, spec-phase.md +42 bytes) - Extended gen-golden-install-parity-zcode.cjs to also regenerate the claude local-layout fixture |