9d5db87249d37e9e229cfe6ec013a660fc55ee2d
2238 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9d5db87249 |
feat(#2975): adopt changeset-fragment workflow to eliminate CHANGELOG conflicts (#2978)
* feat(#2975): adopt changeset-fragment workflow to eliminate CHANGELOG conflicts Two PRs that both edit `### Fixed` in CHANGELOG.md always conflict on merge. Recently bit on #2960/#2972 in the same session — fix-the-conflict-and-rebase tax. Replace the shared-file model with per-PR fragment files that never share lines. Implementation built TDD per #2975, vertical slices with structured-IR assertions throughout: scripts/changeset/parse.cjs - fragment text → typed record + frozen FRAGMENT_ERROR enum (8 tests) scripts/changeset/render.cjs - fragments → structured IR with Keep-a-Changelog section ordering (2 tests) scripts/changeset/serialize.cjs - IR ↔ markdown round-trip pair (parse(serialize(ir)) === ir, 3 tests) scripts/changeset/cli.cjs - file-I/O wrapper with --json mode; reads .changeset/, folds into CHANGELOG.md, deletes consumed fragments. Idempotent. (1 test) scripts/changeset/lint.cjs - pure verdict (changedFiles, labels) → { ok, reason } via LINT_REASON enum. Honors `no-changelog` label. (5 tests) scripts/changeset/new.cjs - fragment scaffolder with random adjective-noun-noun filename. Tests assert via parseFragment round-trip. (3 tests) Total: 22 tests, all assertions on typed structured fields. No regex on text, no String#includes on file content. Lint clean across 356 test files. Supporting: .changeset/README.md - format spec + workflow docs .changeset/eager-hawks-rally.md - dogfood fragment for THIS PR (will be the first thing the new release tool consumes) .github/workflows/changeset-required.yml - CI: every PR runs lint.cjs package.json - npm run changeset, changelog:render, lint:changeset CONTRIBUTING.md - new "CHANGELOG Entries — Drop a Fragment" section between PR Guidelines and Testing Standards Closes #2975 * fix(#2975): address CodeRabbit findings on changeset workflow 7 valid findings (4 Major, 3 Minor); all addressed: scripts/changeset/parse.cjs - Preserve fragment body verbatim. Previously body.trim() ate intentional leading whitespace (code blocks, etc.); now trim() is used only for the emptiness check, and a single trailing newline is stripped (the editor-added one) so well-formed fragments round-trip byte-for-byte. Added a regression test asserting a code-block-leading body is preserved. scripts/changeset/cli.cjs - Validate flag values during argument parsing. parseArgs now returns { ok, opts | error }; rejects `--repo` etc. with no following value or with another flag as the value. main() surfaces the error message before exiting 2. - Handle post-write fragment-deletion failures. After CHANGELOG.md is written, any unlink failure is captured into a structured deleteFailures list with reason 'fail_fragment_delete'; cmdRender returns exitCode=1 with the partial-failure detail instead of leaving the changelog updated and fragments behind (which would cause double-consumption on rerun). scripts/changeset/lint.cjs - Treat CHANGELOG.md as a linted user-facing path. Direct edits to CHANGELOG.md (the bypass route around the new workflow) now fail the lint with FAIL_MISSING_FRAGMENT. Added a regression test for that case. - Use cp.execFileSync instead of cp.execSync for the git diff call. Eliminates the shell-interpolation surface on GITHUB_BASE_REF; git's own arg parser remains the validator. scripts/changeset/new.cjs - Atomic fragment creation. existsSync() + writeFileSync was racy under concurrent invocations. Now writeFileSync uses { flag: 'wx' } which fails EEXIST on collision; the random-name retry loop catches EEXIST and re-rolls. Throws explicitly after 16 attempts rather than silently overwriting. .changeset/README.md - Add language tag `md` to the format example fence (markdownlint MD040). All 25 changeset tests pass; lint clean (356 test files, 0 violations). * fix(#2975): sanitize --type and validate flag values in new.cjs (CR fixes) Two CR findings on scripts/changeset/new.cjs: 1. (Minor) `type` was embedded in frontmatter without sanitization. A newline in the value (e.g. `--type 'Fixed\ntype: Added'`) would corrupt the fragment. scaffoldFragment now validates `type` against the Keep-a-Changelog ALLOWED_TYPES set BEFORE writing — same set parse.cjs uses on consume. Throws with a typed error referencing the allowed values; tests cover the newline case + 4 other non-allowed values. 2. (Minor) `--repo` (and other value-taking flags) without a value silently set opts.repo to undefined, which produced a cryptic ERR_INVALID_ARG_TYPE deep inside path.join. parseArgs now mirrors the cli.cjs convention: returns { ok, opts | error }, validates that the next token exists and is not itself another flag, and surfaces a precise "missing value for --repo" message before exit. Added 3 tests: missing-trailing-value, flag-as-value, well-formed. 29 tests pass across the changeset suite (4 new regression tests). |
||
|
|
cb98a88139 |
fix(#2987): skip dry-run publish validation when version is already on npm (#2988)
The `Dry-run publish validation` step ran `npm publish --dry-run` with
no `if:` guard. `npm publish --dry-run` contacts the registry and
exits 1 with "You cannot publish over the previously published
versions" when the target version exists.
The earlier `Detect prior publish (reconciliation mode)` step already
discovers this case and sets steps.prior_publish.outputs.skip_publish=true.
The actual publish step (further down) is gated on that. The
rehearsal step was missing the gate, so any re-run of an
already-published hotfix blew up at the rehearsal before reaching
the reconciliation logic — exactly when an operator is trying to
recover from a later-step failure (merge-back, summary, etc.).
Add `if: ${{ steps.prior_publish.outputs.skip_publish != 'true' }}`
matching the publish step's gate. The rehearsal still runs on first
publishes where it has value.
Trigger: run 25233855236.
Closes #2987
|
||
|
|
fb92d1e596 |
fix(#2983): classifier exit-code discipline, base-tag staging, drop vestigial merge-back (#2984)
* fix(#2983): classifier exit-code discipline, base-tag staging, drop vestigial merge-back Three issues surfaced by CodeRabbit's post-merge review of #2981 plus a production failure on the v1.39.1 release run. (1) Overloaded classifier exit code scripts/diff-touches-shipped-paths.cjs reused exit 1 for both the legitimate "no shipped paths" result and Node's default exit on uncaught throw, so any classifier failure (corrupt package.json, EPERM, etc.) was indistinguishable from a normal skip — the workflow's `if ! ... ; then skip` idiom would silently drop the commit. Distinct exit codes now: 0 shipped — at least one path is in the npm `files` whitelist 1 not shipped — CI / test / docs / planning only 2 classifier error — workflow MUST fail-fast uncaughtException + unhandledRejection + try/catch around fs/JSON parsing all route to exit 2 with stderr context. (2) Classifier missing at the base tag (CRITICAL) `Prepare hotfix branch` runs `git checkout -b "$BRANCH" "$BASE_TAG"` BEFORE the cherry-pick loop, replacing the working tree with the base tag's contents. Base tags predating #2980 (notably v1.39.0, the most likely next hotfix base) don't have scripts/diff-touches-shipped-paths.cjs at all — `node <missing>` exits non-zero — `if !` skips every commit — empty hotfix branch published. Strictly worse than the original #2980 push-rejection, which at least failed loudly. Stage the classifier from the dispatched ref's working tree into $RUNNER_TEMP at the top of the run script (before any working-tree- mutating git command). The cherry-pick loop now references $CLASSIFIER (staged) instead of the in-tree path. Sanity guards: refuse to start if scripts/diff-touches-shipped-paths.cjs is missing in the dispatched ref, refuse to proceed if cp didn't materialize $CLASSIFIER. The cherry-pick loop captures node's exit via ${PIPESTATUS[1]} and dispatches via explicit case: 0 proceed with cherry-pick 1 skip into NON_SHIPPED_SKIPPED * emit ::error:: + exit "$CLASSIFIER_RC" (3) Drop the merge-back PR step Auto-cherry-pick only picks commits already on main (`git cherry HEAD origin/main` outputs the unmerged ones; we filter fix:/chore: from main). By construction every code commit on the hotfix branch is already on main. The only hotfix-branch-only commit is `chore: bump version to X.Y.Z for hotfix`, which either no-ops against main or rewinds main's in-progress version. The merge-back PR was vestigial. It also failed in production on run 25232968975 with `GitHub Actions is not permitted to create or approve pull requests (createPullRequest)` — org policy blocks PR creation from the workflow's GH_TOKEN. Even without that block, the PR would have nothing useful to merge. Step removed. The `pull-requests: write` permission granted solely for the merge-back step has been dropped from the release job (least-privilege). Regression coverage tests/bug-2983-classifier-exit-codes-and-base-tag-staging.test.cjs adds 12 assertions across two describe blocks: - 5 classifier behavioral: exit 0/1 preserved, exit 2 on missing package.json, exit 2 on malformed JSON, exit-code constants exported. - 7 workflow contract: classifier staged before checkout, target is $RUNNER_TEMP, missing-source guard, missing-staged guard, PIPESTATUS-based dispatch, error branch fails workflow, loop uses staged path (not in-tree). tests/bug-2980-hotfix-only-picks-shipping-changes.test.cjs updated where it asserted the pre-#2983 `if ! ... ; then` shape: now accepts the post-#2983 case-dispatch form. The test still proves the classifier participates; bug-2983 enforces the specific shape. Run summary references for the curious reviewer: - Run 25232010071 — original #2980 trigger (workflow-file push rejection) - Run 25232968975 — failed merge-back step that prompted the "is this even useful?" question that drove the removal Closes #2983 * fix(#2983): address CodeRabbit findings on PR #2984 Two findings, both real, both fixed. (1) [Critical] PIPESTATUS capture clobbered by `|| true` Pre-fix shape: git diff-tree ... | node "$CLASSIFIER" || true CLASSIFIER_RC="${PIPESTATUS[1]}" When the classifier exits 1 ("not shipped" — common case) or 2 (error), `|| true` triggers the right-hand side. `true` is a one-command "pipeline" that overwrites PIPESTATUS to (0). ${PIPESTATUS[1]} on the next line is therefore unset (or stale under set -u). The case dispatch then matched the empty string — falling into `*)` and failing the workflow on every non-shipped commit, OR matching `0)` after some shells default-init unset to 0 and silently picking commits that don't ship. Local repro confirms the issue: $ bash -c 'set -euo pipefail; false | sh -c "exit 7" || true; \ echo "PIPESTATUS: ${PIPESTATUS[*]}"; \ echo "[1]: ${PIPESTATUS[1]:-<unset>}"' PIPESTATUS: 0 [1]: <unset> Fix: bracket the pipeline in `set +e`/`set -e`, snapshot PIPESTATUS into a local array on the very next line, then dispatch on the snapshot: set +e git diff-tree ... | node "$CLASSIFIER" PIPE_RC=("${PIPESTATUS[@]}") set -e DIFFTREE_RC="${PIPE_RC[0]}" CLASSIFIER_RC="${PIPE_RC[1]}" The snapshot must happen on the first line after the pipeline; any intervening simple command resets PIPESTATUS. The array form is invariant against that. Bonus from the new shape: $DIFFTREE_RC is now also captured. git diff-tree is unlikely to fail on a known-good $SHA, but if it does, we no longer feed partial/empty input to the classifier and call it "not shipped." A non-zero DIFFTREE_RC emits ::error::git diff-tree failed and exits. (2) [Minor] Stale "Merge-back PR opened against main" summary line The hotfix run summary still printed: echo "- Merge-back PR opened against main" But the merge-back step itself was removed in the previous commit on this branch. Operators reading the summary would expect a PR that doesn't exist. Replaced with explicit non-action text: echo "- No merge-back PR (auto-picked commits are already on main)" Test coverage bug-2983 test file gains 3 assertions: - PIPE_RC array-snapshot pattern is required (regex matches the exact `PIPE_RC=("${PIPESTATUS[@]}")` form). - The `pipeline || true; ${PIPESTATUS[1]}` antipattern is explicitly forbidden via assert.doesNotMatch. - DIFFTREE_RC is captured from PIPE_RC[0] and a non-zero value triggers ::error::git diff-tree failed. - Run summary forbids `Merge-back PR opened against main` and requires the new non-action sentence. bug-2964 test's loop-anchor window bumped 6 KB → 8 KB to accommodate the additional pre-pick scaffolding (the test's own comment had already anticipated this kind of growth, citing prior precedents from #2970 and #2980). Mark CodeRabbit comments resolved post-commit. Refs CR finding ids 3175253571, 3175253578 on PR #2984. |
||
|
|
7424271aa0 |
fix(#2980): hotfix cherry-pick only picks commits that change what ships (#2981)
* fix(#2980): pre-skip workflow-file cherry-picks in release-sdk hotfix loop The default GITHUB_TOKEN issued to the release-sdk run lacks the `workflow` scope, so the prepare job's `git push origin "$BRANCH"` is rejected by GitHub when any cherry-picked commit modifies a file under `.github/workflows/`: ! [remote rejected] hotfix/X.YY.Z -> hotfix/X.YY.Z (refusing to allow a GitHub App to create or update workflow ... without `workflows` permission) Pre-#2980 behavior: the auto_cherry_pick loop happily picked workflow-file commits, then the trailing push exploded with no clear signal which commit was the culprit. v1.39.1 hit this on PR #2977 (run 25232010071) — earlier release-sdk fixes (#2965, #2967, #2970) had been skipped on conflict so their workflow-file changes never reached the push step, masking the bug; #2977 was the first workflow-file commit to apply cleanly and the push immediately exploded. Fix: pre-pick guard in the cherry-pick loop. Inspect each candidate commit's file list via `git diff-tree --no-commit-id --name-only -r` BEFORE attempting the pick. If any path matches `^\.github/workflows/`, skip the commit, emit a `::warning::` annotation naming the dropped commit, and append to a new `WORKFLOW_SKIPPED` bucket. The run summary surfaces this bucket in its own section, distinct from `CONFLICT_SKIPPED` (real merge conflicts) and `POLICY_SKIPPED` (feat/refactor exclusions), so operators reviewing the run never confuse the remediation paths. The loud-warning piece is non-negotiable: silent drops were explicitly rejected as a failure mode during the option-1/2/3 tradeoff discussion. If a workflow-file fix genuinely needs to ship in a hotfix, the operator applies it manually on the hotfix branch using a token with `workflow` scope, or lands it on main and re-cuts the release. Regression covered by tests/bug-2980-skip-workflow-file-cherrypicks.test.cjs (5 assertions: pre-pick guard exists, uses `git diff-tree`, emits `::warning::`, lands in dedicated bucket, surfaces in summary). The bug-2964 test's 4 KB window after the cherry-pick-loop anchor was nudged to 6 KB to accommodate the new pre-pick scaffolding — the test's own comment had already anticipated this kind of growth (citing #2970's merge-commit pre-skip as prior precedent). Closes #2980 * refactor(#2980): replace workflow-file pre-skip with shipped-paths filter The previous commit on this branch caught only the .github/workflows/* subset of the bug, treating the symptom (push rejection on workflow-file changes) rather than the root cause (the fix:/chore: filter is too broad — it picks any commit with that conventional-commit type even when the diff cannot affect the published npm package). CI-only fixes (release-sdk.yml itself, hotfix tooling, test-only commits) shouldn't flow through hotfix runs at all — they cannot change what `npm install get-shit-done-cc@X.YY.Z` produces. The .github/workflows/* push rejection is just the loudest of these "shouldn't have been picked" cases; tests/, docs/, .planning/ commits get picked silently with the same lack of effect on consumers. Replace the workflow-file pre-skip with a shipped-paths filter: - New scripts/diff-touches-shipped-paths.cjs reads package.json `files`, plus package.json itself (always-shipped per `npm pack` semantics), and exits 0 iff any input path is in the shipped set. Lockfile is not shipped (npm pack excludes it unless explicitly in `files`). - Workflow loop now pipes `git diff-tree --no-commit-id --name-only -r` through the classifier; on exit 1 the commit is skipped and appended to a new NON_SHIPPED_SKIPPED bucket (replaces WORKFLOW_SKIPPED). - Run summary surfaces NON_SHIPPED_SKIPPED as informational — no ::warning:: annotation. A non-shipping commit cannot affect the package, so a yellow alert would imply remediation is possible and would mislead operators. The classifier in a separate .cjs file (rather than inline bash heredoc) is so its rules — directory-prefix vs exact-match, package.json-always-shipped, lockfile-not-shipped — are unit-testable in tests/bug-2980-hotfix-only-picks-shipping-changes.test.cjs (11 new assertions: 4 static workflow + 6 classifier behavioral + 1 mixed- diff edge case). Why this dissolves the original push-rejection bug: workflow files aren't in `files`, so workflow-only commits are skipped pre-pick. The push step never sees them. If a workflow-file fix genuinely needs to ship in a hotfix release (extremely rare — the hotfix workflow is read from main's ref, not the hotfix branch's), the operator applies it manually using a token with `workflow` scope. The pre-skip puts that requirement in the run summary explicitly. Closes #2980 |
||
|
|
7a416b10d4 |
fix(#2976): allow same-version bump in release-sdk hotfix release job (#2977)
The release job's "Bump in-tree version (not committed)" step ran `npm version "$VERSION" --no-git-tag-version` without --allow-same-version, so on real hotfix runs it failed with `npm error Version not changed` — because the prepare job had already committed the bump on the hotfix branch (the release job checks out BRANCH on real runs vs BASE_TAG on dry-runs, which is why dry-run never caught it). Pass --allow-same-version to both bumps, matching release.yml:326. Closes #2976 |
||
|
|
ef43f5161f |
fix(#2969): deterministic Step 5 verification gate for /gsd-reapply-patches (#2972)
* fix(#2969): deterministic Step 5 verification gate for /gsd-reapply-patches The prior Step 5 "Hunk Verification Gate" was prescribed correctly in the workflow text — but executed laxly by the LLM, which filled in `verified: yes` without actually checking content presence. The reporter observed three distinct files (skills/gsd-discuss-phase/SKILL.md, skills/gsd-autonomous/ SKILL.md, get-shit-done/workflows/new-project.md) where archives contained substantive user-added blocks that did not survive into the merged result, yet the gate reported clean. Move verification from LLM-driven prose into a deterministic Node script the workflow calls. The script can't be shortcut. Changes: - scripts/verify-reapply-patches.cjs (new): pure Node, no external deps. For each file in the patches dir, computes user-added significant lines as the line-set diff between backup and pristine baseline (when available; falls back to "every significant backup line" when no pristine — over-broad but the safe direction for this bug class). Asserts each line appears literally in the merged installed file via String.prototype.includes. Filters trivial lines (length < 12 chars, pure punctuation, decorative comments) so harmless drift doesn't trigger false failures. Exits 0 on pass, 1 on any miss with per-file diagnostic, 2 on usage error. Supports --json for workflow consumption. - get-shit-done/workflows/reapply-patches.md: rewrite Step 5 to call the script and parse its JSON output. The Step 4 Hunk Verification Table remains as advisory Claude-readable summary, but the gate is now the script's exit code. - tests/bug-2969-verify-reapply-patches.test.cjs (new): 6 tests covering (a) pass when every line survives, (b) fail when a line is missing, (c) fail when the merged file is deleted entirely, (d) --json structured report shape, (e) backup-meta.json is correctly skipped as metadata, (f) no-pristine-dir fallback exercises the safe over-broad path. All pass. Out of scope: the manifest-baseline tightening described in #2969 Failure 1 (saveLocalPatches comparing against the wrong baseline so prior silent wipes poison subsequent updates). That's a separate, bigger architectural change involving pristine-content infrastructure; this PR addresses the gate fidelity half so users at least see the diagnostic when content goes missing. Closes #2969 (partial — Failure 2 only) * fix(#2969): preserve #1999 Hunk Verification Table assertions alongside new script gate CI failure on PR #2972 surfaced that tests/reapply-patches.test.cjs (the #1999 contract) asserts Step 5 references: - "Hunk Verification Table" - `verified: no` failure condition - explicit STOP/halt/abort directive - "table absent / missing" halt path My initial Step 5 rewrite for #2969 substituted the deterministic script for the table-based gate entirely, stripping those references. The script is the strictly stronger gate, but the existing #1999 test enforces the table-based safety net as a defense-in-depth contract. Restore both gates as a layered Step 5: - 5a (binding): deterministic verifier script — script gate, exits non-zero on any miss, cannot be shortcut by the LLM - 5b (advisory): Hunk Verification Table review — preserved as redundant safety net for the case where the script has a bug or the pristine baseline is unavailable Both gates must pass. Verified: tests/reapply-patches.test.cjs (5 tests in the #1999 suite) and tests/bug-2969-verify-reapply-patches.test.cjs (6 tests in the #2969 suite) all pass — 21/21 total in this fixture. * fix(#2969): address CodeRabbit findings on workflow + script Five CR findings on PR #2972, all valid; addressed in this commit: 1. (Major) Stderr was merged into VERIFY_OUTPUT via `2>&1`, so any Node warning, deprecation notice, or stack trace would corrupt the JSON parse downstream. Capture stdout only; stderr remains on the controlling terminal for operator visibility. 2. (Major) verifyFile() crashed with EISDIR/EACCES instead of producing a structured diagnostic when the installed path was a directory or unreadable. Wrap statSync/readFileSync in try/catch and emit a per-file fail row; the whole-run gate continues with structured output. Added test case asserting the directory-at-installed-path case fails with `not a regular file` diagnostic instead of crashing. 3. (Minor) PRISTINE_FLAG built as a single string + unquoted expansion would split paths with spaces. Switched to a bash array (VERIFY_ARGS) that preserves whitespace through expansion. 4. (Minor) Fenced code block missing language tag (markdownlint MD040). Added `text` tag to the error message block. 5. (Minor) Usage comment said pristine fallback was "backup-meta lookup" but the actual code path falls back to significant-line checks from backup content. Corrected the comment to match implementation. Verified all 21 tests in tests/reapply-patches.test.cjs (#1999 contract) + tests/bug-2969-verify-reapply-patches.test.cjs (now 7 tests with the new directory case) pass. * test(#2969): structured JSON assertions, no substring matching on script output Replace every assert.match(r.stdout, /pattern/) call with structured assertions on the parsed JSON report from the script's own --json mode. The script's --json contract IS the structured shape we test against — the test author should never depend on the human-readable formatter output, just as no test should depend on substring presence in source. Changes: - All 7 tests now run the verifier with --json (via a runVerifier() helper) and parse the resulting JSON document into { status, report, stderr }. Diagnostic stderr is preserved as a separate channel for debug output but is not used for assertions. - Each previously substring-matched diagnostic ("Failures: 1", "not a regular file", "installed file missing after merge", file path, dropped line) is now a deepEqual / equal / Array.includes against typed report fields: report.failures, report.results[i].status, report.results[i].reason, report.results[i].file, report.results[i].missing[]. - Added an explicit "documented shape" test asserting the JSON output has exactly the keys { file, missing, reason, status } per result — locks the public contract of the --json mode. - DRY'd up fixture reset into a resetFixture() helper since every test starts with a fresh patches/installed/pristine triple. Linter: scripts/lint-no-source-grep.cjs reports 0 violations across 348 test files. Combined run of bug-2969-...test.cjs (7 tests) + reapply-patches.test.cjs (5 tests in the #1999 suite) all pass — 22/22 in the relevant fixture. * fix(#2969): typed REASON enum + raw-text-matching rule shipped repo-wide This commit closes the loop on the no-source-grep discipline: 1. scripts/verify-reapply-patches.cjs: - Frozen REASON enum exposes the diagnostic surface as stable codes: OK_NO_USER_LINES_VS_PRISTINE, OK_NO_SIGNIFICANT_BACKUP_LINES, FAIL_INSTALLED_MISSING, FAIL_INSTALLED_NOT_REGULAR_FILE, FAIL_READ_ERROR, FAIL_USER_LINES_MISSING. - Each result.reason is now a code from this enum, not free text. Tests assert via REASON.X equality, not regex on prose. - REASON exported from module.exports. 2. tests/bug-2969-verify-reapply-patches.test.cjs: - Full rewrite. Every assertion on typed structured fields: report.results[0].status === 'fail', report.results[0].reason === REASON.FAIL_INSTALLED_NOT_REGULAR_FILE, report.results[0].missing.includes(droppedLine) (Array set membership, not String substring). - Locks the REASON enum surface via Object.keys(REASON).sort() deepEqual. - Locks the JSON report shape via Object.keys(report).sort() deepEqual. - Zero regex, zero String#includes, zero startsWith/endsWith on text. 3. CONTRIBUTING.md: - New section "Prohibited: Raw Text Matching on Test Outputs" with concrete BAD/GOOD examples (substring on file content; assert.match on stdout; "structured parser" hiding string ops; regex on free-form reason fields). - The rule statement: "Tests assert on typed structured values. If the code under test produces text, the code under test must also expose a structured intermediate representation, and the test must assert on that IR — never on the rendered text." - Required structured-surface table: file IR, --json mode, frozen enum, fs facts. - "Hiding grep behind a function is still grep" callout — the parser-wrapper anti-pattern. - New `pre-existing-text-matching` exemption category for the 8 grandfathered files. Marked Transitional; new tests cannot use it. 4. scripts/lint-no-source-grep.cjs: - Three new patterns enforced (in addition to the existing .cjs-source readFileSync rule): - assert.match/doesNotMatch on .stdout/.stderr - .stdout/.stderr.<includes|startsWith|endsWith>( - readFileSync(...).<includes|startsWith|endsWith>( - Aggregated violations per file (multiple findings now report together). - Updated diagnostic message references both CONTRIBUTING.md sections. 5. 8 pre-existing tests annotated with `// allow-test-rule: pre-existing-text-matching` so the lint passes on this commit; each carries the prose "Tracked for migration to typed-IR assertions; do not copy this pattern." Files: bug-2649, bug-2687, bug-2796, bug-2838, bug-2943, graphify, hooks-opt-in, security-scan. Verification: lint 0 violations across 348 test files; full suite passes. * fix(#2969): rename exemption category to pending-migration-to-typed-ir + cite tracking issue Per maintainer feedback: 1. "Grandfathered" / "legacy" framing is wrong — both terms imply permanent or condoned exemption. The 8 files are tracked for correction, not exempted. 2. Each annotated file must cite the tracking issue so the migration work is auditable. Changes: - CONTRIBUTING.md: rename exemption category from `pre-existing-text-matching` to `pending-migration-to-typed-ir`. Update prose to "Tracked for correction, not exempted" and require each annotation to cite the open migration issue (e.g. `// allow-test-rule: pending-migration-to-typed-ir [#NNNN]`). - 8 test files: update annotation to cite #2974 (the tracking issue opened for migrating these files to typed-IR assertions). |
||
|
|
e9a66da1e7 |
fix(#2962): write npm-style gsd-sdk shim on Windows under --sdk install (#2971)
* fix(#2962): write npm-style gsd-sdk shim on Windows under --sdk install trySelfLinkGsdSdk previously contained `if (process.platform === 'win32') return null;` — a missed gap from #2775's POSIX self-link rather than an intentional design choice. As a result, `npx get-shit-done-cc@latest --claude --global --sdk` on Windows left `gsd-sdk` off PATH despite the installer reporting success, and the obvious recovery (`npm i -g @gsd-build/sdk`) lands the stale 0.1.0 publication that lacks the `query` subcommand the agents call ~40 times. This PR addresses the shim half. The npm-publish half (publishing @gsd-build/sdk at parity with the get-shit-done-cc version) requires maintainer credentials and is left for separate action. Changes: - bin/install.js: replace the unconditional Windows return-null with dispatch to a new trySelfLinkGsdSdkWindows() that: * resolves npm's global bin via `execFileSync('npm', ['prefix', '-g'])` (no shell interpolation; npm is the only PATH-resolved binary) * verifies write access with a probe before producing partial state * writes the standard npm shim triple to npm's global bin: - gsd-sdk.cmd (cmd.exe; CRLF line endings) - gsd-sdk.ps1 (PowerShell) - gsd-sdk (Bash wrapper for Cygwin/MSYS/Git-Bash) * each shim invokes `node "<absolute path to bin/gsd-sdk.js>"` with the passed args, decoupling shim location from SDK location — same logical structure as the POSIX wrapper-via-require() fallback above * unlinks any stale shims before writing so prior installs don't pin callers to a now-absent path * returns the .cmd path on success (handle the existing onPath check looks for) or null on any failure, falling through to the existing "gsd-sdk is not on your PATH" warning at line 8704 - tests/bug-2962-windows-sdk-shim.test.cjs (new): 5 tests exercising trySelfLinkGsdSdkWindows directly with cp.execFileSync mocked to redirect npm prefix to a temp dir. Asserts shim contents reference the absolute path, .cmd uses CRLF, stale shims are replaced not appended, and null is returned when `npm prefix -g` fails. - tests/no-unconditional-win32-skip.test.cjs (new): regression guard that fails CI if any future commit re-introduces `if (process.platform === 'win32') return null;` (or similar skip-only branches) in bin/install.js. Negative test verified by transiently re-introducing the bad pattern → guard fired → restored → passes. Out of scope: publishing @gsd-build/sdk@<current> to npm so the natural `npm i -g @gsd-build/sdk` recovery also lands a usable SDK. That requires maintainer credentials and is the second half of the issue. Closes #2962 * fix(#2962): address CodeRabbit findings — execSync for npm.cmd, behavior-based regression guard CR finding 1 (🟠 Major): Node's child_process docs explicitly call out that .cmd/.bat files cannot be spawned via execFile/execFileSync without a shell ("Spawning .bat and .cmd files on Windows" section). Since `npm` on Windows is `npm.cmd`, my use of execFileSync('npm', ['prefix', '-g'], { shell: false }) would have failed on the very platform this PR is meant to fix. Switched to cp.execSync('npm prefix -g', ...) — matching the existing convention at line ~8718 which makes the same lookup. Args are static literals so shell interpolation is not an injection vector. CR finding 2 (🟠 Major): the source-grep regression test in tests/no-unconditional-win32-skip.test.cjs violated the repo's no-source-grep testing standard (CONTRIBUTING.md). Replaced with a behavior-based test that: - overrides process.platform to 'win32' via Object.defineProperty - mocks cp.execSync to return a temp-dir as npm prefix - calls trySelfLinkGsdSdk(shimSrc) and asserts it returns non-null AND materializes gsd-sdk.cmd on disk The behavior guard is strictly stronger than the regex version: it would catch any equivalent skip pattern (e.g. os.platform() === 'win32', a typeof-based guard, etc.), not just literal `if (process.platform === 'win32')` text. Negative-tested by re-introducing the `return null` skip → test fails with maintainer-quoted diagnostic "trySelfLinkGsdSdk must not silently return null on Windows; a no-op skip is a missed-parity regression"; restored → passes. Test for Windows shim materialization (bug-2962-windows-sdk-shim.test.cjs) also updated to mock cp.execSync (matching the new production code path) instead of cp.execFileSync. Full suite: 6480/6480 pass. * test(#2962): make Windows shim tests self-contained per CR Each test now invokes trySelfLinkGsdSdkWindows() itself before reading the shim files, so they don't implicitly depend on the earlier test's side effects. Addresses CR's order-dependence finding. * test(#2962): structured shim parsing — eliminate substring source-grep CR found that even after the prior refactor, three tests in the suite still used .includes()/.startsWith() against shim file content (cmdContent.includes(\`@node ${jsonQuoted} %*\`) etc.). Substring matching on file text is the same anti-pattern the no-source-grep standard forbids — even when the file is one this test wrote — because it asserts a literal exists rather than that the structured shape is correct. Replace with three small parsers (parseCmdShim, parsePs1Invocation, parseBashInvocation) that split each shim into header + invocation tokens and assert via deepEqual on structured records. The assertions now check that the .cmd has @ECHO OFF / @SETLOCAL / @node <abs> %* in that order with exactly 3 meaningful lines, and that the .ps1 and bash wrappers split into the expected (call, nodeCmd, target, argToken) tuples. The stale-shim replacement test was hardened the same way: instead of proving the absence of a sentinel substring, it now proves the parsed target equals the new shimSrc and != the old path. Verified: scripts/lint-no-source-grep.cjs reports 0 violations across 348 test files. The 6-test windows-sdk-shim + win32-skip-guard suite all pass. * fix(#2962): expose pure shim IR + tests assert on typed fields, not rendered text Earlier "structured parser" approach (parseCmdShim / parsePs1Invocation / parseBashInvocation) was still raw-text manipulation behind a function wrapper — split('\\r\\n'), trim().split(/\\s+/), content.includes('\\r\\n'). Maintainer was right: hiding grep behind a parser is still grep. Real fix: refactor production code to expose the structured intermediate representation, and have tests assert on the IR fields directly. Production: - New buildWindowsShimTriple(shimSrc) — pure function, no fs/spawn. Returns { invocation: { interpreter, target }, eol: { cmd, ps1, sh }, fileNames: { cmd, ps1, sh }, render: { cmd: () => string, ... } }. The IR is the contract; rendered text is an implementation detail of the renderers. - trySelfLinkGsdSdkWindows now calls buildWindowsShimTriple, looks up filenames from triple.fileNames, and writes triple.render[kind]() to each target. Same observable behavior, structurally separated. - buildWindowsShimTriple added to test-mode exports. Tests (full rewrite — no shim file content is read at any point): - Layer 1: pure-IR tests assert on triple.invocation.target, triple.eol === { cmd: '\\r\\n', ps1: '\\n', sh: '\\n' }, triple.fileNames === { cmd: 'gsd-sdk.cmd', ... }, and the documented IR shape via Object.keys().sort() deepEqual. - Layer 2: fs/spawn driver tests assert filesystem FACTS: - return value equals expected path - all three target files exist as regular non-empty files - rendered file byte length === Buffer.byteLength of triple.render(kind) output (proves the writer writes what the renderer produces, no mutation, no truncation, no double-write — without comparing content) - mtime advances on rewrite (proves stale-replace behavior) - returns null when npm prefix -g throws No more split, .includes, .startsWith, .endsWith, or substring matching anywhere in the test suite. Lint clean. 10/10 tests pass. |
||
|
|
b8d9bd69b2 |
fix(release-sdk): skip all cherry-pick conflicts in hotfix loop (full automation) (#2970)
* fix(release-sdk): skip all cherry-pick conflicts in hotfix loop Full-automation policy: any conflict the cherry-pick can't auto-resolve — context-missing (#2966) or real merge conflict — is now skipped, not aborted. The hotfix run completes with whatever applies cleanly; the SKIPPED list in the run summary becomes the operator's post-hoc review queue. Surfaced in run 25227493387 (1.39.1 dry-run): commit |
||
|
|
0d25ef0c47 |
fix(release-sdk): skip cherry-picks whose target context is absent at base (#2967)
* fix(release-sdk): skip cherry-picks whose target context is absent at base When auto_cherry_pick processed a fix:/chore: commit whose patch modified code that didn't exist at the hotfix base tag — typically because the surrounding infrastructure was added later in a feat/refactor commit excluded by the filter — `git cherry-pick` failed with a conflict that no operator could meaningfully resolve, and the loop bricked the run. Discovered re-running the 1.39.1 dry-run after #2965 merged: cherry-pick of `a3467792` (the #2965 merge itself) failed because the auto_cherry_pick block it modifies was added in #2956 ("Add automated cherry-pick + SDK- bundle parity to hotfix flow") — an Add/feat commit, so the fix/chore filter excludes it. v1.39.0 has no such block, so the patch had no anchor. The conflict is unmistakably distinguishable from a real content conflict: git emits marker blocks where every `<<<<<<< HEAD ... =======` HEAD section is empty (no anchor lines to reconcile against), while real conflicts have content on both sides. After cherry-pick fails: 1. List unmerged paths via `git diff --diff-filter=U`. 2. For each, scan conflict markers with awk. If every HEAD section is blank/whitespace-only across every block, classify as context-missing. 3. Context-missing → `git cherry-pick --skip` and append to SKIPPED list with reason "(context absent at base)". 4. Otherwise fall through to the existing abort/push-partial/error path that surfaces the conflict for operator resolution. Real conflicts still surface with the same workflow as before. Tests in tests/bug-2966-cherry-pick-context-missing.test.cjs cover: - Static — extracts the "Prepare hotfix branch" run block via indentation-aware YAML parsing (no raw-text grep) and asserts the classification predicate, --skip call, and skipped-reason annotation are present. - Behavioral — synthetic repo reproducing the real shape of the failure, asserts cherry-pick exits non-zero and produces the empty-HEAD marker shape. - Predicate — pulls the awk script out of the deployed workflow and feeds it sample conflict shapes (empty-HEAD, real, mixed, whitespace-only); asserts each is classified as the workflow will behave. Local: `node --test tests/bug-2966-...test.cjs` → 3/3 pass. Local: `npm run lint:tests` → 0 violations. https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG * fix(release-sdk): pin merge.conflictStyle=merge on hotfix cherry-pick CodeRabbit flagged on #2967 that the awk classifier introduced for #2966 assumes default conflict-marker style (plain `<<<<<<< HEAD ... ======= ... >>>>>>>`). If a runner has merge.conflictStyle=diff3 or zdiff3 set (globally, repo-config, or via git defaults shift), the marker emits an extra `||||||| ancestor` section between HEAD and =======. The awk's `in_head` mode would accumulate that ancestor content into the HEAD buffer, and a context-missing conflict would misclassify as real — sending the workflow into the abort path on a pick that should be silently skipped. Pass `-c merge.conflictStyle=merge` on the cherry-pick command itself (scoped to that one git invocation; doesn't leak to other commands). This guarantees marker shape regardless of the runner's git config. Updated the existing static assertion in tests/bug-2966-cherry-pick-context-missing.test.cjs to require the pin — a future edit dropping it fails the test. Local: `node --test tests/bug-2966-...test.cjs` → 3/3 pass. https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG * test(#2964): allow git options between `git` and `cherry-pick` The previous commit on this branch (d6530190) added `git -c merge.conflictStyle=merge cherry-pick ...` to release-sdk.yml. The bug-2964 static test's regex `/git cherry-pick[^\n]*"\$SHA"/` required `cherry-pick` to be the literal next token after `git`, so it no longer matched the line and CI failed on Node 22 / Node 24 / macOS. Loosen to `/git\b[^\n]*?cherry-pick[^\n]*"\$SHA"/` so any options between `git` and `cherry-pick` (e.g. `-c key=value`) are tolerated. The flag assertions on the matched line still verify --allow-empty and --keep-redundant-commits are present, which is what bug-2964 actually guards. Local: `node --test tests/bug-2964-...test.cjs tests/bug-2966-...test.cjs` → 5/5 pass. https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG * test(#2966): pin merge.conflictStyle in test git wrapper, assert awk status CodeRabbit raised two issues on PR #2967: 1. The synthetic-repo cherry-pick reproducer asserted `<<<<<<< HEAD ...` blocks have empty HEAD sections, but the cherry-pick itself didn't pin `merge.conflictStyle`. A developer or CI runner with global diff3/zdiff3 config would inject `||||||| ancestor` lines into the HEAD scan and the test would fail for environment reasons rather than the bug premise. Pin the style on the test's `git()` wrapper so every git operation in the test is deterministic regardless of user config. 2. `classify()` ran awk and consumed `r.stdout.trim()` without checking `r.status` or `r.error`. A failed awk invocation (missing binary, syntax error, signal) returns empty stdout, which would falsely classify as "context-missing" and the test would silently pass on broken predicates. Add `assert.ok(!r.error, ...)` and `assert.equal(r.status, 0, ...)` before reading stdout. Local: `node --test tests/bug-2966-...test.cjs tests/bug-2964-...test.cjs` → 5/5 pass. https://claude.ai/code/session_01LApueb9PVs2uSBhsLprVzG --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
a346779213 | fix(release-sdk): allow empty/redundant commits during hotfix cherry-pick (#2965) | ||
|
|
0d6abb87ac | fix(#2954): align help.md with post-#2824 skill consolidation (#2959) | ||
|
|
c5dfdbe42e |
fix(#2957): claude+global post-install instructs restart and skill fallback (#2960)
* fix(#2957): claude+global post-install instructs restart and skill fallback `npx get-shit-done-cc --claude --global` writes skills to `~/.claude/skills/gsd-*/SKILL.md` (CC 2.1.88+ format) and removes the legacy `~/.claude/commands/gsd/`. The post-install message still told users to type `/gsd-new-project` without mentioning the required Claude Code restart or the skill-name fallback. On configurations where CC does not auto-surface skills in the slash menu, users hit "no commands appear" and assumed the install failed. Split the post-install message: the existing single-line instruction stays for every non-Claude runtime and for `--claude --local`. For `--claude --global` it now reads: Restart Claude Code, then in any directory either type /gsd-new-project or ask Claude to run the gsd-new-project skill. This covers both invocation paths and surfaces the restart requirement. Add tests/bug-2957-claude-global-postinstall-message.test.cjs as a regression guard: captures the printed message for claude+global, claude+local, and opencode+global; asserts content for each. Verified the test fails on main (pre-fix) and passes after the fix. Closes #2957 * test(#2957): assert legacy generic instruction is replaced not extended CodeRabbit flagged that the test would still pass if the new restart/ fallback copy were printed *alongside* the old 'open a blank directory' instruction. Adding a doesNotMatch assertion proves the claude+global branch replaces the legacy line rather than appending to it. |
||
|
|
9d0d085a17 |
fix(query/agent-skills): emit raw <agent_skills> block instead of JSON-wrapped string (#2917)
* fix(query/agent-skills): emit raw <agent_skills> block instead of JSON-wrapped string The CLI dispatcher (`cli.ts`) JSON-stringifies all query handler results via `console.log(JSON.stringify(result.data, null, 2))`. For the `agent-skills` handler this produced a JSON-quoted string literal — e.g. `"<agent_skills>\n…</agent_skills>"` — which workflows embedded verbatim via `$(gsd-sdk query agent-skills gsd-planner)`, breaking all `<agent_skills>` injection into spawned subagent prompts. Fix: add an optional `format: 'json' | 'text'` field to `QueryResult`. When a handler returns `format: 'text'` and `--pick` is not active, the CLI writes the string directly via `process.stdout.write` instead of JSON-stringifying it. `agentSkills` sets `format: 'text'` for non-empty blocks. Regression guard: two new CLI integration tests in `skills.test.ts` spawn the CLI as a child process and assert that (a) a mapped agent type receives the raw XML block on stdout and (b) an unmapped agent type produces the existing JSON empty-string output. Fixes #2914. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(changelog): add #2917 entry under Unreleased Fixed --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
53cda93a01 |
Add automated cherry-pick + SDK-bundle parity to hotfix flow (#2956)
* feat(workflows): hotfix auto-cherry-pick + SDK-bundle parity (#2955) hotfix.yml: - create: auto-cherry-picks fix:/chore: commits from origin/main since BASE_TAG, oldest-first. Patch-equivalents skipped via git cherry. feat:/refactor: never auto-included. Conflicts halt with offending SHA. - finalize: install-smoke gate, sdk-bundle/gsd-sdk.tgz parity with release-sdk.yml, tightened next dist-tag re-point, --latest on gh release create. SDK package.json bumped in lockstep. release-sdk.yml: - New action input (publish | hotfix) and auto_cherry_pick boolean. - New prepare job branches hotfix/X.YY.Z from highest vX.YY.* tag, cherry-picks same logic as hotfix.yml, outputs effective ref. - install-smoke and release consume prepare.outputs.ref. - Hotfix mode forces tag=latest, opens merge-back PR. Idempotent if branch already exists. VERSIONING.md: documents the cumulative-tag invariant (vX.YY.Z anchors vX.YY.{Z+1}) and both workflow paths. Closes #2955 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(code-review): wire --fix dispatch and update stale command references (#2947) * fix(#2893): surface non-canonical plan filenames instead of silently returning zero plans Reporter saw `plan_count: 0` from `/gsd:execute-phase` even though five plan files existed on disk. Investigation showed the planner had written files like `01-PLAN-01-foundation.md`, while `phase-plan-index`'s strict filter (`f.endsWith('-PLAN.md') || f === 'PLAN.md'`) rejected them silently — collapsing two distinct states into the same `plans: []` return: - directory truly has no plans (legit empty) - directory has plans but the filter rejected them (user/agent error) The canonical contract is documented in three places: - `agents/gsd-planner.md` write_phase_prompt step (lines 1063-1080) - `commands/gsd/plan-phase.md` - `references/universal-anti-patterns.md` (rule 26) It mandates `{padded_phase}-{NN}-PLAN.md` and explicitly forbids `PLAN-NN.md` / `01-PLAN-01.md` / `plan-NN.md` etc. The strict filter is correct per that contract. The bug is that the executor never tells the user when the contract was violated — they just see `plan_count: 0` with no signal. Fix: add a diagnostic helper `describeNonCanonicalPlans()` that scans the phase directory for files matching `*PLAN*.md` (the diagnostic net) that the canonical filter rejected, excluding legit derivatives like `*-PLAN-OUTLINE.md` and `*-PLAN.pre-bounce.md`. When offenders exist, return a `warning` field naming each one and citing the canonical pattern so the user knows what to rename to. Wired into the three filter sites: - `phase-plan-index` (the executor's main entry point) - `phases list --type plans` - `find-phase` The strict filter itself is unchanged — existing canonical plans behave identically. This is purely a diagnostic that converts silent-empty into loud-with-actionable-error. Tests: - `phase-plan-index returns warning for reporter's exact filename pattern (`01-PLAN-01-foundation.md`)` - `truly empty dir does not emit a warning` - `canonical plans + outline + pre-bounce files do not emit a warning` Closes #2893 * test(#2893): add parity tests for find-phase and phases list --type plans warnings CodeRabbit's only finding on the prior commit: I wired the warning into three filter sites (`phase-plan-index`, `find-phase`, `phases list --type plans`) but only `phase-plan-index` had test coverage for the warning shape. The other two paths could silently diverge during future refactors — exactly the silent-drift class of bug this fix exists to prevent. Add four parity tests mirroring the existing two: - find-phase: non-canonical filenames produce a warning naming each offender + citing the canonical pattern. - find-phase: canonical plan + derivative files (PLAN-OUTLINE, pre-bounce) produce no warning. - phases list --type plans: same non-canonical case, but assert the warning is prefixed with `${dir}: ` (this path aggregates across phase directories so each offender is tagged with its dir). - phases list --type plans: canonical case, no warning. `node --test tests/phase.test.cjs`: 98/98 pass (was 94, +4 new). * docs(changelog): hotfix flow auto-cherry-pick + SDK bundle parity (#2955) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(workflows): address CodeRabbit findings on hotfix flow (#2955) 5 findings, all real: 1. BASE_TAG selection used lexicographic awk compare, breaking on multi-digit patches (v1.27.10 wrongly < v1.27.2). Fixed in both hotfix.yml and release-sdk.yml: append TARGET_TAG to candidate list, sort -V, take preceding entry. Semver-correct. 2,4. Cherry-pick conflict aborted locally with no remote branch to resolve from. Now the skeleton branch is pushed up-front (real runs); on conflict we abort, push the partial-pick state with --force-with-lease, and emit operator instructions in the run summary. 3. release-sdk.yml dry_run exited before cherry-pick, defeating the purpose. Now dry_run still applies cherry-picks locally (catches conflicts), just skips push. Downstream install-smoke runs against BASE_TAG; the cherry-pick verification itself is the dry-run signal. 5. release-sdk.yml release job missing pull-requests: write — gh pr create for the merge-back PR would have failed under restricted token defaults. Permission added. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(workflows): CR round 2 — dry-run signal + post-publish reconciliation (#2955) 3 findings, all real: 6. hotfix.yml create dry_run skipped every step (branch creation, cherry-pick, version bump) — a green dry-run gave no signal at all. Now the local checkout/cherry-pick/bump always runs; only the git push calls are gated on dry_run. Conflicts surface in dry-run too. 7,8. "Refuse if version already on npm" preflight hard-failed reruns, so a transient failure between npm publish and a later step (tag push, GH release, merge-back PR, dist-tag re-point) left the release half-shipped with no path to reconcile. Replaced with a prior_publish detect step that warns and sets skip_publish=true; the publish step is gated on that flag, but tag/release/PR/dist-tag continue. GitHub Release create is now idempotent (edit --latest if already exists). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(workflows): CR round 3 — preserve dry-run cherry-pick history in conflict guidance (#2955) Dry-run conflict path discarded successful picks with the runner, but the message told operators to rerun with auto_cherry_pick=false — which recreates the branch from BASE_TAG and silently loses every pick that had succeeded before the conflict. Updated both hotfix.yml and release-sdk.yml: dry-run conflict summary now lists the lost SHAs and recommends re-running with auto_cherry_pick=true (real, not dry-run) to materialize the partial branch on origin. Real-run guidance unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
ec07861228 |
fix(#2948): wire spike --wrap-up flag dispatch (#2951)
* fix(#2948): wire spike --wrap-up flag dispatch Add dispatch block to commands/gsd/spike.md so that /gsd-spike --wrap-up routes to the spike-wrap-up workflow instead of silently no-oping. Also add spike-wrap-up.md to execution_context so the runtime can load it, and update both companion references in workflows/spike.md from the deleted /gsd-spike-wrap-up entry-point to /gsd-spike --wrap-up. Fixes #2948 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(#2948): rewrite dispatch test using parseFrontmatter + section extraction Replace raw fs.readFileSync + text.includes() / regex assertions with structural parsing: parseFrontmatter extracts the YAML frontmatter fields and _body, extractSection pulls named XML blocks, and parseExecutionContextRefs resolves the @-prefixed workflow references. Assertions now target the argument-hint frontmatter field, the execution_context @-ref list, and the routing text within <context>/<process> sections — not arbitrary substrings in the raw file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(#2948): tighten dispatch assertion to line-level rule check Replace the co-occurrence check (dispatchText.includes('--wrap-up') && dispatchText.includes('spike-wrap-up')) with line-level assertions that parse the <process> section's rules array, find the exact '- If it is `--wrap-up`:' line, verify it includes 'strip the flag' and 'spike-wrap-up', and assert the '- Otherwise:' fallback still routes to the spike workflow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(#2948): anchor parseFrontmatter to line 0 to avoid mid-file --- delimiters parseFrontmatter was scanning the whole file for the first two '---' lines, which can match a mid-document horizontal rule as the opening delimiter. Now requires lines[0].trim() === '---'; returns { _body: content } for files with no frontmatter, and searches for the closing '---' from line 1 onward. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
3ba17e872e |
fix(#2950): update stale deleted-command references in workflow files (#2952)
* fix(#2950): update stale deleted-command references in workflow files Eight workflow files (help.md, do.md, settings.md, discuss-phase.md, new-project.md, plan-phase.md, spike.md, sketch.md) referenced command names removed in #2790. Updated all occurrences to canonical new forms: /gsd-phase (--insert / --remove), /gsd-capture, /gsd-config (--profile / --integrations / --advanced), /gsd-spike --wrap-up, /gsd-sketch --wrap-up, /gsd-code-review --fix. Adds regression test (124 assertions) in tests/bug-2950-stale-command-refs.test.cjs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(#2950): update pre-existing assertions to accept new consolidated command forms gsd-settings-advanced.test.cjs and settings-integrations.test.cjs were checking settings.md for the old micro-skill names (/gsd-settings-advanced, /gsd-settings-integrations). Now that #2950 updates settings.md to use the consolidated equivalents, broaden the assertions to accept both old and new forms. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(#2950): require canonical command forms and forbid legacy variants The broadened OR assertions added to unblock CI were too permissive — they could pass with legacy names still present. Now assert the canonical form is present (gsd-config --advanced / gsd-config --integrations) AND the legacy forms are absent (gsd-settings-advanced, gsd:settings-advanced, /gsd-settings-integrations). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
4d628b306a |
fix(#2949): wire sketch --wrap-up flag dispatch (#2953)
* fix(#2949): wire sketch --wrap-up flag dispatch Add dispatch logic to commands/gsd/sketch.md so --wrap-up routes to the sketch-wrap-up workflow instead of silently falling through to the normal sketch workflow. Also adds sketch-wrap-up.md to execution_context and updates companion references in workflows/sketch.md from the deleted /gsd-sketch-wrap-up command to /gsd-sketch --wrap-up. Fixes #2949 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#2949): use exact-match "If it is" instead of "If it contains" for --wrap-up dispatch Aligns with the established pattern across all consolidated commands (workspace.md, update.md, progress.md) where the first-token check uses "If it is `--flag`" for exact equality, not substring matching. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
b328f3269f |
fix(code-review): wire --fix dispatch and update stale command references (#2947)
* fix(#2893): surface non-canonical plan filenames instead of silently returning zero plans Reporter saw `plan_count: 0` from `/gsd:execute-phase` even though five plan files existed on disk. Investigation showed the planner had written files like `01-PLAN-01-foundation.md`, while `phase-plan-index`'s strict filter (`f.endsWith('-PLAN.md') || f === 'PLAN.md'`) rejected them silently — collapsing two distinct states into the same `plans: []` return: - directory truly has no plans (legit empty) - directory has plans but the filter rejected them (user/agent error) The canonical contract is documented in three places: - `agents/gsd-planner.md` write_phase_prompt step (lines 1063-1080) - `commands/gsd/plan-phase.md` - `references/universal-anti-patterns.md` (rule 26) It mandates `{padded_phase}-{NN}-PLAN.md` and explicitly forbids `PLAN-NN.md` / `01-PLAN-01.md` / `plan-NN.md` etc. The strict filter is correct per that contract. The bug is that the executor never tells the user when the contract was violated — they just see `plan_count: 0` with no signal. Fix: add a diagnostic helper `describeNonCanonicalPlans()` that scans the phase directory for files matching `*PLAN*.md` (the diagnostic net) that the canonical filter rejected, excluding legit derivatives like `*-PLAN-OUTLINE.md` and `*-PLAN.pre-bounce.md`. When offenders exist, return a `warning` field naming each one and citing the canonical pattern so the user knows what to rename to. Wired into the three filter sites: - `phase-plan-index` (the executor's main entry point) - `phases list --type plans` - `find-phase` The strict filter itself is unchanged — existing canonical plans behave identically. This is purely a diagnostic that converts silent-empty into loud-with-actionable-error. Tests: - `phase-plan-index returns warning for reporter's exact filename pattern (`01-PLAN-01-foundation.md`)` - `truly empty dir does not emit a warning` - `canonical plans + outline + pre-bounce files do not emit a warning` Closes #2893 * test(#2893): add parity tests for find-phase and phases list --type plans warnings CodeRabbit's only finding on the prior commit: I wired the warning into three filter sites (`phase-plan-index`, `find-phase`, `phases list --type plans`) but only `phase-plan-index` had test coverage for the warning shape. The other two paths could silently diverge during future refactors — exactly the silent-drift class of bug this fix exists to prevent. Add four parity tests mirroring the existing two: - find-phase: non-canonical filenames produce a warning naming each offender + citing the canonical pattern. - find-phase: canonical plan + derivative files (PLAN-OUTLINE, pre-bounce) produce no warning. - phases list --type plans: same non-canonical case, but assert the warning is prefixed with `${dir}: ` (this path aggregates across phase directories so each offender is tagged with its dir). - phases list --type plans: canonical case, no warning. `node --test tests/phase.test.cjs`: 98/98 pass (was 94, +4 new). |
||
|
|
e2792536d9 |
feat(workflows): atomic Write+commit ordering for SUMMARY.md (#2806) (#2939)
* feat(workflows): add atomic Write+commit ordering directive for SUMMARY.md Adds explicit prompt-ordering language to executor spawn prompts and plan-execution steps so agents commit SUMMARY.md before emitting any concluding narrative. Mitigates the truncation-between-Write-and-commit failure mode that has made the #2070 rescue net load-bearing. Refs #2806 * fix(workflows): condense REQUIRED ORDER blocks to fit XL budget The two REQUIRED ORDER directives added in bd1956df pushed execute-phase.md to 1712 lines, exceeding the 1700-line XL budget. Collapse each 6-line block into a single line that preserves the semantic intent (Write SUMMARY.md → commit → narration; no text between Write and commit; #2070 rescue is not primary defense). File is now exactly 1700 lines; workflow-size-budget test passes. * fix(execute-plan): move self-check before commit to preserve atomic Write+commit (#2939) |
||
|
|
7cc6358f91 |
fix(install): honour --minimal across every runtime + manifest fix for Claude local (#2940)
* fix(install): record commands/gsd in manifest for Claude local + per-runtime --minimal coverage writeManifest gated commands/gsd/ recording to Gemini, leaving Claude Code local installs with an incomplete manifest. Audit during #2923 investigation showed every runtime adapter correctly honours --minimal on disk (6 skills, 0 agents) — but Claude local manifest reported 0 skills, breaking saveLocalPatches() drift detection and any downstream tooling that reads manifest.files for the installed surface. Drop the isGemini gate so any runtime that writes commands/gsd/ has those files hashed into the manifest. Adds tests/install-minimal-all-runtimes.test.cjs: spawns the installer end-to-end for all 14 supported runtimes in both --global and --local modes, parses the manifest JSON, and asserts mode === 'minimal', skill set equals MINIMAL_SKILL_ALLOWLIST, and zero gsd-* agents are recorded. Cross-checks the manifest against on-disk skill files. Closes #2923 * test(install): address CR feedback on bug-2923 minimal-runtime tests - Assert installer exit status in runInstall() so failing installs do not produce misleading downstream artifact assertions; include stderr in the failure message for debuggability. - Guard the on-disk vs manifest parity loop with assert.ok(manifest, ...) so the equality check cannot pass accidentally when the manifest is missing. |
||
|
|
8de8acee46 |
fix(workflows): assert HEAD on per-agent branch before worktree commits (#2924) (#2941)
* fix(workflows): assert HEAD on per-agent branch before worktree commits Worktree-mode setup could leave HEAD attached to a protected branch (master), causing agent commits to land there. The previous response was a destructive self-recovery via 'git update-ref refs/heads/master <sha>', which silently rewinds the protected branch and destroys concurrent commits in multi-active scenarios (parallel agents, user committing while agent runs). - Reorder <worktree_branch_check> in execute-phase.md and quick.md to assert HEAD via 'git symbolic-ref' BEFORE any 'git reset --hard'. HALT with a blocker if HEAD is on main/master/develop/trunk/release/* or detached. - Add a per-commit HEAD assertion (step 0) to gsd-executor.md <task_commit_protocol>; HEAD attachment can drift after 'git checkout <sha>'. - Forbid 'git update-ref refs/heads/<protected>' in <destructive_git_prohibition>; surface the blocker rather than self-heal. - Remove '--no-verify' as the worktree-mode default in execute-phase.md, execute-plan.md, quick.md, and references/git-integration.md. Hooks now run on every executor commit; opt out only via workflow.worktree_skip_hooks. - Add regression test that parses the worktree_branch_check blocks structurally and asserts the symbolic-ref check precedes the reset --hard, no workflow performs update-ref on a protected ref, and --no-verify is no longer the default in any parallel-execution prompt. * fix(#2924): address CodeRabbit review findings on worktree HEAD PR - Add positive worktree-agent-* allow-list to <task_commit_protocol> step 0 in gsd-executor.md and to <worktree_branch_check> in execute-phase.md and quick.md. The deny-list (main|master|develop|trunk|release/*) silently allowed feature/* and other arbitrary branches outside the agent namespace. - Register workflow.worktree_skip_hooks in both config schemas (sdk/src/query/config-schema.ts and get-shit-done/bin/lib/config-schema.cjs) and document it in docs/CONFIGURATION.md so config-set accepts it. - Fix stash lifecycle in execute-phase.md post-wave hook validation: stash under a named ref and pop after the hook run; warn on pop failure. - Pre-dispatch PLAN.md commit in quick.md: gate on git diff --cached --quiet for idempotency and exit 1 with a clear error on commit failure (both the --no-verify and the normal branches) — no more swallowing real errors. - Test fixes (tests/bug-2924-worktree-head-attachment.test.cjs): - Parse the protected-branch alternation structurally and require main, master, develop, trunk, release/.* (release/* was previously skipped by the \\b...\\b regex). - Use fs.readdirSync(dir, { recursive: true }) so workflows in nested subdirectories are also asserted against the update-ref ban. - Add allow-list assertions for execute-phase.md, quick.md, and gsd-executor.md to lock in the new positive namespace check. * test(#2924): assert sub-section end marker exists before slicing * test(#2924): use section boundary instead of fixed window for parallel-agents slice |
||
|
|
2cc8796265 |
fix(config-get): return schema default for context_window when absent (#2944)
* fix(config-get): return schema default for context_window when absent (#2943) cmdConfigGet in bin/lib/config.cjs now consults a SCHEMA_DEFAULTS map before emitting "Key not found", so context_window (and any future schema-defaulted keys) return their default value (exit 0) when not set in config.json. Also updates the stale subagent-timeout.test.cjs assertion that expected the old broken behavior (exit 1 / "Key not found") to match the corrected behavior. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: use distinct sentinel to prove --default wins over schema default (#2943) * docs: update CHANGELOG.md for #2943 fix --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
faee0287a0 |
fix(detect-custom-files): add skills/ to GSD_MANAGED_DIRS (#2942) (#2945)
After v1.39.0 skill consolidation (#2790), skills/ became a GSD-managed root that the installer wipes on update. GSD_MANAGED_DIRS in gsd-tools.cjs was missing 'skills', so user-added skill directories (e.g. skills/custom-skill/SKILL.md) were never walked and silently destroyed during /gsd-update. - Add 'skills' to GSD_MANAGED_DIRS so the directory is walked - Add tests/bug-2942-detect-custom-skills.test.cjs with 5 targeted tests - Update tests/update-custom-backup.test.cjs: replace the now-incorrect "skills/ must NOT be scanned" assertion (written pre-#2790) with a test that verifies custom skills ARE detected and GSD-owned skills are not falsely flagged Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
7e9477bb30 |
docs(#2935): refresh README highlights for v1.39.0 across all languages (#2936)
Replaces stale v1.32/v1.37 highlight blocks with v1.39.0 highlights in README.md and four translations, adds /gsd-edit-phase to phase-management tables, documents workstream config inheritance, the post-merge build gate, and per-runtime review.models.<cli> selection. Closes #2935 |
||
|
|
5abf46ac1c |
Merge pull request #2920 from gsd-build/feat/hermes-runtime-2841
feat(install): add Hermes Agent runtime support |
||
|
|
372d3453f5 |
fix(install): tokenize before ALL_RUNTIMES_OPTION check + isolate HERMES_HOME in test
Two CodeRabbit findings on PR #2920: 1. parseRuntimeInput previously only matched the bare "16" exactly for the all-runtimes shortcut. Inputs the prompt explicitly encourages — "16,", "16 1", "1,16" — fell through to per-token parsing and silently installed only Claude or a partial subset. Move the ALL_RUNTIMES_OPTION check after tokenization so any token equal to "16" expands. Added regression coverage in tests/multi-runtime-select.test.cjs for the four mixed-input forms. 2. The "maps Hermes to ~/.hermes for global installs" test invoked getGlobalDir('hermes') without isolating HERMES_HOME. On a developer machine that exports HERMES_HOME the assertion would fail even though getGlobalDir was behaving correctly. Save/clear/restore the env var around the assertion, mirroring the pattern the later describe block already uses. Full suite: 6128/6128 pass. |
||
|
|
c9d6306981 |
fix(hermes): rewrite CLAUDE.md → HERMES.md (revert from .hermes.md per spec)
Per the issue spec for #2841 and CodeRabbit feedback on PR #2920, the project-context filename rewrite should produce HERMES.md, not .hermes.md. Reverts the earlier .hermes.md change at all 5 substitution sites in bin/install.js and updates the corresponding regression test in tests/hermes-install.test.cjs to assert HERMES.md. Full suite: 6127/6127 pass. |
||
|
|
1168e9f59a |
Merge pull request #2921 from gsd-build/fix/2916-handle-branching-default-base
fix(#2916): branch new phases off origin/HEAD instead of current HEAD |
||
|
|
3ed8980519 |
fix(#2916): drop unreachable post-creation merge-base guard
CodeRabbit pointed out the post-creation guard is structurally unreachable: immediately after `git checkout -b X origin/$DEFAULT_BRANCH`, HEAD == origin/$DEFAULT_BRANCH, so both the merge-base form (`MB == DT`) and the alternative "ahead-of" count form (`AHEAD == 0`) are sentinels that always pass on a successful fresh checkout. With the explicit base arg + fail-fast on the checkout, the guard cannot catch anything new. Removing it (rather than swapping in another no-op that satisfies the linter but adds no actual coverage) is the honest fix. Comment retained to explain why no post-creation guard is needed: the explicit base argument to `git checkout -b` is the single source of correctness for #2916. Same simplification mirrored in get-shit-done/workflows/quick.md. Full suite: 6102/6102. |
||
|
|
c3aef27aa6 |
fix(#2916): fail-fast on switch/checkout, gate fork-point warning to fresh branches
Two CodeRabbit findings on PR #2921 (review 4209533909 + comment 3171721073, both still unresolved): A. Branch switch and create steps now abort on non-zero exit. Previously `git switch "$BRANCH_NAME"` and `git checkout -b "$BRANCH_NAME" "origin/$DEFAULT_BRANCH"` could fail (locked worktree, dirty tree refusing the checkout, etc.) and the workflow would silently continue on the wrong branch — sending the phase's later commits to the wrong place. Both calls now `|| { echo "ERROR: …" >&2; exit 1; }`. B. The fork-point base-warning is now scoped to the creation arm of the if/else. Previously it ran for the resume path too, so a legitimate resumed branch where origin/$DEFAULT_BRANCH had advanced since first creation would falsely warn ("does not fork from origin/<DEFAULT_BRANCH>"). Moving the check inside the else arm means it only runs immediately after a fresh `git checkout -b`, when the merge-base check is meaningful. Same fix mirrored in get-shit-done/workflows/quick.md. execute-phase.md stays at the 1700-line XL budget. Full suite: 6102/6102. |
||
|
|
ace61869d0 |
test(#2916): parameterize fixtures so both main and trunk are exercised
Two follow-ups on commit |
||
|
|
80f14cac1f |
test(#2916): scope branch_name scan to init step and harden fixture
- Restrict the "init parse list includes branch_name" assertion to the bash blocks inside Step 2 (Initialize) so an unrelated step that mentions branch_name cannot mask the contract. - Switch the fixture's default branch from main to trunk so the symbolic-ref code path is locked in: a regression that silently defaults to "main" instead of consulting origin/HEAD now fails. Addresses CodeRabbit review on PR #2921. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
2256e4c9a3 |
fix(#2916): use fork-point detection for non-default-base warning
Replace the "ahead-of" heuristic with a structural check that compares the HEAD↔origin/$DEFAULT_BRANCH merge-base to origin/$DEFAULT_BRANCH itself. The previous count-based warning fired on legitimate WIP that was simply ahead of the default branch — the correct signal is that the branch did not fork from the default branch in the first place. Addresses CodeRabbit review on PR #2921. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
e5cd523e7b | test(hermes): use parseFrontmatter for agent assertion (CR #2920) | ||
|
|
b5777572f7 | docs(readme): add Hermes uninstall examples (CR #2920) | ||
|
|
861a7d972b |
test(install): replace source-grep prompt assertions with structured checks
Two test files were asserting installer prompt behavior by regex/.includes() against bin/install.js source. Per CONTRIBUTING.md "no-source-grep" testing standard, replace with structured assertions: - tests/kilo-install.test.cjs: import runtimeMap and buildRuntimePromptText from the install module; assert runtimeMap['11'] === 'kilo' and that the rendered prompt lists Kilo above OpenCode without marketing copy. - tests/multi-runtime-select.test.cjs: import runtimeMap, allRuntimes, parseRuntimeInput, buildRuntimePromptText. Assert exported runtimeMap matches the canonical option list, allRuntimes contains every runtime exactly once, prompt text lists Hermes (10), Qwen Code (13), Trae (14), All (16), and parser splits/dedupes by exercising parseRuntimeInput rather than regexing source code. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
bd0511988b |
fix(hermes): nest GSD skills under skills/gsd/ category (#2841)
Per spec in #2841, all 86 GSD skills must collapse into a single "gsd" category in Hermes' system prompt. Previous code passed skills/ as the install root, producing a flat skills/gsd-*/ layout that inflated Hermes' loader output to 86 top-level entries. Changes: - Install path now writes to skills/gsd/{DESCRIPTION.md, gsd-*/SKILL.md} - Uninstall removes the entire skills/gsd/ category dir plus any leftover flat-layout gsd-*/ from older installs (graceful migration) - writeManifest emits skills/gsd/<skill>/<file> paths for Hermes - --skills-root hermes returns the nested category path so /gsd-sync-skills syncs into the right directory - DESCRIPTION.md at category root carries name/version/description so Hermes' skill loader surfaces the GSD category in the system prompt Also extracts promptRuntime's runtimeMap, allRuntimes, parseRuntimeInput, and buildRuntimePromptText to module scope and exports them so tests can assert structurally instead of grepping bin/install.js source. Existing hermes-install tests updated to expect the nested layout and to verify the category DESCRIPTION.md frontmatter (name, version, description) using the shared parseFrontmatter helper. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
4a5f36df5e |
Merge pull request #2919 from gsd-build/fix/2911-audit-open-output-references
fix(#2911): audit-open emits raw human report and parseable JSON |
||
|
|
840f2b349e |
Merge pull request #2918 from gsd-build/worktree-agent-a4db9db3f3106d4d7
fix(progress): explicit context-authority directive in report step |
||
|
|
140d334dab |
test(#2916): replace string-grep assertions with behavioral fixture test
CodeRabbit nitpick (per project policy `feedback_no_source_grep_tests`):
the prior `tests/quick-branching.test.cjs` asserted branching correctness
by `.includes()`-grepping the raw markdown content for literal command
substrings. Those assertions stayed green even when the underlying
behavior regressed (e.g. when `git checkout -b` was unconditionally run
from the wrong HEAD).
Replace with the same pattern as `bug-2916-handle-branching-default-base
.test.cjs`:
- Structurally extract the Step 2.5 bash block from quick.md by
walking the markdown for fenced ```bash blocks under the heading
(no regex on prose).
- Spin up a fixture git repo with a bare origin, a clone whose
`origin/HEAD` points at `main`, and a checked-out previous-task
branch carrying its own unmerged commit.
- Execute the extracted bash block via `bash -c` and assert that
the new branch's tip equals `origin/main` (0 commits inherited
from the previous-task HEAD).
- Add a reuse test that pre-creates the target branch with its own
commit and verifies the script switches back to it without a
rebase or reset.
The two informational tests (workflow file exists, branching runs
before task-directory creation) are retained, plus the `branch_name`
parsing assertion is rewritten to walk fenced bash blocks rather than
substring-grep arbitrary content.
|
||
|
|
6e4fad7acc |
Merge pull request #2933 from gsd-build/chore/2932-coderabbit-docstring-off
chore(ci): disable CodeRabbit docstring coverage check |
||
|
|
4e2f1105d9 |
fix(#2916): pin new-branch base to origin/$DEFAULT_BRANCH explicitly
Address CodeRabbit HIGH findings on PR #2921. The previous fix had three unconditional code paths where `git checkout -b "$BRANCH_NAME"` would run from the *current* HEAD when the upstream sync failed silently: - the dirty-tree warn-and-continue path, - the clean path where `git switch` / `git merge --ff-only` errors were swallowed by `2>/dev/null` (still falling through to checkout -b), - any case where `git fetch` failed but the script continued. This rewrites both `execute-phase.md` (handle_branching) and `quick.md` (Step 2.5) to: 1. Fetch origin/$DEFAULT_BRANCH; if fetch fails AND no local copy of origin/$DEFAULT_BRANCH exists, abort with a clear ERROR (exit 1) rather than create the branch off arbitrary HEAD. 2. Always create the new branch with an explicit start point: `git checkout -b "$BRANCH_NAME" "origin/$DEFAULT_BRANCH"`. The base is now deterministic regardless of which branch is currently checked out, regardless of whether the optional local fast-forward succeeded, and regardless of dirty-tree state. 3. Carry uncommitted changes onto the new (origin-pinned) branch instead of inheriting the previous-phase HEAD as a fallback base. The post-creation INHERITED check now references origin/$DEFAULT_BRANCH rather than the (possibly-stale) local default branch, so the warning fires accurately even when the local fast-forward was skipped. |
||
|
|
4ce72cdee7 |
fix(hermes): align with Hermes Agent conventions per docs review
Four fixes from review of hermes-agent.nousresearch.com docs: 1. SKILL.md frontmatter now declares `version` (required field per Hermes spec). Plumbed through `convertClaudeCommandToClaudeSkill` gated on runtime='hermes' so other runtimes' frontmatter is unchanged. 2. Project-context filename rewrite changed from `HERMES.md` (not discovered by Hermes) to `.hermes.md` (top of Hermes' discovery list: .hermes.md → AGENTS.md → CLAUDE.md → .cursorrules). 3. README + finishInstall now show `/gsd-help` and `/gsd-new-project` for Hermes; per docs, Hermes auto-exposes skills as slash commands. 4. Hermes tests now parse SKILL.md frontmatter structurally via the shared parseFrontmatter helper instead of substring-matching source text, and assert the version/name/description shape required by Hermes' skill_view(). Full suite: 6128/6128 pass (3 new structural assertions). |
||
|
|
198022f58d |
chore(ci): disable CodeRabbit docstring coverage check (#2932)
The docstring coverage pre-merge check (default: warning at 80% threshold)
produces false-positive warnings on PRs whose new code is entirely test
files: it counts test(...) / beforeEach / afterEach arrow-function
callbacks as functions and reports 0% coverage because nothing has JSDoc.
CR's documented schema for reviews.pre_merge_checks.docstrings only
accepts `mode` and `threshold` — there is no per-check path filter that
would let us exclude tests/** while keeping the check active elsewhere.
The top-level path_filters approach would silence ALL CR review on test
files (security scans, out-of-scope checks, the substantive line-level
findings) which we want to keep.
Disabling the check entirely is the right call for this repo because:
- GSD ships a CLI + agent runtime, not a documented public library
- The internal helpers that warrant JSDoc already have it
- The other CR pre-merge checks (out-of-scope, security, title) are
meaningful for this codebase and stay enabled
Closes #2932
|
||
|
|
ac100ae17b |
test: assert reportStep present before extractBlockquotes (CR #2918)
Two existing tests called extractBlockquotes(reportStep) without first asserting reportStep was non-null. If the workflow file ever loses its `<step name="report">` block, the test would fail with a confusing TypeError on the destructuring inside extractBlockquotes instead of a clear "report step must exist" assertion. Add assert.ok(reportStep, ...) guards at the two missing call sites (lines 100 and 130). The other two call sites (lines 75-83) already had guards. Addresses CodeRabbit comment on PR #2918. |
||
|
|
002db4dd2b |
Merge pull request #2931 from gsd-build/feat/2929-release-sdk-parity
ci(release-sdk): bring CI gates to parity with release.yml |
||
|
|
0e0f6952c5 |
ci(release-sdk): bring CI gates to parity with release.yml (#2929)
Ports the pre-publish CI gates that release.yml applies into release-sdk.yml,
so the stopgap workflow ships releases at the same quality bar as the
canonical workflow (minus the @gsd-build/sdk publish, still intentionally
omitted, and the release-branch ceremony, intentionally omitted).
Changes (all mechanical copies of release.yml patterns):
- install-smoke as needs: dependency. The reusable workflow at
.github/workflows/install-smoke.yml runs the cross-platform install
matrix (Ubuntu 22/24, macOS 24, packed-vs-unpacked). Publish job
won't start until install-smoke passes for the dispatched ref.
- npm test → npm run test:coverage. Full coverage gate, matching
release.yml's pre-publish test step.
- Tolerant tag-existence check. The previous upfront "refuse if tag
exists" was too strict — operators re-running after a mid-flight
publish-step failure would be blocked by the tag they successfully
pushed last time. New behavior matches release.yml: skip the tag
step if the tag points at HEAD; error only if it points elsewhere.
- Tag-and-push step gets the same skip-if-at-HEAD pattern.
- New "Re-point next dist-tag at the new latest" step, gated on
tag=latest. Matches release.yml#finalize "Clean up next dist-tag" —
keeps @next from going stale relative to @latest.
- New "Create GitHub Release" step. Per-tag flag selection:
tag=dev, tag=next → --prerelease (won't be highlighted on repo home)
tag=latest → --latest (becomes the highlighted release)
All use --generate-notes so the release body auto-fills from commits.
- Summary updated to mention the GitHub Release and dist-tag re-point.
Out of scope per #2929:
- canary.yml, release.yml unchanged (verified by file diff)
- bin/install.js unchanged (install path already uses bundled SDK)
- No @gsd-build/sdk publish anywhere
- No release/X.Y.Z branch ceremony (this stopgap targets dispatched
ref directly)
|
||
|
|
bdead2ee6a |
Merge pull request #2927 from gsd-build/feat/2925-release-sdk-main
feat(ci): release-sdk.yml stopgap workflow for dev/next/latest CC publishes |
||
|
|
e107bb35d4 |
feat(ci): add release-sdk.yml stopgap workflow for dev/next/latest CC publishes (#2925)
Adds a workflow_dispatch-only release path that publishes get-shit-done-cc to ONE chosen dist-tag per run (dev | next | latest), with the SDK bundled inside the CC tarball both as the existing loose sdk/dist/ tree and as a fresh sdk-bundle/gsd-sdk.tgz npm-installable artifact. Why: @gsd-build/sdk publishes from canary.yml and release.yml fail because the @gsd-build npm token is currently unavailable. CC users don't consume @gsd-build/sdk directly — bin/gsd-sdk.js resolves sdk/dist/cli.js from inside the installed CC package. This workflow ships only get-shit-done-cc (which we hold the token for) and bundles the SDK two ways so any future install path can pick whichever shape it needs. The new sdk-bundle/ directory is added to the CC files whitelist in-tree at build time only — never committed. Existing canary.yml and release.yml are intentionally untouched; restore them to primary use once the @gsd-build/sdk token is recovered. Per-tag version derivation when the version input is empty: - dev → <base>-dev.N (next sequential, scanning v<base>-dev.* tags) - next → <base>-rc.N (matches release.yml convention) - latest → <base> (clean, no suffix) Refuses to publish when the version already exists on npm or has an existing git tag (no accidental overwrites). Verifies the publish landed on the registry and the dist-tag resolves correctly before marking the run successful. |
||
|
|
294564b951 |
fix(#2916): branch new phases off origin/HEAD instead of current HEAD
handle_branching in execute-phase.md (and the equivalent step in quick.md) created the per-phase branch from whatever branch happened to be checked out — typically the previous phase's still-unmerged feature branch — so consecutive phases compounded on top of each other and stayed unpushed. Detect the default branch via git symbolic-ref refs/remotes/origin/HEAD, fast-forward it from origin, and fork the new phase branch off that tip. Existing branches are still reused as-is. Dirty working trees fall back to current HEAD with a loud warning, and a post-creation guard reports any inherited commits. Regression test extracts the bash from the <step name="handle_branching"> block structurally and runs it against a fixture repo where HEAD sits on a previous-phase branch with extra commits. |