@opengsd/gsd-core 1.20.1 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +33 -2
  4. package/agents/gsd-executor.md +1 -1
  5. package/agents/gsd-plan-checker.md +1 -1
  6. package/agents/gsd-planner.md +3 -3
  7. package/agents/gsd-verifier.md +2 -2
  8. package/bin/install.js +234 -126
  9. package/commands/gsd/autonomous.md +1 -1
  10. package/gsd-core/bin/gsd-tools.cjs +450 -753
  11. package/gsd-core/bin/lib/capability-activation.cjs +8 -2
  12. package/gsd-core/bin/lib/capability-command-router.cjs +6 -2
  13. package/gsd-core/bin/lib/capability-ledger.cjs +1 -1
  14. package/gsd-core/bin/lib/capability-lock.cjs +19 -27
  15. package/gsd-core/bin/lib/capability-registry.cjs +74 -71
  16. package/gsd-core/bin/lib/capability-validator.cjs +1 -0
  17. package/gsd-core/bin/lib/capability-writer.cjs +7 -4
  18. package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +142 -62
  19. package/gsd-core/bin/lib/claude-orchestration.cjs +214 -122
  20. package/gsd-core/bin/lib/cli-surface.cjs +545 -0
  21. package/gsd-core/bin/lib/command-arg-projection.cjs +356 -103
  22. package/gsd-core/bin/lib/commands.cjs +80 -2
  23. package/gsd-core/bin/lib/config-file-read.cjs +85 -0
  24. package/gsd-core/bin/lib/config-loader.cjs +23 -15
  25. package/gsd-core/bin/lib/config.cjs +139 -101
  26. package/gsd-core/bin/lib/configuration.cjs +41 -2
  27. package/gsd-core/bin/lib/criterion-baseline.cjs +346 -0
  28. package/gsd-core/bin/lib/criterion-replay.cjs +380 -0
  29. package/gsd-core/bin/lib/estimate-cli.cjs +5 -1
  30. package/gsd-core/bin/lib/gap-checker.cjs +12 -1
  31. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +593 -27
  32. package/gsd-core/bin/lib/gate-phase-context.cjs +16 -4
  33. package/gsd-core/bin/lib/graphify.cjs +19 -2
  34. package/gsd-core/bin/lib/health-diagnostic.cjs +5 -1
  35. package/gsd-core/bin/lib/init.cjs +13 -11
  36. package/gsd-core/bin/lib/install-effort-resolver.cjs +24 -4
  37. package/gsd-core/bin/lib/install-engine.cjs +11 -19
  38. package/gsd-core/bin/lib/install-fs-adapter.cjs +5 -1
  39. package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -3
  40. package/gsd-core/bin/lib/io.cjs +6 -0
  41. package/gsd-core/bin/lib/markdown-sectionizer.cjs +62 -31
  42. package/gsd-core/bin/lib/markdown-table.cjs +39 -1
  43. package/gsd-core/bin/lib/milestone.cjs +8 -6
  44. package/gsd-core/bin/lib/model-resolver.cjs +8 -4
  45. package/gsd-core/bin/lib/phase-entry-grammar.cjs +14 -0
  46. package/gsd-core/bin/lib/phase-locator.cjs +8 -3
  47. package/gsd-core/bin/lib/phase.cjs +107 -31
  48. package/gsd-core/bin/lib/plan-dependency-graph.cjs +46 -23
  49. package/gsd-core/bin/lib/planning-snapshot.cjs +8 -4
  50. package/gsd-core/bin/lib/planning-workspace.cjs +20 -10
  51. package/gsd-core/bin/lib/profile-output.cjs +9 -6
  52. package/gsd-core/bin/lib/project-context.cjs +439 -0
  53. package/gsd-core/bin/lib/project-root.cjs +9 -230
  54. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +1 -1
  55. package/gsd-core/bin/lib/report-parser.cjs +16 -0
  56. package/gsd-core/bin/lib/roadmap-parser.cjs +328 -50
  57. package/gsd-core/bin/lib/roadmap-upgrade.cjs +24 -5
  58. package/gsd-core/bin/lib/roadmap.cjs +2 -2
  59. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +7 -4
  60. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +81 -3
  61. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +279 -8
  62. package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
  63. package/gsd-core/bin/lib/shell-command-projection.cjs +1010 -10
  64. package/gsd-core/bin/lib/stale-bake-guard.cjs +4 -1
  65. package/gsd-core/bin/lib/state-transition.cjs +91 -41
  66. package/gsd-core/bin/lib/state.cjs +446 -61
  67. package/gsd-core/bin/lib/surface.cjs +4 -7
  68. package/gsd-core/bin/lib/tdd-red-evidence.cjs +10 -3
  69. package/gsd-core/bin/lib/verify-command-router.cjs +6 -1
  70. package/gsd-core/bin/lib/verify.cjs +49 -6
  71. package/gsd-core/bin/lib/worktree-safety.cjs +8 -111
  72. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  73. package/gsd-core/references/autonomous-smart-discuss.md +99 -18
  74. package/gsd-core/references/checkpoints.md +1 -1
  75. package/gsd-core/references/planner-guidance.md +11 -0
  76. package/gsd-core/references/planner-human-verify-mode.md +1 -1
  77. package/gsd-core/references/planner-reversibility.md +2 -0
  78. package/gsd-core/references/planner-verify-command-grounding.md +22 -6
  79. package/gsd-core/workflows/autonomous.md +4 -4
  80. package/gsd-core/workflows/code-review-fix.md +5 -7
  81. package/gsd-core/workflows/execute-phase.md +21 -7
  82. package/gsd-core/workflows/plan-phase/steps/plan-pre-dispatch.md +101 -0
  83. package/gsd-core/workflows/plan-phase.md +18 -19
  84. package/hooks/dist/gsd-agent-isolation-guard.js +3 -2
  85. package/hooks/dist/gsd-config-reload.js +2 -2
  86. package/hooks/dist/gsd-context-monitor.js +2 -1
  87. package/hooks/dist/gsd-cursor-subagent-start.js +8 -5
  88. package/hooks/dist/gsd-read-injection-scanner.js +2 -2
  89. package/hooks/dist/gsd-secret-read-guard.js +141 -6
  90. package/hooks/dist/gsd-statusline.js +3 -2
  91. package/hooks/dist/gsd-workflow-guard.js +2 -1
  92. package/hooks/dist/lib/config-read.js +49 -0
  93. package/hooks/dist/lib/git-cmd.js +3 -1
  94. package/hooks/dist/lib/git-head-tree.js +474 -0
  95. package/hooks/dist/lib/isolation-sentinel.js +97 -17
  96. package/hooks/dist/lib/secret-read-exemptions.js +161 -0
  97. package/hooks/gsd-agent-isolation-guard.js +3 -2
  98. package/hooks/gsd-config-reload.js +2 -2
  99. package/hooks/gsd-context-monitor.js +2 -1
  100. package/hooks/gsd-cursor-subagent-start.js +8 -5
  101. package/hooks/gsd-read-injection-scanner.js +2 -2
  102. package/hooks/gsd-secret-read-guard.js +141 -6
  103. package/hooks/gsd-statusline.js +3 -2
  104. package/hooks/gsd-workflow-guard.js +2 -1
  105. package/hooks/lib/config-read.js +49 -0
  106. package/hooks/lib/git-cmd.js +3 -1
  107. package/hooks/lib/git-head-tree.js +474 -0
  108. package/hooks/lib/isolation-sentinel.js +97 -17
  109. package/hooks/lib/secret-read-exemptions.js +161 -0
  110. package/package.json +7 -2
  111. package/pi/package-agents/gsd-executor.md +1 -1
  112. package/pi/package-agents/gsd-plan-checker.md +1 -1
  113. package/pi/package-agents/gsd-planner.md +3 -3
  114. package/pi/package-agents/gsd-verifier.md +2 -2
  115. package/pi/package-agents/manifest.json +4 -4
  116. package/pi/package-skills/gsd-autonomous/SKILL.md +1 -1
  117. package/scripts/changeset/cli.cjs +2 -2
  118. package/scripts/changeset/serialize.cjs +12 -2
  119. package/scripts/ci-compact-v8-coverage.cjs +204 -0
  120. package/scripts/ci-fix-next-rerun.cjs +149 -0
  121. package/scripts/ci-next-health.cjs +52 -9
  122. package/scripts/ci-test-scope.cjs +77 -71
  123. package/scripts/command-contract-helpers.cjs +56 -7
  124. package/scripts/docs-guard-registry.cjs +3 -0
  125. package/scripts/gen-cli-surface-manifest.cjs +287 -0
  126. package/scripts/gen-platform-conformance-tier.cjs +258 -19
  127. package/scripts/lib/macos-conformance-tier.generated.cjs +55 -47
  128. package/scripts/lib/platform-conformance-tier.generated.cjs +19 -64
  129. package/scripts/lib/shellcheck-fetch.cjs +71 -4
  130. package/scripts/lint-removed-but-needed.cjs +562 -50
  131. package/scripts/release-notes/changelog-release-notes.cjs +345 -0
  132. package/scripts/release-notes/discord-release-summary.cjs +82 -1
  133. package/scripts/timeout-evidence-from-ci.cjs +964 -0
  134. package/skills/gsd-autonomous/SKILL.md +1 -1
  135. package/vscode/package.json +1 -1
