@opengsd/gsd-core 1.5.0-rc.1 → 1.5.0-rc.3

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 (183) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/gsd-code-fixer.md +3 -2
  3. package/agents/gsd-debug-session-manager.md +2 -1
  4. package/agents/gsd-debugger.md +4 -3
  5. package/agents/gsd-executor.md +16 -15
  6. package/agents/gsd-intel-updater.md +38 -41
  7. package/agents/gsd-mempalace-curator.md +47 -0
  8. package/agents/gsd-phase-researcher.md +8 -8
  9. package/agents/gsd-plan-checker.md +12 -11
  10. package/agents/gsd-planner.md +21 -181
  11. package/agents/gsd-project-researcher.md +5 -4
  12. package/agents/gsd-research-synthesizer.md +2 -1
  13. package/agents/gsd-ui-researcher.md +2 -1
  14. package/agents/gsd-verifier.md +9 -8
  15. package/bin/install.js +230 -1409
  16. package/commands/gsd/mempalace-capture.md +71 -0
  17. package/commands/gsd/mempalace-recall.md +102 -0
  18. package/commands/gsd/ns-context.md +4 -2
  19. package/gemini-extension.json +1 -1
  20. package/gsd-core/bin/gsd-tools.cjs +110 -92
  21. package/gsd-core/bin/gsd_run +20 -0
  22. package/gsd-core/bin/lib/capability-activation.cjs +86 -0
  23. package/gsd-core/bin/lib/capability-registry.cjs +3321 -47
  24. package/gsd-core/bin/lib/capability-state.cjs +31 -18
  25. package/gsd-core/bin/lib/check-command-router.cjs +460 -2
  26. package/gsd-core/bin/lib/clusters.cjs +2 -0
  27. package/gsd-core/bin/lib/command-roster.cjs +19 -0
  28. package/gsd-core/bin/lib/commands.cjs +33 -10
  29. package/gsd-core/bin/lib/config-loader.cjs +7 -8
  30. package/gsd-core/bin/lib/config-schema.cjs +32 -3
  31. package/gsd-core/bin/lib/config.cjs +1 -2
  32. package/gsd-core/bin/lib/core.cjs +56 -3
  33. package/gsd-core/bin/lib/edge-probe.cjs +173 -0
  34. package/gsd-core/bin/lib/fallow-runner.cjs +63 -25
  35. package/gsd-core/bin/lib/init.cjs +8 -5
  36. package/gsd-core/bin/lib/install-profiles.cjs +2 -1
  37. package/gsd-core/bin/lib/intel.cjs +3 -3
  38. package/gsd-core/bin/lib/io.cjs +61 -6
  39. package/gsd-core/bin/lib/loop-resolver.cjs +157 -16
  40. package/gsd-core/bin/lib/model-resolver.cjs +47 -5
  41. package/gsd-core/bin/lib/phase-command-router.cjs +20 -0
  42. package/gsd-core/bin/lib/phase.cjs +88 -11
  43. package/gsd-core/bin/lib/probe-core.cjs +257 -0
  44. package/gsd-core/bin/lib/profile-output.cjs +45 -4
  45. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +138 -0
  46. package/gsd-core/bin/lib/roadmap.cjs +137 -7
  47. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +1700 -0
  48. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +67 -165
  49. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +77 -31
  50. package/gsd-core/bin/lib/runtime-homes.cjs +156 -114
  51. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +1439 -0
  52. package/gsd-core/bin/lib/runtime-name-policy.cjs +1 -1
  53. package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
  54. package/gsd-core/bin/lib/state-command-router.cjs +15 -3
  55. package/gsd-core/bin/lib/state-document.cjs +54 -1
  56. package/gsd-core/bin/lib/state.cjs +221 -35
  57. package/gsd-core/bin/lib/uat-predicate.cjs +329 -0
  58. package/gsd-core/bin/lib/update-context.cjs +4 -1
  59. package/gsd-core/bin/lib/verify.cjs +122 -4
  60. package/gsd-core/bin/lib/worktree-base-ref.cjs +33 -8
  61. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -2
  62. package/gsd-core/bin/shared/config-schema.manifest.json +0 -18
  63. package/gsd-core/bin/shared/model-catalog.json +12 -6
  64. package/gsd-core/bin/shared/runtime-aliases.manifest.json +2 -1
  65. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +7 -0
  66. package/gsd-core/references/edge-probe-fixtures/01-round-half-even/requirements.json +1 -0
  67. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +8 -0
  68. package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json +1 -0
  69. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +7 -0
  70. package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json +1 -0
  71. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +7 -0
  72. package/gsd-core/references/edge-probe-fixtures/04-money-rounding/requirements.json +1 -0
  73. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +8 -0
  74. package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json +1 -0
  75. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +8 -0
  76. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json +1 -0
  77. package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json +4 -0
  78. package/gsd-core/references/edge-probe.md +261 -0
  79. package/gsd-core/references/loop-hook-dispatch.md +61 -0
  80. package/gsd-core/references/planner-antipatterns.md +41 -0
  81. package/gsd-core/references/planner-guidance.md +186 -0
  82. package/gsd-core/templates/config.json +1 -1
  83. package/gsd-core/templates/spec.md +12 -0
  84. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  85. package/gsd-core/workflows/add-backlog.md +1 -1
  86. package/gsd-core/workflows/add-phase.md +1 -1
  87. package/gsd-core/workflows/add-tests.md +1 -1
  88. package/gsd-core/workflows/add-todo.md +1 -1
  89. package/gsd-core/workflows/ai-integration-phase.md +1 -1
  90. package/gsd-core/workflows/audit-fix.md +1 -1
  91. package/gsd-core/workflows/audit-milestone.md +6 -4
  92. package/gsd-core/workflows/audit-uat.md +1 -1
  93. package/gsd-core/workflows/autonomous.md +36 -42
  94. package/gsd-core/workflows/check-todos.md +1 -1
  95. package/gsd-core/workflows/cleanup.md +1 -1
  96. package/gsd-core/workflows/code-review-fix.md +10 -8
  97. package/gsd-core/workflows/code-review.md +59 -22
  98. package/gsd-core/workflows/complete-milestone.md +1 -1
  99. package/gsd-core/workflows/debug.md +1 -1
  100. package/gsd-core/workflows/diagnose-issues.md +1 -1
  101. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  102. package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
  103. package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
  104. package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
  105. package/gsd-core/workflows/discuss-phase.md +15 -1
  106. package/gsd-core/workflows/do.md +1 -1
  107. package/gsd-core/workflows/docs-update.md +1 -1
  108. package/gsd-core/workflows/edit-phase.md +1 -1
  109. package/gsd-core/workflows/eval-review.md +1 -1
  110. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  111. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
  112. package/gsd-core/workflows/execute-phase.md +83 -144
  113. package/gsd-core/workflows/execute-plan.md +1 -1
  114. package/gsd-core/workflows/explore.md +1 -1
  115. package/gsd-core/workflows/extract-learnings.md +1 -1
  116. package/gsd-core/workflows/forensics.md +1 -1
  117. package/gsd-core/workflows/graduation.md +1 -1
  118. package/gsd-core/workflows/health.md +1 -1
  119. package/gsd-core/workflows/help/modes/full.md +3 -1
  120. package/gsd-core/workflows/import.md +1 -1
  121. package/gsd-core/workflows/ingest-docs.md +1 -1
  122. package/gsd-core/workflows/insert-phase.md +1 -1
  123. package/gsd-core/workflows/list-workspaces.md +1 -1
  124. package/gsd-core/workflows/manager.md +1 -1
  125. package/gsd-core/workflows/map-codebase.md +1 -1
  126. package/gsd-core/workflows/milestone-summary.md +1 -1
  127. package/gsd-core/workflows/mvp-phase.md +1 -1
  128. package/gsd-core/workflows/new-milestone.md +9 -1
  129. package/gsd-core/workflows/new-project.md +12 -4
  130. package/gsd-core/workflows/new-workspace.md +1 -1
  131. package/gsd-core/workflows/next.md +1 -1
  132. package/gsd-core/workflows/pause-work.md +1 -1
  133. package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
  134. package/gsd-core/workflows/plan-phase.md +120 -240
  135. package/gsd-core/workflows/plan-review-convergence.md +1 -1
  136. package/gsd-core/workflows/plant-seed.md +1 -1
  137. package/gsd-core/workflows/profile-user.md +4 -2
  138. package/gsd-core/workflows/progress.md +59 -2
  139. package/gsd-core/workflows/quick.md +10 -7
  140. package/gsd-core/workflows/remove-phase.md +1 -1
  141. package/gsd-core/workflows/remove-workspace.md +1 -1
  142. package/gsd-core/workflows/resume-project.md +1 -1
  143. package/gsd-core/workflows/review.md +20 -3
  144. package/gsd-core/workflows/scan.md +1 -1
  145. package/gsd-core/workflows/secure-phase.md +5 -3
  146. package/gsd-core/workflows/settings-advanced.md +20 -14
  147. package/gsd-core/workflows/settings-integrations.md +1 -1
  148. package/gsd-core/workflows/settings.md +2 -2
  149. package/gsd-core/workflows/ship.md +58 -1
  150. package/gsd-core/workflows/sketch-wrap-up.md +1 -1
  151. package/gsd-core/workflows/sketch.md +1 -1
  152. package/gsd-core/workflows/spec-phase.md +130 -1
  153. package/gsd-core/workflows/spike-wrap-up.md +1 -1
  154. package/gsd-core/workflows/spike.md +1 -1
  155. package/gsd-core/workflows/stats.md +1 -1
  156. package/gsd-core/workflows/thread.md +1 -1
  157. package/gsd-core/workflows/transition.md +1 -1
  158. package/gsd-core/workflows/ui-phase.md +1 -1
  159. package/gsd-core/workflows/ui-review.md +1 -1
  160. package/gsd-core/workflows/ultraplan-phase.md +1 -1
  161. package/gsd-core/workflows/update.md +2 -2
  162. package/gsd-core/workflows/validate-phase.md +5 -3
  163. package/gsd-core/workflows/verify-phase.md +1 -1
  164. package/gsd-core/workflows/verify-work.md +28 -12
  165. package/package.json +7 -3
  166. package/scripts/affected-tests-lib.cjs +16 -4
  167. package/scripts/gen-capability-registry.cjs +857 -61
  168. package/scripts/gen-inventory-manifest.cjs +1 -4
  169. package/scripts/gen-loop-host-contract.cjs +55 -0
  170. package/scripts/issue-version-gate.cjs +140 -0
  171. package/scripts/lib/allowlist-ratchet.cjs +101 -1
  172. package/scripts/lint-allow-test-rule-refs.allowlist.json +326 -0
  173. package/scripts/lint-allow-test-rule-refs.cjs +162 -0
  174. package/scripts/lint-test-file-count.allowlist.json +23 -2
  175. package/scripts/lint-windows-test-portability.cjs +178 -0
  176. package/scripts/release-tarball-smoke.cjs +7 -1
  177. package/scripts/research-profiles.cjs +10 -10
  178. package/scripts/run-tests.cjs +128 -27
  179. package/scripts/sync-next-version.cjs +133 -0
  180. package/scripts/sync-runtime-launcher.cjs +21 -5
  181. package/scripts/update-size-baseline.cjs +68 -0
  182. package/scripts/workflow-policy.cjs +42 -9
  183. package/scripts/workflow-size.cjs +90 -0
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: gsd:mempalace-capture
3
+ description: "File a phase artifact into MemPalace; mirror decision facts into its temporal KG"
4
+ argument-hint: "[CONTEXT.md|PLAN.md|SUMMARY.md]"
5
+ allowed-tools:
6
+ - Read
7
+ - Bash
8
+ requires: [config]
9
+ ---
10
+
11
+ **STOP -- DO NOT READ THIS FILE. You are already reading it. This prompt was injected into your context by the command system. Using the Read tool on this file wastes tokens. Begin executing Step 0 immediately.**
12
+
13
+ ## Step 0 -- Banner
14
+
15
+ **Before ANY tool calls**, display this banner:
16
+
17
+ ```
18
+ GSD > MEMPALACE CAPTURE
19
+ ```
20
+
21
+ Then proceed to Step 1.
22
+
23
+ ## Step 1 -- Config Gate
24
+
25
+ Check whether the MemPalace capability is enabled by reading `.planning/config.json` directly with the Read tool.
26
+
27
+ 1. Read `.planning/config.json` with the Read tool.
28
+ 2. If the file does not exist, or `config.mempalace` is absent, or `config.mempalace.enabled !== true`, or `config.mempalace.capture_artifacts !== true`: display the disabled message and **STOP**.
29
+ 3. Otherwise proceed to Step 2.
30
+
31
+ **Disabled message:**
32
+
33
+ ```
34
+ GSD > MEMPALACE CAPTURE
35
+
36
+ MemPalace capture is disabled (mempalace.enabled / mempalace.capture_artifacts).
37
+ Nothing was filed; the loop proceeds normally.
38
+ ```
39
+
40
+ This step is `onError: skip` at `discuss:post` / `plan:post` / `verify:post` -- capture never fails a phase.
41
+
42
+ ## Step 2 -- Resolve target
43
+
44
+ 1. **Artifact.** Take the artifact from `$ARGUMENTS`. If absent, infer from the loop point: `discuss:post` → `CONTEXT.md`, `plan:post` → `PLAN.md`, `verify:post` → `SUMMARY.md`.
45
+ 2. **Room.** Map artifact → room:
46
+ - `CONTEXT.md` → `decisions`
47
+ - `PLAN.md` → `planning`
48
+ - `SUMMARY.md` → `milestones`
49
+ (Confirmed problem→fix pairs go to `problems` — see the `capture-problems` fragment used at `execute:wave:post`.)
50
+ 3. **Wing.** `config.mempalace.wing` if non-empty, else `config.project_code`, else the repo directory name.
51
+ 4. **Mode / transport.** Read `config.mempalace.memory_mode`. Prefer MCP (`mempalace_*`) when your MemPalace MCP server is registered and your runtime permits those tools; otherwise use the `mempalace` CLI (covered by this skill's `Bash` allow-tool), as in `mempalace-recall`.
52
+
53
+ ## Step 3 -- File verbatim (idempotent)
54
+
55
+ On any error or timeout, stop and let the phase continue -- capture is best-effort.
56
+
57
+ 1. **Dedup first.** Interactive: `mempalace_check_duplicate` on the artifact's deterministic drawer id. Headless: rely on `mempalace mine`'s content-hash idempotency.
58
+ 2. **Add the drawer (verbatim).** File the exact artifact text into `room: <room>` of `wing: <wing>` with provenance (`source_file`, phase id). Interactive: `mempalace_add_drawer`. Headless: `mempalace mine <path> --wing <wing> --room <room>`.
59
+ 3. **Mirror KG facts** when `config.mempalace.mirror_kg` is true: extract decision/delivery facts and `mempalace_kg_add` them with `valid_from` = the phase date (e.g. `(<project>, decided, <decision>)` from CONTEXT; `(<phase>, delivered, <capability>)` from SUMMARY). Only `augment` is currently wired, so these are an *additive* mirror of `.planning/graphs/`. (`kg_backend`/`replace` are forward-declared and behave as `augment` today.)
60
+ 4. Re-running a phase MUST NOT create duplicate drawers (deterministic ids + `check_duplicate`).
61
+
62
+ ## Step 4 -- Report
63
+
64
+ Print a one-line summary: `Filed <artifact> → <wing>/<room> (<n> KG facts)` or `MemPalace unavailable — capture skipped`.
65
+
66
+ ## Anti-Patterns
67
+
68
+ 1. DO NOT let any MemPalace error fail the step -- capture is `onError: skip`.
69
+ 2. DO NOT write lossy summaries -- store the verbatim artifact text (AAAK compression is a separate, optional index).
70
+ 3. DO NOT prune or delete drawers here -- pruning (`sync --apply`) is the curator agent's job at `ship:post`, wing-scoped only.
71
+ 4. DO NOT skip the config gate or the dedup check.
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: gsd:mempalace-recall
3
+ description: "Recall decisions, patterns, and surprises from MemPalace before planning"
4
+ argument-hint: "[phase-slug]"
5
+ allowed-tools:
6
+ - Read
7
+ - Write
8
+ - Bash
9
+ requires: [config]
10
+ ---
11
+
12
+ **STOP -- DO NOT READ THIS FILE. You are already reading it. This prompt was injected into your context by the command system. Using the Read tool on this file wastes tokens. Begin executing Step 0 immediately.**
13
+
14
+ ## Step 0 -- Banner
15
+
16
+ **Before ANY tool calls**, display this banner:
17
+
18
+ ```
19
+ GSD > MEMPALACE RECALL
20
+ ```
21
+
22
+ Then proceed to Step 1.
23
+
24
+ ## Step 1 -- Config Gate
25
+
26
+ Check whether the MemPalace capability is enabled by reading `.planning/config.json` directly with the Read tool.
27
+
28
+ **DO NOT use `gsd-tools config get-value`** -- it hard-exits on missing keys.
29
+
30
+ 1. Read `.planning/config.json` with the Read tool.
31
+ 2. If the file does not exist: write the "unavailable" stub (Step 4) and **STOP**.
32
+ 3. Parse the JSON. Proceed to Step 2 only if `config.mempalace && config.mempalace.enabled === true` **and** `config.mempalace.recall_on_plan !== false`. Otherwise display the disabled message and **STOP** (`recall_on_plan: false` turns plan-time recall off while leaving the rest of the capability enabled).
33
+
34
+ **Disabled message:**
35
+
36
+ ```
37
+ GSD > MEMPALACE RECALL
38
+
39
+ MemPalace memory is disabled. To activate:
40
+
41
+ node <runtime-home>/gsd-core/bin/gsd-tools.cjs config-set mempalace.enabled true
42
+
43
+ Recall is opt-in; the loop proceeds normally without it.
44
+ ```
45
+
46
+ This step is `onError: skip` at `plan:pre` -- recall never blocks planning.
47
+
48
+ ## Step 2 -- Resolve wing, mode, and transport
49
+
50
+ 1. **Wing.** Use `config.mempalace.wing` if non-empty; otherwise derive from `config.project_code`; otherwise fall back to the repository directory name.
51
+ 2. **Mode.** Read `config.mempalace.memory_mode` (`augment` | `kg_backend` | `replace`, default `augment`). Only `augment` is wired today, so recall always treats the palace as additive; `kg_backend`/`replace` are forward-declared and behave as `augment`.
52
+ 3. **Transport.** Prefer the **MCP tools** (`mempalace_*`) in interactive runs *when your MemPalace MCP server is registered and your runtime permits those tools*. Otherwise — headless/cron/autonomous runs, or runtimes that don't grant the MemPalace MCP tools — use the **CLI** (`mempalace wake-up`, `mempalace search`), which this skill's `Bash` allow-tool always covers. If neither is reachable, go to Step 4.
53
+ 4. **Topic.** Read the phase `CONTEXT.md` (the consumed artifact). Derive a short search query from its title, goal, and key decisions.
54
+
55
+ ## Step 3 -- Retrieve (read-only)
56
+
57
+ All calls in this step are side-effect-free. On any error or timeout, stop retrieving and write whatever was gathered (or the stub) -- never raise.
58
+
59
+ 1. **Wake up** (cheap, ~600--900 tokens):
60
+ - Interactive: read the wing identity/summary, then `mempalace_search`.
61
+ - Headless: `mempalace wake-up --wing <wing>`.
62
+ 2. **Targeted search:**
63
+ - Interactive: `mempalace_search(query=<topic>, wing=<wing>)`.
64
+ - Headless: `mempalace search "<topic>" --wing <wing>`.
65
+ 3. **Knowledge-graph facts** (when `config.mempalace.mirror_kg` is true): `mempalace_kg_query` / `mempalace_kg_timeline` for decisions relevant to the topic and their validity windows. Only `augment` is currently wired, so the palace KG *supplements* GSD's native `.planning/graphs/` — do not treat it as the sole source. (`kg_backend`/`replace` are forward-declared and behave as `augment` today.)
66
+ 4. **Dedup** the returned drawers/facts; keep the top results.
67
+
68
+ ## Step 4 -- Write MEMORY-RECALL.md
69
+
70
+ Write `MEMORY-RECALL.md` in the current phase directory. The planner consumes it.
71
+
72
+ When recall succeeded, structure it as:
73
+
74
+ ```markdown
75
+ # Memory Recall (MemPalace)
76
+
77
+ _Wing: <wing> · Mode: <mode> · Transport: <mcp|cli>_
78
+
79
+ ## Prior decisions
80
+ - <decision> — <provenance: drawer id / kg fact, valid_from>
81
+
82
+ ## Patterns
83
+ - <pattern> — <provenance>
84
+
85
+ ## Surprises / gotchas
86
+ - <surprise> — <provenance>
87
+ ```
88
+
89
+ When MemPalace is unreachable, write the stub and continue:
90
+
91
+ ```markdown
92
+ # Memory Recall (MemPalace)
93
+
94
+ _MemPalace unavailable at recall time — proceeding without recalled memory._
95
+ ```
96
+
97
+ ## Anti-Patterns
98
+
99
+ 1. DO NOT let any MemPalace error fail the step -- recall is `onError: skip`.
100
+ 2. DO NOT write to the palace from this skill -- recall is read-only; capture is a separate skill.
101
+ 3. DO NOT paste raw search output into the file -- distil to decisions/patterns/surprises with provenance.
102
+ 4. DO NOT skip the config gate.
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: gsd-context
3
- description: "codebase intelligence | map graphify docs learnings"
3
+ description: "codebase intel | map graphify docs learnings mempalace"
4
4
  argument-hint: ""
5
5
  allowed-tools:
6
6
  - Read
7
7
  - Skill
8
- requires: [map-codebase, graphify, docs-update, extract-learnings]
8
+ requires: [map-codebase, graphify, docs-update, extract-learnings, mempalace-recall, mempalace-capture]
9
9
  ---
10
10
 
11
11
  Route to the appropriate codebase-intelligence skill based on the user's intent.
@@ -19,5 +19,7 @@ Route to the appropriate codebase-intelligence skill based on the user's intent.
19
19
  | Generate a knowledge graph | gsd-graphify |
20
20
  | Update project documentation | gsd-docs-update |
21
21
  | Extract learnings from a completed phase | gsd-extract-learnings |
22
+ | Recall prior decisions and patterns before planning | gsd-mempalace-recall |
23
+ | File a phase artifact into MemPalace | gsd-mempalace-capture |
22
24
 
23
25
  Invoke the matched skill directly using the Skill tool.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-core",
3
- "version": "1.5.0-rc.1",
3
+ "version": "1.5.0-rc.3",
4
4
  "description": "GSD Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads gsd's operating context into every Gemini CLI session.",
5
5
  "contextFileName": "GEMINI.md"
6
6
  }
