mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-05-05 23:02:20 +02:00
Compare commits
254 Commits
fix/2783-b
...
adr/0002-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a411e08e88 | ||
|
|
b752a9aae7 | ||
|
|
ecf3510511 | ||
|
|
81f9534b5a | ||
|
|
695ad986c0 | ||
|
|
519de8a91d | ||
|
|
c2b3f02d41 | ||
|
|
9811782e6d | ||
|
|
669d6a1f32 | ||
|
|
ba0409e04e | ||
|
|
d993e71adf | ||
|
|
47ed26a01b | ||
|
|
7827e1ddee | ||
|
|
375bf3abd6 | ||
|
|
b0be6755e7 | ||
|
|
3f57a13ccf | ||
|
|
3e2682d3c9 | ||
|
|
ad8ba840bc | ||
|
|
622f3a8ea4 | ||
|
|
5d1e485d05 | ||
|
|
4ab1da354e | ||
|
|
48f09d34af | ||
|
|
9de8e24463 | ||
|
|
811410be61 | ||
|
|
891eae1025 | ||
|
|
858c821829 | ||
|
|
851cddcc03 | ||
|
|
61773332d6 | ||
|
|
9987792c46 | ||
|
|
aa64638176 | ||
|
|
be4a9b3b43 | ||
|
|
e7ecd46bbe | ||
|
|
985b736d45 | ||
|
|
d3d995cfc4 | ||
|
|
43e5fef95e | ||
|
|
083e813aea | ||
|
|
fe4db16769 | ||
|
|
399bb80b40 | ||
|
|
d978ad6b2f | ||
|
|
0fe88b9e7a | ||
|
|
baf0d56063 | ||
|
|
d2d1205691 | ||
|
|
1c1e3b5de4 | ||
|
|
a6d4e61606 | ||
|
|
e2b12bfad2 | ||
|
|
915e7daced | ||
|
|
313f170cf0 | ||
|
|
199083777a | ||
|
|
dbbc7f0942 | ||
|
|
2113902daf | ||
|
|
f01f6b76dd | ||
|
|
4ee6ce4a01 | ||
|
|
67684626d8 | ||
|
|
b331c48261 | ||
|
|
3d2f2e85a0 | ||
|
|
5b63ba6ea9 | ||
|
|
a4d16c3c93 | ||
|
|
78846b1e6a | ||
|
|
59fd17251a | ||
|
|
efa642a078 | ||
|
|
120113c42b | ||
|
|
2d25c97706 | ||
|
|
2dcf374da0 | ||
|
|
50f714cdd5 | ||
|
|
471df09242 | ||
|
|
ecd5d11b32 | ||
|
|
58062a64a0 | ||
|
|
65024683fd | ||
|
|
72f4c3b362 | ||
|
|
538ef683be | ||
|
|
c7886415c3 | ||
|
|
a54dda3837 | ||
|
|
19e580137d | ||
|
|
78c794c016 | ||
|
|
40acf1f02e | ||
|
|
1642f47908 | ||
|
|
38718e9d4b | ||
|
|
a441f96f37 | ||
|
|
0500bdf619 | ||
|
|
c6a35d6398 | ||
|
|
969cfcf998 | ||
|
|
e0c791a5d0 | ||
|
|
deb4477375 | ||
|
|
5aaf0dbea5 | ||
|
|
ace241d0c2 | ||
|
|
0fffc7c055 | ||
|
|
6059a574f2 | ||
|
|
b0e616288b | ||
|
|
ed9d67c91b | ||
|
|
97019d274e | ||
|
|
7311e0a9ab | ||
|
|
c66ff96de8 | ||
|
|
a24de43f8b | ||
|
|
70faa0ff0f | ||
|
|
b9e3979fc1 | ||
|
|
c7d3f83b8b | ||
|
|
bc289fad4a | ||
|
|
9bee4dce4a | ||
|
|
9a469fa05c | ||
|
|
abf7779088 | ||
|
|
16bf552037 | ||
|
|
009cfb1562 | ||
|
|
6fe4af2546 | ||
|
|
41683b2f53 | ||
|
|
7dcafbc211 | ||
|
|
ccda572ade | ||
|
|
1ca7f58831 | ||
|
|
7298a76b20 | ||
|
|
5cfd874058 | ||
|
|
ba6100c548 | ||
|
|
9f5b011b35 | ||
|
|
1037b82a98 | ||
|
|
ac883f8150 | ||
|
|
3e22c70fac | ||
|
|
12fc34689e | ||
|
|
9d096b9925 | ||
|
|
42ed7cee8d | ||
|
|
5e21bf7567 | ||
|
|
9c92c32f6e | ||
|
|
5c9f34bd31 | ||
|
|
b6c401dc90 | ||
|
|
c3f896f311 | ||
|
|
f104dab332 | ||
|
|
5975f06b6a | ||
|
|
0f98952a3d | ||
|
|
eb365f7336 | ||
|
|
1e6737cd8e | ||
|
|
dca12242b5 | ||
|
|
7714b5244b | ||
|
|
117b3ec009 | ||
|
|
95d2bc20f8 | ||
|
|
35fffe7f31 | ||
|
|
d137ce86ec | ||
|
|
8c43ba7301 | ||
|
|
e1d661ece0 | ||
|
|
d812c66020 | ||
|
|
c9f5b7daac | ||
|
|
6df9b44297 | ||
|
|
e3b64b39f8 | ||
|
|
8e25eb6546 | ||
|
|
f2decefede | ||
|
|
a4e5cc7c24 | ||
|
|
f55069ecbf | ||
|
|
de25400b70 | ||
|
|
ca78b65de7 | ||
|
|
1a51ec5829 | ||
|
|
4277f7d7e8 | ||
|
|
cde793f1f0 | ||
|
|
ffeeb92c14 | ||
|
|
4e378d37d8 | ||
|
|
9f09246f3b | ||
|
|
c2ada7e799 | ||
|
|
55ae8e42d2 | ||
|
|
3657c4ea9e | ||
|
|
918f987a19 | ||
|
|
17a4321bf5 | ||
|
|
9d5db87249 | ||
|
|
cb98a88139 | ||
|
|
fb92d1e596 | ||
|
|
7424271aa0 | ||
|
|
7a416b10d4 | ||
|
|
ef43f5161f | ||
|
|
e9a66da1e7 | ||
|
|
b8d9bd69b2 | ||
|
|
0d25ef0c47 | ||
|
|
a346779213 | ||
|
|
0d6abb87ac | ||
|
|
c5dfdbe42e | ||
|
|
9d0d085a17 | ||
|
|
53cda93a01 | ||
|
|
ec07861228 | ||
|
|
3ba17e872e | ||
|
|
4d628b306a | ||
|
|
b328f3269f | ||
|
|
e2792536d9 | ||
|
|
7cc6358f91 | ||
|
|
8de8acee46 | ||
|
|
2cc8796265 | ||
|
|
faee0287a0 | ||
|
|
7e9477bb30 | ||
|
|
5abf46ac1c | ||
|
|
372d3453f5 | ||
|
|
c9d6306981 | ||
|
|
1168e9f59a | ||
|
|
3ed8980519 | ||
|
|
c3aef27aa6 | ||
|
|
ace61869d0 | ||
|
|
80f14cac1f | ||
|
|
2256e4c9a3 | ||
|
|
e5cd523e7b | ||
|
|
b5777572f7 | ||
|
|
861a7d972b | ||
|
|
bd0511988b | ||
|
|
4a5f36df5e | ||
|
|
840f2b349e | ||
|
|
140d334dab | ||
|
|
6e4fad7acc | ||
|
|
4e2f1105d9 | ||
|
|
4ce72cdee7 | ||
|
|
198022f58d | ||
|
|
ac100ae17b | ||
|
|
002db4dd2b | ||
|
|
0e0f6952c5 | ||
|
|
bdead2ee6a | ||
|
|
e107bb35d4 | ||
|
|
294564b951 | ||
|
|
9a13d2fc0b | ||
|
|
d29822c1da | ||
|
|
b126c0579a | ||
|
|
006cdafe8f | ||
|
|
8051bc4fd8 | ||
|
|
444db1714b | ||
|
|
6dce1de4a7 | ||
|
|
abb2cb63f6 | ||
|
|
8cbdbdd2de | ||
|
|
951d5bf7c0 | ||
|
|
ca88429bf8 | ||
|
|
5fdc950eb7 | ||
|
|
c72b893916 | ||
|
|
8fc1fa263c | ||
|
|
87917131f2 | ||
|
|
55298b2f70 | ||
|
|
4d394a249d | ||
|
|
73b9d1dac0 | ||
|
|
99af76b3ba | ||
|
|
ef08a89241 | ||
|
|
f2ada8500c | ||
|
|
f6a6e43226 | ||
|
|
107a83ebf7 | ||
|
|
43a13217b7 | ||
|
|
2498f5649d | ||
|
|
e81592878e | ||
|
|
4815b3c972 | ||
|
|
f9ed47ac8b | ||
|
|
91194cdbff | ||
|
|
74b81379cf | ||
|
|
12b6ba4e34 | ||
|
|
f4412349f0 | ||
|
|
a7f83ee663 | ||
|
|
7fae804296 | ||
|
|
c3a42d66f9 | ||
|
|
0acf1de88c | ||
|
|
5a636bc90a | ||
|
|
eeaf9c556f | ||
|
|
9e58c45ea1 | ||
|
|
897cff6051 | ||
|
|
a4e15d5616 | ||
|
|
eddb2a205b | ||
|
|
5fe1f00a0d | ||
|
|
fa78692167 | ||
|
|
b959b1844f | ||
|
|
7616309a32 | ||
|
|
d46efb4790 | ||
|
|
055b43054f |
44
.changeset/README.md
Normal file
44
.changeset/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Changeset Fragments
|
||||
|
||||
This directory holds **per-PR CHANGELOG fragments**. Every PR with user-facing changes drops one (or more) `<random-name>.md` files here describing its CHANGELOG entry. Fragments are consolidated into the top-level `CHANGELOG.md` at release time.
|
||||
|
||||
## Why
|
||||
|
||||
Two PRs that both edit the `### Fixed` block of `CHANGELOG.md` always conflict on merge — git can't pick a serialization order without human input. Two PRs that each add a fresh `.changeset/<unique-name>.md` never conflict because they don't share lines.
|
||||
|
||||
See [#2975](https://github.com/gsd-build/get-shit-done/issues/2975) for the full rationale.
|
||||
|
||||
## Adding a fragment
|
||||
|
||||
```bash
|
||||
node scripts/changeset/new.cjs \
|
||||
--type Fixed \
|
||||
--pr 1234 \
|
||||
--body "fix the thing — explain the user-visible change in one sentence"
|
||||
```
|
||||
|
||||
This writes `.changeset/<adjective>-<noun>-<noun>.md` with frontmatter and a body. Three random words → concurrent PRs don't collide.
|
||||
|
||||
## Format
|
||||
|
||||
```md
|
||||
---
|
||||
type: Fixed
|
||||
pr: 1234
|
||||
---
|
||||
**`/gsd-foo` no longer drops trailing slashes** — explain the user-visible change.
|
||||
```
|
||||
|
||||
Allowed `type:` values follow [Keep a Changelog](https://keepachangelog.com/): `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`.
|
||||
|
||||
## Opting out
|
||||
|
||||
PRs that legitimately have no user-facing impact can add the `no-changelog` label. CI honors it. When unsure, add the fragment.
|
||||
|
||||
## At release time
|
||||
|
||||
```bash
|
||||
node scripts/changeset/cli.cjs render --version vX.Y.Z --date YYYY-MM-DD
|
||||
```
|
||||
|
||||
Reads every fragment, groups bullets by `type:`, replaces `## [Unreleased]` with a new `## [vX.Y.Z] - YYYY-MM-DD` block, opens a fresh `## [Unreleased]` above, deletes consumed fragments. Idempotent.
|
||||
11
.changeset/adr-0002-command-contract-validation.md
Normal file
11
.changeset/adr-0002-command-contract-validation.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3152
|
||||
---
|
||||
**Command contract validation now enforced in CI (ADR-0002)** — \`scripts/lint-command-contract.cjs\` runs as a pre-test step and validates every \`commands/gsd/*.md\` file against five rules: \`name:\` present + \`gsd:\` prefix, \`description:\` non-empty, \`allowed-tools:\` entries canonical, \`execution_context\` @-refs resolve on disk, @-refs on their own line. Prevents the \`add-backlog.md\`-class gap from silently reappearing on consolidation PRs.
|
||||
|
||||
**~900 tokens/invocation recovered** — prose \`@~/.claude/get-shit-done/...\` path tokens removed from \`<process>\` blocks in 39 command files. The \`<execution_context>\` block is now the single authoritative load declaration; the duplicate prose copies were inert but consumed context on every command invocation.
|
||||
|
||||
**~3,750 tokens removed from eager session load** — \`/gsd-debug\` (9,603 → 1,703 chars) and \`/gsd-thread\` (7,868 → 585 chars) now follow the workflow-delegation pattern used by all other commands. Their implementations moved to \`get-shit-done/workflows/debug.md\` and \`get-shit-done/workflows/thread.md\`. Behavior is unchanged.
|
||||
|
||||
\`get-shit-done/workflows/extract_learnings.md\` renamed to \`extract-learnings.md\` to match the hyphen convention of all other workflow files. Closes #3151.
|
||||
5
.changeset/blue-stones-topology.md
Normal file
5
.changeset/blue-stones-topology.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
---
|
||||
|
||||
**Query command dispatch deepened with Command Topology Module** — query dispatch now consumes a single topology seam that resolves command tokens, binds native handler adapters, and returns structured no-match diagnosis, improving locality and reducing dispatch seam drift.
|
||||
5
.changeset/bold-finches-rally.md
Normal file
5
.changeset/bold-finches-rally.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3058
|
||||
---
|
||||
**GSD transport raw-mode handling and timeout fallback hardened** — fixes undefined raw formatting edge case and adds raw-path coverage to prevent regressions.
|
||||
8
.changeset/brave-mice-build.md
Normal file
8
.changeset/brave-mice-build.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3069
|
||||
---
|
||||
|
||||
**query command metadata now flows through a canonical Command Definition Module seam** — registry assembly, mutation semantics, and alias generation consume one Interface (`family`, `canonical`, `aliases`, `mutation`, `output_mode`, `handler_key`) to improve locality and reduce drift.
|
||||
|
||||
**query fallback error mapping cleanup** — the CJS fallback catch path now passes original `err` to `mapFallbackDispatchError` (follow-up to prior review feedback missed in PR #3066).
|
||||
6
.changeset/bright-pumas-fold.md
Normal file
6
.changeset/bright-pumas-fold.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3075
|
||||
---
|
||||
|
||||
**query architecture deepening pass** — extracted Query Runtime Context, Native Dispatch Adapter, and Query CLI Output Modules so dispatch policy, runtime context policy, and CLI projection logic each live behind focused seams with higher locality and leverage.
|
||||
5
.changeset/calm-birds-greet.md
Normal file
5
.changeset/calm-birds-greet.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2990
|
||||
---
|
||||
gsd-code-fixer worktree no longer fails on the same-branch checkout — the agent now creates a new gsd-reviewfix/ branch via git worktree add -b and fast-forwards the user's branch on cleanup. See #2990.
|
||||
5
.changeset/calm-ibex-jump.md
Normal file
5
.changeset/calm-ibex-jump.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 2986
|
||||
---
|
||||
Test suite for config-schema.cjs is now mutation-resistant — 95 typed assertions kill the 124 surviving Stryker mutants from the 4.62% baseline. Tests target static-key fast path, dynamic-pattern .some semantics, polarity, and regex-anchor tightening. See #2986.
|
||||
5
.changeset/calm-tigers-frolic.md
Normal file
5
.changeset/calm-tigers-frolic.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3008
|
||||
---
|
||||
**`tests/install-minimal.test.cjs:307` no longer races on shared `os.tmpdir()` under parallel CI** — the previous shape compared `listTmpStageDirs()` snapshots before and after the throw. Under `scripts/run-tests.cjs --test-concurrency=4`, `tests/install-minimal-all-runtimes.test.cjs` runs in a parallel process and creates/removes `gsd-minimal-skills-*` dirs in the shared OS tmpdir between snapshots, so `deepStrictEqual` failed deterministically when the parallel process happened to have a live stage dir during the snapshot window. Fix: stub `fs.mkdtempSync` to record THIS call's stage dir, then assert that exact path no longer exists after the throw — no global filesystem snapshot, no race. (#3008)
|
||||
5
.changeset/codex-bare-node-fix.md
Normal file
5
.changeset/codex-bare-node-fix.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3022
|
||||
---
|
||||
**Codex SessionStart hook now uses absolute Node binary path** — closes the gap left after #3002. The Codex install path wrote `command = "node ${path}"` directly into config.toml, bypassing `resolveNodeRunner()`. Under GUI/minimal-PATH runtimes (`/usr/bin:/bin:/usr/sbin:/sbin`), bare `node` failed to resolve, exit 127. Now routed through new `buildCodexHookBlock()` helper. Reinstall path migrates legacy bare-node entries via new `rewriteLegacyCodexHookBlock()`. See #3017.
|
||||
5
.changeset/codex-discuss-fallback.md
Normal file
5
.changeset/codex-discuss-fallback.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: TBD
|
||||
---
|
||||
**Codex skill adapter no longer instructs the agent to silently default discuss-phase decisions.** When `request_user_input` was rejected (Default mode), the generated adapter said "pick a reasonable default" — so `$gsd-discuss-phase` proceeded toward writing CONTEXT.md / DISCUSSION-LOG.md / checkpoints without ever asking the user. Adapter prose now requires the agent to STOP, present plain-text questions, and wait, with explicit named exceptions (`--auto`/`--all`/explicit user approval). See #3018.
|
||||
6
.changeset/cool-monkeys-smell.md
Normal file
6
.changeset/cool-monkeys-smell.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3074
|
||||
---
|
||||
|
||||
**query CLI path extracted into a dedicated Query CLI Adapter Module** — `sdk/src/cli.ts` now delegates query-specific dispatch, error mapping, and output/exit handling to `sdk/src/query/query-cli-adapter.ts` for better locality and testability.
|
||||
5
.changeset/curious-bears-march.md
Normal file
5
.changeset/curious-bears-march.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3012
|
||||
---
|
||||
**Post-install message and update.md no longer recommend the removed `/gsd-reapply-patches` command** — after PR #2824 consolidated 86 skills into ~58, `/gsd-reapply-patches` was folded into a flag (`/gsd-update --reapply`). The 1.39.1 hotfix (#2954) updated `help.md` but missed `bin/install.js`'s `reportLocalPatches` runtime emitter, `get-shit-done/workflows/update.md` Step 4, and the English + zh-CN/ja-JP/ko-KR doc set. Users hit "Unknown command" after every install with backed-up patches. All five runtime branches in `reportLocalPatches` (claude, opencode, kilo, copilot, gemini, codex, cursor) now emit the consolidated form. Regression: `tests/bug-3010-reapply-patches-references.test.cjs` scans `bin/install.js`, every workflow file, and every doc (excluding CHANGELOG history and help.md's deprecation notice) for stale recommendations. See #3010.
|
||||
5
.changeset/docs-1-40-0-audit.md
Normal file
5
.changeset/docs-1-40-0-audit.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 0
|
||||
---
|
||||
**Documentation refreshed for v1.40.0** — full audit of `docs/` against the 1.40.0-rc.1 release surface. Updates command lists, walkthroughs, and inventory rows for the 86→59 skill consolidation (#2790), the six namespace meta-skills with two-stage routing (#2792), the `/gsd-health --context` guard, the phase-lifecycle status-line read-side (#2833), and the Gemini colon-form / non-Gemini hyphen-form slash-command split. Translations in ja-JP/ko-KR/zh-CN/pt-BR mirror the structural changes; new English prose is marked with `<!-- TODO i18n -->` for human translator follow-up. CHANGELOG.md `[Unreleased]` section regrouped under Feature/Enhancement/Fix headers.
|
||||
5
.changeset/dynamic-routing.md
Normal file
5
.changeset/dynamic-routing.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: TBD
|
||||
---
|
||||
**`dynamic_routing` block in `.planning/config.json` for failure-tier escalation (#3024).** Each agent declares a default tier (`light` / `standard` / `heavy`); when `dynamic_routing.enabled: true`, the resolver picks `tier_models[default_tier]` for the first spawn and escalates one tier up on orchestrator-detected soft failure (capped by `max_escalations`). Disabled by default — fully backward compatible. Composes with `model_overrides` (higher precedence) and `models.<phase_type>` (lower) for full cost-control flexibility. Adds new resolver `resolveModelForTier(cwd, agent, attempt)` to `core.cjs` for orchestrator integration.
|
||||
5
.changeset/eager-hawks-rally.md
Normal file
5
.changeset/eager-hawks-rally.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2975
|
||||
---
|
||||
**Changeset-fragment workflow** — eliminates CHANGELOG.md merge conflicts. Each PR drops `.changeset/<random-name>.md` with frontmatter (`type:`, `pr:`) plus a markdown body; the release-time `npm run changelog:render` consolidates fragments into `CHANGELOG.md` and deletes them. CI lint (`npm run lint:changeset`) requires a fragment on any PR touching user-facing files (`bin/`, `get-shit-done/`, `agents/`, `commands/`, `hooks/`, `sdk/src/`); contributors can opt out via the `no-changelog` label for purely internal changes. See [.changeset/README.md](.changeset/README.md) and CONTRIBUTING.md for the workflow.
|
||||
5
.changeset/fix-3054-doc-anchor-and-token-check.md
Normal file
5
.changeset/fix-3054-doc-anchor-and-token-check.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3114
|
||||
---
|
||||
**`/gsd-progress --next` doc migration is fully consistent** — command docs now use clear `--next` wording, FEATURES TOC anchors match renamed headings, and regression tests enforce stale-command detection via structured slash-command token checks.
|
||||
5
.changeset/fix-3056-worktree-path-assertion.md
Normal file
5
.changeset/fix-3056-worktree-path-assertion.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3117
|
||||
---
|
||||
**Worktree prune regression checks are now path-normalized** — pruning safety tests now parse `git worktree list --porcelain` and assert structured normalized paths, preventing path-separator false negatives across platforms while preserving non-destructive prune guarantees.
|
||||
5
.changeset/fix-3072-findings-probe-assertions.md
Normal file
5
.changeset/fix-3072-findings-probe-assertions.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3119
|
||||
---
|
||||
**Optional findings probe guard checks now use structured parsing** — regression tests now parse fenced bash blocks and validate sketch/spike findings probes as structured command records, ensuring non-fatal `|| true` guards are enforced without raw source grep assertions.
|
||||
5
.changeset/fix-3087-planner-directive-language.md
Normal file
5
.changeset/fix-3087-planner-directive-language.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3138
|
||||
---
|
||||
**`gsd-planner.md` directive language restored** — 10 instances of `CRITICAL`/`MANDATORY`/`ALWAYS`/`MUST` emphasis were silently removed in v1.38.4 (PR #2489) without documentation, conflicting with that release's stated sycophancy-hardening intent. Downstream effect: planner output in v1.38.4–v1.40.x exhibited weaker adherence to user decisions and requirement coverage, as observed in #3087. Restored: `CRITICAL: User Decision Fidelity`, `CRITICAL: Never Simplify User Decisions`, `Multi-Source Coverage Audit (MANDATORY in every plan set)`, `Audit ALL four source types`, `Discovery is MANDATORY`, `ALWAYS split if:`, `requirements MUST list`, `CRITICAL: Every requirement ID MUST appear`, `ALWAYS use the Write tool`, and `CRITICAL — File naming convention`. Closes #3087.
|
||||
5
.changeset/fix-3088-milestone-state-fallback-sections.md
Normal file
5
.changeset/fix-3088-milestone-state-fallback-sections.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3122
|
||||
---
|
||||
**Milestone close now repairs missing STATE narrative sections** — when `## Current Position` or `## Operator Next Steps` headings are absent, milestone completion appends canonical sections so state remains deterministic and consistently points operators to `/gsd-new-milestone`.
|
||||
5
.changeset/fix-3094-progress-stale-assumptions.md
Normal file
5
.changeset/fix-3094-progress-stale-assumptions.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3111
|
||||
---
|
||||
**Progress routing command guidance remains canonical** — pre-planning assumption checks in progress routing now consistently assert and document `/gsd-discuss-phase` as the replacement path, with tests enforcing structured slash-command token checks.
|
||||
5
.changeset/fix-3096-ai-integration-parallel-race.md
Normal file
5
.changeset/fix-3096-ai-integration-parallel-race.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3096
|
||||
---
|
||||
**`ai-integration-phase` Steps 7+8 now enforce sequential execution and Edit-only tool discipline** — when `gsd-ai-researcher` and `gsd-domain-researcher` were dispatched in parallel (an optimization an orchestrator could reasonably make since the sections appeared disjoint), `gsd-domain-researcher`'s `Write` call at finalization silently replaced the entire AI-SPEC.md with its pre-researcher copy, losing Sections 3/4. Confirmed at 40% incidence rate (2 of 5 agents on a real run). Fix adds an explicit sequential ordering note to Steps 7+8 ("MUST run sequentially — wait for Step 7 to complete before spawning Step 8") and injects Edit-only tool discipline into both agent prompts ("Use the Edit tool exclusively — NEVER use Write on this file"). Closes #3096.
|
||||
11
.changeset/fix-3097-3099-executor-worktree-path.md
Normal file
11
.changeset/fix-3097-3099-executor-worktree-path.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3097
|
||||
---
|
||||
**Executor agents now detect and halt on cwd-drift out of worktrees (#3097)** — when a Bash call `cd`'d out of a worktree, `[ -f .git ]` became false (main repo's `.git` is a directory), silently skipping all HEAD/branch guards and allowing commits to land on the main repo's branch. Adds step 0a (cwd-drift sentinel using `git rev-parse --git-dir` + a per-worktree sentinel file at `.git/worktrees/<name>/gsd-spawn-toplevel`) to `gsd-executor.md`'s `task_commit_protocol`. Closes #3097.
|
||||
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3099
|
||||
---
|
||||
**Executor agents now detect absolute paths that resolve outside the worktree (#3099)** — absolute paths constructed from the orchestrator's `pwd` (main repo root) resolved to the main repo when used in Edit/Write calls from a worktree, silently losing work. Adds step 0b (absolute-path guard using `WT_ROOT=$(git rev-parse --show-toplevel)`) with a clear warning and instructions to prefer relative paths. Both guards are documented in `references/worktree-path-safety.md` (loaded into every executor spawn prompt via `<execution_context>`). Closes #3099.
|
||||
5
.changeset/fix-3120-secure-phase-empty-register.md
Normal file
5
.changeset/fix-3120-secure-phase-empty-register.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3142
|
||||
---
|
||||
**`secure-phase` no longer rubber-stamps SECURITY.md for legacy phases with no `<threat_model>` blocks** — Step 3's short-circuit previously exited to Step 6 (write clean SECURITY.md) whenever `threats_open: 0`, regardless of whether zero threats meant "all mitigated" or "none were ever written". Legacy phases authored before `<threat_model>` blocks became canonical now trigger **retroactive-STRIDE mode** in Step 5: the auditor builds a register from implementation files before verifying mitigations. Step 2c now tracks `register_authored_at_plan_time` and Step 3 gates the skip on both `threats_open: 0 AND register_authored_at_plan_time: true`. Closes #3120.
|
||||
5
.changeset/fix-3121-gsd-tools-commands-verb.md
Normal file
5
.changeset/fix-3121-gsd-tools-commands-verb.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3121
|
||||
---
|
||||
**`gsd-sdk query commands` no longer returns "Unknown command"** — `commands` was referenced in `references/workstream-flag.md` and by agent tooling for verb discovery but had no SDK handler. A new `commandsList` handler in the native registry returns a sorted JSON array of all registered verb strings. `check.decision-coverage-plan` and `check.decision-coverage-verify` were already registered in the SDK native registry; the remaining gap was the `commands` introspection verb. Closes #3121.
|
||||
5
.changeset/fix-3126-global-skills-base-runtime.md
Normal file
5
.changeset/fix-3126-global-skills-base-runtime.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3126
|
||||
---
|
||||
**`global:` skill resolution now uses the correct runtime home directory** — `buildAgentSkillsBlock()` hardcoded `globalSkillsBase` to `~/.claude/skills` regardless of the active runtime, causing every `global:` skill lookup to silently fail on non-Claude runtimes (Cursor, Gemini, Codex, Windsurf, etc.). Introduces `get-shit-done/bin/lib/runtime-homes.cjs` — a first-class runtime→directory mapping module covering all 15 supported runtimes with their canonical env-var overrides. Notable specifics: Hermes Agent uses a nested `skills/gsd/<skillName>/` layout (#2841); Cline is rules-based and returns `null` (no skills directory); `CLAUDE_CONFIG_DIR` env var was previously missing for Claude. Warning messages now show the actual runtime-specific path. Closes #3126.
|
||||
5
.changeset/fix-3127-state-begin-phase-idempotent.md
Normal file
5
.changeset/fix-3127-state-begin-phase-idempotent.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3127
|
||||
---
|
||||
**`state.begin-phase` is now idempotent** — when called on a phase already in-flight (e.g. `--wave N` resume), it no longer overwrites `Current Plan`, `stopped_at` narrative, `Plan: N of M` body line, or `Last Activity Description` with stale values from the last `plan-phase` run. An idempotency guard reads the current `Status` field before writing: if it already contains `Executing Phase N`, only the `Last Activity` date and a resume-specific activity line are updated; all execution-progress fields are preserved. First-time execution (Status ≠ Executing) continues to write all fields as before. Closes #3127.
|
||||
5
.changeset/fix-3128-roadmap-plan-count-slug.md
Normal file
5
.changeset/fix-3128-roadmap-plan-count-slug.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3128
|
||||
---
|
||||
**`roadmap.cjs` plan_count now correctly detects `{N}-PLAN-{NN}-{slug}.md` files** — the manager-dashboard plan-count filter matched only `*-PLAN.md` and `PLAN.md`, missing the slug-form layout (`5-PLAN-01-setup.md`) that `gsd-plan-phase` actually writes. `init manager` returned `plan_count: 0` / `disk_status: "discussed"` for fully-planned phases, causing the manager to recommend and dispatch redundant background planner agents. Same regex flaw as #2893 (fixed in `phase.cjs` via PR #2896); `roadmap.cjs` was missed in that sweep. Fix applies the same `looksLikePlanFile` logic (with `PLAN-OUTLINE` and `pre-bounce` exclusions) to `countPhasePlansAndSummaries`. Closes #3128.
|
||||
5
.changeset/fix-3129-validate-commit-bypass.md
Normal file
5
.changeset/fix-3129-validate-commit-bypass.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3141
|
||||
---
|
||||
**`gsd-validate-commit.sh` community hook now catches all git commit forms** — the previous `[[ "$CMD" =~ ^git[[:space:]]+commit ]]` bash regex silently bypassed Conventional Commits enforcement for `git -C /path commit`, `GIT_AUTHOR_NAME=x git commit`, and `/usr/bin/git commit`. Introduces `hooks/lib/git-cmd.js` — a token-walk classifier (`isGitSubcommand(cmd, sub)`) that correctly handles env-prefix assignments, `-C path` working-directory flags, full-path executables, `--git-dir=` options, and all git global boolean flags. The hook now delegates detection to this module — the single source of truth for all hooks that gate on git subcommands. Closes #3129.
|
||||
5
.changeset/fix-3130-update-npx-robust.md
Normal file
5
.changeset/fix-3130-update-npx-robust.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3130
|
||||
---
|
||||
**`update.md` npx invocations hardened against cache-stale and Bash-tool token-routing failures** — the previous `npx -y get-shit-done-cc@latest` form had two failure modes: (1) npx serving a cached older version instead of `@latest`, and (2) Bash-tool wrappers misrouting the `@` token, producing `Unknown command: "get-shit-done-cc@latest"`. All three sibling invocations (local, global, unknown/fallback) now use `npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc` — the `--package=` flag forces a fresh registry fetch and the `--` separator prevents token misrouting. Closes #3130.
|
||||
5
.changeset/fix-3135-capture-backlog-workflow.md
Normal file
5
.changeset/fix-3135-capture-backlog-workflow.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3135
|
||||
---
|
||||
**`/gsd-capture --backlog` now has a workflow to load** — PR #2824 consolidated `add-backlog` into the `--backlog` flag on `/gsd-capture` and wired `commands/gsd/capture.md` to delegate to `workflows/add-backlog.md` via `execution_context`. The workflow file was never created, leaving the routing with no implementation to load. Restores `get-shit-done/workflows/add-backlog.md` with the full process from the deleted `commands/gsd/add-backlog.md`: find next 999.x slot via `phase.next-decimal`, write ROADMAP entry before creating the phase directory (preserving the #2280 ordering invariant), create `.planning/phases/{N}-{slug}/`, and commit. Also fixes `docs/INVENTORY.md` which incorrectly attributed `--backlog` routing to `add-todo.md`. Adds a broad regression test that every `execution_context` `@`-reference in any `commands/gsd/*.md` resolves to an existing workflow file, preventing this class of gap from silently re-appearing. Closes #3135.
|
||||
5
.changeset/gemini-skip-local-when-global.md
Normal file
5
.changeset/gemini-skip-local-when-global.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3037
|
||||
---
|
||||
**Gemini local install no longer duplicates `/gsd:*` commands across user and workspace scopes** — when GSD is already installed at the user scope (`~/.gemini/commands/gsd/`) and you run `npx get-shit-done-cc --gemini --local` in a project, the installer now skips writing `commands/gsd/` to `<project>/.gemini/` and prints a one-line warning explaining why. Previously, both scopes received the same 65 command files, and Gemini's conflict detector renamed every `/gsd:*` command to `/workspace.gsd:*` and `/user.gsd:*`, breaking the documented namespace. Closes #3037.
|
||||
5
.changeset/happy-jays-greet.md
Normal file
5
.changeset/happy-jays-greet.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2994
|
||||
---
|
||||
/gsd-reapply-patches Step 5 verifier now resolves at runtime — moved scripts/verify-reapply-patches.cjs to get-shit-done/bin/ which is shipped by the installer. The legacy scripts/ directory is not copied to user installs. See #2994.
|
||||
5
.changeset/happy-tigers-travel.md
Normal file
5
.changeset/happy-tigers-travel.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3060
|
||||
---
|
||||
**Query mutation event mapping moved to dedicated module** — preserves event payloads while improving registry locality and test surface.
|
||||
5
.changeset/help-passthrough.md
Normal file
5
.changeset/help-passthrough.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3026
|
||||
---
|
||||
**`gsd-sdk query <subcommand> --help` now reaches the handler instead of returning top-level usage.** The query argv parser harvested `--help` as a global flag and `main()` short-circuited dispatch — there was no path to discover what arguments a query subcommand accepts. The parser now leaves `--help` in `queryArgv` so the handler/fallback can render contextual help. The `gsd-tools.cjs` fallback now renders top-level usage on `--help` (instead of erroring), preserving #1818's anti-hallucination invariant by NOT executing the destructive command. See #3019.
|
||||
5
.changeset/humble-goats-swim.md
Normal file
5
.changeset/humble-goats-swim.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3060
|
||||
---
|
||||
**Alias-family handler maps moved to dedicated catalog module** — keeps command keys/order while reducing createRegistry coupling and improving family-level locality.
|
||||
5
.changeset/install-shell-path-probe.md
Normal file
5
.changeset/install-shell-path-probe.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3028
|
||||
---
|
||||
**Installer no longer prints `✓ GSD SDK ready` when the shim is unreachable from the user's runtime shells.** The previous check used `process.env.PATH` from the install subprocess, which often differs from the user's later interactive shells (POSIX `~/.local/bin` not in login shell, node-version-manager PATH shims). Added `getUserShellPath()` helper that probes `$SHELL -lc 'printf %s "$PATH"'` and `isGsdSdkOnPath(pathString?)` overload that accepts an explicit PATH; the install-time check now downgrades to the actionable `⚠` diagnostic from PR #3014 when install-PATH and user-shell-PATH disagree. Windows cross-shell support tracked separately. See #3020.
|
||||
5
.changeset/issue-driven-orchestration.md
Normal file
5
.changeset/issue-driven-orchestration.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2840
|
||||
---
|
||||
**`docs/issue-driven-orchestration.md` — recipe for driving GSD from a tracker issue** — new guide that maps Symphony-style orchestration concepts (workflow, isolated agent workspace, proof-of-work, human review gate, follow-up capture) onto existing GSD primitives (`/gsd-new-workspace`, `/gsd-manager`, `/gsd-autonomous`, `/gsd-verify-work`, `/gsd-review`, `/gsd-ship`, `STATE.md`, phase artifacts). Documentation only — no new commands, no daemon, no tracker integration.
|
||||
5
.changeset/jolly-newts-roam.md
Normal file
5
.changeset/jolly-newts-roam.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2994
|
||||
---
|
||||
/gsd-reapply-patches Step 5 verifier now resolves at runtime — moved scripts/verify-reapply-patches.cjs to get-shit-done/bin/ which is shipped by the installer. The legacy scripts/ directory is not copied to user installs. See #2994.
|
||||
5
.changeset/jolly-pumas-dance.md
Normal file
5
.changeset/jolly-pumas-dance.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2979
|
||||
---
|
||||
Managed JS hooks now resolve under GUI/minimal-PATH runtimes — installer emits process.execPath (absolute, quoted, forward-slash-normalized) as the runner for every .js hook command instead of bare node. See #2979.
|
||||
5
.changeset/lively-goats-run.md
Normal file
5
.changeset/lively-goats-run.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2995
|
||||
---
|
||||
Post-install path smoke test for workflow-invoked scripts — audits every node ${GSD_HOME}/...cjs invocation in workflows resolves at the runtime-installed path. See #2995.
|
||||
5
.changeset/lively-moles-caper.md
Normal file
5
.changeset/lively-moles-caper.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3043
|
||||
---
|
||||
milestone complete now scopes phase stats to the explicit version argument and errors when that version is missing from a versioned ROADMAP milestone section.
|
||||
5
.changeset/lively-otters-gather.md
Normal file
5
.changeset/lively-otters-gather.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3011
|
||||
---
|
||||
**Actionable diagnostic when `gsd-sdk` is not on PATH after install** — Windows users (and others on multi-shell setups) reported that the previous "GSD SDK files are present but `gsd-sdk` is not on your PATH" warning gave them no way to fix it: no path to look at, no shell-specific commands, no mention of the npx-cache caveat. New `formatSdkPathDiagnostic({ shimDir, platform, runDir })` helper returns a typed IR with the resolved shim location, platform-specific PATH-export commands (PowerShell / cmd.exe / Git Bash on Windows; `export PATH` on POSIX), and an npx-specific note when running under an `_npx` cache segment (where the shim may be written to a temp dir that won't persist). The console renderer in `bin/install.js` emits the lines from the IR; tests assert on the typed fields directly. (#3011)
|
||||
5
.changeset/mcp-token-budget-docs.md
Normal file
5
.changeset/mcp-token-budget-docs.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 3032
|
||||
---
|
||||
**Documentation: MCP tool schema as a context-budget concern (#3025).** Adds new sections to `get-shit-done/references/context-budget.md` and `docs/USER-GUIDE.md` explaining that every enabled MCP server injects its tool schema into every turn — heavyweight servers (browser/playwright, Mac-tools, Windows-tools) can cost 20k+ tokens each, often dwarfing what `model_profile` tuning saves. The toggle lives in `.claude/settings.json` (`enabledMcpjsonServers` / `disabledMcpjsonServers`) and is a Claude Code harness concern, not a GSD concern. Includes a pre-phase audit checklist (browser, platform-specific, cross-project, duplicates) and notes the multiplier interaction with `model_profile`. Companion to #3023 (per-phase-type model map) and #3024 (dynamic routing); together they cover the three biggest cost levers.
|
||||
5
.changeset/merry-foxes-climb.md
Normal file
5
.changeset/merry-foxes-climb.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2997
|
||||
---
|
||||
SDK config-set/config-get and init responses no longer echo plaintext API keys. New sdk/src/query/secrets.ts ports SECRET_CONFIG_KEYS masking from CJS; init bundles only mask string values to preserve the boolean availability-flag contract. See #2997.
|
||||
5
.changeset/merry-lynx-sing.md
Normal file
5
.changeset/merry-lynx-sing.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2992
|
||||
---
|
||||
/gsd-update queries wrong npm package names — moved package name into a deterministic check-latest-version.cjs script and updated the workflow to use ${GSD_DIR} from get_installed_version. See #2992.
|
||||
5
.changeset/merry-lynx-wander.md
Normal file
5
.changeset/merry-lynx-wander.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3007
|
||||
---
|
||||
**PR templates now point at the changeset workflow** — the `Fix`, `Enhancement`, and `Feature` PR templates previously asked contributors to tick `CHANGELOG.md updated`, which contradicted the post-#2978 rule that `CHANGELOG.md` must not be edited directly. Each checkbox now references `npm run changeset` (and the `no-changelog` opt-out where applicable).
|
||||
5
.changeset/merry-moles-chatter.md
Normal file
5
.changeset/merry-moles-chatter.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3060
|
||||
---
|
||||
**CLI query CJS fallback execution extracted to dedicated adapter module** — preserves logs/help passthrough behavior while improving fallback locality and testability.
|
||||
5
.changeset/noble-badgers-roar.md
Normal file
5
.changeset/noble-badgers-roar.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3060
|
||||
---
|
||||
**Query mutation event emission now uses a dedicated decorator seam** — preserves fire-and-forget behavior while reducing registry coupling and improving testability.
|
||||
5
.changeset/per-phase-type-models.md
Normal file
5
.changeset/per-phase-type-models.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 3030
|
||||
---
|
||||
**`models` block in `.planning/config.json` for per-phase-type model selection (#3023).** A new resolution layer between per-agent `model_overrides` and the `model_profile` tier table. Six named slots (`planning` / `discuss` / `research` / `execution` / `verification` / `completion`) accept tier aliases (`opus` / `sonnet` / `haiku` / `inherit`). Lets you express "Opus for planning, Sonnet for the rest" in two lines without learning the agent taxonomy. Fully backward compatible — configs without `models` behave exactly as today.
|
||||
5
.changeset/plucky-ibex-gather.md
Normal file
5
.changeset/plucky-ibex-gather.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2998
|
||||
---
|
||||
gsd-pristine/ is now populated by the installer when local patches are detected — saveLocalPatches calls a new populatePristineDir helper that runs the install transform pipeline into a tmp staging dir and copies modified files into pristineDir. The reapply-patches Step 5 verifier no longer falls back to its over-broad heuristic. See #2998.
|
||||
5
.changeset/plucky-moles-roam.md
Normal file
5
.changeset/plucky-moles-roam.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2997
|
||||
---
|
||||
SDK config-set/config-get and init responses no longer echo plaintext API keys. New sdk/src/query/secrets.ts ports SECRET_CONFIG_KEYS masking from CJS; init bundles only mask string values to preserve the boolean availability-flag contract. See #2997.
|
||||
5
.changeset/plucky-otters-roam.md
Normal file
5
.changeset/plucky-otters-roam.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2995
|
||||
---
|
||||
Post-install path smoke test for workflow-invoked scripts — audits every node ${GSD_HOME}/...cjs invocation in workflows resolves at the runtime-installed path. See #2995.
|
||||
5
.changeset/plucky-pandas-sprint.md
Normal file
5
.changeset/plucky-pandas-sprint.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3108
|
||||
---
|
||||
Query module architecture deepened with compatibility-preserving seams — command policy now derives from command definitions, and dispatch/topology/registry seams are consolidated for better locality while preserving existing query behavior.
|
||||
5
.changeset/pr-3112-release-note.md
Normal file
5
.changeset/pr-3112-release-note.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3112
|
||||
---
|
||||
Fixes for issue #3112 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.
|
||||
5
.changeset/pr-3113-release-note.md
Normal file
5
.changeset/pr-3113-release-note.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3113
|
||||
---
|
||||
Fixes for issue #3113 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.
|
||||
5
.changeset/pr-3115-release-note.md
Normal file
5
.changeset/pr-3115-release-note.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3115
|
||||
---
|
||||
Fixes for issue #3115 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.
|
||||
5
.changeset/pr-3116-release-note.md
Normal file
5
.changeset/pr-3116-release-note.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3116
|
||||
---
|
||||
Fixes for issue #3116 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.
|
||||
5
.changeset/pr-3118-release-note.md
Normal file
5
.changeset/pr-3118-release-note.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3118
|
||||
---
|
||||
Fixes for issue #3118 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.
|
||||
5
.changeset/pr-3123-release-note.md
Normal file
5
.changeset/pr-3123-release-note.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3123
|
||||
---
|
||||
Fixes for issue #3123 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.
|
||||
5
.changeset/pr-3124-release-note.md
Normal file
5
.changeset/pr-3124-release-note.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3124
|
||||
---
|
||||
Fixes for issue #3124 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.
|
||||
5
.changeset/pr-3125-release-note.md
Normal file
5
.changeset/pr-3125-release-note.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3125
|
||||
---
|
||||
Fixes for issue #3098 were applied to keep command/workflow behavior and SDK parity aligned with current documented usage.
|
||||
5
.changeset/quick-geese-hum.md
Normal file
5
.changeset/quick-geese-hum.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3060
|
||||
---
|
||||
**Query fallback orchestration now shared** — CLI and SDK query dispatch now use one planning seam for native vs CJS fallback decisions with behavior parity preserved.
|
||||
5
.changeset/rapid-goats-munch.md
Normal file
5
.changeset/rapid-goats-munch.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3060
|
||||
---
|
||||
**Query/transport policy data now converged in shared module** — mutation and raw-output policy wiring now share one source of truth to reduce drift.
|
||||
5
.changeset/research-flag-and-stale-refs.md
Normal file
5
.changeset/research-flag-and-stale-refs.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3042
|
||||
---
|
||||
**`/gsd-research-phase` consolidated into `/gsd-plan-phase --research-phase <N>`** — the standalone research command's slash-command stub was never registered (#3042). Rather than restore the orphan, the research-only capability now lives as a flag on `/gsd-plan-phase`. New modifiers: `--view` prints existing `RESEARCH.md` to stdout without spawning, `--research` forces refresh, otherwise prompts `update / view / skip` when `RESEARCH.md` already exists. Also scrubs four other stale slash-command references (`/gsd-check-todos`, `/gsd-new-workspace`, `/gsd-status`, residual `/gsd-plan-milestone-gaps`) across English + 4 localized doc sets (#3044). Closes #3042 and #3044.
|
||||
6
.changeset/rewire-orphaned-workflows-3131.md
Normal file
6
.changeset/rewire-orphaned-workflows-3131.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3131
|
||||
---
|
||||
|
||||
**Re-wired 4 orphaned workflows as flags on parent commands** — six workflows were mis-categorised as "outright deleted dead skills" during the #2790 consolidation; two were caught by prior PRs (#3045, #3038) and four are fixed here. New flags: `/gsd-discuss-phase --assumptions` (surfaces Claude's implementation assumptions before planning), `/gsd-pause-work --report` (generates a post-session summary in `.planning/reports/`), `/gsd-manager --analyze-deps` (scans ROADMAP phases for dependency relationships before parallel execution), `/gsd-import --from-gsd2` (reverse-migrates a GSD-2 `.gsd/` project back to GSD v1 `.planning/` format). Also sweeps 29 stale `/gsd-*` command references across 27 user-facing files (English + 4 locales). Closes #3131.
|
||||
5
.changeset/scrub-stale-command-routes.md
Normal file
5
.changeset/scrub-stale-command-routes.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3029
|
||||
---
|
||||
**`/gsd-code-review-fix` and `/gsd-plan-milestone-gaps` no longer surface as "Unknown command"** — both were consolidated by #2790 (`/gsd-code-review --fix` and inline gap planning in `/gsd-audit-milestone` respectively), but several user-facing surfaces still emitted the old slash forms in their offer text. Fixed audit-milestone offer blocks, gsd-complete-milestone routing, code-review/execute-phase offer text, gsd-code-fixer agent role card, and the doc surfaces (USER-GUIDE, FEATURES, INVENTORY, AGENTS, CONFIGURATION). Closes #3029, closes #3034.
|
||||
5
.changeset/silly-foxes-wander.md
Normal file
5
.changeset/silly-foxes-wander.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2990
|
||||
---
|
||||
gsd-code-fixer worktree no longer fails on the same-branch checkout — the agent now creates a new gsd-reviewfix/ branch via git worktree add -b and fast-forwards the user's branch on cleanup. See #2990.
|
||||
5
.changeset/silly-newts-swim.md
Normal file
5
.changeset/silly-newts-swim.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2982
|
||||
---
|
||||
Extended no-source-grep lint to catch var-binding readFileSync.includes() pattern. Tests now fail when source-grep is hidden behind a parser wrapper. See #2982.
|
||||
6
.changeset/steady-ravens-shape.md
Normal file
6
.changeset/steady-ravens-shape.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3065
|
||||
---
|
||||
|
||||
**Dispatch policy seam now returns a structured result contract** across native and fallback query execution paths (`ok`, typed error `kind`, `details`, and final `exit_code`), with CLI consuming the unified result instead of mixed throw/result handling.
|
||||
5
.changeset/sturdy-jays-glide.md
Normal file
5
.changeset/sturdy-jays-glide.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3060
|
||||
---
|
||||
**Query static command registrations now split into domain catalog modules** — preserves command order/strings while improving registry locality and maintenance.
|
||||
5
.changeset/tidy-tunas-zip.md
Normal file
5
.changeset/tidy-tunas-zip.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 3085
|
||||
---
|
||||
**`GSDTools` query execution internals now use deep Module seams** — refactors runtime composition, native/subprocess adapters, and output projection behind stable public interfaces for better locality and testability.
|
||||
5
.changeset/typed-rivers-flow.md
Normal file
5
.changeset/typed-rivers-flow.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 2974
|
||||
---
|
||||
Migrated 8 test files from raw text matching (`stdout.includes(...)`, `assert.match(stderr, ...)`) to typed-IR assertions per CONTRIBUTING.md. Adds shared `ERROR_REASON` enum and `--json-errors` flag in `core.cjs`, typed `GRAPHIFY_REASON` in `graphify.cjs`, pure `buildSdkFailFastReport()` IR builder in `bin/install.js`, and Claude Code JSON envelope output (`hookSpecificOutput` with typed fields) for `gsd-session-state.sh` and `gsd-phase-boundary.sh`. Tests now assert on structured fields (`reason`, `context`, `state_present`, `planning_modified`, etc.) instead of substring matching. See #2974.
|
||||
5
.changeset/update-banner-opt-in.md
Normal file
5
.changeset/update-banner-opt-in.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2795
|
||||
---
|
||||
**Optional update banner for non-GSD statusline users** — when the installer detects you've declined or kept a non-GSD statusline, it now offers an opt-in `SessionStart` banner that surfaces update availability via the existing `~/.cache/gsd/gsd-update-check.json` cache. Silent when up-to-date, rate-limits failure diagnostics to once per 24h, removed cleanly by `npx get-shit-done-cc --uninstall`.
|
||||
5
.changeset/witty-hawks-jump.md
Normal file
5
.changeset/witty-hawks-jump.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2973
|
||||
---
|
||||
/gsd-profile-user --refresh writes dev-preferences.md to ~/.claude/skills/gsd-dev-preferences/SKILL.md instead of the legacy commands/gsd/ directory. Installer migrates any preserved legacy file to the new location. See #2973.
|
||||
5
.changeset/witty-newts-greet.md
Normal file
5
.changeset/witty-newts-greet.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2992
|
||||
---
|
||||
/gsd-update queries wrong npm package names — moved package name into a deterministic check-latest-version.cjs script and updated the workflow to use ${GSD_DIR} from get_installed_version. See #2992.
|
||||
5
.changeset/zesty-jays-wake.md
Normal file
5
.changeset/zesty-jays-wake.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 2979
|
||||
---
|
||||
Managed JS hooks now resolve under GUI/minimal-PATH runtimes — installer emits process.execPath (absolute, quoted, forward-slash-normalized) as the runner for every .js hook command instead of bare node. See #2979.
|
||||
5
.changeset/zesty-moles-forage.md
Normal file
5
.changeset/zesty-moles-forage.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Added
|
||||
pr: 2982
|
||||
---
|
||||
Extended no-source-grep lint to catch var-binding readFileSync.includes() pattern. Tests now fail when source-grep is hidden behind a parser wrapper. See #2982.
|
||||
26
.coderabbit.yaml
Normal file
26
.coderabbit.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
# CodeRabbit configuration — gsd-build/get-shit-done
|
||||
#
|
||||
# Schema: https://docs.coderabbit.ai/reference/yaml-template/
|
||||
#
|
||||
# Project context: GSD ships a CLI tool + an agent runtime, not a documented
|
||||
# public library. We carry rich JSDoc on internal helpers that warrant it
|
||||
# (see bin/install.js, get-shit-done/bin/lib/*.cjs) but we do not enforce a
|
||||
# blanket docstring coverage bar — see issue #2932 for rationale.
|
||||
|
||||
reviews:
|
||||
pre_merge_checks:
|
||||
# Disable docstring coverage check.
|
||||
#
|
||||
# The check produces false-positive warnings on PRs whose new code is
|
||||
# entirely test files: it counts test(...) / beforeEach / afterEach
|
||||
# arrow-function callbacks as functions and then reports 0% coverage
|
||||
# because nothing has JSDoc. There is no per-check path filter in CR's
|
||||
# documented schema that would let us exclude tests/** while keeping
|
||||
# the check active elsewhere, and the top-level path_filters approach
|
||||
# would silence ALL CR review on tests (security scans, out-of-scope
|
||||
# checks, line-level findings) which we want to keep.
|
||||
#
|
||||
# All other CR pre-merge checks (out-of-scope, security, title) remain
|
||||
# at their defaults.
|
||||
docstrings:
|
||||
mode: off
|
||||
6
.githooks/pre-commit
Executable file
6
.githooks/pre-commit
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if git diff --cached --name-only | grep -Eq "^sdk/src/query/command-manifest\.|^sdk/src/query/command-aliases\.generated\.ts$|^get-shit-done/bin/lib/command-aliases\.generated\.cjs$|^sdk/scripts/gen-command-aliases\.ts$"; then
|
||||
npm run check:alias-drift
|
||||
fi
|
||||
48
.githooks/pre-push
Executable file
48
.githooks/pre-push
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
zero_sha='0000000000000000000000000000000000000000'
|
||||
blocked_regex="${GSD_BLOCKED_AUTHOR_REGEX:-}"
|
||||
|
||||
# Local-only guard: no-op unless the developer opts in via env var, e.g.
|
||||
# export GSD_BLOCKED_AUTHOR_REGEX='@example-corp\.com$'
|
||||
if [[ -z "$blocked_regex" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
violations=()
|
||||
|
||||
while read -r local_ref local_sha remote_ref remote_sha; do
|
||||
# branch/tag deletion
|
||||
if [[ "$local_sha" == "$zero_sha" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$remote_sha" == "$zero_sha" ]]; then
|
||||
# New remote ref: inspect commits not already on any remote
|
||||
commit_list=$(git rev-list "$local_sha" --not --remotes)
|
||||
else
|
||||
commit_list=$(git rev-list "$remote_sha..$local_sha")
|
||||
fi
|
||||
|
||||
while read -r commit; do
|
||||
[[ -z "$commit" ]] && continue
|
||||
author_email=$(git show -s --format='%ae' "$commit")
|
||||
lower_email=$(printf '%s' "$author_email" | tr '[:upper:]' '[:lower:]')
|
||||
if printf '%s' "$lower_email" | grep -Eq "$blocked_regex"; then
|
||||
violations+=("$commit <$author_email>")
|
||||
fi
|
||||
done <<< "$commit_list"
|
||||
done
|
||||
|
||||
if [[ ${#violations[@]} -gt 0 ]]; then
|
||||
{
|
||||
echo "Push blocked: commit author email matched local blocked regex ($blocked_regex)."
|
||||
echo "Rewrite author info before pushing these commits:"
|
||||
for v in "${violations[@]}"; do
|
||||
echo " - $v"
|
||||
done
|
||||
echo "Suggested fix: git rebase -i <base> --exec \"git commit --amend --no-edit --author='Your Name <non-enterprise@email>'\""
|
||||
} >&2
|
||||
exit 1
|
||||
fi
|
||||
2
.github/PULL_REQUEST_TEMPLATE/enhancement.md
vendored
2
.github/PULL_REQUEST_TEMPLATE/enhancement.md
vendored
@@ -73,7 +73,7 @@ Closes #
|
||||
- [ ] Changes are scoped to the approved enhancement — nothing extra included
|
||||
- [ ] All existing tests pass (`npm test`)
|
||||
- [ ] New or updated tests cover the enhanced behavior
|
||||
- [ ] CHANGELOG.md updated
|
||||
- [ ] `.changeset/` fragment added (`npm run changeset -- --type Changed --pr <NNN> --body "..."`) — or `no-changelog` label applied if not user-facing
|
||||
- [ ] Documentation updated if behavior or output changed
|
||||
- [ ] No unnecessary dependencies added
|
||||
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE/feature.md
vendored
2
.github/PULL_REQUEST_TEMPLATE/feature.md
vendored
@@ -94,7 +94,7 @@ Closes #
|
||||
- [ ] Implementation scope matches the approved spec exactly
|
||||
- [ ] All existing tests pass (`npm test`)
|
||||
- [ ] New tests cover the happy path, error cases, and edge cases
|
||||
- [ ] CHANGELOG.md updated with a user-facing description of the feature
|
||||
- [ ] `.changeset/` fragment added with a user-facing description of the feature (`npm run changeset -- --type Added --pr <NNN> --body "..."`)
|
||||
- [ ] Documentation updated — commands, workflows, references, README if applicable
|
||||
- [ ] No unnecessary external dependencies added
|
||||
- [ ] Works on Windows (backslash paths handled)
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE/fix.md
vendored
2
.github/PULL_REQUEST_TEMPLATE/fix.md
vendored
@@ -63,7 +63,7 @@ Fixes #
|
||||
- [ ] Fix is scoped to the reported bug — no unrelated changes included
|
||||
- [ ] Regression test added (or explained why not)
|
||||
- [ ] All existing tests pass (`npm test`)
|
||||
- [ ] CHANGELOG.md updated if this is a user-facing fix
|
||||
- [ ] `.changeset/` fragment added if this is a user-facing fix (`npm run changeset -- --type Fixed --pr <NNN> --body "..."`) — or `no-changelog` label applied
|
||||
- [ ] No unnecessary dependencies added
|
||||
|
||||
## Breaking changes
|
||||
|
||||
157
.github/workflows/canary.yml
vendored
Normal file
157
.github/workflows/canary.yml
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
# Release stream policy:
|
||||
# dev → @canary (this workflow — preview builds for the long-lived integration branch)
|
||||
# main → @next (RC train, see release.yml)
|
||||
# main → @latest (stable cuts, see release.yml)
|
||||
#
|
||||
# Streams do not mix. The publish/tag steps below gate on `refs/heads/dev` so a
|
||||
# workflow_dispatch run on any other branch (including main) completes the
|
||||
# build/test/dry-run validation but does not publish or tag.
|
||||
|
||||
name: Canary
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: 'Dry run (skip npm publish, tagging, and push)'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: canary
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
NODE_VERSION: 24
|
||||
|
||||
jobs:
|
||||
canary:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
environment: npm-publish
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Determine canary version
|
||||
id: canary
|
||||
run: |
|
||||
# Strip any pre-release suffix from package.json version to get base (e.g. 1.39.0-rc.4 → 1.39.0)
|
||||
RAW=$(node -p "require('./package.json').version")
|
||||
BASE=$(echo "$RAW" | sed 's/-.*//')
|
||||
# Find next sequential canary number from existing tags
|
||||
N=1
|
||||
while git tag -l "v${BASE}-canary.${N}" | grep -q .; do
|
||||
N=$((N + 1))
|
||||
done
|
||||
CANARY_VERSION="${BASE}-canary.${N}"
|
||||
echo "canary_version=$CANARY_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Bump to canary version
|
||||
env:
|
||||
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
|
||||
run: |
|
||||
npm version "$CANARY_VERSION" --no-git-tag-version
|
||||
cd sdk && npm version "$CANARY_VERSION" --no-git-tag-version && cd ..
|
||||
|
||||
- name: Install and test
|
||||
run: |
|
||||
npm ci
|
||||
npm test
|
||||
|
||||
- name: Build SDK dist for tarball
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Verify tarball ships sdk/dist/cli.js (bug #2647)
|
||||
run: bash scripts/verify-tarball-sdk-dist.sh
|
||||
|
||||
- name: Dry-run publish validation
|
||||
run: |
|
||||
npm publish --dry-run --tag canary
|
||||
cd sdk && npm publish --dry-run --tag canary
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Tag and push
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
env:
|
||||
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
|
||||
run: |
|
||||
git tag "v${CANARY_VERSION}"
|
||||
git push origin "v${CANARY_VERSION}"
|
||||
|
||||
- name: Publish to npm (canary)
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
run: npm publish --provenance --access public --tag canary
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish SDK to npm (canary)
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
run: cd sdk && npm publish --provenance --access public --tag canary
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Verify publish
|
||||
if: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
env:
|
||||
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
|
||||
run: |
|
||||
PUBLISHED="NOT_FOUND"
|
||||
SDK_PUBLISHED="NOT_FOUND"
|
||||
for delay in 5 10 20 30 45; do
|
||||
PUBLISHED=$(npm view get-shit-done-cc@"$CANARY_VERSION" version 2>/dev/null || echo "NOT_FOUND")
|
||||
SDK_PUBLISHED=$(npm view @gsd-build/sdk@"$CANARY_VERSION" version 2>/dev/null || echo "NOT_FOUND")
|
||||
if [ "$PUBLISHED" = "$CANARY_VERSION" ] && [ "$SDK_PUBLISHED" = "$CANARY_VERSION" ]; then
|
||||
break
|
||||
fi
|
||||
echo "Not yet live (sleeping ${delay}s)..."
|
||||
sleep "$delay"
|
||||
done
|
||||
if [ "$PUBLISHED" != "$CANARY_VERSION" ]; then
|
||||
echo "::error::Published version verification failed. Expected $CANARY_VERSION, got $PUBLISHED"
|
||||
exit 1
|
||||
fi
|
||||
echo "Verified: get-shit-done-cc@$CANARY_VERSION is live on npm"
|
||||
if [ "$SDK_PUBLISHED" != "$CANARY_VERSION" ]; then
|
||||
echo "::error::SDK version verification failed. Expected $CANARY_VERSION, got $SDK_PUBLISHED"
|
||||
exit 1
|
||||
fi
|
||||
echo "Verified: @gsd-build/sdk@$CANARY_VERSION is live on npm"
|
||||
CANARY_TAG=$(npm dist-tag ls get-shit-done-cc 2>/dev/null | grep "canary:" | awk '{print $2}')
|
||||
echo "canary dist-tag points to: $CANARY_TAG"
|
||||
|
||||
- name: Summary
|
||||
env:
|
||||
CANARY_VERSION: ${{ steps.canary.outputs.canary_version }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
PUBLISH_ELIGIBLE: ${{ github.ref == 'refs/heads/dev' && !inputs.dry_run }}
|
||||
BRANCH_REF: ${{ github.ref }}
|
||||
run: |
|
||||
echo "## Canary v${CANARY_VERSION}" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "**DRY RUN** — npm publish, tagging, and push skipped" >> "$GITHUB_STEP_SUMMARY"
|
||||
elif [ "$PUBLISH_ELIGIBLE" != "true" ]; then
|
||||
echo "**VALIDATION ONLY** — publish/tag skipped for \`${BRANCH_REF}\`; canary publish is gated to \`refs/heads/dev\`." >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "- Published to npm as \`canary\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- SDK also published: \`@gsd-build/sdk@${CANARY_VERSION}\` on \`canary\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Tagged \`v${CANARY_VERSION}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Install: \`npx get-shit-done-cc@canary\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
24
.github/workflows/changeset-required.yml
vendored
Normal file
24
.github/workflows/changeset-required.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Changeset Required
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
changeset-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
- name: Run changeset lint
|
||||
env:
|
||||
GITHUB_BASE_REF: ${{ github.base_ref }}
|
||||
run: node scripts/changeset/lint.cjs
|
||||
366
.github/workflows/hotfix.yml
vendored
366
.github/workflows/hotfix.yml
vendored
@@ -1,5 +1,27 @@
|
||||
name: Hotfix Release
|
||||
|
||||
# Hotfix flow for X.YY.Z patch releases (Z > 0).
|
||||
#
|
||||
# create:
|
||||
# - Branches hotfix/X.YY.Z from the highest existing vX.YY.* tag (1.27.2 from
|
||||
# v1.27.1, 1.27.1 from v1.27.0). The base IS the cumulative-fix anchor for
|
||||
# the previous patch.
|
||||
# - Auto-cherry-picks every fix:/chore: commit on origin/main that isn't
|
||||
# already in the base, oldest-first. Patch-equivalents (already applied)
|
||||
# are skipped via `git cherry`. feat:/refactor: are NEVER auto-included.
|
||||
# - Conflicts fail the workflow with the offending SHA so the operator can
|
||||
# resolve manually on the branch and re-run finalize with auto_cherry_pick=false.
|
||||
# - Step summary lists every included SHA so the eventual vX.YY.Z tag
|
||||
# self-documents what shipped.
|
||||
#
|
||||
# finalize:
|
||||
# - install-smoke gate (cross-platform, parity with release.yml/release-sdk.yml)
|
||||
# - Bundles SDK as both loose tree (sdk/dist/cli.js) and recoverable tarball
|
||||
# (sdk-bundle/gsd-sdk.tgz) — parity with release-sdk.yml so a hotfix shipped
|
||||
# during the @gsd-build-token outage carries the same payload shape.
|
||||
# - Publishes to @latest, tags vX.YY.Z, re-points @next → vX.YY.Z, opens
|
||||
# merge-back PR.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -14,6 +36,11 @@ on:
|
||||
description: 'Patch version (e.g., 1.27.1)'
|
||||
required: true
|
||||
type: string
|
||||
auto_cherry_pick:
|
||||
description: 'Auto-cherry-pick fix:/chore: commits from origin/main since base tag (create only)'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
dry_run:
|
||||
description: 'Dry run (skip npm publish, tagging, and push)'
|
||||
required: false
|
||||
@@ -54,10 +81,13 @@ jobs:
|
||||
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1-2)
|
||||
TARGET_TAG="v${VERSION}"
|
||||
BRANCH="hotfix/${VERSION}"
|
||||
BASE_TAG=$(git tag -l "v${MAJOR_MINOR}.*" \
|
||||
| grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" \
|
||||
# Append TARGET_TAG to the candidate list, then sort -V, then walk the
|
||||
# sorted list and print whatever immediately precedes TARGET_TAG. This
|
||||
# is semver-correct for multi-digit patches (v1.27.10 > v1.27.9) where
|
||||
# a plain `awk '$1 < target'` lexicographic compare would mis-order.
|
||||
BASE_TAG=$( ( git tag -l "v${MAJOR_MINOR}.*" | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$"; echo "$TARGET_TAG" ) \
|
||||
| sort -V \
|
||||
| awk -v target="$TARGET_TAG" '$1 < target { last=$1 } END { if (last != "") print last }')
|
||||
| awk -v target="$TARGET_TAG" '$1 == target { print prev; exit } { prev = $1 }')
|
||||
if [ -z "$BASE_TAG" ]; then
|
||||
echo "::error::No prior stable tag found for ${MAJOR_MINOR}.x before $TARGET_TAG"
|
||||
exit 1
|
||||
@@ -95,29 +125,160 @@ jobs:
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Create hotfix branch
|
||||
if: inputs.dry_run != 'true'
|
||||
- name: Create hotfix branch from base tag and push (skeleton)
|
||||
env:
|
||||
BRANCH: ${{ needs.validate-version.outputs.branch }}
|
||||
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git checkout -b "$BRANCH" "$BASE_TAG"
|
||||
# Push the skeleton branch up-front so any subsequent cherry-pick
|
||||
# conflict leaves a remote artefact the operator can fetch, resolve,
|
||||
# and re-push. Skipped on dry-run — local checkout still exercises
|
||||
# the same cherry-pick + bump flow so conflicts are caught.
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
git push -u origin "$BRANCH"
|
||||
fi
|
||||
|
||||
- name: Cherry-pick fix/chore commits from origin/main since base tag
|
||||
if: ${{ inputs.auto_cherry_pick }}
|
||||
env:
|
||||
BRANCH: ${{ needs.validate-version.outputs.branch }}
|
||||
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch origin main:refs/remotes/origin/main
|
||||
|
||||
# `git cherry $BASE_TAG origin/main` lists every commit on main not
|
||||
# patch-equivalent in BASE_TAG. + means needs picking, - means
|
||||
# already applied (skipped silently).
|
||||
CANDIDATES=$(git cherry "$BASE_TAG" origin/main | awk '/^\+ / {print $2}')
|
||||
|
||||
if [ -z "$CANDIDATES" ]; then
|
||||
echo "No commits on origin/main beyond $BASE_TAG."
|
||||
echo "## Cherry-pick summary" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Base: \`$BASE_TAG\` — no commits to consider." >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Re-order chronologically (oldest first) for predictable application.
|
||||
ORDERED=$(git log --reverse --format='%H' "$BASE_TAG..origin/main" \
|
||||
| grep -F -f <(echo "$CANDIDATES") || true)
|
||||
|
||||
INCLUDED=""
|
||||
SKIPPED=""
|
||||
while IFS= read -r SHA; do
|
||||
[ -z "$SHA" ] && continue
|
||||
SUBJECT=$(git log -1 --format='%s' "$SHA")
|
||||
# fix: or chore:, optional scope, optional ! breaking marker
|
||||
if echo "$SUBJECT" | grep -qE '^(fix|chore)(\([^)]+\))?!?: '; then
|
||||
echo "→ cherry-picking $SHA $SUBJECT"
|
||||
if ! git cherry-pick -x "$SHA"; then
|
||||
# Abort restores HEAD to the last successful pick. On real
|
||||
# runs, push that state so the operator can fetch, resolve
|
||||
# $SHA manually, and finalize with auto_cherry_pick=false.
|
||||
git cherry-pick --abort || true
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
git push --force-with-lease origin "$BRANCH" || git push origin "$BRANCH" || true
|
||||
fi
|
||||
{
|
||||
echo "## Cherry-pick conflict"
|
||||
echo ""
|
||||
echo "Failed at: \`${SHA}\` — \`${SUBJECT}\`"
|
||||
echo ""
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "**Dry run:** branch was not pushed, so the picks below were discarded with the runner."
|
||||
if [ -n "$INCLUDED" ]; then
|
||||
echo ""
|
||||
echo "Already-applied picks (lost — must be re-applied before resolving \`${SHA}\`):"
|
||||
echo ""
|
||||
echo "$INCLUDED"
|
||||
fi
|
||||
echo ""
|
||||
echo "**To resolve:** re-run \`create\` with \`auto_cherry_pick=true\` (real, not dry-run) to materialize the partial branch on origin, then resolve \`${SHA}\` manually. Re-running with \`auto_cherry_pick=false\` would recreate the branch from \`${BASE_TAG}\` and lose every pick listed above."
|
||||
else
|
||||
echo "Branch \`${BRANCH}\` was pushed with picks applied up to (but not including) the conflicting commit."
|
||||
echo ""
|
||||
echo "**To resolve:** \`git fetch origin && git checkout ${BRANCH} && git cherry-pick -x ${SHA}\`, fix the conflict, push, then re-run \`finalize\` with \`auto_cherry_pick=false\`."
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "::error::Cherry-pick of $SHA failed. See summary."
|
||||
exit 1
|
||||
fi
|
||||
INCLUDED="${INCLUDED}- \`${SHA}\` ${SUBJECT}"$'\n'
|
||||
else
|
||||
echo " skip $SHA $SUBJECT (not fix/chore)"
|
||||
SKIPPED="${SKIPPED}- \`${SHA}\` ${SUBJECT}"$'\n'
|
||||
fi
|
||||
done <<< "$ORDERED"
|
||||
|
||||
{
|
||||
echo "## Cherry-pick summary"
|
||||
echo ""
|
||||
echo "Base: \`$BASE_TAG\`"
|
||||
echo ""
|
||||
if [ -n "$INCLUDED" ]; then
|
||||
echo "### Included (fix/chore)"
|
||||
echo ""
|
||||
echo "$INCLUDED"
|
||||
else
|
||||
echo "_No fix/chore commits to include._"
|
||||
echo ""
|
||||
fi
|
||||
if [ -n "$SKIPPED" ]; then
|
||||
echo "### Skipped (feat/refactor/etc — not auto-included)"
|
||||
echo ""
|
||||
echo "$SKIPPED"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Bump version and push
|
||||
env:
|
||||
BRANCH: ${{ needs.validate-version.outputs.branch }}
|
||||
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
git checkout -b "$BRANCH" "$BASE_TAG"
|
||||
# Bump version in package.json
|
||||
set -euo pipefail
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
git add package.json package-lock.json
|
||||
# Keep sdk/package.json in lockstep (parity with release-sdk.yml).
|
||||
if [ -f sdk/package.json ]; then
|
||||
(cd sdk && npm version "$VERSION" --no-git-tag-version)
|
||||
git add sdk/package.json
|
||||
[ -f sdk/package-lock.json ] && git add sdk/package-lock.json
|
||||
fi
|
||||
git commit -m "chore: bump version to $VERSION for hotfix"
|
||||
git push origin "$BRANCH"
|
||||
echo "## Hotfix branch created" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Branch: \`$BRANCH\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Based on: \`$BASE_TAG\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Apply your fix, push, then run this workflow again with \`finalize\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
git push origin "$BRANCH"
|
||||
else
|
||||
echo "DRY RUN — branch not pushed. Local checkout exercised the cherry-pick and bump flow."
|
||||
fi
|
||||
{
|
||||
echo "## Hotfix branch created"
|
||||
echo ""
|
||||
echo "- Branch: \`$BRANCH\`"
|
||||
echo "- Based on: \`$BASE_TAG\`"
|
||||
echo "- Apply additional manual fixes if needed, then run \`finalize\`."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
finalize:
|
||||
install-smoke:
|
||||
needs: validate-version
|
||||
if: inputs.action == 'finalize'
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/install-smoke.yml
|
||||
with:
|
||||
ref: ${{ needs.validate-version.outputs.branch }}
|
||||
|
||||
finalize:
|
||||
needs: [validate-version, install-smoke]
|
||||
if: inputs.action == 'finalize'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
@@ -140,31 +301,83 @@ jobs:
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Detect prior publish (reconciliation mode)
|
||||
id: prior_publish
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
EXISTING=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || true)
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo "::warning::get-shit-done-cc@${VERSION} is already on the registry — entering reconciliation mode (skip publish, continue with tag/release/PR/dist-tag)."
|
||||
echo "skip_publish=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "skip_publish=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Install and test
|
||||
run: |
|
||||
npm ci
|
||||
npm run test:coverage
|
||||
|
||||
- name: Create PR to merge hotfix back to main
|
||||
if: ${{ !inputs.dry_run }}
|
||||
- name: Build SDK dist for tarball
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Verify CC tarball ships sdk/dist/cli.js (bug #2647 guard)
|
||||
run: bash scripts/verify-tarball-sdk-dist.sh
|
||||
|
||||
- name: Pack SDK as tarball and bundle into CC source tree
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
BRANCH: ${{ needs.validate-version.outputs.branch }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
EXISTING_PR=$(gh pr list --base main --head "$BRANCH" --state open --json number --jq '.[0].number')
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
echo "PR #$EXISTING_PR already exists; updating"
|
||||
gh pr edit "$EXISTING_PR" \
|
||||
--title "chore: merge hotfix v${VERSION} back to main" \
|
||||
--body "Merge hotfix changes back to main after v${VERSION} release."
|
||||
else
|
||||
gh pr create \
|
||||
--base main \
|
||||
--head "$BRANCH" \
|
||||
--title "chore: merge hotfix v${VERSION} back to main" \
|
||||
--body "Merge hotfix changes back to main after v${VERSION} release."
|
||||
set -e
|
||||
cd sdk
|
||||
npm pack
|
||||
TARBALL="gsd-build-sdk-${VERSION}.tgz"
|
||||
if [ ! -f "$TARBALL" ]; then
|
||||
echo "::error::Expected $TARBALL but npm pack did not produce it."
|
||||
ls -la
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p ../sdk-bundle
|
||||
mv "$TARBALL" ../sdk-bundle/gsd-sdk.tgz
|
||||
cd ..
|
||||
ls -la sdk-bundle/
|
||||
|
||||
- name: Add sdk-bundle to CC files whitelist (in-tree, not committed)
|
||||
run: |
|
||||
node <<'NODE'
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
if (!Array.isArray(pkg.files)) {
|
||||
console.error('::error::package.json files is not an array');
|
||||
process.exit(1);
|
||||
}
|
||||
if (!pkg.files.includes('sdk-bundle')) {
|
||||
pkg.files.push('sdk-bundle');
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
console.log('Added sdk-bundle/ to package.json files whitelist');
|
||||
}
|
||||
NODE
|
||||
|
||||
- name: Verify CC tarball will contain sdk-bundle/gsd-sdk.tgz
|
||||
run: |
|
||||
set -e
|
||||
TARBALL=$(npm pack --ignore-scripts 2>/dev/null | tail -1)
|
||||
if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then
|
||||
echo "::error::npm pack produced no tarball"
|
||||
exit 1
|
||||
fi
|
||||
if ! tar -tzf "$TARBALL" | grep -q "package/sdk-bundle/gsd-sdk.tgz"; then
|
||||
echo "::error::CC tarball is missing package/sdk-bundle/gsd-sdk.tgz"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ CC tarball contains sdk-bundle/gsd-sdk.tgz"
|
||||
rm -f "$TARBALL"
|
||||
|
||||
- name: Dry-run publish validation
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --dry-run --tag latest
|
||||
|
||||
- name: Tag and push
|
||||
if: ${{ !inputs.dry_run }}
|
||||
@@ -185,55 +398,98 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Publish to npm (latest)
|
||||
if: ${{ !inputs.dry_run }}
|
||||
run: npm publish --provenance --access public
|
||||
if: ${{ !inputs.dry_run && steps.prior_publish.outputs.skip_publish != 'true' }}
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --provenance --access public --tag latest
|
||||
|
||||
- name: Create GitHub Release
|
||||
- name: Re-point next dist-tag at this hotfix
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
npm dist-tag add "get-shit-done-cc@${VERSION}" next
|
||||
echo "✅ next dist-tag re-pointed to v${VERSION} (matches latest)"
|
||||
|
||||
- name: Create GitHub Release (idempotent)
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
gh release create "v${VERSION}" \
|
||||
--title "v${VERSION} (hotfix)" \
|
||||
--generate-notes
|
||||
if gh release view "v${VERSION}" >/dev/null 2>&1; then
|
||||
echo "GitHub Release v${VERSION} already exists; ensuring --latest flag is set"
|
||||
gh release edit "v${VERSION}" --latest || true
|
||||
else
|
||||
gh release create "v${VERSION}" \
|
||||
--title "v${VERSION} (hotfix)" \
|
||||
--generate-notes \
|
||||
--latest
|
||||
fi
|
||||
|
||||
- name: Clean up next dist-tag
|
||||
- name: Create PR to merge hotfix back to main
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
BRANCH: ${{ needs.validate-version.outputs.branch }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
# Point next to the stable release so @next never returns something
|
||||
# older than @latest. This prevents stale pre-release installs.
|
||||
npm dist-tag add "get-shit-done-cc@${VERSION}" next 2>/dev/null || true
|
||||
echo "✓ next dist-tag updated to v${VERSION}"
|
||||
EXISTING_PR=$(gh pr list --base main --head "$BRANCH" --state open --json number --jq '.[0].number')
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
gh pr edit "$EXISTING_PR" \
|
||||
--title "chore: merge hotfix v${VERSION} back to main" \
|
||||
--body "Merge hotfix changes back to main after v${VERSION} release."
|
||||
else
|
||||
gh pr create \
|
||||
--base main \
|
||||
--head "$BRANCH" \
|
||||
--title "chore: merge hotfix v${VERSION} back to main" \
|
||||
--body "Merge hotfix changes back to main after v${VERSION} release."
|
||||
fi
|
||||
|
||||
- name: Verify publish
|
||||
- name: Verify publish landed on registry
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
sleep 10
|
||||
PUBLISHED=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || echo "NOT_FOUND")
|
||||
PUBLISHED="NOT_FOUND"
|
||||
for delay in 5 10 20 30 45; do
|
||||
PUBLISHED=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || echo "NOT_FOUND")
|
||||
if [ "$PUBLISHED" = "$VERSION" ]; then
|
||||
break
|
||||
fi
|
||||
echo "Waiting ${delay}s for registry to catch up (saw: $PUBLISHED)..."
|
||||
sleep "$delay"
|
||||
done
|
||||
if [ "$PUBLISHED" != "$VERSION" ]; then
|
||||
echo "::error::Published version verification failed. Expected $VERSION, got $PUBLISHED"
|
||||
echo "::error::Version $VERSION did not appear on the registry within timeout"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Verified: get-shit-done-cc@$VERSION is live on npm"
|
||||
LATEST_VER=$(npm view get-shit-done-cc dist-tags.latest 2>/dev/null || echo "NOT_FOUND")
|
||||
if [ "$LATEST_VER" != "$VERSION" ]; then
|
||||
echo "::error::dist-tag 'latest' resolves to '$LATEST_VER', expected '$VERSION'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Verified: get-shit-done-cc@$VERSION is live on @latest"
|
||||
|
||||
- name: Summary
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
BASE_TAG: ${{ needs.validate-version.outputs.base_tag }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
echo "## Hotfix v${VERSION}" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "**DRY RUN** — npm publish, tagging, and push skipped" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "- Published to npm as \`latest\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Tagged \`v${VERSION}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- PR created to merge back to main" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
{
|
||||
echo "## Hotfix v${VERSION}"
|
||||
echo ""
|
||||
echo "- Base (cumulative-fix anchor): \`${BASE_TAG}\`"
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "- **DRY RUN** — npm publish, tagging, and push skipped"
|
||||
else
|
||||
echo "- Published to npm as \`latest\`"
|
||||
echo "- \`next\` dist-tag re-pointed to v${VERSION}"
|
||||
echo "- Tagged \`v${VERSION}\` (anchor for the next hotfix's cherry-pick base)"
|
||||
echo "- SDK bundled at \`sdk-bundle/gsd-sdk.tgz\` inside CC tarball"
|
||||
echo "- Merge-back PR opened against main"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
790
.github/workflows/release-sdk.yml
vendored
Normal file
790
.github/workflows/release-sdk.yml
vendored
Normal file
@@ -0,0 +1,790 @@
|
||||
# Release SDK Bundle
|
||||
#
|
||||
# Stopgap workflow_dispatch publish path: builds get-shit-done-cc with the
|
||||
# compiled SDK and the SDK .tgz bundled inside the CC tarball, then
|
||||
# publishes the CC package to ONE chosen dist-tag (dev | next | latest)
|
||||
# per run.
|
||||
#
|
||||
# Why this exists: @gsd-build/sdk publishes from canary.yml and release.yml
|
||||
# fail because the @gsd-build npm token is currently unavailable. CC users
|
||||
# do not consume @gsd-build/sdk directly — bin/gsd-sdk.js resolves
|
||||
# sdk/dist/cli.js from inside the installed CC package, so the bundled
|
||||
# copy is sufficient for full functionality. This workflow ships CC alone
|
||||
# (no separate @gsd-build/sdk publish attempt) and additionally bakes a
|
||||
# bundled gsd-sdk-<version>.tgz at sdk-bundle/gsd-sdk.tgz inside the CC
|
||||
# tarball as a recoverable npm-installable artifact.
|
||||
#
|
||||
# Existing canary.yml and release.yml are intentionally untouched. They
|
||||
# remain the canonical two-package publish path; restore them to primary
|
||||
# use once @gsd-build/sdk ownership is recovered.
|
||||
#
|
||||
# Tracking issues: #2925 (initial workflow), #2929 (CI-gate parity with release.yml)
|
||||
|
||||
name: Release SDK Bundle
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: 'publish = normal dev/next/latest publish; hotfix = create hotfix/X.YY.Z branch from latest vX.YY.* tag, cherry-pick fix:/chore: from main, publish to @latest'
|
||||
required: true
|
||||
type: choice
|
||||
default: publish
|
||||
options:
|
||||
- publish
|
||||
- hotfix
|
||||
tag:
|
||||
description: 'npm dist-tag (publish action only; hotfix forces latest)'
|
||||
required: false
|
||||
type: choice
|
||||
default: latest
|
||||
options:
|
||||
- dev
|
||||
- next
|
||||
- latest
|
||||
version:
|
||||
description: 'Version. publish: explicit (e.g. 1.50.0-dev.3) or empty to derive. hotfix: REQUIRED patch (e.g. 1.27.1, Z>0).'
|
||||
required: false
|
||||
type: string
|
||||
ref:
|
||||
description: 'Branch or ref to build from. Ignored for hotfix (workflow uses hotfix/X.YY.Z).'
|
||||
required: false
|
||||
type: string
|
||||
auto_cherry_pick:
|
||||
description: 'Hotfix only: auto-cherry-pick fix:/chore: commits from origin/main since base tag.'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
dry_run:
|
||||
description: 'Dry run (skip npm publish, git tag, and push). Hotfix branch creation/push also skipped.'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# Per stream (dist-tag for publish, version for hotfix) — no concurrent publishes for the same stream.
|
||||
concurrency:
|
||||
group: release-sdk-${{ inputs.action == 'hotfix' && format('hotfix-{0}', inputs.version) || inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
NODE_VERSION: 24
|
||||
|
||||
jobs:
|
||||
# Resolves the effective git ref for this run.
|
||||
#
|
||||
# action=publish → outputs inputs.ref verbatim (may be empty = workflow ref)
|
||||
# action=hotfix → branches hotfix/X.YY.Z from highest existing vX.YY.* tag,
|
||||
# auto-cherry-picks fix:/chore: from origin/main, pushes,
|
||||
# and outputs the new branch as ref. Idempotent: if branch
|
||||
# already exists (operator pre-prepared it via hotfix.yml),
|
||||
# we just check it out and re-run the cherry-pick step
|
||||
# no-ops since `git cherry` will report nothing new.
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
outputs:
|
||||
ref: ${{ steps.out.outputs.ref }}
|
||||
base_tag: ${{ steps.hotfix.outputs.base_tag }}
|
||||
steps:
|
||||
- name: Validate hotfix inputs
|
||||
if: inputs.action == 'hotfix'
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "::error::action=hotfix requires the 'version' input (e.g. 1.27.1)"
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[1-9][0-9]*$'; then
|
||||
echo "::error::Hotfix version must match X.YY.Z with Z>0 (got: $VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
if: inputs.action == 'hotfix'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure git identity
|
||||
if: inputs.action == 'hotfix'
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Prepare hotfix branch
|
||||
id: hotfix
|
||||
if: inputs.action == 'hotfix'
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
AUTO_CHERRY_PICK: ${{ inputs.auto_cherry_pick }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Stash the shipped-paths classifier from the dispatched ref's
|
||||
# working tree BEFORE `git checkout -b ... "$BASE_TAG"` below
|
||||
# overwrites it. Base tags predating #2980 don't have the
|
||||
# classifier in their tree, so the loop must reference a
|
||||
# location that survives the working-tree swap. Bug #2983.
|
||||
CLASSIFIER_SRC="scripts/diff-touches-shipped-paths.cjs"
|
||||
if [ ! -f "$CLASSIFIER_SRC" ]; then
|
||||
echo "::error::shipped-paths classifier not found at $CLASSIFIER_SRC in dispatched ref — refusing to run"
|
||||
exit 1
|
||||
fi
|
||||
CLASSIFIER="${RUNNER_TEMP}/diff-touches-shipped-paths.cjs"
|
||||
cp "$CLASSIFIER_SRC" "$CLASSIFIER"
|
||||
if [ ! -f "$CLASSIFIER" ]; then
|
||||
echo "::error::failed to stage classifier at $CLASSIFIER"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1-2)
|
||||
TARGET_TAG="v${VERSION}"
|
||||
BRANCH="hotfix/${VERSION}"
|
||||
# Semver-correct selection: append TARGET_TAG, sort -V, take preceding entry.
|
||||
# Plain lexicographic compare mis-orders multi-digit patches (v1.27.10 vs v1.27.9).
|
||||
BASE_TAG=$( ( git tag -l "v${MAJOR_MINOR}.*" | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$"; echo "$TARGET_TAG" ) \
|
||||
| sort -V \
|
||||
| awk -v target="$TARGET_TAG" '$1 == target { print prev; exit } { prev = $1 }')
|
||||
if [ -z "$BASE_TAG" ]; then
|
||||
echo "::error::No prior stable tag found for ${MAJOR_MINOR}.x before $TARGET_TAG"
|
||||
exit 1
|
||||
fi
|
||||
echo "base_tag=$BASE_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Idempotent branch creation — operator may have pre-prepared via hotfix.yml.
|
||||
git fetch origin main:refs/remotes/origin/main
|
||||
if git ls-remote --exit-code origin "refs/heads/$BRANCH" >/dev/null 2>&1; then
|
||||
echo "Branch $BRANCH already exists on origin; checking out"
|
||||
git fetch origin "$BRANCH"
|
||||
git checkout "$BRANCH"
|
||||
BRANCH_PRE_EXISTED=1
|
||||
else
|
||||
git checkout -b "$BRANCH" "$BASE_TAG"
|
||||
BRANCH_PRE_EXISTED=0
|
||||
# Push the skeleton up-front (real runs only) so cherry-pick conflicts
|
||||
# leave a remote artefact the operator can resolve. Dry-run keeps
|
||||
# everything local — no orphan branch created on origin.
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
git push -u origin "$BRANCH"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$AUTO_CHERRY_PICK" = "true" ]; then
|
||||
CANDIDATES=$(git cherry HEAD origin/main | awk '/^\+ / {print $2}')
|
||||
if [ -n "$CANDIDATES" ]; then
|
||||
ORDERED=$(git log --reverse --format='%H' "${BASE_TAG}..origin/main" \
|
||||
| grep -F -f <(echo "$CANDIDATES") || true)
|
||||
INCLUDED=""
|
||||
# POLICY_SKIPPED — commits intentionally not picked because they
|
||||
# don't match the fix/chore filter (feat/refactor/docs/etc).
|
||||
# CONFLICT_SKIPPED — fix/chore commits whose cherry-pick failed
|
||||
# and were skipped per the full-automation policy (#2968).
|
||||
# NON_SHIPPED_SKIPPED — fix/chore commits whose diff doesn't
|
||||
# touch any path in the npm tarball's `files` whitelist
|
||||
# (CI / test / docs / planning-only changes). They can't
|
||||
# affect the published package's behavior, so picking them
|
||||
# into a hotfix is meaningless — and picking workflow-file
|
||||
# changes specifically would also fail the push step because
|
||||
# the default GITHUB_TOKEN lacks the `workflow` scope. The
|
||||
# shipped-paths filter is the precise root cause: bug #2980.
|
||||
# Operators reviewing the run summary need these distinct so
|
||||
# the manual-review queue (CONFLICT_SKIPPED) isn't buried in
|
||||
# the noise from the other two buckets.
|
||||
POLICY_SKIPPED=""
|
||||
CONFLICT_SKIPPED=""
|
||||
NON_SHIPPED_SKIPPED=""
|
||||
while IFS= read -r SHA; do
|
||||
[ -z "$SHA" ] && continue
|
||||
SUBJECT=$(git log -1 --format='%s' "$SHA")
|
||||
if echo "$SUBJECT" | grep -qE '^(fix|chore)(\([^)]+\))?!?: '; then
|
||||
# Merge commits with fix:/chore: titles can't be cherry-picked
|
||||
# without `-m <parent>` and we can't pick the parent
|
||||
# automatically. They fail BEFORE entering cherry-pick state
|
||||
# (no CHERRY_PICK_HEAD), so an unconditional `--skip` would
|
||||
# then fail and brick the loop. Skip them upfront with a
|
||||
# distinct reason. Bug #2968 / CodeRabbit on PR #2970.
|
||||
PARENT_COUNT=$(git rev-list --parents -n 1 "$SHA" | awk '{print NF - 1}')
|
||||
if [ "$PARENT_COUNT" -gt 1 ]; then
|
||||
REASON="merge commit — manual -m parent selection required"
|
||||
echo "↷ skipping $SHA — $REASON"
|
||||
CONFLICT_SKIPPED="${CONFLICT_SKIPPED}- \`${SHA}\` ${SUBJECT} ($REASON)"$'\n'
|
||||
continue
|
||||
fi
|
||||
# Pre-pick guard: a hotfix release can only be affected
|
||||
# by commits whose diff intersects the npm tarball's
|
||||
# shipped paths (package.json `files` whitelist plus
|
||||
# package.json itself, which `npm pack` always
|
||||
# includes). Commits that touch only CI workflows,
|
||||
# tests, docs, or planning artifacts cannot change what
|
||||
# ships, so picking them into a hotfix is meaningless.
|
||||
# As a side benefit, this excludes
|
||||
# `.github/workflows/*` changes whose push would
|
||||
# otherwise be rejected by GitHub because the default
|
||||
# GITHUB_TOKEN lacks the `workflow` scope. The filter
|
||||
# is implemented in
|
||||
# scripts/diff-touches-shipped-paths.cjs rather than
|
||||
# inline so the rules (read package.json `files`,
|
||||
# treat entries as file-OR-directory prefix, the
|
||||
# `package.json`-always-shipped rule) are
|
||||
# unit-testable. Bug #2980.
|
||||
#
|
||||
# Use $CLASSIFIER (staged at workflow-start, before
|
||||
# `git checkout -b ... "$BASE_TAG"` swapped the working
|
||||
# tree) rather than `scripts/...` directly — base tags
|
||||
# older than #2980 don't have the classifier in their
|
||||
# tree. Capture the exit code via PIPESTATUS and
|
||||
# dispatch on it: 0 = shipped, 1 = not shipped, 2+ =
|
||||
# classifier error → fail-fast (don't silently treat
|
||||
# tooling errors as informational skips). Bug #2983.
|
||||
#
|
||||
# PIPESTATUS capture must happen IMMEDIATELY after the
|
||||
# pipeline — the previous form (`pipeline || true; RC=
|
||||
# ${PIPESTATUS[1]}`) had a subtle bug: when the
|
||||
# pipeline fails (exit 1 or 2 — exactly the cases we
|
||||
# care about), `|| true` runs `true` as a one-command
|
||||
# pipeline, overwriting PIPESTATUS to (0). The fix is
|
||||
# to wrap the pipeline in `set +e`/`set -e` and snapshot
|
||||
# PIPESTATUS into a local array on the very next line.
|
||||
# CodeRabbit on PR #2984.
|
||||
set +e
|
||||
git diff-tree --no-commit-id --name-only -r "$SHA" \
|
||||
| node "$CLASSIFIER"
|
||||
PIPE_RC=("${PIPESTATUS[@]}")
|
||||
set -e
|
||||
DIFFTREE_RC="${PIPE_RC[0]}"
|
||||
CLASSIFIER_RC="${PIPE_RC[1]}"
|
||||
if [ "$DIFFTREE_RC" -ne 0 ]; then
|
||||
echo "::error::git diff-tree failed for $SHA (exit $DIFFTREE_RC) — refusing to classify on incomplete input."
|
||||
exit "$DIFFTREE_RC"
|
||||
fi
|
||||
case "$CLASSIFIER_RC" in
|
||||
0) ;;
|
||||
1)
|
||||
REASON="touches no shipped paths (CI / test / docs / planning only)"
|
||||
echo "↷ skipping $SHA — $REASON"
|
||||
NON_SHIPPED_SKIPPED="${NON_SHIPPED_SKIPPED}- \`${SHA}\` ${SUBJECT}"$'\n'
|
||||
continue
|
||||
;;
|
||||
*)
|
||||
echo "::error::shipped-paths classifier failed for $SHA (exit $CLASSIFIER_RC). Refusing to silently skip — bug #2983."
|
||||
exit "$CLASSIFIER_RC"
|
||||
;;
|
||||
esac
|
||||
echo "→ cherry-picking $SHA $SUBJECT"
|
||||
# Pin merge.conflictStyle=merge on the cherry-pick so the
|
||||
# awk classifier below sees deterministic marker shapes —
|
||||
# diff3/zdiff3 would inject `||||||| ancestor` lines into
|
||||
# the HEAD section and cause context-missing conflicts to
|
||||
# misclassify as real. Bug #2966.
|
||||
if ! git -c merge.conflictStyle=merge cherry-pick -x --allow-empty --keep-redundant-commits "$SHA"; then
|
||||
# Full automation policy (bug #2968): any conflict the
|
||||
# cherry-pick can't auto-resolve is skipped, not aborted.
|
||||
# The hotfix run completes with whatever applies cleanly;
|
||||
# the CONFLICT_SKIPPED list below becomes the operator's
|
||||
# review queue (see "Cherry-pick summary" in the run
|
||||
# summary).
|
||||
#
|
||||
# Classify the conflict for the skip reason (operator-
|
||||
# facing diagnostic — doesn't change control flow):
|
||||
# - context absent at base: HEAD section in every
|
||||
# conflict marker is empty (the picked commit modifies
|
||||
# code that doesn't exist at the base). Bug #2966.
|
||||
# - merge conflict: HEAD section has content (both base
|
||||
# and patch want different content for the same
|
||||
# region). Typical when the base tag was cut from a
|
||||
# branch that has diverged from main. Bug #2968.
|
||||
UNMERGED=$(git diff --name-only --diff-filter=U)
|
||||
REASON="merge conflict — manual review"
|
||||
if [ -n "$UNMERGED" ]; then
|
||||
ALL_EMPTY_HEAD=true
|
||||
while IFS= read -r CONFLICTED; do
|
||||
[ -z "$CONFLICTED" ] && continue
|
||||
# Guard the classifier against degenerate cases that
|
||||
# would otherwise skew toward "context absent" (the
|
||||
# auto-skip path) when they're actually unsafe to skip:
|
||||
# - file missing or unreadable: don't pretend the
|
||||
# conflict is benign; treat as real.
|
||||
# - file listed as unmerged but no conflict markers
|
||||
# present: anomalous git state; treat as real so
|
||||
# the pick goes to the manual-review queue.
|
||||
# CodeRabbit on PR #2970.
|
||||
if [ ! -r "$CONFLICTED" ] || ! grep -q '^<<<<<<< ' "$CONFLICTED" 2>/dev/null; then
|
||||
ALL_EMPTY_HEAD=false
|
||||
break
|
||||
fi
|
||||
REAL=$(awk '
|
||||
/^<<<<<<< / { in_head=1; head=""; next }
|
||||
/^=======$/ && in_head { in_head=0; next }
|
||||
/^>>>>>>> / {
|
||||
if (head ~ /[^[:space:]]/) { print "real"; exit }
|
||||
head=""
|
||||
next
|
||||
}
|
||||
in_head { head = head $0 "\n" }
|
||||
' "$CONFLICTED" 2>/dev/null || echo "real")
|
||||
if [ "$REAL" = "real" ]; then
|
||||
ALL_EMPTY_HEAD=false
|
||||
break
|
||||
fi
|
||||
done <<< "$UNMERGED"
|
||||
if [ "$ALL_EMPTY_HEAD" = "true" ]; then
|
||||
REASON="context absent at base"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "↷ skipping $SHA — $REASON"
|
||||
# Guard `--skip`: cherry-pick can fail before entering the
|
||||
# conflict state (e.g. unreadable commit, empty-without-
|
||||
# --allow-empty edge cases the flag misses). Calling
|
||||
# `--skip` outside an in-progress cherry-pick exits non-
|
||||
# zero and would brick the loop. CodeRabbit on PR #2970.
|
||||
if git rev-parse -q --verify CHERRY_PICK_HEAD >/dev/null 2>&1; then
|
||||
git cherry-pick --skip
|
||||
fi
|
||||
CONFLICT_SKIPPED="${CONFLICT_SKIPPED}- \`${SHA}\` ${SUBJECT} ($REASON)"$'\n'
|
||||
continue
|
||||
fi
|
||||
INCLUDED="${INCLUDED}- \`${SHA}\` ${SUBJECT}"$'\n'
|
||||
else
|
||||
POLICY_SKIPPED="${POLICY_SKIPPED}- \`${SHA}\` ${SUBJECT}"$'\n'
|
||||
fi
|
||||
done <<< "$ORDERED"
|
||||
{
|
||||
echo "## Cherry-pick summary"
|
||||
echo ""
|
||||
echo "Base: \`$BASE_TAG\` → Branch: \`$BRANCH\`$([ "$DRY_RUN" = "true" ] && echo " (DRY RUN — local only)")"
|
||||
echo ""
|
||||
if [ -n "$INCLUDED" ]; then
|
||||
echo "### Included (fix/chore)"
|
||||
echo ""
|
||||
echo "$INCLUDED"
|
||||
else
|
||||
echo "_No fix/chore commits to include._"
|
||||
fi
|
||||
if [ -n "$NON_SHIPPED_SKIPPED" ]; then
|
||||
echo "### Skipped — touches no shipped paths (informational)"
|
||||
echo ""
|
||||
echo "These fix/chore commits don't touch any path in the npm tarball's \`files\` whitelist (or \`package.json\`), so they cannot change the published package's behavior. CI / test / docs / planning-only changes belong on \`main\`, not in a hotfix. No action needed."
|
||||
echo ""
|
||||
echo "$NON_SHIPPED_SKIPPED"
|
||||
fi
|
||||
if [ -n "$CONFLICT_SKIPPED" ]; then
|
||||
echo "### Skipped — cherry-pick conflict (manual review)"
|
||||
echo ""
|
||||
echo "$CONFLICT_SKIPPED"
|
||||
fi
|
||||
if [ -n "$POLICY_SKIPPED" ]; then
|
||||
echo "### Not auto-included (feat/refactor/docs/etc)"
|
||||
echo ""
|
||||
echo "$POLICY_SKIPPED"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Bump version on the branch (committed) so downstream install-smoke +
|
||||
# release jobs build the correct version. The release job's own in-tree
|
||||
# bump becomes a no-op when the file already has the right version.
|
||||
CURRENT=$(node -p "require('./package.json').version")
|
||||
if [ "$CURRENT" != "$VERSION" ]; then
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
git add package.json package-lock.json
|
||||
if [ -f sdk/package.json ]; then
|
||||
(cd sdk && npm version "$VERSION" --no-git-tag-version)
|
||||
git add sdk/package.json
|
||||
[ -f sdk/package-lock.json ] && git add sdk/package-lock.json
|
||||
fi
|
||||
git commit -m "chore: bump version to $VERSION for hotfix"
|
||||
fi
|
||||
if [ "$DRY_RUN" != "true" ]; then
|
||||
git push origin "$BRANCH"
|
||||
else
|
||||
echo "DRY RUN — cherry-picks applied locally; branch not pushed. Downstream install-smoke will run against \`$BASE_TAG\` (the cherry-pick verification above is the dry-run signal)."
|
||||
fi
|
||||
|
||||
- name: Determine effective ref
|
||||
id: out
|
||||
env:
|
||||
ACTION: ${{ inputs.action }}
|
||||
INPUT_REF: ${{ inputs.ref }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
BASE_TAG: ${{ steps.hotfix.outputs.base_tag }}
|
||||
BRANCH: ${{ steps.hotfix.outputs.branch }}
|
||||
run: |
|
||||
if [ "$ACTION" = "hotfix" ]; then
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "ref=$BASE_TAG" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "ref=$BRANCH" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
else
|
||||
echo "ref=$INPUT_REF" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Cross-platform install validation gate (parity with release.yml).
|
||||
install-smoke:
|
||||
needs: prepare
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/install-smoke.yml
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
|
||||
release:
|
||||
needs: [prepare, install-smoke]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: write # tag + push + GitHub Release
|
||||
id-token: write # provenance
|
||||
# The merge-back PR step (and the pull-request scope it required)
|
||||
# was removed in #2983 — auto-cherry-pick hotfix flow only picks
|
||||
# commits already on main, so there's nothing to merge back.
|
||||
environment: npm-publish
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Determine version
|
||||
id: ver
|
||||
env:
|
||||
ACTION: ${{ inputs.action }}
|
||||
INPUT_TAG: ${{ inputs.tag }}
|
||||
INPUT_OVERRIDE: ${{ inputs.version }}
|
||||
run: |
|
||||
set -e
|
||||
# Hotfix forces version=inputs.version and dist-tag=latest.
|
||||
if [ "$ACTION" = "hotfix" ]; then
|
||||
if [ -z "$INPUT_OVERRIDE" ]; then
|
||||
echo "::error::action=hotfix requires the 'version' input"
|
||||
exit 1
|
||||
fi
|
||||
VERSION="$INPUT_OVERRIDE"
|
||||
EFFECTIVE_TAG="latest"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=$EFFECTIVE_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "→ Hotfix: will publish v${VERSION} to dist-tag '${EFFECTIVE_TAG}'"
|
||||
exit 0
|
||||
fi
|
||||
RAW=$(node -p "require('./package.json').version")
|
||||
BASE=$(echo "$RAW" | sed 's/-.*//')
|
||||
if [ -n "$INPUT_OVERRIDE" ]; then
|
||||
VERSION="$INPUT_OVERRIDE"
|
||||
else
|
||||
case "$INPUT_TAG" in
|
||||
dev)
|
||||
N=1
|
||||
while git tag -l "v${BASE}-dev.${N}" | grep -q .; do
|
||||
N=$((N + 1))
|
||||
done
|
||||
VERSION="${BASE}-dev.${N}"
|
||||
;;
|
||||
next)
|
||||
N=1
|
||||
while git tag -l "v${BASE}-rc.${N}" | grep -q .; do
|
||||
N=$((N + 1))
|
||||
done
|
||||
VERSION="${BASE}-rc.${N}"
|
||||
;;
|
||||
latest)
|
||||
VERSION="$BASE"
|
||||
;;
|
||||
*)
|
||||
echo "::error::Unknown tag '$INPUT_TAG' (expected dev|next|latest)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=$INPUT_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "→ Will publish v${VERSION} to dist-tag '${INPUT_TAG}'"
|
||||
|
||||
# Reconciliation mode: if version is already on npm (a prior run
|
||||
# published successfully but a downstream step failed), don't hard-fail.
|
||||
# Set a flag and skip the publish step below; tag/release/PR/dist-tag
|
||||
# steps still execute so the rerun can finish reconciling state.
|
||||
- name: Detect prior publish (reconciliation mode)
|
||||
id: prior_publish
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
EXISTING=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || true)
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo "::warning::get-shit-done-cc@${VERSION} is already on the registry — entering reconciliation mode (skip publish, continue with tag/release/PR/dist-tag)."
|
||||
echo "skip_publish=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "skip_publish=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Tolerant tag-existence check (matches release.yml pattern). An
|
||||
# operator re-running after a mid-flight publish-step failure should
|
||||
# not be blocked just because the tag step succeeded last time. Only
|
||||
# error if the existing tag points at a different commit than HEAD.
|
||||
- name: Check git tag (skip if matches HEAD, error if mismatched)
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
if git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then
|
||||
EXISTING_SHA=$(git rev-parse "refs/tags/v${VERSION}")
|
||||
HEAD_SHA=$(git rev-parse HEAD)
|
||||
if [ "$EXISTING_SHA" != "$HEAD_SHA" ]; then
|
||||
echo "::error::git tag v${VERSION} already exists pointing at ${EXISTING_SHA}, but HEAD is ${HEAD_SHA}"
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::tag v${VERSION} already exists at HEAD; tag step will skip"
|
||||
fi
|
||||
|
||||
- name: Configure git identity
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Bump in-tree version (not committed)
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
# --allow-same-version: prepare may have already committed this bump
|
||||
# on the hotfix branch (release checks out BRANCH in real runs,
|
||||
# BASE_TAG in dry-runs — only the latter has the older version).
|
||||
npm version "$VERSION" --no-git-tag-version --allow-same-version
|
||||
cd sdk && npm version "$VERSION" --no-git-tag-version --allow-same-version
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run full test suite with coverage (parity with release.yml)
|
||||
run: npm run test:coverage
|
||||
|
||||
- name: Build SDK dist for tarball
|
||||
run: npm run build:sdk
|
||||
|
||||
- name: Verify CC tarball ships sdk/dist/cli.js (bug #2647 guard)
|
||||
run: bash scripts/verify-tarball-sdk-dist.sh
|
||||
|
||||
- name: Pack SDK as tarball and bundle into CC source tree
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
set -e
|
||||
cd sdk
|
||||
npm pack
|
||||
# npm pack emits gsd-build-sdk-<version>.tgz in the cwd
|
||||
TARBALL="gsd-build-sdk-${VERSION}.tgz"
|
||||
if [ ! -f "$TARBALL" ]; then
|
||||
echo "::error::Expected $TARBALL but npm pack did not produce it. Listing sdk/:"
|
||||
ls -la
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p ../sdk-bundle
|
||||
mv "$TARBALL" ../sdk-bundle/gsd-sdk.tgz
|
||||
cd ..
|
||||
ls -la sdk-bundle/
|
||||
|
||||
- name: Add sdk-bundle to CC files whitelist (in-tree, not committed)
|
||||
run: |
|
||||
node <<'NODE'
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
if (!Array.isArray(pkg.files)) {
|
||||
console.error('::error::package.json files is not an array');
|
||||
process.exit(1);
|
||||
}
|
||||
if (!pkg.files.includes('sdk-bundle')) {
|
||||
pkg.files.push('sdk-bundle');
|
||||
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
console.log('Added sdk-bundle/ to package.json files whitelist');
|
||||
} else {
|
||||
console.log('sdk-bundle/ already in files whitelist');
|
||||
}
|
||||
NODE
|
||||
|
||||
- name: Verify CC tarball will contain sdk-bundle/gsd-sdk.tgz
|
||||
run: |
|
||||
set -e
|
||||
TARBALL=$(npm pack --ignore-scripts 2>/dev/null | tail -1)
|
||||
if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then
|
||||
echo "::error::npm pack produced no tarball"
|
||||
exit 1
|
||||
fi
|
||||
echo "Inspecting $TARBALL for sdk-bundle/gsd-sdk.tgz:"
|
||||
if ! tar -tzf "$TARBALL" | grep -q "package/sdk-bundle/gsd-sdk.tgz"; then
|
||||
echo "::error::CC tarball is missing package/sdk-bundle/gsd-sdk.tgz"
|
||||
tar -tzf "$TARBALL" | grep -E "sdk-bundle|sdk/dist" | head -20
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ CC tarball contains sdk-bundle/gsd-sdk.tgz"
|
||||
rm -f "$TARBALL"
|
||||
|
||||
- name: Dry-run publish validation
|
||||
# Skip the rehearsal when the version is already on npm
|
||||
# (reconciliation mode). `npm publish --dry-run` contacts the
|
||||
# registry and fails with "You cannot publish over the
|
||||
# previously published versions" if the version exists, even
|
||||
# though no actual publish would be attempted. The real publish
|
||||
# step (further down) is gated on the same condition; gate the
|
||||
# rehearsal too so re-runs of an already-published hotfix don't
|
||||
# fail here on a check that doesn't apply. Bug #2987.
|
||||
if: ${{ steps.prior_publish.outputs.skip_publish != 'true' }}
|
||||
env:
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --dry-run --tag "$TAG"
|
||||
|
||||
- name: Tag and push
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
run: |
|
||||
if git rev-parse -q --verify "refs/tags/v${VERSION}" >/dev/null; then
|
||||
echo "Tag v${VERSION} already exists at HEAD (per pre-flight check); skipping git tag step"
|
||||
else
|
||||
git tag "v${VERSION}"
|
||||
fi
|
||||
git push origin "v${VERSION}"
|
||||
|
||||
- name: Publish to npm (CC bundle, SDK included as both loose tree and .tgz)
|
||||
if: ${{ !inputs.dry_run && steps.prior_publish.outputs.skip_publish != 'true' }}
|
||||
env:
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --provenance --access public --tag "$TAG"
|
||||
|
||||
# Keep `next` from going stale relative to `latest`. When publishing a
|
||||
# stable release, also point `next` at it so users on `@next` don't
|
||||
# get stuck on an older pre-release than what's now stable. Parity
|
||||
# with release.yml#finalize "Clean up next dist-tag" step.
|
||||
- name: Re-point next dist-tag at the new latest (only when tag=latest)
|
||||
if: ${{ !inputs.dry_run && steps.ver.outputs.tag == 'latest' }}
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
npm dist-tag add "get-shit-done-cc@${VERSION}" next
|
||||
echo "✅ next dist-tag re-pointed to v${VERSION} (matches latest)"
|
||||
|
||||
- name: Create GitHub Release (idempotent)
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
run: |
|
||||
# Per-tag release flags:
|
||||
# dev, next → --prerelease (won't be highlighted as the latest release on the repo page)
|
||||
# latest → --latest (becomes the highlighted release)
|
||||
# Idempotent: if release already exists (rerun after a transient
|
||||
# downstream failure), edit the latest flag instead of failing.
|
||||
if gh release view "v${VERSION}" >/dev/null 2>&1; then
|
||||
echo "GitHub Release v${VERSION} already exists; reconciling --latest flag"
|
||||
if [ "$TAG" = "latest" ]; then
|
||||
gh release edit "v${VERSION}" --latest || true
|
||||
fi
|
||||
elif [ "$TAG" = "latest" ]; then
|
||||
gh release create "v${VERSION}" \
|
||||
--title "v${VERSION}" \
|
||||
--generate-notes \
|
||||
--latest
|
||||
else
|
||||
gh release create "v${VERSION}" \
|
||||
--title "v${VERSION}" \
|
||||
--generate-notes \
|
||||
--prerelease
|
||||
fi
|
||||
echo "✅ GitHub Release v${VERSION} ready"
|
||||
|
||||
# Merge-back PR step removed — bug #2983.
|
||||
#
|
||||
# The auto-cherry-pick hotfix flow only picks commits already on
|
||||
# main (`git cherry HEAD origin/main` outputs unmerged commits;
|
||||
# we filter to 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 would either no-op against main (already past
|
||||
# X.Y.Z) or rewind main's in-progress version — strictly
|
||||
# counterproductive in either case.
|
||||
#
|
||||
# The original merge-back step also failed in production with
|
||||
# `GitHub Actions is not permitted to create or approve pull
|
||||
# requests (createPullRequest)` (org policy), but even if the
|
||||
# policy were lifted the PR would have nothing useful to merge.
|
||||
# Run 25232968975 was the trigger for removal.
|
||||
|
||||
- name: Verify publish landed on registry
|
||||
if: ${{ !inputs.dry_run }}
|
||||
env:
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
run: |
|
||||
PUBLISHED="NOT_FOUND"
|
||||
for delay in 5 10 20 30 45; do
|
||||
PUBLISHED=$(npm view get-shit-done-cc@"$VERSION" version 2>/dev/null || echo "NOT_FOUND")
|
||||
if [ "$PUBLISHED" = "$VERSION" ]; then
|
||||
break
|
||||
fi
|
||||
echo "Waiting ${delay}s for registry to catch up (saw: $PUBLISHED)..."
|
||||
sleep "$delay"
|
||||
done
|
||||
if [ "$PUBLISHED" != "$VERSION" ]; then
|
||||
echo "::error::Version $VERSION did not appear on the registry within timeout"
|
||||
exit 1
|
||||
fi
|
||||
TAG_VERSION=$(npm view get-shit-done-cc dist-tags."$TAG" 2>/dev/null || echo "NOT_FOUND")
|
||||
if [ "$TAG_VERSION" != "$VERSION" ]; then
|
||||
echo "::error::dist-tag '$TAG' resolves to '$TAG_VERSION', expected '$VERSION'"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ get-shit-done-cc@${VERSION} live on dist-tag '${TAG}'"
|
||||
|
||||
- name: Summary
|
||||
env:
|
||||
ACTION: ${{ inputs.action }}
|
||||
VERSION: ${{ steps.ver.outputs.version }}
|
||||
TAG: ${{ steps.ver.outputs.tag }}
|
||||
BASE_TAG: ${{ needs.prepare.outputs.base_tag }}
|
||||
BRANCH: ${{ needs.prepare.outputs.ref }}
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
run: |
|
||||
{
|
||||
if [ "$ACTION" = "hotfix" ]; then
|
||||
echo "## Release SDK Bundle (hotfix): v${VERSION} → @${TAG}"
|
||||
echo ""
|
||||
echo "- Base (cumulative-fix anchor): \`${BASE_TAG}\`"
|
||||
echo "- Branch: \`${BRANCH}\`"
|
||||
else
|
||||
echo "## Release SDK Bundle: v${VERSION} → @${TAG}"
|
||||
fi
|
||||
echo ""
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo "**DRY RUN** — npm publish, git tag, push, and GitHub Release were skipped."
|
||||
else
|
||||
echo "- Published \`get-shit-done-cc@${VERSION}\` to dist-tag \`${TAG}\`"
|
||||
echo "- SDK bundled inside the CC tarball at:"
|
||||
echo " - \`sdk/dist/cli.js\` (loose tree, consumed by \`bin/gsd-sdk.js\` shim)"
|
||||
echo " - \`sdk-bundle/gsd-sdk.tgz\` (npm-installable artifact)"
|
||||
echo "- Git tag \`v${VERSION}\` pushed"
|
||||
echo "- GitHub Release \`v${VERSION}\` created"
|
||||
if [ "$TAG" = "latest" ]; then
|
||||
echo "- \`next\` dist-tag re-pointed at \`v${VERSION}\` (kept current with \`latest\`)"
|
||||
fi
|
||||
if [ "$ACTION" = "hotfix" ]; then
|
||||
# Auto-cherry-pick hotfixes only pick commits already on
|
||||
# main, so there's nothing to merge back. The merge-back
|
||||
# PR step was removed in #2983; this line surfaces the
|
||||
# explicit non-action so operators don't expect a PR
|
||||
# that was never opened.
|
||||
echo "- No merge-back PR (auto-picked commits are already on main)"
|
||||
fi
|
||||
echo "- Install: \`npm install -g get-shit-done-cc@${TAG}\`"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
17
.github/workflows/require-issue-link.yml
vendored
17
.github/workflows/require-issue-link.yml
vendored
@@ -24,19 +24,20 @@ jobs:
|
||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Comment and fail if no issue link
|
||||
- name: Comment, close, and fail if no issue link
|
||||
if: steps.check.outputs.found == 'false'
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
# Uses GitHub API SDK — no shell string interpolation of untrusted input
|
||||
script: |
|
||||
const repoUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}`;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
issue_number: prNumber,
|
||||
body: [
|
||||
'## Missing issue link',
|
||||
'## Missing issue link — PR auto-closed',
|
||||
'',
|
||||
'This PR does not reference an issue. **All PRs must link to an open issue** using a closing keyword in the PR body:',
|
||||
'',
|
||||
@@ -46,7 +47,13 @@ jobs:
|
||||
'',
|
||||
`If no issue exists for this change, [open one first](${repoUrl}/issues/new/choose), then update this PR body with the reference.`,
|
||||
'',
|
||||
'This PR will remain blocked until a valid `Closes #NNN`, `Fixes #NNN`, or `Resolves #NNN` line is present in the description.',
|
||||
'To resume work after fixing the body: edit the PR description to add a valid `Closes #NNN`, `Fixes #NNN`, or `Resolves #NNN` line, then click **Reopen pull request**. The workflow will re-evaluate on reopen.',
|
||||
].join('\n')
|
||||
});
|
||||
core.setFailed('PR body must contain a closing issue reference (e.g. "Closes #123")');
|
||||
await github.rest.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
state: 'closed',
|
||||
});
|
||||
core.setFailed('PR body must contain a closing issue reference (e.g. "Closes #123") — PR closed.');
|
||||
|
||||
15
.github/workflows/test.yml
vendored
15
.github/workflows/test.yml
vendored
@@ -30,6 +30,9 @@ jobs:
|
||||
- name: Lint — no source-grep tests
|
||||
shell: bash
|
||||
run: node scripts/lint-no-source-grep.cjs
|
||||
- name: Lint — command contract (ADR-0002)
|
||||
shell: bash
|
||||
run: node scripts/lint-command-contract.cjs
|
||||
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -88,6 +91,18 @@ jobs:
|
||||
- name: Build SDK dist (required by installer)
|
||||
run: npm run build:sdk
|
||||
|
||||
# Seam contract gate: keep manifest -> generated aliases -> registry/CJS adapters aligned.
|
||||
# Run once per workflow on the primary Linux node to avoid redundant matrix cost.
|
||||
- name: SDK seam coverage tests
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node-version == 24
|
||||
shell: bash
|
||||
run: cd sdk && npx vitest run src/query/command-seam-coverage.test.ts
|
||||
|
||||
- name: SDK generated alias artifact drift check
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node-version == 24
|
||||
shell: bash
|
||||
run: node sdk/scripts/check-command-aliases-fresh.mjs
|
||||
|
||||
- name: Run tests with coverage
|
||||
shell: bash
|
||||
run: npm run test:coverage
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -66,3 +66,4 @@ vendor/
|
||||
.cache/
|
||||
tmp/
|
||||
.worktrees
|
||||
.envrc
|
||||
|
||||
104
.out-of-scope/agent-template-rendering.md
Normal file
104
.out-of-scope/agent-template-rendering.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Render agent definitions from templates at install/config-change time
|
||||
|
||||
**Source:** [#2758](https://github.com/gsd-build/get-shit-done/issues/2758)
|
||||
**Decision:** wontfix — closed on the technical merits
|
||||
**Date:** 2026-05-02
|
||||
|
||||
## Proposal summary
|
||||
|
||||
Move config-gated prose out of `agents/*.md` into `agents/templates/*.md.tmpl`,
|
||||
rendered at install time and after `.planning/config.json` writes via a new
|
||||
`gsd-sdk agents render` subcommand. Conditional branches resolve at render time
|
||||
(deterministic code) instead of at inference time (LLM interpretation).
|
||||
|
||||
Three named benefits:
|
||||
|
||||
1. Token reduction proportional to disabled features.
|
||||
2. Deterministic feature gating (impossible-by-construction vs. test-for).
|
||||
3. Single source of truth for contributor-facing gating.
|
||||
|
||||
Cites PR #2279 (Codex/OpenCode model embedding at install time) as direct
|
||||
precedent for compile-time embedding.
|
||||
|
||||
## Why GSD does not own this
|
||||
|
||||
### 1. The determinism claim is theoretical, not observed
|
||||
|
||||
The proposal's strongest argument is that config-gated branches in agent prose
|
||||
are a determinism failure surface. The actual patterns in the codebase today are
|
||||
already heavily mitigated:
|
||||
|
||||
- The `use_worktrees` branch in `gsd-executor` is resolved deterministically via
|
||||
`gsd-sdk query config-get` in bash — it is not LLM-interpreted.
|
||||
- "Skip if `workflow.X` is `false`" prose patterns are short, stable, and
|
||||
follow a uniform "missing key = enabled" convention. There is no documented
|
||||
history of LLMs running disabled checks or skipping enabled ones because of
|
||||
this prose.
|
||||
|
||||
A theoretical failure surface should not be traded for a real, high-risk
|
||||
patch-migration surface (`gsd-local-patches/` rebase logic, by the reporter's own
|
||||
admission "the highest-risk piece of the change"). The reporter was asked for
|
||||
documented evidence; none was provided.
|
||||
|
||||
### 2. Token waste is small and bounded
|
||||
|
||||
The codebase has roughly 5 `workflow.*` toggle references in agent files and
|
||||
~20 "Skip if" conditional-prose patterns total — most 1–2 sentences. The
|
||||
"real spend across multi-phase milestones" claim was not measured against
|
||||
`gsd-context-monitor` output despite being asked. Without a measured baseline,
|
||||
the token-savings argument is asserted rather than demonstrated, and the savings
|
||||
ceiling on ~20 short conditionals is small enough that it does not justify a new
|
||||
template-and-rendering subsystem with a CI-enforced template/generated split.
|
||||
|
||||
### 3. The deterministic-gating need is already served
|
||||
|
||||
PR #2279 established orchestrator-time config embedding for the cases that
|
||||
genuinely need deterministic resolution (model selection, reasoning effort,
|
||||
worktree mode). That mechanism is the right layer for orchestration-time
|
||||
decisions and can be extended toggle-by-toggle along the existing path without
|
||||
introducing a parallel templating subsystem. The proposal's own "Alternative #1"
|
||||
(continue the orchestrator-embedding pattern) was rejected on the grounds that
|
||||
agent-internal conditionals belong in the agent layer, but the asks behind the
|
||||
proposal — determinism, lower token cost — are equally satisfied by extending
|
||||
PR #2279 incrementally without a second mechanism.
|
||||
|
||||
Adding a templating layer alongside orchestrator-embedding means two mechanisms
|
||||
own the same problem. The proposal does not specify a partition rule, and the
|
||||
reporter did not respond when asked for one.
|
||||
|
||||
### 4. Patch-migration risk is disproportionate to benefit
|
||||
|
||||
The `/gsd-reapply-patches` three-way-merge migration for `gsd-local-patches/`
|
||||
is, in the proposal's own words, the highest-risk piece of the change. It exists
|
||||
solely to absorb a contributor-workflow shift — the user-facing surface is
|
||||
unchanged. Risk that flows entirely from internal restructuring, where the
|
||||
benefit is unmeasured token savings and a theoretical determinism gain, is the
|
||||
wrong trade.
|
||||
|
||||
The reduced-scope variant (Alternative #5: fresh installs only, defer the
|
||||
migration) avoids that specific risk but still ships a parallel mechanism for
|
||||
benefits that remain unmeasured and that PR #2279's path can absorb.
|
||||
|
||||
## Re-open criteria
|
||||
|
||||
This may be revisited if a contributor:
|
||||
|
||||
- Provides measured token deltas via `gsd-context-monitor` against a
|
||||
representative all-toggles-off config, and the delta is materially larger
|
||||
than what extending PR #2279's orchestrator-embedding path one toggle at a
|
||||
time would produce.
|
||||
- Documents a real LLM misinterpretation of an existing toggle conditional
|
||||
(executor ignored `workflow.use_worktrees: false`, verifier ran when
|
||||
`workflow.verifier: false`, etc.) — not a projected failure mode.
|
||||
- Proposes a clear partition rule between orchestrator-time embedding (PR #2279)
|
||||
and any new install-time templating layer, so the two mechanisms do not
|
||||
overlap.
|
||||
|
||||
## Related
|
||||
|
||||
- PR #2279 — Codex/OpenCode model embedding at install time (the established
|
||||
precedent for deterministic compile-time embedding into agent files)
|
||||
- v1.37.0 release notes — shared-boilerplate extraction (reference files for
|
||||
mandatory-initial-read, project-skills-discovery)
|
||||
- `get-shit-done/workflows/` — workflow-level config embedding before subagent
|
||||
spawn (the path of least friction for incremental deterministic gating)
|
||||
56
.out-of-scope/temporal-context.md
Normal file
56
.out-of-scope/temporal-context.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Temporal context as a first-class GSD signal
|
||||
|
||||
**Source:** [#2756](https://github.com/gsd-build/get-shit-done/issues/2756)
|
||||
**Decision:** wontfix — closed without further engagement
|
||||
**Date:** 2026-05-02
|
||||
|
||||
## Proposal summary
|
||||
|
||||
Reporter proposed treating idle-time-between-turns as a first-class context signal in
|
||||
GSD. Three flavors floated across the issue:
|
||||
|
||||
1. **Passive** — block at session resume injecting "you've been idle Nh, here's what was
|
||||
open" into the orchestrator prompt.
|
||||
2. **Active** — `/resume-context` slash command.
|
||||
3. **Retrospective** — `HANDOFF.json` written at session end, read at next start.
|
||||
|
||||
Framed initially as a `claude-inject-idle-time` plugin, with a request that GSD treat
|
||||
the pattern as core.
|
||||
|
||||
## Why GSD does not own this
|
||||
|
||||
- **Subagent gap unsolved.** Passive injection lands in the orchestrator's context
|
||||
only. Subagents (the workers that actually do GSD's planning, execution, verification)
|
||||
spawn fresh and never see the temporal signal. The proposal does not solve this, and
|
||||
any GSD-core integration would inherit the gap. Until the subagent boundary is
|
||||
addressed, "first-class temporal context" is at best a partial feature.
|
||||
- **`HANDOFF.json` duplicates existing artifacts.** GSD already persists session
|
||||
continuity through `.planning/state/*` and per-phase artifacts (PLAN.md, RESEARCH.md,
|
||||
REVIEW.md, VERIFICATION.md). A separate handoff file would either drift from those or
|
||||
redundantly mirror them. The right primitive for "what was I doing" already exists.
|
||||
- **Statusline / TUI re-entry is platform-level, not GSD-level.** A statusline showing
|
||||
idle time belongs in Claude Code itself or in a thin user plugin, not in GSD's phase
|
||||
machinery.
|
||||
- **Scope is unstable.** Reporter agreed with the narrowed minimum ask ("doc mention
|
||||
only, rest opt-in"), then partially retracted it in a follow-up comment ("very
|
||||
integral to myself"). The maintainer asked which version of the ask should move
|
||||
forward; reporter did not respond.
|
||||
|
||||
## Re-open criteria
|
||||
|
||||
This may be revisited if a reporter:
|
||||
|
||||
- Engages with the subagent-gap problem and proposes a concrete mechanism for
|
||||
temporal context to reach subagents (not just the orchestrator).
|
||||
- Demonstrates a use case `.planning/state/*` provably cannot serve.
|
||||
- Commits to a single stable scope (doc mention OR core integration OR plugin
|
||||
reference) rather than oscillating between them mid-thread.
|
||||
|
||||
A drive-by enhancement request that the author does not return to engage with after
|
||||
maintainer questions is not actionable. Future proposers: please plan to participate
|
||||
through to a triage decision rather than dropping an issue and moving on.
|
||||
|
||||
## Related
|
||||
|
||||
- `.planning/state/` — existing session-continuity artifacts
|
||||
- `get-shit-done/references/` — where any future plugin-interface doc would live
|
||||
191
CHANGELOG.md
191
CHANGELOG.md
@@ -4,9 +4,27 @@ All notable changes to GSD will be documented in this file.
|
||||
|
||||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [Unreleased](https://github.com/gsd-build/get-shit-done/compare/v1.38.5...HEAD)
|
||||
## [Unreleased](https://github.com/gsd-build/get-shit-done/compare/v1.39.1...HEAD)
|
||||
|
||||
### Added
|
||||
### Feature
|
||||
|
||||
- **Six namespace meta-skills with keyword-tag descriptions** — replace the flat 86-skill
|
||||
listing with two-stage hierarchical routing. Model sees 6 namespace routers
|
||||
(`gsd:workflow`, `gsd:project`, `gsd:review`, `gsd:context`, `gsd:manage`,
|
||||
`gsd:ideate`) instead of 86 flat entries; selects a namespace, then routes to the
|
||||
sub-skill. Descriptions use pipe-separated keyword tags (≤ 60 chars). Cuts cold-start
|
||||
system-prompt overhead from ~2,150 tokens to ~120. Existing sub-skills are unchanged
|
||||
and still invocable directly. (#2792)
|
||||
- **`/gsd-health --context` utilization guard** — context-window quality guard with two
|
||||
thresholds: 60 % warns ("consider `/gsd-thread`"), 70 % is critical ("reasoning
|
||||
quality may degrade"). Exposed via `/gsd-health --context` and as a structured
|
||||
`gsd-tools validate context` command. (#2792)
|
||||
- **Phase-lifecycle status-line — read-side** — `parseStateMd()` now reads four new
|
||||
STATE.md frontmatter fields: `active_phase`, `next_action`, `next_phases`, and
|
||||
`progress` (nested completed/total/percent). `formatGsdState()` gains scenes for
|
||||
in-flight, idle, and progress display. All fields default to undefined so existing
|
||||
STATE.md files keep rendering. Write-side and status-line wiring follow in a later
|
||||
RC. (#2833)
|
||||
- `--minimal` install flag (alias `--core-only`) writes only the main-loop core skills
|
||||
(`new-project`, `discuss-phase`, `plan-phase`, `execute-phase`, `help`, `update`) and
|
||||
zero `gsd-*` subagents. Cuts cold-start system-prompt overhead from ~12k tokens to
|
||||
@@ -29,8 +47,93 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
`.planning/config.json` is loaded first and deep-merged with the workstream config
|
||||
(workstream wins on conflict). Explicit `null` in a workstream config now correctly
|
||||
overrides a root value. (#2714)
|
||||
- **Manual canary release workflow** — `.github/workflows/canary.yml` publishes
|
||||
`{base}-canary.{N}` builds of `get-shit-done-cc` and `@gsd-build/sdk` under the
|
||||
`canary` dist-tag on demand via `workflow_dispatch` (manual trigger only — auto-publish
|
||||
on every push to main was rejected because submission rate is too high). Includes an
|
||||
optional `dry_run` boolean and the same publish-verification gate as `release.yml`. (#2828)
|
||||
|
||||
### Fixed
|
||||
### Enhancement
|
||||
|
||||
- **Test suite for `config-schema.cjs` is now mutation-resistant** — Stryker measured a 4.62% mutation score on `get-shit-done/bin/lib/config-schema.cjs` (6 killed, 124 survived out of 130). Surviving mutants flagged that existing tests were exercising paths but not verifying outputs: a polarity flip (`return true` → `return false`), a predicate swap (`.some` → `.every`), or a guard removal (`if (VALID_CONFIG_KEYS.has(...)) return true;` → unguarded fallthrough) all passed every test. New `tests/bug-2986-config-schema-mutation-killers.test.cjs` adds 95 tests across four suites that target each surviving mutant class: (1) parameterized `isValidConfigKey('${key}') === true` for every member of `VALID_CONFIG_KEYS` (kills the static-key-fast-path mutation), (2) representative dynamic-pattern keys that match exactly one pattern (kills the `.some` → `.every` mutation, with an inline mutual-exclusivity invariant check), (3) `strictEqual` against the literal boolean `true`/`false` instead of `assert.ok` truthy checks (kills polarity-flip mutations), (4) anchor-tightening cases that differ from valid keys by one character beyond the documented shape (kills regex-loosening mutations on `^`, `$`, and character-class boundaries). Tests use the lib's public surface (typed boolean assertions on `isValidConfigKey` return values), no source-grep. (#2986)
|
||||
- **Hotfix release flow now auto-incorporates fixes from `main` and bundles the SDK** — `hotfix.yml create` auto-cherry-picks every `fix:`/`chore:` commit on `origin/main` not yet shipped (oldest-first; patch-equivalents skipped via `git cherry`; `feat:`/`refactor:` excluded; conflicts halt with the offending SHA; run summary lists every included SHA). `hotfix.yml finalize` adds the `install-smoke` cross-platform gate, bundles `sdk-bundle/gsd-sdk.tgz` inside the CC tarball (parity with `release-sdk.yml`), tightens the `next` dist-tag re-point, and marks the GitHub Release `--latest`. `release-sdk.yml` gains `action: publish | hotfix` plus an `auto_cherry_pick` toggle, with a new `prepare` job that branches `hotfix/X.YY.Z` from the highest existing `vX.YY.*` tag and runs the same cherry-pick logic — idempotent if the branch was pre-prepared via `hotfix.yml`. Hotfix `vX.YY.Z` is now defined as everything in `vX.YY.{Z-1}` plus every `fix:`/`chore:` since that base, so each tag is the cumulative-fix anchor for the next. (#2955)
|
||||
- **Planning workspace seam extracted from `core.cjs` into `planning-workspace.cjs`** — path/workstream/lock behavior now lives in a dedicated module (`planningDir`, `planningPaths`, `planningRoot`, active-workstream routing, `withPlanningLock`). `core.cjs` keeps compatibility re-exports while call-sites migrate to direct imports, improving locality and reducing coupling. (#2900)
|
||||
- **Skill surface consolidated 86 → 59 `commands/gsd/*.md` entries** — four new
|
||||
grouped skills (`capture`, `phase`, `config`, `workspace`) replace clusters of
|
||||
micro-skills. Six existing parents absorb wrap-up and sub-operations as flags:
|
||||
`update --sync/--reapply`, `sketch --wrap-up`, `spike --wrap-up`,
|
||||
`map-codebase --fast/--query`, `code-review --fix`, `progress --do/--next`. Zero
|
||||
functional loss; 31 micro-skills deleted. `autonomous.md` corrected to call
|
||||
`gsd:code-review --fix` (was invoking deleted `gsd:code-review-fix`). (#2790)
|
||||
- **PRs missing `Closes #NNN` are auto-closed** — the `Issue link required` workflow
|
||||
now auto-closes PRs opened without a closing keyword that links a tracking issue,
|
||||
posting a comment that points to the contribution guide. (#2872)
|
||||
- **Canary release workflow now publishes from `dev` branch only** — `.github/workflows/canary.yml`
|
||||
swaps its four publish-step guards from `refs/heads/main` to `refs/heads/dev`. Aligns the
|
||||
workflow with the new branch→dist-tag policy (`dev` → `@canary`, `main` → `@next`/`@latest`).
|
||||
Added a header comment documenting the policy. `workflow_dispatch` runs on `main` (or any
|
||||
other branch) now complete build/test/dry-run validation but skip publish + tag, instead
|
||||
of the previous behaviour where `main` published and `dev` silently no-op'd. (#2868)
|
||||
- **Skill descriptions trimmed to ≤ 100 chars across all `commands/gsd/*.md`** — three
|
||||
anti-patterns eliminated: flag documentation already present in `argument-hint:` (e.g.
|
||||
`discuss-phase` was 380 chars, now 76), `Triggers:` keyword-stuffing lists, and
|
||||
numbered enumeration patterns. Range was 45–380 chars; now 45–99. (#2789)
|
||||
- **`scripts/lint-descriptions.cjs` added** — CI lint gate that fails if any
|
||||
`commands/gsd/*.md` description exceeds 100 chars. Run via `npm run lint:descriptions`.
|
||||
(#2789)
|
||||
- **Skill surface consolidated from 86 → 59 `commands/gsd/*.md` entries** — four new
|
||||
grouped skills replace clusters of micro-skills: `capture` (add-todo, note, add-backlog,
|
||||
plant-seed, check-todos), `phase` (add-phase, insert-phase, remove-phase, edit-phase),
|
||||
`config` (settings-advanced, settings-integrations, set-profile), `workspace`
|
||||
(new-workspace, list-workspaces, remove-workspace). Six parent skills absorb wrap-up
|
||||
and sub-operations as flags: `update --sync/--reapply`, `sketch --wrap-up`,
|
||||
`spike --wrap-up`, `map-codebase --fast/--query`, `code-review --fix`,
|
||||
`progress --do/--next`. Zero functional loss. (#2790)
|
||||
- **`autonomous.md` corrected** — was invoking deleted `gsd:code-review-fix`; now calls
|
||||
`gsd:code-review --fix`. (#2790)
|
||||
- **31 micro-skills deleted** — absorbed into consolidated parents or removed outright:
|
||||
add-todo, note, add-backlog, plant-seed, check-todos, add-phase, insert-phase,
|
||||
remove-phase, edit-phase, settings-advanced, settings-integrations, set-profile,
|
||||
new-workspace, list-workspaces, remove-workspace, sync-skills, reapply-patches,
|
||||
sketch-wrap-up, spike-wrap-up, scan, intel, code-review-fix, next, do,
|
||||
join-discord, research-phase, session-report, from-gsd2, analyze-dependencies,
|
||||
list-phase-assumptions, plan-milestone-gaps. All functionality preserved via flags on
|
||||
consolidated skills. (#2790)
|
||||
- **`discuss-phase` lazy file loading** — entry-point `@file` directives replaced with
|
||||
on-demand `Read()` calls gated behind mode routing. Tokens loaded at skill entry drop
|
||||
from ~13k to near zero; only the branch actually invoked is loaded. (#2606)
|
||||
|
||||
### Fix
|
||||
|
||||
- **`gsd-pristine/` is now populated by the installer when local patches are detected** — `saveLocalPatches` declared a `pristineDir` variable and JSDoc'd "saves pristine copies (from manifest) to gsd-pristine/ to enable three-way merge during reapply-patches", but no code ever wrote to that directory. Effect: the `/gsd-reapply-patches` Step 5 verifier (#2972) silently degraded to its over-broad fallback heuristic ("every significant backup line"), exactly the silent-success-on-lost-content failure mode #2969 was designed to prevent. Fix: new `populatePristineDir({ packageSrc, pristineDir, modified, runtime, pathPrefix, isGlobal })` helper runs the install transform pipeline (`copyWithPathReplacement`) into a tmp staging dir, then copies out only the modified-file paths into `gsd-pristine/`. `saveLocalPatches` now accepts a `pristineCtx` and calls the helper when local patches are detected; the install entry point passes the package source root, runtime, pathPrefix, and isGlobal so transforms produce byte-identical output to what `copyWithPathReplacement` would have written under normal install. Soft-fails on transform errors (logs a warning, continues with empty pristine — no worse than pre-fix behavior). Pristine reflects the about-to-install version's content, which is what the verifier needs as the "what would survive without the user's modifications" baseline. Regression covered by `tests/bug-2998-pristine-dir-populated.test.cjs` (6 tests across two suites): asserts the helper is exported, returns 0 for empty modified list, writes one pristine file per source-existing path, skips ghost paths without corrupting pristine, and produces deterministic output (two runs with same inputs yield byte-identical pristine — the property `pristine_hashes` in `backup-meta.json` depends on). (#2998)
|
||||
- **`release-sdk` hotfix re-run no longer fails at `Dry-run publish validation` when the version is already on npm** — the `Detect prior publish (reconciliation mode)` step sets `skip_publish=true` when the package version is already on the registry, and the actual publish step honors that gate. The `Dry-run publish validation` step was missing the same guard, so any operator re-run of an already-published hotfix (the typical recovery path when later steps fail mid-flight) hit `npm publish --dry-run` first and got `npm error You cannot publish over the previously published versions: X.Y.Z` — `npm publish --dry-run` contacts the registry and rejects existing-version targets even though it doesn't actually publish. The dry-run validation step is now gated on the same `steps.prior_publish.outputs.skip_publish != 'true'` condition as the publish step. The rehearsal still runs on first publishes (where it has value); it skips only in the specific reconciliation case where the publish itself would be skipped. Trigger run: [25233855236](https://github.com/gsd-build/get-shit-done/actions/runs/25233855236/job/73995605643). Regression covered by `tests/bug-2987-dry-run-validation-skip-on-reconciliation.test.cjs`. (#2987)
|
||||
- **`release-sdk` hotfix flow hardened against silent classifier failures, missing-classifier-at-base-tag, and a vestigial merge-back PR step** — three issues surfaced by CodeRabbit's post-merge review of #2981 plus a production failure on the v1.39.1 release run. **(1)** `scripts/diff-touches-shipped-paths.cjs` reused exit code `1` for both the legitimate "no shipped paths" classifier result and Node's default uncaught-throw exit, so any tooling failure was indistinguishable from a normal skip. The script now uses `0` (shipped), `1` (not shipped), `2` (classifier error) with `try`/`catch` + `uncaughtException`/`unhandledRejection` handlers routing all failure paths to exit `2`. **(2)** The workflow's `git checkout -b "$BRANCH" "$BASE_TAG"` overwrote the working tree with the base tag's contents *before* the cherry-pick loop ran the classifier — but base tags predating the classifier's introduction (notably v1.39.0) don't have the file in their tree, so `node scripts/diff-touches-shipped-paths.cjs` would exit non-zero and silently drop every commit, producing an empty hotfix release. The classifier is now staged into `$RUNNER_TEMP` at the top of `Prepare hotfix branch` (before any working-tree-mutating git command), and the loop references that staged copy. The cherry-pick loop snapshots `$PIPESTATUS` into a local array (`PIPE_RC=("${PIPESTATUS[@]}")`) immediately after the classifier pipeline — under bracketed `set +e`/`set -e` — and dispatches via explicit `case`: `0` proceeds, `1` skips into `NON_SHIPPED_SKIPPED`, anything else emits `::error::shipped-paths classifier failed for $SHA (exit N)` and fails the workflow. CodeRabbit on PR #2984 caught a subtler bug in the first iteration: `pipeline \|\| true; RC=${PIPESTATUS[1]}` is broken because `\|\| true` runs `true` as its own one-command pipeline on the failure paths, overwriting `PIPESTATUS` to `(0)` and leaving `${PIPESTATUS[1]}` unset. The array-snapshot form is invariant against this. The same hardening also surfaces `git diff-tree`'s exit code (via `PIPE_RC[0]`); a non-zero diff-tree result now also fails the workflow rather than feeding partial input to the classifier. **(3)** Removed the `Open merge-back PR (hotfix only)` step. The auto-cherry-pick hotfix flow only picks commits already on main (`git cherry HEAD origin/main` outputs the unmerged ones), so by construction every code commit on the hotfix branch is already on main. The only hotfix-branch-only commit is the version-bump chore, which would either no-op against main or rewind main's in-progress version. The step also failed in production with `GitHub Actions is not permitted to create or approve pull requests (createPullRequest)` (org policy) on run [25232968975](https://github.com/gsd-build/get-shit-done/actions/runs/25232968975). The `pull-requests: write` permission previously granted to the release job has been dropped in line with least-privilege. The run-summary line that previously echoed `Merge-back PR opened against main` has been replaced with `No merge-back PR (auto-picked commits are already on main)` so operators reading the summary see an accurate non-action statement (CodeRabbit on PR #2984). Regression covered by `tests/bug-2983-classifier-exit-codes-and-base-tag-staging.test.cjs` (15 assertions across exit-code semantics, classifier staging, error dispatch, PIPESTATUS-snapshot hardening, diff-tree fail-fast, merge-back removal, and run-summary accuracy). (#2983)
|
||||
- **`release-sdk` hotfix only cherry-picks commits that change what actually ships** — the `fix:`/`chore:` filter in `Prepare hotfix branch` was too broad: it picked any commit with that conventional-commit type regardless of whether the diff could affect the published npm package. CI-only fixes (release-sdk.yml itself, hotfix tooling, test-only commits) were getting cherry-picked into hotfix branches even though they cannot change the tarball — and the subset touching `.github/workflows/*` then caused the prepare job's `git push` to be rejected by GitHub because the default `GITHUB_TOKEN` lacks the `workflow` scope, aborting the run. v1.39.1 hit this on PR #2977 (run [25232010071](https://github.com/gsd-build/get-shit-done/actions/runs/25232010071)). The loop now pre-skips any candidate commit whose `git diff-tree` output doesn't intersect the npm tarball's shipped paths (entries in `package.json` `files`, plus `package.json` itself, which `npm pack` always includes). Skipped commits land in a new `NON_SHIPPED_SKIPPED` summary bucket framed as informational — non-shipping commits cannot affect the package, so the skip needs no operator action. The shipped-paths classifier lives in `scripts/diff-touches-shipped-paths.cjs` so its rules (file-OR-directory prefix matching `npm pack` semantics, the always-shipped rule for `package.json`, the lockfile-not-shipped rule) are unit-testable. Regression covered by `tests/bug-2980-hotfix-only-picks-shipping-changes.test.cjs`. (#2980)
|
||||
- **`release-sdk` hotfix workflow fails on real run with `npm error Version not changed`** — the `release` job's `Bump in-tree version (not committed)` step ran `npm version "$VERSION"` without `--allow-same-version`, so it errored on real (non-dry-run) hotfix runs because `prepare` had already committed the bump on the hotfix branch. The release job's checkout `ref` is asymmetric — `BRANCH` (already bumped) on real runs vs `BASE_TAG` (older version) on dry-runs — which is why dry-run never caught the bug. Both `npm version` calls in that step now pass `--allow-same-version`, matching the existing pattern in `release.yml:326`. (#2976)
|
||||
- **Stale deleted command references updated across workflow files** — `help.md`, `do.md`, `settings.md`, `discuss-phase.md`, `new-project.md`, `plan-phase.md`, `spike.md`, and `sketch.md` referenced command names removed in #2790; updated to new consolidated equivalents. (#2950)
|
||||
- **`spike --wrap-up` now dispatches correctly** — `/gsd-spike --wrap-up` was silently no-oping because the flag dispatch wiring was omitted when the micro-skill entry point was absorbed in #2790. (#2948)
|
||||
- **`config-get context_window` returns `200000` when key absent** — querying an unset `context_window` previously exited 1 with "Key not found", surfacing a confusing error in planning logs even though the workflow fallback worked correctly. `cmdConfigGet` now consults a `SCHEMA_DEFAULTS` map and returns the documented default (`200000`, exit 0) for absent schema-defaulted keys; unknown absent keys still error as before. (#2943)
|
||||
- **`gap-analysis` now parses non-`REQ-` requirement IDs and ignores traceability table headers** — `parseRequirements()` no longer hard-codes the `REQ-` prefix and now accepts uppercase prefixed IDs such as `TST-01`, `BACK-07`, and `INSP-04`; markdown table header rows (for example `| REQ-ID | ... |`) are excluded so header tokens are not reported as phantom uncovered requirements. Added regression coverage for mixed-prefix REQUIREMENTS files with traceability tables. (#2897)
|
||||
- **Gemini slash commands namespaced as `/gsd:<cmd>` instead of `/gsd-<cmd>`** —
|
||||
Gemini CLI namespaces commands under `gsd:`, so `/gsd-plan-phase` was unexecutable.
|
||||
Body-text references in commands, agents, banners, and patch-reapply hints are now
|
||||
converted via a roster-checked regex (boundary lookbehind + extension-aware
|
||||
lookahead + roster lookup, defense-in-depth). The roster fail-loud guard prevents
|
||||
silent no-op'ing if `commands/gsd/` is ever missing. (#2768, #2783)
|
||||
- **`SKILL.md` description quoted for Copilot / Antigravity / Trae / CodeBuddy** —
|
||||
descriptions starting with a YAML 1.2 flow indicator (`[BETA]`, `{`, `*`, `&`, `!`,
|
||||
`|`, `>`, `%`, `@`, backtick) crashed gh-copilot's strict YAML loader. Six emission
|
||||
sites now wrap descriptions in `yamlQuote(...)` (= `JSON.stringify`, a valid YAML
|
||||
1.2 double-quoted scalar). (#2876)
|
||||
- **`gsd-tools` invocations use the absolute installed path** — bare `gsd-tools …`
|
||||
calls inside skill bodies relied on PATH resolution that is not guaranteed in every
|
||||
runtime; replaced with the absolute path emitted at install time. (#2851)
|
||||
- **Codex installer preserves trailing newline when stripping legacy hooks** — the
|
||||
legacy-hook strip in the Codex installer ran against files with no terminating
|
||||
newline at EOF and emitted a config that lost the newline, breaking downstream
|
||||
parsers. (#2866)
|
||||
- **GSD slash command namespace drift cleaned up across docs, workflows, and autocomplete** — remaining active `/gsd:<cmd>` references now use canonical `/gsd-<cmd>`, escaped workflow `Skill(skill=\"gsd:...\")` prompts now use hyphenated skill names, `scripts/fix-slash-commands.cjs` rewrites retired colon syntax to hyphen syntax, and the extract-learnings command file now uses `extract-learnings.md` so generated Claude/Qwen skill autocomplete exposes `gsd-extract-learnings` instead of `gsd-extract_learnings`. (#2855)
|
||||
- **`extractCurrentMilestone` no longer truncates ROADMAP.md at heading-like lines inside fenced code blocks** — the milestone-end search now scans line-by-line while tracking ` ``` ` / `~~~` fence state, so a line like `# Ops runbook (v1.0 compat)` inside a code block no longer acts as a milestone boundary. Previously, any phase defined after such a block was invisible to `roadmap analyze`, `roadmap get-phase`, `/gsd-autonomous`, and all phase-number commands. (#2787)
|
||||
- **Codex install no longer corrupts existing `~/.codex/config.toml`** — the installer
|
||||
now defensively strips legacy `[agents]` (single-bracket) and `[[agents]]` (sequence)
|
||||
blocks regardless of GSD marker presence (both invalid in current Codex schema), emits
|
||||
@@ -42,7 +145,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
values, and unsupported value types. Both pre-write helper failures and write-time
|
||||
failures restore the pre-install snapshot and abort with a clear error rather than
|
||||
warn-and-continue. (#2760)
|
||||
- **Codex hooks migrator correctness hardening** — five edge-cases in the
|
||||
- **Codex hooks migrator correctness hardening** — four edge-cases in the
|
||||
`[[hooks.<Event>]]` → `[[hooks.<Event>.hooks]]` migration path fixed: (1) the TOML
|
||||
key parser in hook-body classification now uses `parseTomlKey()` instead of a bare
|
||||
regex, so hyphenated keys (e.g. `status-message`) and quoted keys are no longer
|
||||
@@ -140,11 +243,83 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
include an explicit `ORCHESTRATOR RULE` blockquote immediately after every `Task()`
|
||||
spawn, preventing the Codex parallel-work anti-pattern where the parent continues
|
||||
reading files and producing conflicting output. (#2729)
|
||||
- **`audit-uat` parser reads `human_verification:` from frontmatter array** — the
|
||||
previous body-only regex was too strict and missed valid UAT items declared in YAML
|
||||
frontmatter, surfacing false-positive open gaps at every `/gsd-complete-milestone`
|
||||
audit. (#2788)
|
||||
- **`gsd-sdk` binary collision with `@gsd-build/sdk` resolved** — workstream-aware
|
||||
query registry now respects `GSD_WORKSTREAM` env var; `gsd-tools` bin alias added so
|
||||
the two SDK packages no longer fight over the `gsd-sdk` name in `node_modules/.bin`.
|
||||
(#2791)
|
||||
- **OpenCode generated agents embed `model_profile_overrides.opencode.<tier>`** —
|
||||
per-tier model overrides set via `/gsd-settings-advanced` are now propagated into the
|
||||
generated agent files instead of being silently ignored. (#2794)
|
||||
- **`roadmap update-plan-progress` accepts `--phase` flag form** — SDK arg-parsing
|
||||
regression in v0.1.0 silently dropped `--phase`/`--name`/`--plans` flags, causing
|
||||
`state.begin-phase` and `roadmap update-plan-progress` to corrupt STATE.md. (#2796)
|
||||
- **`context_window` added to `VALID_CONFIG_KEYS` allowlist** — `/gsd-settings-advanced`
|
||||
could not set `context_window` because the key was missing from the allowlist used by
|
||||
`config-set` validation. (#2798)
|
||||
- **`gsd-tools init` dispatches `ingest-docs` handler** — `/gsd-ingest-docs` was broken
|
||||
in v1.38.5 because the workflow called `gsd-sdk` (now `gsd-tools`) but no
|
||||
`ingest-docs` init handler was registered. (#2801)
|
||||
- **`config-get` honors `--default <value>` flag** — fallback for missing keys was
|
||||
ported from the CJS implementation (#1893) into the SDK. (#2803)
|
||||
- **`find-phase` returns `null` for archived phases** — when the current-milestone
|
||||
phase had no directory yet, `init.plan-phase` / `init.execute-phase` returned the
|
||||
archived prior-milestone directory instead of `null`, causing wrong-phase work. (#2805)
|
||||
- **SKILL.md frontmatter `name:` migrated to hyphen form** — files that still used the
|
||||
deprecated colon form (`gsd:cmd`) caused autocomplete to suggest `/gsd:command`.
|
||||
Frontmatter now uses canonical `gsd-cmd` hyphen names. (#2808)
|
||||
- **`gsd-sdk` resolvable in local-mode installs** — the previous `isLocal` short-circuit
|
||||
in `installSdkIfNeeded()` returned before the PATH probe + self-link path could run
|
||||
(the same path that fixed npx-cache global installs in #2775). When `sdk/dist/cli.js`
|
||||
is present, local installs now run the same probe-and-link flow as global installs.
|
||||
(#2829)
|
||||
- **OpenCode `@file` references use absolute paths on all platforms** — OpenCode does
|
||||
not shell-expand `$HOME` in `@file` references on any platform, but the Windows-only
|
||||
guard from #2376 left macOS/Linux producing literal `@$HOME/...` strings that resolved
|
||||
to `command/$HOME/...` (file not found). Guard now applies to OpenCode unconditionally.
|
||||
(#2831)
|
||||
- **`gsd-sdk auto` detects Codex runtime correctly** — `auto` mode ignored
|
||||
`runtime: codex` and routed through `@anthropic-ai/claude-agent-sdk`, producing the
|
||||
`[FAILED] $0.00 0.1s` symptom on autonomous runs. New `runtime-gate` raises a clear
|
||||
error for non-Claude runtimes; `resolveModel()` is now runtime-aware (honours
|
||||
`GSD_RUNTIME` env precedence) and never injects a Claude profile id under non-Claude
|
||||
runtimes. (#2832)
|
||||
- **CR-INTEGRATION tests aligned with hyphen-form skill names** — tests previously
|
||||
asserted `gsd:code-review` (colon) against `autonomous.md` which now uses the canonical
|
||||
hyphen form. Tests now parse `Skill(skill="...")` invocations structurally and reject
|
||||
the legacy colon form. (#2835)
|
||||
- **`audit-open` quick-task scanner accepts `${quick_id}-SUMMARY.md`** — the previous
|
||||
bare-`SUMMARY.md` filename check produced false-positive `status: missing` for every
|
||||
documented quick task. UAT terminal-status enum also adds `resolved` (matches
|
||||
`execute-phase.md`'s post-gap-closure terminal); `help.md` one-liner reconciled with
|
||||
the canonical `quick.md` workflow. (#2836)
|
||||
- **`quick.md` / `execute-phase.md` SUMMARY rescue handles gitignored `.planning/`** —
|
||||
rescue blocks used `git ls-files --exclude-standard` which honoured `.gitignore`,
|
||||
silently no-op'ing when `.planning/` was excluded; the worktree was then deleted with
|
||||
the SUMMARY. Replaced with filesystem-level `find` + idempotent `cp` that bypasses git
|
||||
entirely. (#2838)
|
||||
- **`/gsd-code-review-fix` cleanup tail is transactional** — JSON recovery sentinel at
|
||||
`${phase_dir}/.review-fix-recovery-pending.json` is written after `git worktree add`
|
||||
succeeds and removed only after `git worktree remove` returns. A new run that finds a
|
||||
pre-existing sentinel force-removes the orphan worktree before starting fresh, making
|
||||
the agent self-healing across crashes. (#2839)
|
||||
|
||||
### Performance
|
||||
- **`discuss-phase` lazy file loading** — entry-point `@file` directives replaced with
|
||||
on-demand `Read()` calls gated behind mode routing. Tokens loaded at skill entry drop
|
||||
from ~13k to near zero; only the branch actually invoked is loaded. (#2606)
|
||||
|
||||
## [1.39.1] - 2026-05-01
|
||||
|
||||
Hotfix release. Cherry-picks user-facing fixes from `main` onto the v1.39.0 stable
|
||||
line. Install: `npm install -g get-shit-done-cc@latest` (or `@1.39.1` to pin).
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`gsd-sdk query agent-skills` emits raw `<agent_skills>` block instead of JSON-wrapped string** — workflows that embed via `$(gsd-sdk query agent-skills <agent>)` were receiving a JSON-quoted string literal mid-prompt (e.g. `"<agent_skills>\n…"`), silently breaking all `<agent_skills>` injection into spawned subagents. The CLI dispatcher now honors an opt-in `format: 'text'` field on `QueryResult` and writes such results raw via `process.stdout.write`; `--pick` always returns JSON regardless. (#2917)
|
||||
- **`sketch --wrap-up` now dispatches correctly** — `/gsd-sketch --wrap-up` was silently no-oping because the flag dispatch wiring was omitted when the micro-skill entry point was absorbed in #2790. (#2949)
|
||||
- **`help.md` no longer advertises eight slash commands removed by the #2824 consolidation** — `/gsd-do`, `/gsd-note`, `/gsd-check-todos`, `/gsd-plant-seed`, `/gsd-research-phase`, `/gsd-list-phase-assumptions`, `/gsd-plan-milestone-gaps`, and `/gsd-join-discord` were removed when 86 skills were folded into 59. `help.md` was not updated alongside, so users typing the documented commands hit *Unknown command*. Each entry is now either rewritten to the surviving flag-based dispatcher (e.g., `/gsd-do …` → `/gsd-progress --do "…"`, `/gsd-note` → `/gsd-capture --note`, `/gsd-plant-seed` → `/gsd-capture --seed`, `/gsd-check-todos` → `/gsd-capture --list`) or removed for skills with no replacement. A regression test now asserts every `/gsd-*` reference in `help.md` has a matching `commands/gsd/*.md` stub. (#2954)
|
||||
- **`--sdk` install on Windows now writes a callable `gsd-sdk` shim** — `npx get-shit-done-cc@latest --claude --global --sdk` on Windows previously left `gsd-sdk` off PATH because `trySelfLinkGsdSdk` returned `null` unconditionally on `win32` (a missed gap from #2775's POSIX self-link, not an intentional deferral). The function now dispatches to a Windows counterpart that writes the standard npm shim triple (`gsd-sdk.cmd`, `gsd-sdk.ps1`, and a Bash wrapper) to npm's global bin, so `gsd-sdk` resolves in a fresh shell across cmd.exe, PowerShell, and Cygwin/MSYS/Git-Bash. A new regression guard in `tests/no-unconditional-win32-skip.test.cjs` blocks any future `if (process.platform === 'win32') return null;` skip-only branches in `bin/install.js`. (#2962)
|
||||
- **`/gsd-reapply-patches` Step 5 gate is now deterministic — no more silent content drops** — the prior gate parsed a Claude-generated *Hunk Verification Table* whose `verified: yes` rows were filled in without actually checking content presence, leading to merged files that lost user-added blocks (e.g., a `<visual_companion>` section, an `--execute-only` flag block) while the workflow reported success. The gate now invokes a Node script (`scripts/verify-reapply-patches.cjs`) that diffs each backup against the pristine baseline, computes the user-added significant lines, and asserts each one is present in the merged file. Exits non-zero with a per-file diagnostic on any miss; the workflow halts and surfaces the JSON output to the user. The verifier ignores low-signal lines (too short, pure whitespace, decorative comments) so trivial differences don't trigger false failures. Out of scope here: the manifest-baseline tightening described in #2969 Failure 1 — that's separate work. (#2969)
|
||||
|
||||
## [1.38.5] - 2026-04-25
|
||||
|
||||
|
||||
106
CONTEXT.md
Normal file
106
CONTEXT.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Context
|
||||
|
||||
## Domain terms
|
||||
|
||||
### Dispatch Policy Module
|
||||
Module owning dispatch error mapping, fallback policy, timeout classification, and CLI exit mapping contract.
|
||||
|
||||
Canonical error kind set:
|
||||
- `unknown_command`
|
||||
- `native_failure`
|
||||
- `native_timeout`
|
||||
- `fallback_failure`
|
||||
- `validation_error`
|
||||
- `internal_error`
|
||||
|
||||
### Command Definition Module
|
||||
Canonical command metadata Interface powering alias, catalog, and semantics generation.
|
||||
|
||||
### Query Runtime Context Module
|
||||
Module owning query-time context resolution for `projectDir` and `ws`, including precedence and validation policy used by query adapters.
|
||||
|
||||
### Native Dispatch Adapter Module
|
||||
Adapter Module that satisfies native query dispatch at the Dispatch Policy seam, so policy modules consume a focused dispatch Interface instead of closure-wired call sites.
|
||||
|
||||
### Query CLI Output Module
|
||||
Module owning projection from dispatch results/errors to CLI `{ exitCode, stdoutChunks, stderrLines }` output contract.
|
||||
|
||||
### Query Execution Policy Module
|
||||
Module owning query transport routing policy projection (`preferNative`, fallback policy, workstream subprocess forcing) at execution seam.
|
||||
|
||||
### Query Subprocess Adapter Module
|
||||
Adapter Module owning subprocess execution contract for query commands (JSON/raw invocation, `@file:` indirection parsing, timeout/exit error projection).
|
||||
|
||||
### Query Command Resolution Module
|
||||
Canonical command normalization and resolution Interface (`query-command-resolution-strategy`) used by internal query/transport paths after dead-wrapper convergence.
|
||||
|
||||
### Command Topology Module
|
||||
Module owning command resolution, policy projection (`mutation`, `output_mode`), unknown-command diagnosis, and handler Adapter binding at one seam for query dispatch.
|
||||
|
||||
### Query Pre-Project Config Policy Module
|
||||
Module policy that defines query-time behavior when `.planning/config.json` is absent: use built-in defaults for parity-sensitive query Interfaces, and emit parity-aligned empty model ids for pre-project model resolution surfaces.
|
||||
|
||||
---
|
||||
|
||||
## Recurring PR mistakes (distilled from CodeRabbit reviews, 2026-05-05)
|
||||
|
||||
### Tests — no source-grep
|
||||
- **Rule**: never bind `readFileSync` result to a var then call `.includes()` / `.match()` / `.startsWith()` on it. CI runs `scripts/lint-no-source-grep.cjs` and exits 1.
|
||||
- **Escape**: add `// allow-test-rule: <reason>` anywhere in the file to exempt the whole file. Use when reading product markdown or runtime output (not `.cjs` source).
|
||||
- **Pattern to reach for instead**: call the exported function, capture stdout/JSON, assert on typed fields.
|
||||
|
||||
### Tests — no unescaped RegExp interpolation
|
||||
- `new RegExp(\`prefix${someVar}\`)` — if `someVar` can contain `.` or other metacharacters (e.g. phase id `5.1`), the pattern is wrong. Always `escapeRegex(someVar)`. The `escapeRegex` utility is in `core.cjs` and already imported in most modules.
|
||||
|
||||
### Tests — no dead regex branches in `.includes()`
|
||||
- `src.includes('foo.*bar')` is always false — `.*` is a regex metacharacter, not a wildcard in `includes`. Either use `new RegExp('foo.*bar').test(src)` or delete the branch.
|
||||
|
||||
### Tests — guard top-level `readFileSync` against ENOENT
|
||||
- Module-level `const src = fs.readFileSync(...)` throws before any `test()` registers, aborting the runner with an unhandled exception instead of a named failure. Wrap in try/catch and rethrow with a helpful message.
|
||||
|
||||
### Changesets — `pr:` field must be the PR number, not the issue number
|
||||
- The `pr:` key in `.changeset/*.md` frontmatter must reference the PR introducing the fix (e.g. `3142`), not the issue it closes (e.g. `3120`). Changelog tooling links to GitHub PRs by this value.
|
||||
|
||||
### Shell hooks — never interpolate `$VAR` into single-quoted JS strings
|
||||
- `node -e "require('$HOOK_DIR/lib/foo.js')"` breaks silently if `$HOOK_DIR` contains a single quote (POSIX-legal). Pass paths via env vars: `GIT_CMD_LIB="$HOOK_DIR/lib/foo.js" node -e "require(process.env.GIT_CMD_LIB)"`.
|
||||
|
||||
### Shell guards — `[ -f .git ]` does not detect worktrees from main repo
|
||||
- In the main repo `.git` is a directory, so `[ -f .git ]` is false and the entire guard is skipped. Use `git rev-parse --git-dir` and match `*.git/worktrees/*` in a `case` statement instead.
|
||||
|
||||
### Shell guards — absolute-path containment must use `root/` prefix, not glob
|
||||
- `[[ "$PATH" != "$ROOT"* ]]` matches sibling prefixes (`/repo-extra` passes when `ROOT=/repo`). Use `[[ "$P" != "$ROOT" && "$P" != "$ROOT/"* ]]`. Also: check `[ -z "$ROOT" ]` and exit 1 before the containment test. Warn → fail-closed for security-relevant path checks.
|
||||
|
||||
### Docs — keep internal reference counts consistent
|
||||
- When a heading says `(N shipped)` and a footnote says `N-1 top-level references`, update the footnote. CodeRabbit catches this every time.
|
||||
|
||||
---
|
||||
|
||||
## Workflow learnings (distilled from triage + PR cycle, 2026-05-05)
|
||||
|
||||
### Skill consolidation gap class — missing workflow files
|
||||
- When a command absorbs a micro-skill as a flag (e.g. `capture --backlog`), the old command's process steps must be ported to a `get-shit-done/workflows/<name>.md` file. The routing wrapper in `commands/gsd/*.md` declares an `execution_context` `@`-reference to that workflow — if the file doesn't exist the agent loads nothing and has no steps to follow.
|
||||
- **Detection**: `tests/bug-3135-capture-backlog-workflow.test.cjs` adds a broad regression — every `execution_context` `@`-reference in any `commands/gsd/*.md` must resolve to an existing file on disk. This test will catch all future gaps of this class immediately.
|
||||
- **Prior art**: `reapply-patches.md` was the first gap found and fixed in PR #2824 itself. `add-backlog.md` was missed in the same PR and caught later in #3135. Run the regression test after every consolidation PR.
|
||||
|
||||
### CodeRabbit thread resolution — stale threads after allow-test-rule fixes
|
||||
- After adding `// allow-test-rule:` to silence lint, CodeRabbit's existing inline threads remain open even though the acknowledged fix is in place. Resolve them via `resolveReviewThread` GraphQL mutation before merging — open threads block clean merge history and mislead future reviewers.
|
||||
- Pattern: `gh api graphql -f query='mutation { resolveReviewThread(input:{threadId:"PRRT_..."}) { thread { isResolved } } }'`
|
||||
|
||||
### PR discipline — split unrelated changes into separate PRs
|
||||
- A bug fix and a docs rewrite committed to the same branch produce a noisy diff and a PR that reviewers can't cleanly approve. Cherry-pick doc changes to a dedicated branch (`docs/`) immediately, then force-push the original branch to remove the commit. One concern per PR.
|
||||
|
||||
### INVENTORY.md must be updated alongside every workflow file addition/removal
|
||||
- `docs/INVENTORY.md` tracks the shipped workflow count (`## Workflows (N shipped)`) and has one row per file. Adding or removing a workflow without updating INVENTORY produces an internally inconsistent doc.
|
||||
- Also update `docs/INVENTORY-MANIFEST.json` — it is the machine-readable manifest and must stay in sync with the filesystem.
|
||||
- When a flag absorbs a micro-skill, the old skill's `Invoked by` attribution in INVENTORY must move to the new parent (e.g. `add-todo.md` incorrectly claimed `/gsd-capture --backlog` until #3135 corrected it).
|
||||
|
||||
### README — keep root README as storyline only; all detail lives in docs/
|
||||
- Root `README.md` should be ≤300 lines: hero, author note, 6-step loop, install, core command table, why-it-works bullets, config key dials, docs index, minimal troubleshooting.
|
||||
- Every removed detail section needs a link to the canonical doc that covers it. All doc links must resolve before committing.
|
||||
- Markdownlint rules to watch: MD001 (heading level skip — don't use `###` directly inside admonitions; use bold instead), MD040 (fenced code blocks must declare a language identifier).
|
||||
|
||||
### Issue triage — always check for existing work before filing as new
|
||||
- Before writing an agent brief for a confirmed bug, check: (1) local branches (`git branch -a | grep <issue>`), (2) untracked/modified files on that branch, (3) stash, (4) open PRs with matching head branch. A crash may have left work 90% done — recover and commit rather than re-implementing.
|
||||
|
||||
### SDK-only verbs — golden-policy exemption required
|
||||
- Any `gsd-sdk query` verb implemented only in the SDK native registry (no `gsd-tools.cjs` mirror) must be added to `NO_CJS_SUBPROCESS_REASON` in `sdk/src/golden/golden-policy.ts`. Without this entry the golden-policy test fails, treating the verb as a missing implementation rather than an intentional SDK-only path.
|
||||
169
CONTRIBUTING.md
169
CONTRIBUTING.md
@@ -81,6 +81,20 @@ PRs that arrive without a properly-labeled linked issue are closed automatically
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
### Architecture & Domain Standards (Maintainer-Defined)
|
||||
|
||||
The following files are maintainer-owned coding standards and must be treated as canonical when contributing:
|
||||
|
||||
- `CONTEXT.md` — domain language and module naming standards
|
||||
- `docs/adr/` — Architecture Decision Records (ADRs) for accepted architectural decisions
|
||||
|
||||
Contributor requirements:
|
||||
- Read `CONTEXT.md` before naming or refactoring modules/interfaces/seams.
|
||||
- Use `CONTEXT.md` vocabulary consistently in code comments, tests, issue/PR text, and docs for the touched area.
|
||||
- Check relevant ADRs in `docs/adr/` before proposing or implementing architectural changes.
|
||||
- If a change intentionally revisits an ADR decision, call it out explicitly in the linked issue and PR rationale.
|
||||
- Do not rewrite maintainer intent in `CONTEXT.md`/ADRs as part of drive-by cleanup; propose focused updates tied to approved scope.
|
||||
|
||||
**Every PR must link to an approved issue.** PRs without a linked issue are closed without review, no exceptions.
|
||||
|
||||
- **No draft PRs** — draft PRs are automatically closed. Only open a PR when it is complete, tested, and ready for review. If your work is not finished, keep it on your local branch until it is.
|
||||
@@ -91,6 +105,23 @@ PRs that arrive without a properly-labeled linked issue are closed automatically
|
||||
- **CI must pass** — all matrix jobs (Ubuntu × Node 22, 24; macOS × Node 24) must be green
|
||||
- **Scope matches the approved issue** — if your PR does more than what the issue describes, the extra changes will be asked to be removed or moved to a new issue
|
||||
|
||||
## CHANGELOG Entries — Drop a Fragment
|
||||
|
||||
**Do not edit `CHANGELOG.md` directly.** Two PRs that both append to a `### Fixed` block always conflict on merge — git can't pick a serialization order without a human. Instead, every PR with user-facing changes drops a fragment file in `.changeset/`.
|
||||
|
||||
```bash
|
||||
npm run changeset -- --type Fixed --pr <YOUR_PR_NUMBER> \
|
||||
--body "**\`/gsd-foo\` no longer drops trailing slashes** — explain the user-visible change."
|
||||
```
|
||||
|
||||
This writes `.changeset/<adjective>-<noun>-<noun>.md`. Three random words → concurrent PRs never collide. Allowed `type:` values follow [Keep a Changelog](https://keepachangelog.com/): `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`.
|
||||
|
||||
Fragments are consolidated into `CHANGELOG.md` at release time by the release workflow. See [`.changeset/README.md`](.changeset/README.md) for the format spec and [#2975](https://github.com/gsd-build/get-shit-done/issues/2975) for the rationale.
|
||||
|
||||
**CI enforcement:** the `Changeset Required` workflow (`scripts/changeset/lint.cjs`) fails any PR that touches `bin/`, `get-shit-done/`, `agents/`, `commands/`, `hooks/`, or `sdk/src/` without a `.changeset/*.md` fragment.
|
||||
|
||||
**Opt-out:** PRs with no user-facing impact (test refactors, lint config changes, CI tweaks, formatting-only changes) can add the `no-changelog` label. The lint honors it. When unsure whether a change is user-facing, **add the fragment**.
|
||||
|
||||
## Testing Standards
|
||||
|
||||
All tests use Node.js built-in test runner (`node:test`) and assertion library (`node:assert`). **Do not use Jest, Mocha, Chai, or any external test framework.**
|
||||
@@ -281,6 +312,7 @@ Some tests legitimately read source files. There are six recognized categories:
|
||||
| `docs-parity` | A reference doc must stay in sync with source-defined constants (e.g., `CONFIG_DEFAULTS`). The source is the canonical list; there is no runtime API to enumerate it. |
|
||||
| `integration-test-input` | A source file is used as a real fixture input to a transformation function under test — the file is not inspected for strings but passed as data. |
|
||||
| `structural-implementation-guard` | A feature's interception or wiring point is not reachable end-to-end via `runGsdTools`. Used temporarily until a behavioral path exists. |
|
||||
| `pending-migration-to-typed-ir` | **Tracked for correction, not exempted.** Test was identified by the lint as carrying a raw-text-matching pattern that contradicts the rule above. Each annotated file MUST cite the open migration issue (e.g. `// allow-test-rule: pending-migration-to-typed-ir [#NNNN]`) so the tracking is auditable. New tests cannot use this category — they must refactor production to expose typed IR. The annotation is removed when the test is corrected. |
|
||||
|
||||
Annotate with a standalone `//` comment before the file's opening block comment:
|
||||
|
||||
@@ -296,6 +328,68 @@ Annotate with a standalone `//` comment before the file's opening block comment:
|
||||
|
||||
The annotation **must** be a standalone `// allow-test-rule:` line, not inside a `/** */` block comment — the CI linter scans for the pattern `// allow-test-rule:`.
|
||||
|
||||
### Prohibited: Raw Text Matching on Test Outputs (file content, stdout, stderr)
|
||||
|
||||
**Source-grep is not just `readFileSync` of a `.cjs` file.** The same anti-pattern shows up wherever a test pattern-matches against text that a system-under-test produced, regardless of whether that text came from a source file, a rendered shim, a child process's stdout, or a free-form `reason` string. **All forms are forbidden.**
|
||||
|
||||
The following are all violations of the same rule:
|
||||
|
||||
```javascript
|
||||
// BAD — substring match on text written by the code under test
|
||||
const cmdContent = fs.readFileSync(path.join(tmpDir, 'gsd-sdk.cmd'), 'utf8');
|
||||
assert.ok(cmdContent.includes(`@node ${jsonQuoted} %*`), '.cmd embeds shim path');
|
||||
|
||||
// BAD — regex match on a child process's human-readable stdout formatter
|
||||
const r = cp.spawnSync(SCRIPT, ['--patches-dir', dir]);
|
||||
assert.match(r.stdout, /Failures: 1/);
|
||||
assert.match(r.stdout, /not a regular file/);
|
||||
|
||||
// BAD — "structured parser" that hides string ops behind a function wrapper
|
||||
function parseCmdShim(content) {
|
||||
const lines = content.split('\r\n').filter((l) => l.length > 0);
|
||||
return { header: lines[0], usesCRLF: content.includes('\r\n') };
|
||||
}
|
||||
|
||||
// BAD — assert.match on a free-form `reason` string from a JSON report
|
||||
assert.ok(/not a regular file/.test(report.results[0].reason));
|
||||
```
|
||||
|
||||
Each of these passes on accidental near-matches (a comment containing `@node` somewhere, a stack trace that happens to say `Failures: 1`, a mis-typed reason that still contains the substring you're matching) and fails on harmless reformatting (changing `Failures: 1` to `1 failure`, swapping CRLF rendering style, rewording the error prose).
|
||||
|
||||
#### The rule
|
||||
|
||||
> **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.**
|
||||
|
||||
Concretely: for any system-under-test that produces text output (a file renderer, a CLI formatter, an error-message builder), the production code MUST expose a typed alternative that the test consumes:
|
||||
|
||||
| Output kind | Required structured surface | What the test asserts on |
|
||||
|---|---|---|
|
||||
| Rendered file (shim, template, generated code) | A pure builder function returning the IR (`{ invocation, eol, fileNames, render }`) | `triple.invocation.target === expected`, `triple.eol.cmd === '\r\n'` |
|
||||
| CLI human-formatter output | A `--json` mode that emits the same data structurally | `report.results[0].reason === REASON.FAIL_INSTALLED_NOT_REGULAR_FILE` |
|
||||
| Error / status / reason | A frozen enum (`Object.freeze({ FAIL_X: 'fail_x', ... })`) | `assert.equal(result.reason, REASON.FAIL_X)` |
|
||||
| File presence after a write | `fs.statSync().isFile()`, `.size > 0`, `.mtimeMs` advances | Filesystem facts; never read the file content back |
|
||||
|
||||
#### Concrete examples from this repo
|
||||
|
||||
`buildWindowsShimTriple(shimSrc)` in `bin/install.js` is the canonical IR pattern: pure function, no I/O, returns `{ invocation, eol, fileNames, render }`. `trySelfLinkGsdSdkWindows` calls it and writes `triple.render[kind]()` to disk. Tests assert on `triple.invocation.target`, `triple.eol.cmd`, `Object.keys(triple).sort()` — never on the rendered text. Filesystem-level tests assert `fs.statSync(target).size === Buffer.byteLength(triple.render.cmd())` to prove the writer writes what the renderer produces, **without comparing content**.
|
||||
|
||||
`scripts/verify-reapply-patches.cjs` exposes a frozen `REASON` enum and emits it through `--json`. Tests assert `report.results[0].reason === REASON.FAIL_USER_LINES_MISSING`. The human formatter exists for operator console output only — tests must not depend on its prose. Adding a new reason code requires updating the `REASON` enum, the `--json` output, AND the test that locks `Object.keys(REASON).sort()` — three coordinated changes that prevent the code surface from drifting from the test surface.
|
||||
|
||||
#### Hiding grep behind a function is still grep
|
||||
|
||||
`parseCmdShim`, `parsePs1Invocation`, etc. that internally do `content.split(...)`, `lines[1].trim()`, `content.includes(...)` are still string manipulation. The fact that the entry point looks like a parser doesn't change what's happening underneath — the test is still asserting on the lexical shape of rendered text. The fix is not "wrap the grep in a function with a typed-looking return value." The fix is to **eliminate the rendered text from the test path entirely** by surfacing the IR.
|
||||
|
||||
#### When you cannot eliminate text matching
|
||||
|
||||
There are exactly two cases where text content is the legitimate object of a test, both already covered by the existing exemption matrix:
|
||||
|
||||
1. `source-text-is-the-product` — workflow `.md` / agent `.md` / command `.md` files where the deployed text IS what the runtime loads.
|
||||
2. `docs-parity` — a reference doc must mirror source-defined constants and there is no runtime enumeration API.
|
||||
|
||||
For everything else, if a test reaches for `.includes()` / `.startsWith()` / `assert.match(text, /…/)`, the production code is missing a typed surface. **Add the typed surface; do not work around it.**
|
||||
|
||||
**CI enforcement:** `scripts/lint-no-source-grep.cjs` is being extended (see issue tracker for the latest scope) to flag `String#includes`/`String#startsWith`/`String#endsWith`/`assert.match` on `readFileSync` results and on `cp.spawnSync` stdout/stderr in test files, with the same `// allow-test-rule:` exemption mechanism.
|
||||
|
||||
### Node.js Version Compatibility
|
||||
|
||||
**Node 22 is the minimum supported version.** Node 24 is the primary CI target. All tests must pass on both.
|
||||
@@ -345,6 +439,73 @@ node --test tests/core.test.cjs
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
### Pre-PR Seam Checks (Manifest/Alias Routing)
|
||||
|
||||
If you touched any of the command-manifest or generated alias files, run:
|
||||
|
||||
```bash
|
||||
npm run check:alias-drift
|
||||
```
|
||||
|
||||
This verifies generated alias artifacts are in sync with manifest source-of-truth.
|
||||
|
||||
Optional local pre-commit hook entry (Git-native):
|
||||
|
||||
```bash
|
||||
# one-time setup
|
||||
mkdir -p .githooks
|
||||
cat > .githooks/pre-commit <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if git diff --cached --name-only | grep -Eq "^sdk/src/query/command-manifest\.|^sdk/src/query/command-aliases\.generated\.ts$|^get-shit-done/bin/lib/command-aliases\.generated\.cjs$|^sdk/scripts/gen-command-aliases\.ts$"; then
|
||||
npm run check:alias-drift
|
||||
fi
|
||||
EOF
|
||||
chmod +x .githooks/pre-commit
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
Optional local pre-push hook to block a private author-email pattern:
|
||||
|
||||
```bash
|
||||
# set locally in your shell profile (example)
|
||||
export GSD_BLOCKED_AUTHOR_REGEX='@example-corp\\.com$'
|
||||
|
||||
cat > .githooks/pre-push <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
zero_sha='0000000000000000000000000000000000000000'
|
||||
blocked_regex="${GSD_BLOCKED_AUTHOR_REGEX:-}"
|
||||
[[ -z "$blocked_regex" ]] && exit 0
|
||||
violations=()
|
||||
|
||||
while read -r local_ref local_sha remote_ref remote_sha; do
|
||||
[[ "$local_sha" == "$zero_sha" ]] && continue
|
||||
if [[ "$remote_sha" == "$zero_sha" ]]; then
|
||||
commits=$(git rev-list "$local_sha" --not --remotes)
|
||||
else
|
||||
commits=$(git rev-list "$remote_sha..$local_sha")
|
||||
fi
|
||||
while read -r commit; do
|
||||
[[ -z "$commit" ]] && continue
|
||||
email=$(git show -s --format='%ae' "$commit" | tr '[:upper:]' '[:lower:]')
|
||||
if printf '%s' "$email" | grep -Eq "$blocked_regex"; then
|
||||
violations+=("$commit <$email>")
|
||||
fi
|
||||
done <<< "$commits"
|
||||
done
|
||||
|
||||
if [[ ${#violations[@]} -gt 0 ]]; then
|
||||
echo "Push blocked: commit author email matched local blocked regex ($blocked_regex)." >&2
|
||||
printf ' - %s\n' "${violations[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
EOF
|
||||
chmod +x .githooks/pre-push
|
||||
```
|
||||
|
||||
### CI Test Quality Checks
|
||||
|
||||
The following checks run on every PR in addition to the test suite:
|
||||
@@ -357,6 +518,14 @@ Run locally before pushing: `npm run lint:tests`
|
||||
|
||||
### Test Requirements by Contribution Type
|
||||
|
||||
### Architecture-Aware Testing Requirements
|
||||
|
||||
When work touches architecture, routing, policy, registry assembly, or command semantics:
|
||||
- Write tests against module **interfaces** and seam behavior, not implementation trivia.
|
||||
- Prefer invariant/contract tests that protect ADR-backed behavior and `CONTEXT.md` terminology.
|
||||
- Ensure tests validate canonical behavior through the defined seam (for example: structured result contracts, canonical command metadata, and adapter parity), not source-text coupling.
|
||||
- If ADRs define expected behavior, tests should assert those expectations directly.
|
||||
|
||||
The required tests differ depending on what you are contributing:
|
||||
|
||||
**Bug Fix:** A regression test is required. Write the test first — it must demonstrate the original failure before your fix is applied, then pass after the fix. A PR that fixes a bug without a regression test will be asked to add one. "Tests pass" does not prove correctness; it proves the bug isn't present in the tests that exist.
|
||||
|
||||
@@ -75,15 +75,17 @@ GSDはそれを解決します。Claude Codeを信頼性の高いものにする
|
||||
|
||||
ビルトインの品質ゲートが本当の問題を検出します:スキーマドリフト検出はマイグレーション漏れのORM変更をフラグし、セキュリティ強制は検証を脅威モデルに紐付け、スコープ削減検出はプランナーが要件を暗黙的に落とすのを防止します。
|
||||
|
||||
### v1.32.0 ハイライト
|
||||
### v1.39.0 ハイライト
|
||||
|
||||
- **STATE.md整合性ゲート** — `state validate`がSTATE.mdとファイルシステムの差分を検出、`state sync`が実際のプロジェクト状態から再構築
|
||||
- **`--to N`フラグ** — 自律実行を特定のフェーズ完了後に停止
|
||||
- **リサーチゲート** — RESEARCH.mdに未解決の質問がある場合、計画をブロック
|
||||
- **検証マイルストーンスコープフィルタリング** — 後のフェーズで対処されるギャップは「ギャップ」ではなく「延期」としてマーク
|
||||
- **読み取り後編集ガード** — 非Claudeランタイムでの無限リトライループを防止するアドバイザリーフック
|
||||
- **コンテキスト削減** — Markdownのトランケーションとキャッシュフレンドリーなプロンプト順序でトークン使用量を削減
|
||||
- **4つの新ランタイム** — Trae、Kilo、Augment、Cline(合計12ランタイム)
|
||||
完全なリストは [v1.39.0 リリースノート](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0) を参照してください。
|
||||
|
||||
- **`--minimal` インストールプロファイル** — エイリアス `--core-only`。メインループの6スキル(`new-project`、`discuss-phase`、`plan-phase`、`execute-phase`、`help`、`update`)のみをインストールし、`gsd-*` サブエージェントはゼロ。コールドスタート時のシステムプロンプトのオーバーヘッドを ~12kトークンから ~700トークンへ削減(≥94%減)。32K〜128Kコンテキストのローカル LLM やトークン課金 API に有効。
|
||||
- **`/gsd-phase --edit`** — `ROADMAP.md` 上の既存フェーズの任意フィールドをその場で編集(番号や位置は変更されない)。`--force` で確認 diff をスキップ、`depends_on` の参照を検証し、書き込み時に `STATE.md` も更新。
|
||||
- **マージ後ビルド & テストゲート** — `execute-phase` のステップ 5.6 が `workflow.build_command` の設定を自動検出し、無ければ Xcode(`.xcodeproj`)、Makefile、Justfile、Cargo、Go、Python、npm の順にフォールバック。Xcode/iOS プロジェクトでは `xcodebuild build` と `xcodebuild test` を自動実行。並列・直列両モードで動作。
|
||||
- **ランタイム別レビューモデル選択** — `review.models.<cli>` で各外部レビュー CLI(codex、gemini など)が使うモデルをプランナー/実行プロファイルとは独立に指定可能。
|
||||
- **ワークストリーム設定の継承** — `GSD_WORKSTREAM` が設定されている場合、ルートの `.planning/config.json` を先に読み込み、ワークストリーム設定をディープマージ(衝突時はワークストリーム側が優先)。ワークストリーム設定で明示的に `null` を指定するとルート値を上書き可能。
|
||||
- **手動カナリアリリースワークフロー** — `.github/workflows/canary.yml` が `workflow_dispatch` 経由で `dev` ブランチから `{base}-canary.{N}` ビルドを `@canary` dist-tag に手動公開(`get-shit-done-cc` と `@gsd-build/sdk`)。
|
||||
- **スキルの統合:86 → 59** — 4つの新しいグループ化スキル(`capture`、`phase`、`config`、`workspace`)が31のマイクロスキルを吸収。既存の親スキル6つはラップアップやサブ操作をフラグ化:`update --sync/--reapply`、`sketch --wrap-up`、`spike --wrap-up`、`map-codebase --fast/--query`、`code-review --fix`、`progress --do/--next`。機能の欠損なし。
|
||||
|
||||
---
|
||||
|
||||
@@ -394,7 +396,7 @@ claude --dangerously-skip-permissions
|
||||
またはGSDに次のステップを自動判定させます:
|
||||
|
||||
```
|
||||
/gsd-next # 次のステップを自動検出して実行
|
||||
/gsd-progress --next # 次のステップを自動検出して実行
|
||||
```
|
||||
|
||||
**discuss → plan → execute → verify → ship** のループをマイルストーン完了まで繰り返します。
|
||||
@@ -542,7 +544,7 @@ lmn012o feat(08-02): create registration endpoint
|
||||
| `/gsd-execute-phase <N>` | 全プランを並列ウェーブで実行し、完了時に検証 |
|
||||
| `/gsd-verify-work [N]` | 手動ユーザー受入テスト ¹ |
|
||||
| `/gsd-ship [N] [--draft]` | 検証済みのフェーズ作業から自動生成された本文付きのPRを作成 |
|
||||
| `/gsd-next` | 次の論理的なワークフローステップに自動的に進む |
|
||||
| `/gsd-progress --next` | 次の論理的なワークフローステップに自動的に進む |
|
||||
| `/gsd-fast <text>` | インラインの軽微タスク — 計画を完全にスキップし即座に実行 |
|
||||
| `/gsd-audit-milestone` | マイルストーンが完了の定義を達成したか検証 |
|
||||
| `/gsd-complete-milestone` | マイルストーンをアーカイブし、リリースをタグ付け |
|
||||
@@ -563,9 +565,9 @@ lmn012o feat(08-02): create registration endpoint
|
||||
|
||||
| コマンド | 説明 |
|
||||
|---------|--------------|
|
||||
| `/gsd-new-workspace` | リポジトリのコピー(worktreeまたはクローン)で隔離されたワークスペースを作成 |
|
||||
| `/gsd-list-workspaces` | すべてのGSDワークスペースとそのステータスを表示 |
|
||||
| `/gsd-remove-workspace` | ワークスペースを削除しworktreeをクリーンアップ |
|
||||
| `/gsd-workspace --new` | リポジトリのコピー(worktreeまたはクローン)で隔離されたワークスペースを作成 |
|
||||
| `/gsd-workspace --list` | すべてのGSDワークスペースとそのステータスを表示 |
|
||||
| `/gsd-workspace --remove` | ワークスペースを削除しworktreeをクリーンアップ |
|
||||
|
||||
### UIデザイン
|
||||
|
||||
@@ -579,10 +581,9 @@ lmn012o feat(08-02): create registration endpoint
|
||||
| コマンド | 説明 |
|
||||
|---------|--------------|
|
||||
| `/gsd-progress` | 今どこにいる?次は何? |
|
||||
| `/gsd-next` | 状態を自動検出し次のステップを実行 |
|
||||
| `/gsd-progress --next` | 状態を自動検出し次のステップを実行 |
|
||||
| `/gsd-help` | 全コマンドと使い方ガイドを表示 |
|
||||
| `/gsd-update` | チェンジログプレビュー付きでGSDをアップデート |
|
||||
| `/gsd-join-discord` | GSD Discordコミュニティに参加 |
|
||||
| `/gsd-manager` | 複数フェーズ管理用のインタラクティブコマンドセンター |
|
||||
|
||||
### ブラウンフィールド
|
||||
@@ -595,11 +596,12 @@ lmn012o feat(08-02): create registration endpoint
|
||||
|
||||
| コマンド | 説明 |
|
||||
|---------|--------------|
|
||||
| `/gsd-add-phase` | ロードマップにフェーズを追加 |
|
||||
| `/gsd-insert-phase [N]` | フェーズ間に緊急作業を挿入 |
|
||||
| `/gsd-remove-phase [N]` | 将来のフェーズを削除し番号を振り直し |
|
||||
| `/gsd-list-phase-assumptions [N]` | 計画前にClaudeの意図するアプローチを確認 |
|
||||
| `/gsd-plan-milestone-gaps` | 監査で見つかったギャップを埋めるフェーズを作成 |
|
||||
| `/gsd-phase` | ロードマップにフェーズを追加 |
|
||||
| `/gsd-phase --insert [N]` | フェーズ間に緊急作業を挿入 |
|
||||
| `/gsd-phase --edit [N] [--force]` | 既存フェーズの任意フィールドをその場で編集 — 番号と位置は変更されない |
|
||||
| `/gsd-phase --remove [N]` | 将来のフェーズを削除し番号を振り直し |
|
||||
| `/gsd-discuss-phase --assumptions [N]` | 計画前にClaudeの意図するアプローチを確認 |
|
||||
| `/gsd-audit-milestone --fix` | 監査で見つかったギャップを埋めるフェーズを作成 |
|
||||
|
||||
### セッション
|
||||
|
||||
@@ -607,7 +609,7 @@ lmn012o feat(08-02): create registration endpoint
|
||||
|---------|--------------|
|
||||
| `/gsd-pause-work` | フェーズ途中で停止する際の引き継ぎを作成(HANDOFF.jsonを書き込み) |
|
||||
| `/gsd-resume-work` | 前回のセッションから復元 |
|
||||
| `/gsd-session-report` | 実行した作業と結果のセッションサマリーを生成 |
|
||||
| `/gsd-pause-work --report` | 実行した作業と結果のセッションサマリーを生成 |
|
||||
|
||||
### ワークストリーム
|
||||
|
||||
@@ -627,8 +629,8 @@ lmn012o feat(08-02): create registration endpoint
|
||||
|
||||
| コマンド | 説明 |
|
||||
|---------|--------------|
|
||||
| `/gsd-plant-seed <idea>` | トリガー条件付きの将来志向のアイデアをキャプチャ — 適切なマイルストーンで浮上 |
|
||||
| `/gsd-add-backlog <desc>` | バックログのパーキングロットにアイデアを追加(999.xナンバリング、アクティブシーケンス外) |
|
||||
| `/gsd-capture --seed <idea>` | トリガー条件付きの将来志向のアイデアをキャプチャ — 適切なマイルストーンで浮上 |
|
||||
| `/gsd-capture --backlog <desc>` | バックログのパーキングロットにアイデアを追加(999.xナンバリング、アクティブシーケンス外) |
|
||||
| `/gsd-review-backlog` | バックログ項目をレビューし、アクティブマイルストーンに昇格またはstaleエントリを削除 |
|
||||
| `/gsd-thread [name]` | 永続コンテキストスレッド — 複数セッションにまたがる作業用の軽量クロスセッション知識 |
|
||||
|
||||
@@ -637,9 +639,9 @@ lmn012o feat(08-02): create registration endpoint
|
||||
| コマンド | 説明 |
|
||||
|---------|--------------|
|
||||
| `/gsd-settings` | モデルプロファイルとワークフローエージェントを設定 |
|
||||
| `/gsd-set-profile <profile>` | モデルプロファイルを切り替え(quality/balanced/budget/inherit) |
|
||||
| `/gsd-add-todo [desc]` | 後で取り組むアイデアをキャプチャ |
|
||||
| `/gsd-check-todos` | 保留中のtodoを一覧表示 |
|
||||
| `/gsd-config --profile <profile>` | モデルプロファイルを切り替え(quality/balanced/budget/inherit) |
|
||||
| `/gsd-capture [desc]` | 後で取り組むアイデアをキャプチャ |
|
||||
| `/gsd-capture --list` | 保留中のtodoを一覧表示 |
|
||||
| `/gsd-debug [desc]` | 永続状態を持つ体系的デバッグ |
|
||||
| `/gsd-do <text>` | フリーフォームテキストを適切なGSDコマンドに自動ルーティング |
|
||||
| `/gsd-note <text>` | ゼロフリクションのアイデアキャプチャ — ノートの追加、一覧、todoへの昇格 |
|
||||
@@ -676,7 +678,7 @@ GSDはプロジェクト設定を `.planning/config.json` に保存します。`
|
||||
|
||||
プロファイルの切り替え:
|
||||
```
|
||||
/gsd-set-profile budget
|
||||
/gsd-config --profile budget
|
||||
```
|
||||
|
||||
非Anthropicプロバイダー(OpenRouter、ローカルモデル)を使用する場合や、現在のランタイムのモデル選択に従う場合(例:OpenCode `/model`)は `inherit` を使用してください。
|
||||
|
||||
@@ -75,15 +75,17 @@ GSD가 그걸 고칩니다. Claude Code를 신뢰할 수 있게 만드는 컨텍
|
||||
|
||||
내장 품질 게이트가 실제 문제를 잡아냅니다: 스키마 드리프트 감지는 마이그레이션 누락된 ORM 변경을 플래그하고, 보안 강제는 검증을 위협 모델에 고정시키고, 스코프 축소 감지는 플래너가 요구사항을 몰래 빠뜨리는 걸 방지합니다.
|
||||
|
||||
### v1.32.0 하이라이트
|
||||
### v1.39.0 하이라이트
|
||||
|
||||
- **STATE.md 일관성 게이트** — `state validate`가 STATE.md와 파일시스템 간 드리프트를 감지, `state sync`가 실제 프로젝트 상태에서 재구성
|
||||
- **`--to N` 플래그** — 자율 실행을 특정 단계 완료 후 중지
|
||||
- **리서치 게이트** — RESEARCH.md에 미해결 질문이 있으면 기획을 차단
|
||||
- **검증 마일스톤 스코프 필터링** — 이후 단계에서 처리될 격차는 "격차"가 아닌 "지연됨"으로 표시
|
||||
- **읽기-후-편집 가드** — 비Claude 런타임에서 무한 재시도 루프를 방지하는 어드바이저리 훅
|
||||
- **컨텍스트 축소** — 마크다운 잘라내기 및 캐시 친화적 프롬프트 순서로 토큰 사용량 절감
|
||||
- **4개의 새 런타임** — Trae, Kilo, Augment, Cline (총 12개 런타임)
|
||||
전체 목록은 [v1.39.0 릴리스 노트](https://github.com/gsd-build/get-shit-done/releases/tag/v1.39.0)를 참고하세요.
|
||||
|
||||
- **`--minimal` 설치 프로파일** — 별칭 `--core-only`. 메인 루프 6개 스킬(`new-project`, `discuss-phase`, `plan-phase`, `execute-phase`, `help`, `update`)만 설치하고 `gsd-*` 서브에이전트는 설치하지 않음. 콜드 스타트 시스템 프롬프트 오버헤드를 ~12k 토큰에서 ~700 토큰으로 축소(≥94% 감소). 32K–128K 컨텍스트의 로컬 LLM이나 토큰 과금 API에 유용.
|
||||
- **`/gsd-phase --edit`** — `ROADMAP.md`에 있는 기존 단계의 임의 필드를 그 자리에서 수정(번호와 위치는 변경되지 않음). `--force`는 확인 diff를 건너뛰고, `depends_on` 참조를 검증하며 쓰기 시 `STATE.md`도 갱신.
|
||||
- **머지 후 빌드 & 테스트 게이트** — `execute-phase` 5.6 단계가 `workflow.build_command` 설정을 우선 자동 감지하고, 없으면 Xcode(`.xcodeproj`), Makefile, Justfile, Cargo, Go, Python, npm 순으로 폴백. Xcode/iOS 프로젝트는 `xcodebuild build` 및 `xcodebuild test`를 자동 실행. 병렬·직렬 모드 모두에서 동작.
|
||||
- **런타임별 리뷰 모델 선택** — `review.models.<cli>`로 각 외부 리뷰 CLI(codex, gemini 등)가 플래너/실행 프로파일과 독립적으로 자체 모델을 선택할 수 있음.
|
||||
- **워크스트림 설정 상속** — `GSD_WORKSTREAM`이 설정되면 루트 `.planning/config.json`을 먼저 로드한 뒤 워크스트림 설정을 딥 머지(충돌 시 워크스트림 우선). 워크스트림 설정에서 명시적 `null`은 루트 값을 덮어씀.
|
||||
- **수동 카나리 릴리스 워크플로** — `.github/workflows/canary.yml`이 `workflow_dispatch`로 `dev` 브랜치에서 `{base}-canary.{N}` 빌드를 `@canary` dist-tag로 수동 게시(`get-shit-done-cc`와 `@gsd-build/sdk`).
|
||||
- **스킬 통합: 86 → 59** — 4개의 새로운 그룹 스킬(`capture`, `phase`, `config`, `workspace`)이 31개의 마이크로 스킬을 흡수. 기존 6개의 부모 스킬은 래퍼업/하위 동작을 플래그로 흡수: `update --sync/--reapply`, `sketch --wrap-up`, `spike --wrap-up`, `map-codebase --fast/--query`, `code-review --fix`, `progress --do/--next`. 기능 손실 없음.
|
||||
|
||||
---
|
||||
|
||||
@@ -394,7 +396,7 @@ claude --dangerously-skip-permissions
|
||||
또는 GSD가 다음 단계를 자동으로 파악하게 합니다:
|
||||
|
||||
```
|
||||
/gsd-next # 다음 단계 자동 감지 및 실행
|
||||
/gsd-progress --next # 다음 단계 자동 감지 및 실행
|
||||
```
|
||||
|
||||
마일스톤이 완료될 때까지 **논의 → 기획 → 실행 → 검증 → 출시** 반복.
|
||||
@@ -539,7 +541,7 @@ lmn012o feat(08-02): create registration endpoint
|
||||
| `/gsd-execute-phase <N>` | 병렬 웨이브로 모든 계획 실행, 완료 시 검증 |
|
||||
| `/gsd-verify-work [N]` | 수동 사용자 인수 테스트 ¹ |
|
||||
| `/gsd-ship [N] [--draft]` | 자동 생성된 본문으로 검증된 단계 작업에서 PR 생성 |
|
||||
| `/gsd-next` | 다음 논리적 워크플로우 단계로 자동 진행 |
|
||||
| `/gsd-progress --next` | 다음 논리적 워크플로우 단계로 자동 진행 |
|
||||
| `/gsd-fast <text>` | 인라인 사소한 작업 — 기획 완전 건너뛰고 즉시 실행 |
|
||||
| `/gsd-audit-milestone` | 마일스톤이 완료 정의를 달성했는지 검증 |
|
||||
| `/gsd-complete-milestone` | 마일스톤 아카이브, 릴리스 태그 |
|
||||
@@ -560,9 +562,9 @@ lmn012o feat(08-02): create registration endpoint
|
||||
|
||||
| 명령어 | 역할 |
|
||||
|---------|------------|
|
||||
| `/gsd-new-workspace` | 저장소 복사본으로 격리된 워크스페이스 생성 (worktrees 또는 clones) |
|
||||
| `/gsd-list-workspaces` | 모든 GSD 워크스페이스와 상태 표시 |
|
||||
| `/gsd-remove-workspace` | 워크스페이스 제거 및 worktree 정리 |
|
||||
| `/gsd-workspace --new` | 저장소 복사본으로 격리된 워크스페이스 생성 (worktrees 또는 clones) |
|
||||
| `/gsd-workspace --list` | 모든 GSD 워크스페이스와 상태 표시 |
|
||||
| `/gsd-workspace --remove` | 워크스페이스 제거 및 worktree 정리 |
|
||||
|
||||
### UI 디자인
|
||||
|
||||
@@ -576,10 +578,9 @@ lmn012o feat(08-02): create registration endpoint
|
||||
| 명령어 | 역할 |
|
||||
|---------|------------|
|
||||
| `/gsd-progress` | 지금 어디에 있나? 다음은? |
|
||||
| `/gsd-next` | 상태 자동 감지 및 다음 단계 실행 |
|
||||
| `/gsd-progress --next` | 상태 자동 감지 및 다음 단계 실행 |
|
||||
| `/gsd-help` | 모든 명령어와 사용 가이드 표시 |
|
||||
| `/gsd-update` | 변경 로그 미리보기와 함께 GSD 업데이트 |
|
||||
| `/gsd-join-discord` | GSD Discord 커뮤니티 참여 |
|
||||
| `/gsd-manager` | 여러 단계 관리를 위한 대화형 커맨드 센터 |
|
||||
|
||||
### 브라운필드
|
||||
@@ -592,11 +593,12 @@ lmn012o feat(08-02): create registration endpoint
|
||||
|
||||
| 명령어 | 역할 |
|
||||
|---------|------------|
|
||||
| `/gsd-add-phase` | 로드맵에 단계 추가 |
|
||||
| `/gsd-insert-phase [N]` | 단계 사이에 긴급 작업 삽입 |
|
||||
| `/gsd-remove-phase [N]` | 미래 단계 제거, 번호 재정렬 |
|
||||
| `/gsd-list-phase-assumptions [N]` | 기획 전 Claude의 의도된 접근 방식 확인 |
|
||||
| `/gsd-plan-milestone-gaps` | 감사에서 발견된 갭을 해소하기 위한 단계 생성 |
|
||||
| `/gsd-phase` | 로드맵에 단계 추가 |
|
||||
| `/gsd-phase --insert [N]` | 단계 사이에 긴급 작업 삽입 |
|
||||
| `/gsd-phase --edit [N] [--force]` | 기존 단계의 임의 필드를 그 자리에서 수정 — 번호와 위치는 그대로 |
|
||||
| `/gsd-phase --remove [N]` | 미래 단계 제거, 번호 재정렬 |
|
||||
| `/gsd-discuss-phase --assumptions [N]` | 기획 전 Claude의 의도된 접근 방식 확인 |
|
||||
| `/gsd-audit-milestone --fix` | 감사에서 발견된 갭을 해소하기 위한 단계 생성 |
|
||||
|
||||
### 세션
|
||||
|
||||
@@ -604,7 +606,7 @@ lmn012o feat(08-02): create registration endpoint
|
||||
|---------|------------|
|
||||
| `/gsd-pause-work` | 단계 중간에 멈출 때 핸드오프 생성 (HANDOFF.json 작성) |
|
||||
| `/gsd-resume-work` | 마지막 세션에서 복원 |
|
||||
| `/gsd-session-report` | 수행한 작업과 결과가 담긴 세션 요약 생성 |
|
||||
| `/gsd-pause-work --report` | 수행한 작업과 결과가 담긴 세션 요약 생성 |
|
||||
|
||||
### 코드 품질
|
||||
|
||||
@@ -618,8 +620,8 @@ lmn012o feat(08-02): create registration endpoint
|
||||
|
||||
| 명령어 | 역할 |
|
||||
|---------|------------|
|
||||
| `/gsd-plant-seed <idea>` | 트리거 조건이 있는 아이디어 저장 — 때가 되면 알아서 올라옴 |
|
||||
| `/gsd-add-backlog <desc>` | 백로그 파킹 롯에 아이디어 추가 (999.x 번호 지정, 활성 시퀀스 외부) |
|
||||
| `/gsd-capture --seed <idea>` | 트리거 조건이 있는 아이디어 저장 — 때가 되면 알아서 올라옴 |
|
||||
| `/gsd-capture --backlog <desc>` | 백로그 파킹 롯에 아이디어 추가 (999.x 번호 지정, 활성 시퀀스 외부) |
|
||||
| `/gsd-review-backlog` | 백로그 항목 리뷰 및 활성 마일스톤으로 승격하거나 오래된 항목 제거 |
|
||||
| `/gsd-thread [name]` | 지속적 컨텍스트 스레드 — 여러 세션에 걸친 작업을 위한 가벼운 크로스 세션 지식 |
|
||||
|
||||
@@ -628,9 +630,9 @@ lmn012o feat(08-02): create registration endpoint
|
||||
| 명령어 | 역할 |
|
||||
|---------|------------|
|
||||
| `/gsd-settings` | 모델 프로필 및 워크플로우 에이전트 설정 |
|
||||
| `/gsd-set-profile <profile>` | 모델 프로필 전환 (quality/balanced/budget/inherit) |
|
||||
| `/gsd-add-todo [desc]` | 나중을 위한 아이디어 캡처 |
|
||||
| `/gsd-check-todos` | 대기 중인 할 일 목록 |
|
||||
| `/gsd-config --profile <profile>` | 모델 프로필 전환 (quality/balanced/budget/inherit) |
|
||||
| `/gsd-capture [desc]` | 나중을 위한 아이디어 캡처 |
|
||||
| `/gsd-capture --list` | 대기 중인 할 일 목록 |
|
||||
| `/gsd-debug [desc]` | 지속적 상태를 이용한 체계적 디버깅 |
|
||||
| `/gsd-do <text>` | 자유 형식 텍스트를 적절한 GSD 명령어로 자동 라우팅 |
|
||||
| `/gsd-note <text>` | 마찰 없는 아이디어 캡처 — 추가, 목록, 또는 할 일로 승격 |
|
||||
@@ -667,7 +669,7 @@ GSD는 프로젝트 설정을 `.planning/config.json`에 저장합니다. `/gsd-
|
||||
|
||||
프로필 전환:
|
||||
```
|
||||
/gsd-set-profile budget
|
||||
/gsd-config --profile budget
|
||||
```
|
||||
|
||||
비-Anthropic 제공업체 (OpenRouter, 로컬 모델) 사용 시 또는 현재 런타임 모델 선택을 따를 때 (예: OpenCode `/model`) `inherit`를 사용하세요.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user