@@ -0,0 +1,545 @@
1
+ "use strict";
2
+ /**
3
+ * CLI surface registry (ADR-4909 D1, #6087, P1a of #6051).
4
+ *
5
+ * One declaration of every flag a `gsd-tools` verb accepts. Routers read their
6
+ * spec through `specFor(verb)`; `scripts/gen-cli-surface-manifest.cjs` emits
7
+ * `docs/CLI-SURFACE-MANIFEST.json` from this module (ADR-4909 D2).
8
+ *
9
+ * Hyrum's Law: each spec states the flags the router reads TODAY (the declared
10
+ * surface equals the observed surface) and `requiredFlags` only where the
11
+ * handler already fails without the flag. Conditional requirements (one-of,
12
+ * required-when-another-is-present) stay in the handler.
13
+ *
14
+ * Keys are the canonical verb: the `command-aliases` canonical where one
15
+ * exists, else the `HOST_COMMAND_ROUTERS` key, else `<host>.<sub>` for a router
16
+ * that switches on a subcommand. `positionals` counts the leading argv slots
17
+ * the router owns, INCLUDING the verb tokens (`frontmatter get <file>` is 3).
18
+ * Global flags (`GLOBAL_FLAGS`) are stripped by `main()` before dispatch and are
19
+ * never redeclared by a verb (`--raw` is therefore absent from every spec).
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.UNDECLARED_VERBS = exports.CLI_SURFACE = exports.GLOBAL_FLAGS = void 0;
23
+ exports.specFor = specFor;
24
+ exports.isCovered = isCovered;
25
+ exports.verbUniverse = verbUniverse;
26
+ function deepFreeze(value) {
27
+ if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {
28
+ Object.freeze(value);
29
+ for (const child of Object.values(value))
30
+ deepFreeze(child);
31
+ }
32
+ return value;
33
+ }
34
+ /**
35
+ * What `main()` consumes before dispatch (gsd-tools.cjs 5459-5680): `--cwd`,
36
+ * `--project-dir` (also `=`-form), `--ws`, `--pick`, `--default`, `--raw`,
37
+ * `--json-errors`, and the `=`-form-only `--exit-contract`. Help tokens are
38
+ * honored at any position; `--version`/`-v` are always rejected.
39
+ */
40
+ exports.GLOBAL_FLAGS = deepFreeze({
41
+ valueFlags: ['cwd', 'default', 'pick', 'project-dir', 'ws'],
42
+ booleanFlags: ['json-errors', 'raw'],
43
+ eqFlags: ['cwd', 'exit-contract', 'project-dir'],
44
+ helpTokens: ['-h', '--help', '-?', '--h', '--usage'],
45
+ rejectedTokens: ['--version', '-v'],
46
+ });
47
+ const DRY_RUN = ['dry-run'];
48
+ const JSON_FLAG = ['json'];
49
+ const RUN_LANE_COMMON = ['run-dir', 'repo-root', 'selected'];
50
+ exports.CLI_SURFACE = deepFreeze({
51
+ // ─── resolve / config ──────────────────────────────────────────────────────
52
+ 'resolve-model': { positionals: 2 },
53
+ // The phase-type positional may sit before or after --granularity, so words past the boundary are free.
54
+ 'resolve-granularity': { positionals: 1, freePositionals: true, valueFlags: ['granularity'] },
55
+ // Positional agent type may interleave with the hand-parsed flags. A flag without a next token (or with a
56
+ // flag-shaped one) is `Missing value`; a literal empty value (`--effort ''`, `--effort=`) was always a value,
57
+ // and the handler judges it (`--attempt ''` is "requires a non-negative integer").
58
+ 'resolve-execution': {
59
+ positionals: 1,
60
+ freePositionals: true,
61
+ valueFlags: ['attempt', 'effort', 'failure-class', 'fast-mode', 'host'],
62
+ eqFlags: ['attempt', 'effort', 'failure-class', 'fast-mode'],
63
+ nonEmptyFlags: ['attempt', 'effort', 'failure-class', 'fast-mode', 'host'],
64
+ emptyValueFlags: ['attempt', 'effort', 'failure-class', 'fast-mode', 'host'],
65
+ emptyEqFlags: ['attempt', 'effort', 'failure-class', 'fast-mode'],
66
+ // Any dash-led token that is not a declared flag was `Unknown flag for resolve-execution`.
67
+ rejectShortFlags: true,
68
+ unknownFlagMessage: 'Unknown flag for resolve-execution: {flag}',
69
+ },
70
+ 'config-set': { positionals: 3, booleanFlags: DRY_RUN },
71
+ // The choices JSON is the one positional, wherever --dry-run sits; the handler refuses a second one.
72
+ // A dash+letter token is a flag, not choices JSON (`-1` and `-` stay choices); the shipped wording is kept (#5268).
73
+ 'config-new-project': {
74
+ positionals: 1,
75
+ freePositionals: true,
76
+ booleanFlags: DRY_RUN,
77
+ rejectShortFlags: true,
78
+ unknownFlagMessage: 'Unknown flag for config-new-project: {flag}',
79
+ },
80
+ 'config-path': { positionals: 1 },
81
+ 'migrate-config': { positionals: 1 },
82
+ // ─── research ──────────────────────────────────────────────────────────────
83
+ // The usage line names `[--kind <k>]` for get; the handler never reads it but callers pass it (research-cli tests).
84
+ 'research-store.get': { positionals: 3, valueFlags: ['kind'] },
85
+ 'research-store.put': {
86
+ positionals: 3,
87
+ valueFlags: ['confidence', 'content', 'kind', 'provider', 'source'],
88
+ requiredFlags: ['confidence', 'content', 'kind', 'provider', 'source'],
89
+ },
90
+ 'research-plan': { positionals: 1, valueFlags: ['input'], requiredFlags: ['input'] },
91
+ // ─── codebase map / revert / commit ────────────────────────────────────────
92
+ 'stamp-codebase-map': { positionals: 1, valueFlags: ['files'], bareEmptyFlags: ['files'] },
93
+ // phase xor plan is conditional and stays in the handler.
94
+ 'select-revert-commits': {
95
+ positionals: 1,
96
+ valueFlags: ['phase', 'plan', 'range'],
97
+ requiredFlags: ['range'],
98
+ },
99
+ // Message tokens are variadic and precede the first list flag, so words past the verb are free.
100
+ commit: {
101
+ positionals: 1,
102
+ freePositionals: true,
103
+ booleanFlags: ['amend', 'no-verify'],
104
+ listFlags: ['files', 'files-removed'],
105
+ },
106
+ 'check-commit': { positionals: 1 },
107
+ // #6043: prints the ProjectContext main() resolved; it reads no flags of its own.
108
+ 'project-context': { positionals: 1 },
109
+ 'commit-docs-guard.disable': { positionals: 2 },
110
+ 'commit-docs-guard.enable': { positionals: 2 },
111
+ 'commit-to-subrepo': { positionals: 2, listFlags: ['files'] },
112
+ // The message slot is optional, so the boundary moves (routePrSubrepo: messagePresent ? 2 : 1).
113
+ 'pr-subrepo': { positionals: 1, freePositionals: true, valueFlags: ['branch', 'repo'] },
114
+ 'verify-summary': { positionals: 2, valueFlags: ['check-count'] },
115
+ // ─── template / frontmatter ────────────────────────────────────────────────
116
+ 'template.select': { positionals: 3 },
117
+ 'template.fill': {
118
+ positionals: 3,
119
+ valueFlags: ['fields', 'name', 'phase', 'plan', 'type', 'wave'],
120
+ },
121
+ // The field is `--field <k>` or one free word after the file (`frontmatter.get <file> <field>` is the shipped form).
122
+ 'frontmatter.get': { positionals: 3, freePositionals: true, valueFlags: ['field'] },
123
+ 'frontmatter.set': { positionals: 3, valueFlags: ['field', 'value'] },
124
+ 'frontmatter.merge': { positionals: 3, valueFlags: ['data'] },
125
+ 'frontmatter.validate': { positionals: 3, valueFlags: ['schema'] },
126
+ // ─── small readers ─────────────────────────────────────────────────────────
127
+ 'current-timestamp': { positionals: 2 },
128
+ 'runtime-identity': { positionals: 1 },
129
+ // The runtime is a bare token OR a flag, in either order, so the bare token is a free word.
130
+ 'project-instruction-file': { positionals: 1, freePositionals: true, valueFlags: ['runtime'], eqFlags: ['runtime'] },
131
+ 'list-todos': { positionals: 2 },
132
+ 'list-seeds': { positionals: 2 },
133
+ 'verify-path-exists': { positionals: 2 },
134
+ // `--task` falls back to a bare positional, so it is not unconditionally required.
135
+ 'quick-tasks-append': {
136
+ positionals: 1,
137
+ freePositionals: true,
138
+ valueFlags: ['directory', 'quick-id', 'slug', 'status', 'task'],
139
+ },
140
+ 'quick-tasks-migrate': { positionals: 1 },
141
+ 'normalize-test-command': { positionals: 2 },
142
+ 'resolve-package-manager': { positionals: 2 },
143
+ // ─── dispatch ──────────────────────────────────────────────────────────────
144
+ 'dispatch-should-flatten': { positionals: 1, booleanFlags: JSON_FLAG },
145
+ 'dispatch-isolation': {
146
+ positionals: 1,
147
+ valueFlags: ['cwd-target', 'force-isolation', 'phase', 'plan', 'prompt'],
148
+ booleanFlags: JSON_FLAG,
149
+ },
150
+ 'dispatch-capacity': { positionals: 1, booleanFlags: JSON_FLAG },
151
+ // --force-isolation, --phase and --plan are read by the router only to refuse them with the
152
+ // "recording-only" message, so they are declared to reach it; bareEmptyFlags makes a bare flag read as '' (present).
153
+ 'inspect-dispatch-isolation': {
154
+ positionals: 1,
155
+ valueFlags: ['cwd-target', 'force-isolation', 'phase', 'plan', 'prompt'],
156
+ bareEmptyFlags: ['force-isolation', 'phase', 'plan'],
157
+ booleanFlags: JSON_FLAG,
158
+ },
159
+ // A missing or invalid --isolation prints the verb's usage line, so it is not a requiredFlag.
160
+ // `--json` is accepted and ignored. `--worktree` is Cursor's bare harness flag: the legacy space
161
+ // form `--harness-flag --worktree` reads no value and the stray `--worktree` is tolerated.
162
+ 'record-dispatch-isolation': {
163
+ positionals: 1,
164
+ valueFlags: ['harness-flag', 'isolation', 'phase', 'plan'],
165
+ eqFlags: ['harness-flag'],
166
+ booleanFlags: ['json', 'worktree'],
167
+ },
168
+ 'resolve-dispatch-type': { positionals: 1, valueFlags: ['requested'], booleanFlags: JSON_FLAG },
169
+ 'resolve-agent': { positionals: 1, valueFlags: ['name'], booleanFlags: JSON_FLAG },
170
+ // The agent type is a free word: `agent-skills --json <type>` and `agent-skills <type> --json` both work.
171
+ 'agent-skills': {
172
+ positionals: 1,
173
+ freePositionals: true,
174
+ booleanFlags: ['json', 'skills-only'],
175
+ workstreamScoped: true,
176
+ },
177
+ 'history-digest': { positionals: 1 },
178
+ // ─── milestone / progress / uat / todo ─────────────────────────────────────
179
+ // `scan --terms a,b <phase>` exits sdk_unknown_command from the handler's phase check today; free words keep that reason.
180
+ // `--json` is passed by the shipped plan-pre probe (capabilities/assumption-delta/fragments/plan-pre.md); the output is JSON either way.
181
+ 'assumption-delta.scan': { positionals: 2, freePositionals: true, valueFlags: ['terms'], booleanFlags: JSON_FLAG },
182
+ 'milestone.archive-quick': { positionals: 3, booleanFlags: DRY_RUN },
183
+ 'milestone.complete': {
184
+ positionals: 3,
185
+ // `--archive-phases` is the documented default spelled out; nothing reads it, callers pass it (milestone tests).
186
+ booleanFlags: ['archive-phases', 'archive-quick', 'confirm', 'dry-run', 'force', 'no-archive-phases'],
187
+ // A bare --release-map reads as '' (not null) so the handler can refuse it with today's exit reason.
188
+ valueFlags: ['release-map'],
189
+ bareEmptyFlags: ['release-map'],
190
+ multiwordFlags: ['name'],
191
+ },
192
+ 'progress.bar': { positionals: 2 },
193
+ 'stats.json': { positionals: 2 },
194
+ 'uat.classify-coverage': { positionals: 2, valueFlags: ['file', 'summary'] },
195
+ 'uat.complete-session': { positionals: 3, valueFlags: ['message'] },
196
+ 'uat.render-checkpoint': { positionals: 2, valueFlags: ['file'] },
197
+ // The router refuses any dash-led token other than --dry-run itself (#4096); extra bare words were always ignored.
198
+ // #4096: an accepted-but-ignored short flag (`-n`) would turn a preview into the mutation, so short flags are refused too.
199
+ 'todo.complete': {
200
+ positionals: 3,
201
+ freePositionals: true,
202
+ booleanFlags: DRY_RUN,
203
+ rejectShortFlags: true,
204
+ unknownFlagMessage: 'Unknown flag for todo complete: {flag}',
205
+ },
206
+ 'todo.match-phase': { positionals: 3 },
207
+ // `--phase` is value; `--name` swallows the words up to the next flag.
208
+ scaffold: { positionals: 2, valueFlags: ['phase'], multiwordFlags: ['name'] },
209
+ 'loop.render-hooks': {
210
+ positionals: 3,
211
+ valueFlags: ['active-cap', 'after-fingerprint', 'config-dir', 'runtime'],
212
+ eqFlags: ['active-cap', 'after-fingerprint', 'config-dir', 'runtime'],
213
+ nonEmptyFlags: ['active-cap', 'after-fingerprint', 'config-dir', 'runtime'],
214
+ // Only the `=` form was ever trimmed; a space-form value reaches the handler as typed.
215
+ trimEqFlags: ['active-cap', 'after-fingerprint', 'config-dir', 'runtime'],
216
+ },
217
+ // `--json` is accepted and ignored: the index is JSON either way and callers pass it.
218
+ 'phase-plan-index': { positionals: 2, booleanFlags: JSON_FLAG },
219
+ 'state-snapshot': { positionals: 1 },
220
+ 'summary-extract': { positionals: 2, valueFlags: ['fields'] },
221
+ websearch: { positionals: 2, valueFlags: ['freshness', 'limit'] },
222
+ // ─── workstream / learnings ────────────────────────────────────────────────
223
+ 'workstream.complete': { positionals: 3 },
224
+ 'workstream.create': { positionals: 3, valueFlags: ['migrate-name'], booleanFlags: ['no-migrate'] },
225
+ 'workstream.get': { positionals: 2 },
226
+ 'workstream.list': { positionals: 2 },
227
+ 'workstream.progress': { positionals: 2 },
228
+ 'workstream.set': { positionals: 3 },
229
+ 'workstream.status': { positionals: 3 },
230
+ 'learnings.copy': { positionals: 2 },
231
+ 'learnings.delete': { positionals: 3 },
232
+ 'learnings.list': { positionals: 2 },
233
+ 'learnings.prune': { positionals: 2, valueFlags: ['older-than'], requiredFlags: ['older-than'] },
234
+ 'learnings.query': { positionals: 2, valueFlags: ['tag'], requiredFlags: ['tag'] },
235
+ 'teams-status': { positionals: 1, booleanFlags: ['active'] },
236
+ // ─── custom files / prompt budget ──────────────────────────────────────────
237
+ 'detect-custom-files': {
238
+ positionals: 1,
239
+ valueFlags: ['config-dir'],
240
+ requiredFlags: ['config-dir'],
241
+ },
242
+ // Last --config-dir wins today; repeatable keeps every occurrence so the handler takes the last, and
243
+ // nonEmptyFlags checks that last occurrence (a trailing `--config-dir` with no value is a usage error).
244
+ 'restore-custom-files': {
245
+ positionals: 1,
246
+ valueFlags: ['config-dir'],
247
+ repeatableFlags: ['config-dir'],
248
+ nonEmptyFlags: ['config-dir'],
249
+ requiredFlags: ['config-dir'],
250
+ booleanFlags: ['apply'],
251
+ },
252
+ 'prompt-budget': {
253
+ positionals: 1,
254
+ valueFlags: [
255
+ 'budget',
256
+ 'context-file',
257
+ 'instructions-file',
258
+ 'output-metadata',
259
+ 'output-prompt',
260
+ 'plan-file',
261
+ 'project-file',
262
+ 'project-md-head-lines',
263
+ 'requirements-file',
264
+ 'research-file',
265
+ 'roadmap-file',
266
+ 'safety-margin-pct',
267
+ ],
268
+ repeatableFlags: ['plan-file'],
269
+ // An empty --plan-file list counts as missing; --budget's value validation stays in the handler.
270
+ // Declaration order is the order a run missing several reports them; it mirrors the handler's old checks.
271
+ requiredFlags: [
272
+ 'budget',
273
+ 'instructions-file',
274
+ 'roadmap-file',
275
+ 'plan-file',
276
+ 'output-prompt',
277
+ 'output-metadata',
278
+ ],
279
+ },
280
+ // ─── context / confidence / legitimacy / effort / stories / drift ──────────
281
+ // "At least one of class/prefix/contains" is conditional and stays in the handler.
282
+ 'context-predicates': {
283
+ positionals: 1,
284
+ valueFlags: ['class', 'contains', 'prefix'],
285
+ eqFlags: ['class', 'contains', 'prefix'],
286
+ nonEmptyFlags: ['class', 'contains', 'prefix'],
287
+ // A whitespace-only value (`--class ' '`) is as missing as `--class=`.
288
+ blankIsMissingFlags: ['class', 'contains', 'prefix'],
289
+ // `--class==A` is a typo shape, never the literal `=A`; the space form `--class =A` is a value.
290
+ eqRejectPrefix: { class: '=', contains: '=', prefix: '=' },
291
+ missingValueMessage: 'context-predicates: {flag} requires a non-empty value',
292
+ },
293
+ 'update-context': {
294
+ positionals: 1,
295
+ valueFlags: ['config-dir', 'runtime'],
296
+ eqFlags: ['config-dir', 'runtime'],
297
+ // The workflow passes empty hints (`--runtime ""`, `--config-dir=`) when it has none; only a flag with
298
+ // no value token at all is refused.
299
+ nonEmptyFlags: ['config-dir', 'runtime'],
300
+ emptyValueFlags: ['config-dir', 'runtime'],
301
+ emptyEqFlags: ['config-dir', 'runtime'],
302
+ booleanFlags: JSON_FLAG,
303
+ },
304
+ // `--ecosystem` is required only when `--package` is given and stays in the handler.
305
+ 'classify-confidence': {
306
+ positionals: 1,
307
+ valueFlags: ['ecosystem', 'package', 'provider'],
308
+ requiredFlags: ['provider'],
309
+ booleanFlags: ['verified'],
310
+ },
311
+ // Package names are the variadic positionals.
312
+ 'package-legitimacy.check': {
313
+ positionals: 2,
314
+ freePositionals: true,
315
+ valueFlags: ['ecosystem'],
316
+ requiredFlags: ['ecosystem'],
317
+ },
318
+ 'effort.sync': {
319
+ positionals: 2,
320
+ booleanFlags: ['apply', 'dry-run'],
321
+ valueFlags: ['config-dir', 'runtime'],
322
+ eqFlags: ['config-dir', 'runtime'],
323
+ // `--config-dir ''` is refused but `--config-dir=` was accepted (and means "no override").
324
+ nonEmptyFlags: ['config-dir', 'runtime'],
325
+ emptyEqFlags: ['config-dir', 'runtime'],
326
+ },
327
+ 'user-story.validate': { positionals: 2, valueFlags: ['story'] },
328
+ 'drift-guard.authority': { positionals: 2 },
329
+ 'drift-guard.phase-status': { positionals: 2, valueFlags: ['phase'] },
330
+ 'drift-guard.severity': {
331
+ positionals: 2,
332
+ valueFlags: ['authority', 'status'],
333
+ requiredFlags: ['status'],
334
+ },
335
+ 'skills-root': { positionals: 2 },
336
+ // ─── review-lane ───────────────────────────────────────────────────────────
337
+ // Every sub reads --run-dir, --repo-root and --selected before branching (gsd-tools.cjs 1673-1799).
338
+ 'review-lane.availability': { positionals: 2, valueFlags: RUN_LANE_COMMON },
339
+ 'review-lane.dispatch-step': {
340
+ positionals: 2,
341
+ valueFlags: [...RUN_LANE_COMMON, 'base-sha', 'cap-id', 'depth', 'explicit', 'point'],
342
+ },
343
+ // Tokens after a literal `--` are candidate flags, matched against lane descriptors.
344
+ 'review-lane.explicit-from-argv': {
345
+ positionals: 2,
346
+ valueFlags: RUN_LANE_COMMON,
347
+ passthroughAfter: '--',
348
+ },
349
+ 'review-lane.flags': { positionals: 2, valueFlags: RUN_LANE_COMMON },
350
+ 'review-lane.invoke': {
351
+ positionals: 2,
352
+ valueFlags: [...RUN_LANE_COMMON, 'agent', 'as', 'model', 'prompt-file', 'slug'],
353
+ // `--json` is documented for this verb (docs/how-to/fix-a-refused-reviewer-lane.md) and always passed by callers; the result is JSON either way.
354
+ booleanFlags: ['explicit', 'json'],
355
+ requiredFlags: ['slug'],
356
+ },
357
+ 'review-lane.plan': { positionals: 2, valueFlags: RUN_LANE_COMMON },
358
+ 'review-lane.sections': { positionals: 2, valueFlags: RUN_LANE_COMMON },
359
+ });
360
+ /**
361
+ * Every dispatchable verb whose flags are still parsed outside this registry
362
+ * (forwarders to `src/` routers, `_dispatchNonFamily` verbs, capability
363
+ * families, alias canonicals). Sorted. Shrinks to `[]` across #6088-#6090; the
364
+ * parity test (T-C3) fails if it grows.
365
+ */
366
+ exports.UNDECLARED_VERBS = deepFreeze([
367
+ 'agent',
368
+ 'agent.classify-failure',
369
+ 'audit-open',
370
+ 'audit-uat',
371
+ 'capability',
372
+ 'check',
373
+ 'check.decision-coverage-plan',
374
+ 'check.decision-coverage-verify',
375
+ 'claude-orchestration',
376
+ 'config-ensure-section',
377
+ 'config-get',
378
+ 'config-set-model-profile',
379
+ 'docs-init',
380
+ 'estimate-calibrate',
381
+ 'estimate-calibration',
382
+ 'estimate-check',
383
+ 'eval',
384
+ 'eval.score',
385
+ 'extract-messages',
386
+ 'find-phase',
387
+ 'from-gsd2',
388
+ 'gap-analysis',
389
+ 'generate-claude-md',
390
+ 'generate-claude-profile',
391
+ 'generate-dev-preferences',
392
+ 'generate-slug',
393
+ 'git',
394
+ 'graphify',
395
+ 'init',
396
+ 'init.autonomous',
397
+ 'init.code-review',
398
+ 'init.complete-milestone',
399
+ 'init.debug',
400
+ 'init.discuss-phase-assumptions',
401
+ 'init.docs-update',
402
+ 'init.execute-phase',
403
+ 'init.ingest-docs',
404
+ 'init.list-workspaces',
405
+ 'init.manager',
406
+ 'init.map-codebase',
407
+ 'init.milestone-op',
408
+ 'init.new-milestone',
409
+ 'init.new-project',
410
+ 'init.new-workspace',
411
+ 'init.onboard',
412
+ 'init.phase-op',
413
+ 'init.plan-phase',
414
+ 'init.progress',
415
+ 'init.quick',
416
+ 'init.quick-batch',
417
+ 'init.remove-workspace',
418
+ 'init.resume',
419
+ 'init.review',
420
+ 'init.todos',
421
+ 'init.transition',
422
+ 'init.update',
423
+ 'init.verify-work',
424
+ 'intel',
425
+ 'milestone.release-tags',
426
+ 'phase',
427
+ 'phase.add',
428
+ 'phase.add-batch',
429
+ 'phase.complete',
430
+ 'phase.insert',
431
+ 'phase.list-plans',
432
+ 'phase.mvp-mode',
433
+ 'phase.next-decimal',
434
+ 'phase.remove',
435
+ 'phase.scaffold',
436
+ 'phase.uat-passed',
437
+ 'phases',
438
+ 'phases.archive',
439
+ 'phases.clear',
440
+ 'phases.list',
441
+ 'planning',
442
+ 'profile-questionnaire',
443
+ 'profile-sample',
444
+ 'quick-batch',
445
+ 'refactor',
446
+ 'requirements',
447
+ 'requirements.mark-complete',
448
+ 'requirements.ready-ids',
449
+ 'requirements.revert-phase',
450
+ 'roadmap',
451
+ 'roadmap.analyze',
452
+ 'roadmap.annotate-dependencies',
453
+ 'roadmap.get-phase',
454
+ 'roadmap.milestone-scope',
455
+ 'roadmap.update-plan-progress',
456
+ 'roadmap.upgrade',
457
+ 'roadmap.validate',
458
+ 'scan-sessions',
459
+ 'skill-manifest',
460
+ 'smart-entry',
461
+ 'state',
462
+ 'state.add-blocker',
463
+ 'state.add-decision',
464
+ 'state.add-roadmap-evolution',
465
+ 'state.advance-plan',
466
+ 'state.begin-phase',
467
+ 'state.get',
468
+ 'state.json',
469
+ 'state.load',
470
+ 'state.milestone-switch',
471
+ 'state.patch',
472
+ 'state.planned-phase',
473
+ 'state.prune',
474
+ 'state.rebuild',
475
+ 'state.record-metric',
476
+ 'state.record-session',
477
+ 'state.resolve-blocker',
478
+ 'state.signal-resume',
479
+ 'state.signal-waiting',
480
+ 'state.sync',
481
+ 'state.update',
482
+ 'state.update-progress',
483
+ 'state.validate',
484
+ 'task',
485
+ 'task.is-behavior-adding',
486
+ 'validate',
487
+ 'validate.agents',
488
+ 'validate.consistency',
489
+ 'validate.context',
490
+ 'validate.health',
491
+ 'verification',
492
+ 'verify',
493
+ 'verify.artifacts',
494
+ 'verify.codebase-drift',
495
+ 'verify.commits',
496
+ 'verify.context-drift',
497
+ 'verify.key-links',
498
+ 'verify.phase-completeness',
499
+ 'verify.plan-structure',
500
+ 'verify.references',
501
+ 'verify.schema-drift',
502
+ 'windows',
503
+ 'worktree',
504
+ 'write-profile',
505
+ ]);
506
+ function specFor(verb) {
507
+ const spec = Object.prototype.hasOwnProperty.call(exports.CLI_SURFACE, verb) ? exports.CLI_SURFACE[verb] : undefined;
508
+ if (spec === undefined) {
509
+ throw new Error(`cli-surface: verb "${verb}" has no declared flag spec. Declare it in src/cli-surface.cts ` +
510
+ '(CLI_SURFACE) before reading it through specFor().');
511
+ }
512
+ return spec;
513
+ }
514
+ /** True when `verb` is declared or undeclared, or a declared/undeclared key is one of its sub-verbs (`verb.<sub>`). */
515
+ function isCovered(verb) {
516
+ const prefix = `${verb}.`;
517
+ const keys = [...Object.keys(exports.CLI_SURFACE), ...exports.UNDECLARED_VERBS];
518
+ return keys.some((k) => k === verb || k.startsWith(prefix));
519
+ }
520
+ /**
521
+ * The dispatchable verb universe: gsd-tools `HOST_COMMAND_ROUTERS` keys, the
522
+ * capability-registry `commandFamilies` keys, and the command-aliases
523
+ * canonicals. `gsd-tools.cjs` is required lazily, from the compiled location
524
+ * (`gsd-core/bin/lib/`), because it is the CLI entry module.
525
+ */
526
+ function verbUniverse() {
527
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
528
+ const gsdTools = require('../gsd-tools.cjs');
529
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
530
+ const capabilityRegistry = require('./capability-registry.cjs');
531
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
532
+ const aliases = require('./command-aliases.cjs');
533
+ const universe = new Set(gsdTools.hostCommandNames());
534
+ for (const family of Object.keys(capabilityRegistry.commandFamilies))
535
+ universe.add(family);
536
+ for (const [name, list] of Object.entries(aliases)) {
537
+ if (!name.endsWith('_COMMAND_ALIASES') || !Array.isArray(list))
538
+ continue;
539
+ for (const entry of list) {
540
+ if (typeof entry.canonical === 'string')
541
+ universe.add(entry.canonical);
542
+ }
543
+ }
544
+ return [...universe].sort();
545
+ }