@@ -58,6 +58,11 @@
58
58
  * [--name <name>]
59
59
  * [--archive-phases] Move phase dirs to milestones/vX.Y-phases/
60
60
  *
61
+ * User Story Validation:
62
+ * user-story validate --story "..." Validate "As a / I want to / so that" format
63
+ * Returns JSON { valid, errors[], slots: {role,capability,outcome} | null }
64
+ * --pick valid Emit bare boolean (for workflow boolean checks)
65
+ *
61
66
  * Validation:
62
67
  * validate consistency Check phase numbering, disk/roadmap sync
63
68
  * validate health [--repair] Check .planning/ integrity, optionally repair
@@ -201,8 +206,6 @@ const milestone = require('./lib/milestone.cjs');
201
206
  const commands = require('./lib/commands.cjs');
202
207
  const init = require('./lib/init.cjs');
203
208
  const frontmatter = require('./lib/frontmatter.cjs');
204
- const profilePipeline = require('./lib/profile-pipeline.cjs');
205
- const profileOutput = require('./lib/profile-output.cjs');
206
209
  const workstream = require('./lib/workstream.cjs');
207
210
  const docs = require('./lib/docs.cjs');
208
211
  const learnings = require('./lib/learnings.cjs');
@@ -510,7 +513,7 @@ async function main() {
510
513
  'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
511
514
  'capability, classify-confidence, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
512
515
  'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
513
- 'task, template, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
516
+ 'task, template, user-story, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
514
517
  'Global flags:\n' +
515
518
  ' --raw Emit raw output without post-processing\n' +
516
519
  ' --pick <field> Extract a single field from JSON output (dot/bracket notation)\n' +
@@ -559,6 +562,7 @@ async function main() {
559
562
  'verify-summary', 'template', 'frontmatter', 'detect-custom-files',
560
563
  'worktree', 'prompt-budget',
561
564
  'research-store', 'research-plan', 'package-legitimacy', 'classify-confidence',
565
+ 'user-story', // pure string validation — no .planning/ access needed
562
566
  ]);
563
567
  if (!SKIP_ROOT_RESOLUTION.has(command)) {
564
568
  cwd = findProjectRoot(cwd);
@@ -1236,7 +1240,39 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1236
1240
  // loop render-hooks <point>
1237
1241
  const loopSubcommand = args[1];
1238
1242
  if (loopSubcommand === 'render-hooks') {
1239
- loopResolver.cmdLoopRenderHooks(cwd, args[2], raw, {});
1243
+ let loopConfigDir = null;
1244
+ const configDirEqArg = args.find(arg => arg.startsWith('--config-dir='));
1245
+ const configDirIdx = args.indexOf('--config-dir');
1246
+ if (configDirEqArg) {
1247
+ const value = configDirEqArg.slice('--config-dir='.length).trim();
1248
+ if (!value) error('Missing value for --config-dir', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1249
+ loopConfigDir = value;
1250
+ } else if (configDirIdx !== -1) {
1251
+ const value = args[configDirIdx + 1];
1252
+ if (!value || value.startsWith('--')) {
1253
+ error('Missing value for --config-dir', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1254
+ }
1255
+ loopConfigDir = value;
1256
+ }
1257
+ // --active-cap <capId>: parse and validate before delegating
1258
+ let loopActiveCap = undefined;
1259
+ const activeCapEqArg = args.find(arg => arg.startsWith('--active-cap='));
1260
+ const activeCapIdx = args.indexOf('--active-cap');
1261
+ if (activeCapEqArg) {
1262
+ const value = activeCapEqArg.slice('--active-cap='.length).trim();
1263
+ if (!value) error('Missing value for --active-cap (e.g. --active-cap tdd)', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1264
+ loopActiveCap = value;
1265
+ } else if (activeCapIdx !== -1) {
1266
+ const value = args[activeCapIdx + 1];
1267
+ if (!value || value.startsWith('--')) {
1268
+ error('Missing value for --active-cap (e.g. --active-cap tdd)', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
1269
+ }
1270
+ loopActiveCap = value;
1271
+ }
1272
+ loopResolver.cmdLoopRenderHooks(cwd, args[2], raw, {
1273
+ configDir: loopConfigDir ? path.resolve(loopConfigDir) : undefined,
1274
+ activeCap: loopActiveCap,
1275
+ });
1240
1276
  } else {
1241
1277
  error(
1242
1278
  `Unknown loop subcommand: ${loopSubcommand}. Available: render-hooks`,
@@ -1305,94 +1341,6 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1305
1341
  break;
1306
1342
  }
1307
1343
 
1308
- // ─── Profiling Pipeline ────────────────────────────────────────────────
1309
-
1310
- case 'scan-sessions': {
1311
- const pathIdx = args.indexOf('--path');
1312
- const sessionsPath = pathIdx !== -1 ? args[pathIdx + 1] : null;
1313
- const verboseFlag = args.includes('--verbose');
1314
- const jsonFlag = args.includes('--json');
1315
- await profilePipeline.cmdScanSessions(sessionsPath, { verbose: verboseFlag, json: jsonFlag }, raw);
1316
- break;
1317
- }
1318
-
1319
- case 'extract-messages': {
1320
- const sessionIdx = args.indexOf('--session');
1321
- const sessionId = sessionIdx !== -1 ? args[sessionIdx + 1] : null;
1322
- const limitIdx = args.indexOf('--limit');
1323
- const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : null;
1324
- const pathIdx = args.indexOf('--path');
1325
- const sessionsPath = pathIdx !== -1 ? args[pathIdx + 1] : null;
1326
- const projectArg = args[1];
1327
- if (!projectArg || projectArg.startsWith('--')) {
1328
- error('Usage: gsd-tools extract-messages <project> [--session <id>] [--limit N] [--path <dir>]\nRun scan-sessions first to see available projects.', ERROR_REASON.USAGE);
1329
- }
1330
- await profilePipeline.cmdExtractMessages(projectArg, { sessionId, limit }, raw, sessionsPath);
1331
- break;
1332
- }
1333
-
1334
- case 'profile-sample': {
1335
- const pathIdx = args.indexOf('--path');
1336
- const sessionsPath = pathIdx !== -1 ? args[pathIdx + 1] : null;
1337
- const limitIdx = args.indexOf('--limit');
1338
- const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 150;
1339
- const maxPerIdx = args.indexOf('--max-per-project');
1340
- const maxPerProject = maxPerIdx !== -1 ? parseInt(args[maxPerIdx + 1], 10) : null;
1341
- const maxCharsIdx = args.indexOf('--max-chars');
1342
- const maxChars = maxCharsIdx !== -1 ? parseInt(args[maxCharsIdx + 1], 10) : 500;
1343
- await profilePipeline.cmdProfileSample(sessionsPath, { limit, maxPerProject, maxChars }, raw);
1344
- break;
1345
- }
1346
-
1347
- // ─── Profile Output ──────────────────────────────────────────────────
1348
-
1349
- case 'write-profile': {
1350
- const inputIdx = args.indexOf('--input');
1351
- const inputPath = inputIdx !== -1 ? args[inputIdx + 1] : null;
1352
- if (!inputPath) error('--input <analysis-json-path> is required', ERROR_REASON.USAGE);
1353
- const outputIdx = args.indexOf('--output');
1354
- const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
1355
- profileOutput.cmdWriteProfile(cwd, { input: inputPath, output: outputPath }, raw);
1356
- break;
1357
- }
1358
-
1359
- case 'profile-questionnaire': {
1360
- const answersIdx = args.indexOf('--answers');
1361
- const answers = answersIdx !== -1 ? args[answersIdx + 1] : null;
1362
- profileOutput.cmdProfileQuestionnaire({ answers }, raw);
1363
- break;
1364
- }
1365
-
1366
- case 'generate-dev-preferences': {
1367
- const analysisIdx = args.indexOf('--analysis');
1368
- const analysisPath = analysisIdx !== -1 ? args[analysisIdx + 1] : null;
1369
- const outputIdx = args.indexOf('--output');
1370
- const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
1371
- const stackIdx = args.indexOf('--stack');
1372
- const stack = stackIdx !== -1 ? args[stackIdx + 1] : null;
1373
- profileOutput.cmdGenerateDevPreferences(cwd, { analysis: analysisPath, output: outputPath, stack }, raw);
1374
- break;
1375
- }
1376
-
1377
- case 'generate-claude-profile': {
1378
- const analysisIdx = args.indexOf('--analysis');
1379
- const analysisPath = analysisIdx !== -1 ? args[analysisIdx + 1] : null;
1380
- const outputIdx = args.indexOf('--output');
1381
- const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
1382
- const globalFlag = args.includes('--global');
1383
- profileOutput.cmdGenerateClaudeProfile(cwd, { analysis: analysisPath, output: outputPath, global: globalFlag }, raw);
1384
- break;
1385
- }
1386
-
1387
- case 'generate-claude-md': {
1388
- const outputIdx = args.indexOf('--output');
1389
- const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
1390
- const autoFlag = args.includes('--auto');
1391
- const forceFlag = args.includes('--force');
1392
- profileOutput.cmdGenerateClaudeMd(cwd, { output: outputPath, auto: autoFlag, force: forceFlag }, raw);
1393
- break;
1394
- }
1395
-
1396
1344
  case 'workstream': {
1397
1345
  const subcommand = args[1];
1398
1346
  if (subcommand === 'create') {
@@ -1984,6 +1932,76 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1984
1932
  break;
1985
1933
  }
1986
1934
 
1935
+ // ─── User Story Validation (bug #1145) ────────────────────────────────────
1936
+ //
1937
+ // Invocation shapes (from mvp-phase.md and verify-work.md):
1938
+ // gsd_run query user-story.validate --story "$USER_STORY"
1939
+ // gsd_run query user-story.validate --story "$PHASE_GOAL" --pick valid
1940
+ //
1941
+ // Returns JSON: { valid: boolean, errors: string[], slots: { role, capability, outcome } | null }
1942
+ // - valid: true only when the story fully matches the canonical format
1943
+ // - errors: per-slot diagnostic strings (empty on success)
1944
+ // - slots: extracted role/capability/outcome on success; null on failure
1945
+ //
1946
+ // Canonical format (user-story-template.md):
1947
+ // "As a [user role], I want to [capability], so that [outcome]."
1948
+ // Each slot must be non-empty and contain non-whitespace content.
1949
+ //
1950
+ // No .planning/ access needed — pure string validation.
1951
+ case 'user-story': {
1952
+ const subcommand = args[1];
1953
+ if (subcommand !== 'validate') {
1954
+ error(`Unknown user-story subcommand: ${subcommand || '(none)'}. Available: validate`, ERROR_REASON.SDK_UNKNOWN_COMMAND);
1955
+ break;
1956
+ }
1957
+
1958
+ const storyIdx = args.indexOf('--story');
1959
+ const story = (storyIdx !== -1 && args[storyIdx + 1] && !args[storyIdx + 1].startsWith('--'))
1960
+ ? args[storyIdx + 1]
1961
+ : '';
1962
+
1963
+ // Canonical extraction regex — requires non-whitespace content in each slot
1964
+ // (\S.*? ensures the slot isn't whitespace-only).
1965
+ // Named groups: role / capability / outcome.
1966
+ const USER_STORY_RE = /^As a (\S.*?), I want to (\S.*?), so that (\S.*?)\.$/;
1967
+
1968
+ const errors = [];
1969
+ const trimmed = story.trim();
1970
+ let slots = null;
1971
+
1972
+ if (!trimmed) {
1973
+ errors.push('Story is empty. Required format: "As a [role], I want to [capability], so that [outcome]."');
1974
+ } else {
1975
+ // Per-clause guards produce targeted, actionable error messages before
1976
+ // attempting the full regex. Guards are ordered: role → capability → outcome → period.
1977
+ if (!/^As a \S/i.test(trimmed)) {
1978
+ errors.push('Story must start with "As a [user role]," (role must be non-empty).');
1979
+ }
1980
+ if (!/, I want to \S/i.test(trimmed)) {
1981
+ errors.push('Story must include ", I want to [capability]," (capability must be non-empty).');
1982
+ }
1983
+ if (!/, so that \S/i.test(trimmed)) {
1984
+ errors.push('Story must include ", so that [outcome]." (outcome must be non-empty).');
1985
+ }
1986
+ if (!trimmed.endsWith('.')) {
1987
+ errors.push('Story must end with a period (.).');
1988
+ }
1989
+ // Full-regex check only when per-clause guards all passed — avoids
1990
+ // redundant "format mismatch" noise on top of specific error messages.
1991
+ if (errors.length === 0) {
1992
+ const m = USER_STORY_RE.exec(trimmed);
1993
+ if (!m) {
1994
+ errors.push('Story does not match the canonical format: "As a [role], I want to [capability], so that [outcome]."');
1995
+ } else {
1996
+ slots = { role: m[1], capability: m[2], outcome: m[3] };
1997
+ }
1998
+ }
1999
+ }
2000
+
2001
+ core.output({ valid: errors.length === 0, errors, slots }, raw);
2002
+ break;
2003
+ }
2004
+
1987
2005
  default: {
1988
2006
  // ADR-959: try capability-registry dispatch before emitting the unknown-command error.
1989
2007
  // An unmigrated command still hits its hardcoded `case` above — untouched.
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env sh
2
+ # gsd_run — standalone launcher so workflow bash blocks can invoke the GSD tools
3
+ # in a fresh shell. Claude Code (and similar runtimes) runs each fenced bash block
4
+ # in a separate process, so an inline gsd_run() function defined in an earlier
5
+ # block is undefined in later ones (issue #381). This executable is shipped beside
6
+ # gsd-tools.cjs in gsd-core/bin/ and exposed on PATH via the npm "bin" field and
7
+ # the per-file preamble's CLAUDE_ENV_FILE export, so later blocks resolve it.
8
+ # It resolves its own real location (following symlinks) and delegates to gsd-tools.cjs.
9
+ set -e
10
+ src="$0"
11
+ while [ -h "$src" ]; do
12
+ dir="$(cd -P "$(dirname "$src")" && pwd)"
13
+ src="$(readlink "$src")"
14
+ case "$src" in
15
+ /*) ;;
16
+ *) src="$dir/$src" ;;
17
+ esac
18
+ done
19
+ dir="$(cd -P "$(dirname "$src")" && pwd)"
20
+ exec node "$dir/gsd-tools.cjs" "$@"
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * Capability activation helpers.
4
+ *
5
+ * Shared by the Capability State Resolver and Loop Resolver so config-key
6
+ * activation uses one precedence chain and one prototype-pollution guard.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ const node_fs_1 = __importDefault(require("node:fs"));
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
14
+ const planningWorkspaceMod = require("./planning-workspace.cjs");
15
+ const { planningDir, planningRoot } = planningWorkspaceMod;
16
+ function _getNestedConfigValue(config, dotKey) {
17
+ const segments = dotKey.split('.');
18
+ let current = config;
19
+ for (const seg of segments) {
20
+ if (seg === '__proto__' || seg === 'constructor' || seg === 'prototype') {
21
+ return { found: false, value: undefined };
22
+ }
23
+ if (typeof current !== 'object' || current === null) {
24
+ return { found: false, value: undefined };
25
+ }
26
+ const cur = current;
27
+ if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
28
+ return { found: false, value: undefined };
29
+ }
30
+ current = cur[seg];
31
+ }
32
+ return { found: true, value: current };
33
+ }
34
+ const _warnedRawConfigPaths = new Set();
35
+ function _readRawConfigKey(filePath, dotKey) {
36
+ try {
37
+ const raw = node_fs_1.default.readFileSync(filePath, 'utf8');
38
+ let parsed;
39
+ try {
40
+ parsed = JSON.parse(raw);
41
+ }
42
+ catch {
43
+ if (!_warnedRawConfigPaths.has(filePath)) {
44
+ _warnedRawConfigPaths.add(filePath);
45
+ try {
46
+ process.stderr.write(`gsd-tools: warning: failed to parse ${filePath} as JSON — skipping for activation resolution\n`);
47
+ }
48
+ catch { /* stderr might be closed */ }
49
+ }
50
+ return { found: false, value: undefined };
51
+ }
52
+ return _getNestedConfigValue(parsed, dotKey);
53
+ }
54
+ catch {
55
+ return { found: false, value: undefined };
56
+ }
57
+ }
58
+ function _resolveActivationValue(dotKey, config, cwd, registry) {
59
+ const fromConfig = _getNestedConfigValue(config, dotKey);
60
+ if (fromConfig.found)
61
+ return Boolean(fromConfig.value);
62
+ if (cwd) {
63
+ const wsConfigPath = node_path_1.default.join(planningDir(cwd), 'config.json');
64
+ const rootConfigPath = node_path_1.default.join(planningRoot(cwd), 'config.json');
65
+ const fromWs = _readRawConfigKey(wsConfigPath, dotKey);
66
+ if (fromWs.found)
67
+ return Boolean(fromWs.value);
68
+ if (wsConfigPath !== rootConfigPath) {
69
+ const fromRoot = _readRawConfigKey(rootConfigPath, dotKey);
70
+ if (fromRoot.found)
71
+ return Boolean(fromRoot.value);
72
+ }
73
+ }
74
+ const schemaEntry = registry['configSchema']?.[dotKey];
75
+ if (schemaEntry && typeof schemaEntry === 'object' && schemaEntry !== null) {
76
+ const def = schemaEntry['default'];
77
+ if (def !== undefined)
78
+ return Boolean(def);
79
+ }
80
+ return false;
81
+ }
82
+ module.exports = {
83
+ _getNestedConfigValue,
84
+ _readRawConfigKey,
85
+ _resolveActivationValue,
86
+ };