@opengsd/gsd-core 1.3.1 → 1.4.0-rc.2
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.
- package/.claude-plugin/plugin.json +23 -0
- package/GEMINI.md +53 -0
- package/agents/gsd-advisor-researcher.md +1 -20
- package/agents/gsd-ai-researcher.md +2 -21
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-domain-researcher.md +2 -21
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-eval-planner.md +1 -1
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-framework-selector.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-pattern-mapper.md +1 -1
- package/agents/gsd-phase-researcher.md +92 -166
- package/agents/gsd-planner.md +9 -36
- package/agents/gsd-project-researcher.md +62 -141
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.md +3 -22
- package/agents/gsd-user-profiler.md +1 -1
- package/agents/gsd-verifier.md +8 -2
- package/bin/install.js +1977 -339
- package/commands/gsd/autonomous.md +2 -0
- package/commands/gsd/execute-phase.md +2 -0
- package/commands/gsd/graphify.md +11 -6
- package/commands/gsd/import.md +6 -2
- package/commands/gsd/plan-phase.md +4 -2
- package/commands/gsd/progress.md +1 -0
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/update.md +3 -2
- package/gemini-extension.json +6 -0
- package/gsd-core/bin/check-latest-version.cjs +61 -6
- package/gsd-core/bin/gsd-tools.cjs +238 -32
- package/gsd-core/bin/lib/check-command-router.cjs +1 -0
- package/gsd-core/bin/lib/cli-exit.cjs +42 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +1 -1
- package/gsd-core/bin/lib/commands.cjs +5 -4
- package/gsd-core/bin/lib/config.cjs +28 -4
- package/gsd-core/bin/lib/core.cjs +72 -28
- package/gsd-core/bin/lib/graphify.cjs +2 -2
- package/gsd-core/bin/lib/init-command-router.cjs +2 -2
- package/gsd-core/bin/lib/init.cjs +19 -3
- package/gsd-core/bin/lib/install-profiles.cjs +58 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +1 -1
- package/gsd-core/bin/lib/intel.cjs +3 -20
- package/gsd-core/bin/lib/package-legitimacy.cjs +368 -0
- package/gsd-core/bin/lib/phase.cjs +3 -3
- package/gsd-core/bin/lib/research-provider.cjs +137 -0
- package/gsd-core/bin/lib/research-store.cjs +167 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +4 -19
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +67 -9
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +56 -0
- package/gsd-core/bin/lib/runtime-homes.cjs +32 -11
- package/gsd-core/bin/lib/security.cjs +73 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +9 -0
- package/gsd-core/bin/lib/surface.cjs +54 -11
- package/gsd-core/bin/lib/validate.cjs +2 -2
- package/gsd-core/bin/lib/verification-command-router.cjs +31 -0
- package/gsd-core/bin/lib/verification.cjs +193 -0
- package/gsd-core/bin/lib/verify.cjs +2 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -1
- package/gsd-core/bin/lib/worktree-base-ref.cjs +325 -0
- package/gsd-core/bin/lib/worktree-safety.cjs +31 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +8 -11
- package/gsd-core/references/planner-load-graph-context.md +36 -0
- package/gsd-core/references/planning-config.md +3 -1
- package/gsd-core/references/research-documentation-lookup.md +29 -0
- package/gsd-core/references/research-philosophy.md +29 -0
- package/gsd-core/references/research-verification-protocol.md +27 -0
- package/gsd-core/workflows/execute-phase.md +19 -8
- package/gsd-core/workflows/help/modes/full.md +4 -3
- package/gsd-core/workflows/ingest-docs.md +3 -2
- package/gsd-core/workflows/plan-phase.md +14 -10
- package/gsd-core/workflows/plan-review-convergence.md +3 -3
- package/gsd-core/workflows/review.md +24 -7
- package/gsd-core/workflows/ship.md +5 -8
- package/gsd-core/workflows/spec-phase.md +2 -1
- package/gsd-core/workflows/update.md +34 -6
- package/hooks/dist/gsd-config-reload.js +133 -0
- package/hooks/dist/gsd-context-monitor.js +1 -1
- package/hooks/dist/gsd-cursor-post-tool.js +75 -0
- package/hooks/dist/gsd-cursor-session-start.js +52 -0
- package/hooks/dist/gsd-workflow-guard.js +1 -0
- package/hooks/dist/gsd-worktree-path-guard.js +1 -1
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-config-reload.js +133 -0
- package/hooks/gsd-context-monitor.js +1 -1
- package/hooks/gsd-cursor-post-tool.js +75 -0
- package/hooks/gsd-cursor-session-start.js +52 -0
- package/hooks/gsd-workflow-guard.js +1 -0
- package/hooks/gsd-worktree-path-guard.js +1 -1
- package/hooks/hooks.json +69 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +8 -1
- package/scripts/affected-tests-lib.cjs +3 -2
- package/scripts/build-hooks.js +7 -0
- package/scripts/changeset/cli.cjs +226 -28
- package/scripts/changeset/lint.cjs +5 -4
- package/scripts/changeset/new.cjs +4 -4
- package/scripts/check-alias-drift.cjs +77 -71
- package/scripts/check-env.cjs +185 -179
- package/scripts/check-npm-integrity.cjs +115 -109
- package/scripts/ci-guard-runner.cjs +11 -5
- package/scripts/ci-prepare-test-scope.cjs +27 -22
- package/scripts/ci-rebase-check.cjs +46 -45
- package/scripts/ci-test-scope.cjs +126 -22
- package/scripts/diff-touches-shipped-paths.cjs +52 -44
- package/scripts/gen-inventory-manifest.cjs +38 -32
- package/scripts/gen-research-agents.cjs +276 -0
- package/scripts/issue-dedupe.cjs +278 -0
- package/scripts/lib/cli-exit.cjs +56 -0
- package/scripts/lint-command-contract.cjs +28 -22
- package/scripts/lint-descriptions.cjs +32 -28
- package/scripts/lint-docs-required.cjs +4 -4
- package/scripts/lint-legacy-dir-name.cjs +56 -52
- package/scripts/lint-pr-check-project-dir.cjs +3 -1
- package/scripts/lint-shell-command-projection-drift.cjs +27 -22
- package/scripts/lint-skill-deps.cjs +31 -26
- package/scripts/lint-test-file-count.allowlist.json +2 -0
- package/scripts/lint-test-file-count.cjs +5 -4
- package/scripts/mutation-matrix.cjs +6 -3
- package/scripts/prompt-injection-scan.sh +1 -1
- package/scripts/release-notes/discord-release-summary.cjs +373 -0
- package/scripts/release-notes/format-github-release-notes.cjs +8 -3
- package/scripts/release-tarball-smoke.cjs +6 -4
- package/scripts/research-profiles.cjs +149 -0
- package/scripts/run-affected-tests.cjs +2 -1
- package/scripts/run-cross-platform-tests.cjs +11 -7
- package/scripts/run-tests.cjs +8 -7
- package/scripts/strip-prose-atrefs.cjs +1 -1
- package/scripts/sync-manifest-versions.cjs +119 -0
- package/scripts/sync-runtime-launcher.cjs +0 -3
- package/scripts/verify-npm-publish.cjs +14 -26
|
@@ -86,7 +86,7 @@ Create detailed execution plan for a specific phase.
|
|
|
86
86
|
|
|
87
87
|
- `--skip-research` — bypass the research subagent
|
|
88
88
|
- `--research-phase <N>` — research-only mode. Spawns the research agent for phase `<N>`, writes `RESEARCH.md`, then exits before the planner runs. Useful for cross-phase research, doc review before committing to a planning approach, and correction-without-replanning loops. Replaces the deleted `gsd-research-phase` standalone command (#3042).
|
|
89
|
-
- Modifiers: `--research` forces refresh (re-spawn researcher
|
|
89
|
+
- Modifiers: `--research` forces refresh (re-spawn researcher). `--view` prints existing `RESEARCH.md` to stdout without spawning. With neither, auto-uses an existing `RESEARCH.md` (one-line notice, then clean exit).
|
|
90
90
|
- `--gaps` — focus only on closing gaps from a prior plan-check
|
|
91
91
|
- `--skip-verify` — skip the post-plan verifier loop
|
|
92
92
|
- `--ingest <path-or-glob>` — pre-ingest external ADRs/PRDs/SPECs before planning (see *PRD Express Path* below)
|
|
@@ -100,7 +100,7 @@ Create detailed execution plan for a specific phase.
|
|
|
100
100
|
- Multiple plans per phase supported (XX-01, XX-02, etc.)
|
|
101
101
|
|
|
102
102
|
Usage: `/gsd:plan-phase 1`
|
|
103
|
-
Usage: `/gsd:plan-phase --research-phase 2` — research only on phase 2 (
|
|
103
|
+
Usage: `/gsd:plan-phase --research-phase 2` — research only on phase 2 (auto-uses existing `RESEARCH.md`, no prompt)
|
|
104
104
|
Usage: `/gsd:plan-phase --research-phase 2 --view` — print existing `RESEARCH.md`, no spawn
|
|
105
105
|
Usage: `/gsd:plan-phase --research-phase 2 --research` — force-refresh, no prompt
|
|
106
106
|
Result: Creates `.planning/phases/01-foundation/01-01-PLAN.md`
|
|
@@ -550,11 +550,12 @@ Usage: `/gsd:help --full`
|
|
|
550
550
|
Usage: `/gsd:help debug`
|
|
551
551
|
Usage: `/gsd:help --brief debug`
|
|
552
552
|
|
|
553
|
-
**`/gsd:update [--sync] [--reapply]`**
|
|
553
|
+
**`/gsd:update [--sync] [--reapply] [--next | --rc]`**
|
|
554
554
|
Update GSD to latest version with changelog preview.
|
|
555
555
|
|
|
556
556
|
- `--sync` — sync managed GSD skills across runtime roots (replaces the former `gsd-sync-skills`)
|
|
557
557
|
- `--reapply` — reapply local modifications after an update (replaces the former `gsd-reapply-patches`)
|
|
558
|
+
- `--next` (alias `--rc`) — install/refresh from the `@next` RC dist-tag instead of `@latest` (ADR #660); omit for the stable channel
|
|
558
559
|
|
|
559
560
|
- Shows installed vs latest version comparison
|
|
560
561
|
- Displays changelog entries for versions you've missed
|
|
@@ -52,7 +52,8 @@ If `PATH_NOT_FOUND` or `MANIFEST_NOT_FOUND`: display error and exit.
|
|
|
52
52
|
Run the init query:
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
|
|
55
|
+
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi
|
|
56
|
+
INIT=$(gsd_run init ingest-docs)
|
|
56
57
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
57
58
|
```
|
|
58
59
|
|
|
@@ -295,7 +296,7 @@ Preview the merge diff to the user and gate via approve-revise-abort before writ
|
|
|
295
296
|
Commit the ingest results:
|
|
296
297
|
|
|
297
298
|
```bash
|
|
298
|
-
|
|
299
|
+
gsd_run commit \
|
|
299
300
|
"docs: ingest {N} docs from {SCAN_PATH} (#2387)" --files \
|
|
300
301
|
.planning/PROJECT.md \
|
|
301
302
|
.planning/REQUIREMENTS.md \
|
|
@@ -32,7 +32,8 @@ Load all context in one call (paths only to minimize orchestrator context):
|
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi
|
|
35
|
-
|
|
35
|
+
GRAN_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--granularity[[:space:]]+([^[:space:]-][^[:space:]]*) ]]; then GRAN_PARAM="--granularity ${BASH_REMATCH[2]}"; fi
|
|
36
|
+
INIT=$(gsd_run query init.plan-phase "$PHASE" $GRAN_PARAM)
|
|
36
37
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
37
38
|
AGENT_SKILLS_RESEARCHER=$(gsd_run query agent-skills gsd-phase-researcher)
|
|
38
39
|
AGENT_SKILLS_PLANNER=$(gsd_run query agent-skills gsd-planner)
|
|
@@ -46,7 +47,7 @@ When `TDD_MODE` is `true`, the planner agent is instructed to apply `type: tdd`
|
|
|
46
47
|
|
|
47
48
|
When `CONTEXT_WINDOW >= 500000`, the planner prompt includes the 3 most recent prior phase CONTEXT.md and SUMMARY.md files PLUS any phases explicitly listed in the current phase's `Depends on:` field in ROADMAP.md. Explicit dependencies always load regardless of recency (e.g., Phase 7 declaring `Depends on: Phase 2` always sees Phase 2's context). Bounded recency keeps the planner's context budget focused on recent work.
|
|
48
49
|
|
|
49
|
-
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `commit_docs`, `text_mode`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_reviews`, `has_plans`, `plan_count`, `phase_status` (#3569), `planning_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`.
|
|
50
|
+
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `commit_docs`, `text_mode`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_reviews`, `has_plans`, `plan_count`, `phase_status` (#3569), `planning_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`, `granularity`.
|
|
50
51
|
|
|
51
52
|
**If `response_language` is set:** Include `response_language: {value}` in all spawned subagent prompts so any user-facing output stays in the configured language.
|
|
52
53
|
|
|
@@ -99,7 +100,7 @@ The gate fires only on `Complete`. `Executed` and `Needs Review` are not gated
|
|
|
99
100
|
|
|
100
101
|
## 2. Parse and Normalize Arguments
|
|
101
102
|
|
|
102
|
-
Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--research-phase <N>`, `--gaps`, `--skip-verify`, `--skip-ui`, `--prd <filepath>`, `--ingest <path-or-glob>`, `--ingest-format <auto|nygard|madr|narrative>`, `--reviews`, `--text`, `--bounce`, `--skip-bounce`, `--chunked`, `--mvp`, `--tdd`, `--force` (override closed-phase gate, see §1.5)).
|
|
103
|
+
Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--research-phase <N>`, `--gaps`, `--skip-verify`, `--skip-ui`, `--prd <filepath>`, `--ingest <path-or-glob>`, `--ingest-format <auto|nygard|madr|narrative>`, `--reviews`, `--text`, `--bounce`, `--skip-bounce`, `--chunked`, `--mvp`, `--tdd`, `--granularity <coarse|standard|fine>`, `--force` (override closed-phase gate, see §1.5)).
|
|
103
104
|
|
|
104
105
|
**`--research-phase <N>` — research-only mode (#3042 + #3044).** When this flag is present, parse `<N>` as the phase number (overrides any positional phase argument), set `RESEARCH_ONLY=true`, and treat the rest of this workflow as a research-dispatch only — the planner spawn (step 8), plan-checker, verification, gaps, bounce, and post-planning-gaps blocks all skip on `RESEARCH_ONLY`. Use this for cross-phase research, doc review before committing to a planning approach, and correction-without-replanning loops. Replaces the deleted `/gsd-research-phase` command.
|
|
105
106
|
|
|
@@ -120,6 +121,8 @@ if $RESEARCH_ONLY && [[ "$ARGUMENTS" =~ (^|[[:space:]])--view([[:space:]]|$) ]];
|
|
|
120
121
|
fi
|
|
121
122
|
```
|
|
122
123
|
|
|
124
|
+
**`--granularity <coarse|standard|fine>` — CLI override (#703).** When present, this value is the resolved granularity passed to the planner — it wins over any per-phase `granularities.<type>` config, top-level `granularity` config, or project defaults. The init JSON always includes a `granularity` field reflecting the resolved value; read it from there. Invalid values (anything other than `coarse`, `standard`, `fine`) cause an error at the CLI boundary.
|
|
125
|
+
|
|
123
126
|
Set `TEXT_MODE=true` if `--text` is present in $ARGUMENTS OR `text_mode` from init JSON is `true`. When `TEXT_MODE` is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for Claude Code remote sessions (`/rc` mode) where TUI menus don't work through the Claude App.
|
|
124
127
|
|
|
125
128
|
**MVP_MODE resolution.** Resolve `MVP_MODE` once via the centralized `phase.mvp-mode` query verb. Precedence (first hit wins): CLI flag → ROADMAP.md `**Mode:** mvp` → `workflow.mvp_mode` config → false. The verb is the single source of truth — do not re-implement the chain.
|
|
@@ -143,7 +146,7 @@ fi
|
|
|
143
146
|
```
|
|
144
147
|
|
|
145
148
|
When `WALKING_SKELETON=true`:
|
|
146
|
-
- Planner is instructed to produce `SKELETON.md` in the phase directory alongside `PLAN.md`. The template lives at
|
|
149
|
+
- Planner is instructed to produce `SKELETON.md` in the phase directory alongside `PLAN.md`. The template lives at `~/.claude/gsd-core/references/skeleton-template.md` — the planner reads it when producing SKELETON.md (lazy; not loaded on non-skeleton runs).
|
|
147
150
|
- The plan must scaffold project + routing + one real DB read/write + one real UI interaction + dev deployment — the thinnest possible end-to-end working slice.
|
|
148
151
|
|
|
149
152
|
**Interaction with `--prd <filepath>`.** `--mvp` and `--prd` compose. The PRD express path (Step 3.5) creates `CONTEXT.md` from the PRD file and continues to research; the Walking Skeleton gate fires independently from the conditions above. When both are active on Phase 1 of a new project, the planner receives `WALKING_SKELETON=true` and PRD-derived context simultaneously — the PRD informs *what the skeleton should prove*. No precedence is needed; the two signals are orthogonal. See [`references/mvp-concepts.md`](../references/mvp-concepts.md) for the broader interaction map.
|
|
@@ -417,15 +420,15 @@ Pass `ai_spec_path` and `framework_line` to planner in step 7 so it can referenc
|
|
|
417
420
|
|
|
418
421
|
**Skip if:** `--gaps` flag or `--skip-research` flag or `--reviews` flag.
|
|
419
422
|
|
|
420
|
-
### 5.0. Research-Only Modifiers (`--view`, `--research
|
|
423
|
+
### 5.0. Research-Only Modifiers (`--view`, `--research`)
|
|
421
424
|
|
|
422
425
|
**Skip if:** `RESEARCH_ONLY` is `false`.
|
|
423
426
|
|
|
424
427
|
Three branches in research-only mode (`--research-phase <N>`):
|
|
425
428
|
|
|
426
|
-
1. **`--view
|
|
429
|
+
1. **`--view`**: print `RESEARCH.md` to stdout, no spawn, exit. If `RESEARCH.md` is missing, error with: `--view requires an existing RESEARCH.md; drop --view to spawn the researcher.`
|
|
427
430
|
2. **`--research`** (force-refresh): re-spawn researcher unconditionally — fall through to "Spawn gsd-phase-researcher" below.
|
|
428
|
-
3. **Neither flag AND `has_research=true`:**
|
|
431
|
+
3. **Neither flag AND `has_research=true`:** auto-use the existing research and exit cleanly — do not prompt, do not re-spawn. Emit `RESEARCH.md already exists for Phase ${PHASE}, using it. To force-refresh, re-invoke with --research; to print, re-invoke with --view. Path: ${research_path}` then exit. The explicit-flag escape hatches cover any deviation; this matches §5.1's promptless auto-use of existing research, removing the §5.0/§5.1 inconsistency (#159).
|
|
429
432
|
|
|
430
433
|
```bash
|
|
431
434
|
if [[ "$VIEW_ONLY" == "true" ]]; then
|
|
@@ -933,12 +936,13 @@ Each TDD plan gets one feature with RED/GREEN/REFACTOR gate sequence.
|
|
|
933
936
|
</tdd_mode_active>
|
|
934
937
|
` : ''}
|
|
935
938
|
|
|
936
|
-
**MVP_MODE:** ${MVP_MODE} (when true, follow vertical-slice rules from
|
|
937
|
-
**WALKING_SKELETON:** ${WALKING_SKELETON} (when true, the first deliverable must be a Walking Skeleton — produce SKELETON.md alongside PLAN.md.)
|
|
939
|
+
**MVP_MODE:** ${MVP_MODE} (when true, follow vertical-slice rules from `~/.claude/gsd-core/references/planner-mvp-mode.md`; when false, ignore MVP guidance entirely.)
|
|
940
|
+
**WALKING_SKELETON:** ${WALKING_SKELETON} (when true, the first deliverable must be a Walking Skeleton — Read the template at `~/.claude/gsd-core/references/skeleton-template.md` and produce SKELETON.md alongside PLAN.md.)
|
|
941
|
+
**Granularity:** {granularity}
|
|
938
942
|
|
|
939
943
|
${MVP_MODE === 'true' ? `
|
|
940
944
|
<mvp_mode_active>
|
|
941
|
-
**MVP Mode is ENABLED.**
|
|
945
|
+
**MVP Mode is ENABLED.** Read `~/.claude/gsd-core/references/planner-mvp-mode.md` now and follow its vertical-slice planning rules. Each plan must deliver a complete vertical slice — thin end-to-end functionality rather than horizontal layers.
|
|
942
946
|
</mvp_mode_active>
|
|
943
947
|
` : ''}
|
|
944
948
|
</planning_context>
|
|
@@ -63,7 +63,7 @@ Then re-run: /gsd:plan-review-convergence {PHASE}
|
|
|
63
63
|
## 2. Initialize
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
|
-
INIT=$(
|
|
66
|
+
INIT=$(gsd_run init plan-phase "$PHASE")
|
|
67
67
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
68
68
|
```
|
|
69
69
|
|
|
@@ -76,7 +76,7 @@ Set `TEXT_MODE=true` if `--text` is present in $ARGUMENTS OR `text_mode` from in
|
|
|
76
76
|
## 3. Validate Phase + Pre-flight Gate
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
|
-
PHASE_INFO=$(
|
|
79
|
+
PHASE_INFO=$(gsd_run roadmap get-phase "${PHASE}")
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
**If `found` is false:** Error with available phases. Exit.
|
|
@@ -230,7 +230,7 @@ fi
|
|
|
230
230
|
**If HIGH_COUNT == 0 (converged):**
|
|
231
231
|
|
|
232
232
|
```bash
|
|
233
|
-
|
|
233
|
+
gsd_run state planned-phase --phase "${PHASE}" --name "${phase_name}" --plans "${PLAN_COUNT}"
|
|
234
234
|
```
|
|
235
235
|
|
|
236
236
|
Display:
|
|
@@ -22,7 +22,7 @@ command -v codex >/dev/null 2>&1 && echo "codex:available" || echo "codex:missin
|
|
|
22
22
|
command -v coderabbit >/dev/null 2>&1 && echo "coderabbit:available" || echo "coderabbit:missing"
|
|
23
23
|
command -v opencode >/dev/null 2>&1 && echo "opencode:available" || echo "opencode:missing"
|
|
24
24
|
command -v qwen >/dev/null 2>&1 && echo "qwen:available" || echo "qwen:missing"
|
|
25
|
-
command -v cursor >/dev/null 2>&1 && echo "cursor:available" || echo "cursor:missing"
|
|
25
|
+
command -v cursor-agent >/dev/null 2>&1 && echo "cursor:available" || echo "cursor:missing"
|
|
26
26
|
command -v agy >/dev/null 2>&1 && echo "antigravity:available" || echo "antigravity:missing"
|
|
27
27
|
|
|
28
28
|
# Check local model servers (OpenAI-compatible HTTP API — no CLI binary required)
|
|
@@ -248,9 +248,9 @@ fi
|
|
|
248
248
|
**Codex:**
|
|
249
249
|
```bash
|
|
250
250
|
if [ -n "$CODEX_MODEL" ] && [ "$CODEX_MODEL" != "null" ]; then
|
|
251
|
-
cat /tmp/gsd-review-prompt-{phase}.md | codex exec --model "$CODEX_MODEL" --skip-git-repo-check - 2>/dev/null > /tmp/gsd-review-codex-{phase}.md
|
|
251
|
+
cat /tmp/gsd-review-prompt-{phase}.md | codex exec --ephemeral --dangerously-bypass-hook-trust --model "$CODEX_MODEL" --skip-git-repo-check - 2>/dev/null > /tmp/gsd-review-codex-{phase}.md
|
|
252
252
|
else
|
|
253
|
-
cat /tmp/gsd-review-prompt-{phase}.md | codex exec --skip-git-repo-check - 2>/dev/null > /tmp/gsd-review-codex-{phase}.md
|
|
253
|
+
cat /tmp/gsd-review-prompt-{phase}.md | codex exec --ephemeral --dangerously-bypass-hook-trust --skip-git-repo-check - 2>/dev/null > /tmp/gsd-review-codex-{phase}.md
|
|
254
254
|
fi
|
|
255
255
|
```
|
|
256
256
|
|
|
@@ -284,9 +284,15 @@ fi
|
|
|
284
284
|
|
|
285
285
|
**Cursor:**
|
|
286
286
|
```bash
|
|
287
|
-
|
|
287
|
+
# cursor-agent is a SEPARATE binary from the `cursor` IDE launcher; print mode (-p) takes the
|
|
288
|
+
# prompt as an ARGUMENT, not stdin. A full review prompt can exceed the OS argument limit, so
|
|
289
|
+
# reference the prompt file by path rather than inlining it. Capture stderr so a failure is
|
|
290
|
+
# diagnosable instead of a silent empty result.
|
|
291
|
+
CURSOR_PROMPT_ARG="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. Output only the resulting markdown review. Do not edit any files."
|
|
292
|
+
cursor-agent -p --mode ask --trust --output-format text "$CURSOR_PROMPT_ARG" 2>/tmp/gsd-review-cursor-{phase}.err > /tmp/gsd-review-cursor-{phase}.md
|
|
288
293
|
if [ ! -s /tmp/gsd-review-cursor-{phase}.md ]; then
|
|
289
|
-
echo "Cursor review failed or returned empty output." > /tmp/gsd-review-cursor-{phase}.md
|
|
294
|
+
echo "Cursor review failed or returned empty output. stderr:" > /tmp/gsd-review-cursor-{phase}.md
|
|
295
|
+
cat /tmp/gsd-review-cursor-{phase}.err >> /tmp/gsd-review-cursor-{phase}.md
|
|
290
296
|
fi
|
|
291
297
|
```
|
|
292
298
|
|
|
@@ -350,8 +356,19 @@ if [ -f "$_AGY_CACHE" ]; then
|
|
|
350
356
|
fi
|
|
351
357
|
fi
|
|
352
358
|
|
|
353
|
-
# Step 1 — primary invocation: stdout works on macOS, Linux, and WSL
|
|
354
|
-
agy -
|
|
359
|
+
# Step 1 — primary invocation: stdout works on macOS, Linux, and WSL.
|
|
360
|
+
# Bound the run with agy's OWN `--print-timeout` (issue #687). On a large,
|
|
361
|
+
# file-path-rich prompt agy's agentic Cascade can loop on its code_search/grep
|
|
362
|
+
# steps and never converge; `--print-timeout` is agy's native cap for print mode
|
|
363
|
+
# (defaults to 5m — see maintainer note above), so we pass it explicitly to let a
|
|
364
|
+
# stalled run self-terminate through the tool's own mechanism. A non-zero exit
|
|
365
|
+
# (timeout or crash) discards any partial output so the Step 2 transcript fallback
|
|
366
|
+
# / Step 3 stub take over.
|
|
367
|
+
agy --print-timeout 300s -p "$(cat /tmp/gsd-review-prompt-{phase}.md)" 2>/dev/null > /tmp/gsd-review-antigravity-{phase}.md
|
|
368
|
+
_AGY_RC=$?
|
|
369
|
+
if [ "$_AGY_RC" -ne 0 ]; then
|
|
370
|
+
: > /tmp/gsd-review-antigravity-{phase}.md
|
|
371
|
+
fi
|
|
355
372
|
|
|
356
373
|
# Step 2 — transcript fallback: catches Windows agy -p stdout bug (and any future stdout-silent edge cases).
|
|
357
374
|
# Reads only lines appended AFTER the pre-flight watermark. If agy failed before writing a new response,
|
|
@@ -41,15 +41,12 @@ Verify the work is ready to ship:
|
|
|
41
41
|
|
|
42
42
|
1. **Verification passed?**
|
|
43
43
|
```bash
|
|
44
|
-
|
|
45
|
-
STATUS=$(
|
|
44
|
+
VERIFICATION=$(gsd_run query verification.status "${PHASE_DIR}" 2>/dev/null)
|
|
45
|
+
STATUS=$(printf '%s' "$VERIFICATION" | jq -r '.status' 2>/dev/null || echo "")
|
|
46
|
+
NEXT_ACTION=$(printf '%s' "$VERIFICATION" | jq -r '.next_action' 2>/dev/null || echo "")
|
|
47
|
+
NEXT_COMMAND=$(printf '%s' "$VERIFICATION" | jq -r '.next_command' 2>/dev/null || echo "")
|
|
46
48
|
```
|
|
47
|
-
|
|
48
|
-
- `passed` → verification complete; continue to the next preflight check.
|
|
49
|
-
- `gaps_found` → run `/gsd:plan-phase ${PHASE_NUMBER} --gaps` to plan the fixes, then re-run `/gsd:execute-phase` before shipping.
|
|
50
|
-
- `human_needed` → complete the manual tests in `${PHASE_DIR}/*-UAT.md`, then re-run the verify step until status is `passed`.
|
|
51
|
-
- empty (no `*-VERIFICATION.md`) → the verify step never completed; re-run `/gsd:execute-phase`.
|
|
52
|
-
- any other value → unexpected status `${STATUS}`; re-run `/gsd:execute-phase` verification.
|
|
49
|
+
Only `passed` may ship. If `$STATUS` is `passed`, verification is complete — continue to the next preflight check. Any other value (including `gaps_found`, `human_needed`, `missing`, and `unknown`) blocks with `PHASE_VERIFICATION_INCOMPLETE`: present `$NEXT_ACTION` to the user and, when `$NEXT_COMMAND` is non-empty, show it as the command to run next. The query already handles missing files and unexpected values, so no per-status arm is needed.
|
|
53
50
|
|
|
54
51
|
2. **Clean working tree?**
|
|
55
52
|
```bash
|
|
@@ -56,7 +56,8 @@ Rotate through these perspectives — each naturally surfaces different blindspo
|
|
|
56
56
|
## Step 1: Initialize
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
|
|
59
|
+
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi
|
|
60
|
+
INIT=$(gsd_run init phase-op "${PHASE}")
|
|
60
61
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
61
62
|
```
|
|
62
63
|
|
|
@@ -75,6 +75,25 @@ If multiple runtime installs are detected and the invoking runtime cannot be det
|
|
|
75
75
|
**If VERSION file missing (version resolves to `0.0.0`):** report the installed version as Unknown and proceed to install (treated as `0.0.0` for comparison).
|
|
76
76
|
</step>
|
|
77
77
|
|
|
78
|
+
<step name="parse_update_channel">
|
|
79
|
+
Determine the release channel from `$ARGUMENTS`. This selects which npm dist-tag the entire update flow targets — `latest` (stable) by default, or `next` (the RC channel established by ADR #660) when the user opts in with `--next`/`--rc`:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
case " $ARGUMENTS " in
|
|
83
|
+
*" --next "*|*" --rc "*)
|
|
84
|
+
TAG="next"
|
|
85
|
+
CHANNEL_LABEL="next (RC)"
|
|
86
|
+
;;
|
|
87
|
+
*)
|
|
88
|
+
TAG="latest"
|
|
89
|
+
CHANNEL_LABEL="latest (stable)"
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`TAG` is restricted to `latest`/`next` by `check-latest-version.cjs` (it rejects any other value with exit 2), so no arbitrary dist-tag can leak through. Omitting `--next`/`--rc` reproduces the prior behavior exactly: `TAG=latest`.
|
|
95
|
+
</step>
|
|
96
|
+
|
|
78
97
|
<step name="check_latest_version">
|
|
79
98
|
Check npm for latest version via the deterministic script. **Do NOT run `npm view` or `npm search` directly** — the package name must come from the script, not from a free choice at execution time. (#2992: LLM-driven prescriptions of npm package names produced wrong-package queries; moving the package name into a script constant closes that gap.)
|
|
80
99
|
|
|
@@ -91,7 +110,7 @@ if [ -z "$GSD_DIR" ]; then
|
|
|
91
110
|
LATEST_VERSION=""
|
|
92
111
|
LATEST_REASON="no_install_detected"
|
|
93
112
|
else
|
|
94
|
-
LATEST_RESULT="$(node "$GSD_DIR/gsd-core/bin/check-latest-version.cjs" --json 2>/dev/null)"
|
|
113
|
+
LATEST_RESULT="$(node "$GSD_DIR/gsd-core/bin/check-latest-version.cjs" --json --tag "$TAG" 2>/dev/null)"
|
|
95
114
|
LATEST_STATUS=$?
|
|
96
115
|
# #2993 CR: when node is missing or the script doesn't exist, LATEST_RESULT
|
|
97
116
|
# is empty and piping it to `jq` produces a parse error on stderr while
|
|
@@ -114,7 +133,7 @@ fi
|
|
|
114
133
|
```text
|
|
115
134
|
Couldn't check for updates (reason: {LATEST_REASON}, exit: {LATEST_STATUS}).
|
|
116
135
|
|
|
117
|
-
To update manually: `npx -y --package=@opengsd/gsd-core@
|
|
136
|
+
To update manually: `npx -y --package=@opengsd/gsd-core@{TAG} -- gsd-core --global`
|
|
118
137
|
```
|
|
119
138
|
|
|
120
139
|
Exit.
|
|
@@ -123,6 +142,14 @@ Exit.
|
|
|
123
142
|
<step name="compare_versions">
|
|
124
143
|
Compare installed vs latest:
|
|
125
144
|
|
|
145
|
+
**Only when `TAG=next`** (the user passed `--next`/`--rc`), prepend a channel banner so they know they are leaving the stable line — add this line immediately after the `**Latest:**` line in whichever output block renders:
|
|
146
|
+
|
|
147
|
+
**Channel:** {CHANNEL_LABEL}
|
|
148
|
+
|
|
149
|
+
On the default stable channel (`TAG=latest`), do NOT add a channel line — the output must match the prior stable behavior exactly.
|
|
150
|
+
|
|
151
|
+
When `TAG=next`, the "latest" value is the release candidate published under `@next` (e.g. `1.4.0-rc.1`). Apply standard semver precedence for prereleases (`1.4.0-rc.1` is newer than `1.3.1` but older than the final `1.4.0`). Do NOT treat an `-rc.N` suffix as a dev install or as "behind" — offer it as an available update.
|
|
152
|
+
|
|
126
153
|
**If installed == latest:**
|
|
127
154
|
```
|
|
128
155
|
## GSD Update
|
|
@@ -174,7 +201,6 @@ EXTRACT_JSON=$(node "$GSD_DIR/gsd-core/scripts/changeset/cli.cjs" extract \
|
|
|
174
201
|
--changelog "$CHANGELOG_TMP" \
|
|
175
202
|
--json 2>/dev/null)
|
|
176
203
|
EXTRACT_EXIT=$?
|
|
177
|
-
rm -f "$CHANGELOG_TMP"
|
|
178
204
|
|
|
179
205
|
if [ "$EXTRACT_EXIT" -eq 2 ]; then
|
|
180
206
|
# Exit 2 = no releases in range (e.g. versions are equal or changelog is sparse)
|
|
@@ -188,6 +214,8 @@ else
|
|
|
188
214
|
--to "$LATEST_VERSION" \
|
|
189
215
|
--changelog "$CHANGELOG_TMP" 2>/dev/null || echo "(changelog unavailable)")
|
|
190
216
|
fi
|
|
217
|
+
# Clean up temp changelog now that both extract runs are done
|
|
218
|
+
rm -f "$CHANGELOG_TMP"
|
|
191
219
|
```
|
|
192
220
|
|
|
193
221
|
3. Display preview and ask for confirmation, using `$CHANGELOG_PREVIEW` from the extract step above:
|
|
@@ -326,17 +354,17 @@ RUNTIME_FLAG="--$TARGET_RUNTIME"
|
|
|
326
354
|
|
|
327
355
|
**If LOCAL install:**
|
|
328
356
|
```bash
|
|
329
|
-
npx -y --package=@opengsd/gsd-core@
|
|
357
|
+
npx -y --package=@opengsd/gsd-core@"$TAG" -- gsd-core "$RUNTIME_FLAG" --local
|
|
330
358
|
```
|
|
331
359
|
|
|
332
360
|
**If GLOBAL install:**
|
|
333
361
|
```bash
|
|
334
|
-
npx -y --package=@opengsd/gsd-core@
|
|
362
|
+
npx -y --package=@opengsd/gsd-core@"$TAG" -- gsd-core "$RUNTIME_FLAG" --global
|
|
335
363
|
```
|
|
336
364
|
|
|
337
365
|
**If UNKNOWN install:**
|
|
338
366
|
```bash
|
|
339
|
-
npx -y --package=@opengsd/gsd-core@
|
|
367
|
+
npx -y --package=@opengsd/gsd-core@"$TAG" -- gsd-core --claude --global
|
|
340
368
|
```
|
|
341
369
|
|
|
342
370
|
Capture output. If install fails, show error and exit.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// gsd-hook-version: {{GSD_VERSION}}
|
|
3
|
+
// gsd-config-reload.js — FileChanged hook: hot-reload GSD config context
|
|
4
|
+
// Fires when .planning/config.json is modified, created, or deleted.
|
|
5
|
+
//
|
|
6
|
+
// When the user edits .planning/config.json mid-session, this hook reads the
|
|
7
|
+
// updated config and injects a summary as additionalContext so the agent knows
|
|
8
|
+
// the new configuration without requiring a session restart.
|
|
9
|
+
//
|
|
10
|
+
// Input (from Claude Code):
|
|
11
|
+
// { session_id, cwd, hook_event_name: "FileChanged",
|
|
12
|
+
// file_path: "/abs/path/.planning/config.json", event: "change"|"add"|"unlink" }
|
|
13
|
+
//
|
|
14
|
+
// Output:
|
|
15
|
+
// { hookSpecificOutput: { hookEventName: "FileChanged", additionalContext: "..." } }
|
|
16
|
+
// or exits 0 silently (if config absent, unreadable, or event is "unlink").
|
|
17
|
+
//
|
|
18
|
+
// Enabled for all Claude Code installs. This hook is always-on — it is a
|
|
19
|
+
// no-op when .planning/config.json is absent (ENOENT → exit 0).
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
let input = '';
|
|
25
|
+
// Timeout guard: if stdin does not close within 8s exit silently rather than
|
|
26
|
+
// hanging until Claude Code kills the process and reports "hook error".
|
|
27
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 8000);
|
|
28
|
+
process.stdin.setEncoding('utf8');
|
|
29
|
+
process.stdin.on('data', chunk => (input += chunk));
|
|
30
|
+
process.stdin.on('end', () => {
|
|
31
|
+
clearTimeout(stdinTimeout);
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.parse(input);
|
|
34
|
+
const event = data.event; // "change" | "add" | "unlink"
|
|
35
|
+
const filePath = data.file_path || '';
|
|
36
|
+
const cwd = data.cwd || process.cwd();
|
|
37
|
+
|
|
38
|
+
// Only handle the GSD planning config — verify both basename and that the
|
|
39
|
+
// resolved path is .planning/config.json relative to cwd. The hook
|
|
40
|
+
// matcher ('config.json') fires on any watched config.json; this guard
|
|
41
|
+
// ensures an unrelated config.json in node_modules/ or elsewhere does not
|
|
42
|
+
// inject spurious additionalContext.
|
|
43
|
+
const basename = path.basename(filePath);
|
|
44
|
+
if (basename !== 'config.json') {
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
const expectedPath = path.resolve(cwd, '.planning', 'config.json');
|
|
48
|
+
if (path.resolve(filePath) !== expectedPath) {
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// On unlink (deletion) emit a brief notice and exit
|
|
53
|
+
if (event === 'unlink') {
|
|
54
|
+
process.stdout.write(JSON.stringify({
|
|
55
|
+
hookSpecificOutput: {
|
|
56
|
+
hookEventName: 'FileChanged',
|
|
57
|
+
additionalContext:
|
|
58
|
+
'GSD config (.planning/config.json) was deleted. ' +
|
|
59
|
+
'Falling back to built-in defaults for this session.',
|
|
60
|
+
},
|
|
61
|
+
}));
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Read the updated config file
|
|
66
|
+
let config;
|
|
67
|
+
try {
|
|
68
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
69
|
+
config = JSON.parse(raw);
|
|
70
|
+
} catch (e) {
|
|
71
|
+
if (e && e.code === 'ENOENT') process.exit(0);
|
|
72
|
+
// Malformed JSON — inform the agent without crashing
|
|
73
|
+
process.stdout.write(JSON.stringify({
|
|
74
|
+
hookSpecificOutput: {
|
|
75
|
+
hookEventName: 'FileChanged',
|
|
76
|
+
additionalContext:
|
|
77
|
+
'GSD config (.planning/config.json) was modified but could not be parsed. ' +
|
|
78
|
+
'Check the file for JSON syntax errors.',
|
|
79
|
+
},
|
|
80
|
+
}));
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Build a concise summary of key config fields the agent cares about
|
|
85
|
+
const lines = ['GSD config reloaded (.planning/config.json updated):'];
|
|
86
|
+
|
|
87
|
+
if (config.runtime) lines.push(` runtime: ${config.runtime}`);
|
|
88
|
+
if (config.mode) lines.push(` mode: ${config.mode}`);
|
|
89
|
+
|
|
90
|
+
// hooks section (opt-in toggles agents act on)
|
|
91
|
+
if (config.hooks && typeof config.hooks === 'object') {
|
|
92
|
+
const hookKeys = Object.entries(config.hooks)
|
|
93
|
+
.filter(([, v]) => v !== undefined)
|
|
94
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
95
|
+
.join(', ');
|
|
96
|
+
if (hookKeys) lines.push(` hooks: { ${hookKeys} }`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// workflow section (key toggles)
|
|
100
|
+
if (config.workflow && typeof config.workflow === 'object') {
|
|
101
|
+
const wfKeys = Object.entries(config.workflow)
|
|
102
|
+
.filter(([, v]) => v !== undefined)
|
|
103
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
104
|
+
.join(', ');
|
|
105
|
+
if (wfKeys) lines.push(` workflow: { ${wfKeys} }`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// model overrides (agents use these)
|
|
109
|
+
if (config.models && typeof config.models === 'object') {
|
|
110
|
+
const modelKeys = Object.entries(config.models)
|
|
111
|
+
.filter(([, v]) => v !== undefined)
|
|
112
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
113
|
+
.join(', ');
|
|
114
|
+
if (modelKeys) lines.push(` models: { ${modelKeys} }`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (lines.length === 1) {
|
|
118
|
+
// No notable fields — still confirm the reload happened
|
|
119
|
+
lines.push(' (no notable keys changed)');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const additionalContext = lines.join('\n');
|
|
123
|
+
process.stdout.write(JSON.stringify({
|
|
124
|
+
hookSpecificOutput: {
|
|
125
|
+
hookEventName: 'FileChanged',
|
|
126
|
+
additionalContext,
|
|
127
|
+
},
|
|
128
|
+
}));
|
|
129
|
+
} catch (e) {
|
|
130
|
+
// Silent fail — never block the session on a config reload error
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
@@ -150,7 +150,7 @@ process.stdin.on('end', () => {
|
|
|
150
150
|
spawn(
|
|
151
151
|
process.execPath,
|
|
152
152
|
[gsdTools, 'state', 'record-session', '--stopped-at', stoppedAt],
|
|
153
|
-
{ cwd, detached: true, stdio: 'ignore' }
|
|
153
|
+
{ cwd, detached: true, stdio: 'ignore', windowsHide: true }
|
|
154
154
|
).unref();
|
|
155
155
|
warnData.criticalRecorded = true;
|
|
156
156
|
// Persist the sentinel so subsequent debounce cycles don't re-fire
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// gsd-hook-version: {{GSD_VERSION}}
|
|
3
|
+
// gsd-cursor-post-tool.js — Cursor postToolUse hook (issue #777)
|
|
4
|
+
//
|
|
5
|
+
// Cursor invokes this script after each tool call completes.
|
|
6
|
+
// Protocol: JSON from Cursor on stdin; JSON response on stdout.
|
|
7
|
+
//
|
|
8
|
+
// Input schema (cursor postToolUse):
|
|
9
|
+
// { tool_name, tool_input, tool_output, duration,
|
|
10
|
+
// conversation_id, generation_id, model, hook_event_name,
|
|
11
|
+
// cursor_version, workspace_roots, user_email, transcript_path }
|
|
12
|
+
//
|
|
13
|
+
// Output schema (cursor postToolUse):
|
|
14
|
+
// { additional_context?: string } ← injected as context after the tool use
|
|
15
|
+
//
|
|
16
|
+
// Behaviour:
|
|
17
|
+
// - After a write-class tool that targets .planning/, reminds the agent
|
|
18
|
+
// to keep STATE.md current.
|
|
19
|
+
// - Fails open: any error silently exits 0.
|
|
20
|
+
//
|
|
21
|
+
// Cursor docs: https://cursor.com/docs/hooks
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const WRITE_TOOL_RE = /write|edit|replace|create|delete|remove|append|apply|patch|insert|mkdir/i;
|
|
26
|
+
const PATH_KEY_RE = /^(path|file|file_?path|filepath|target_?path|target|dir|directory|uri|filename)$/i;
|
|
27
|
+
const PLANNING_PATH_RE = /(^|[\\/])\.planning([\\/]|$)/;
|
|
28
|
+
|
|
29
|
+
let raw = '';
|
|
30
|
+
const stdinTimeout = setTimeout(() => {
|
|
31
|
+
// Timeout guard: exit silently rather than hanging.
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}, 10000);
|
|
34
|
+
|
|
35
|
+
process.stdin.setEncoding('utf8');
|
|
36
|
+
process.stdin.on('data', (chunk) => { raw += chunk; });
|
|
37
|
+
process.stdin.on('end', () => {
|
|
38
|
+
clearTimeout(stdinTimeout);
|
|
39
|
+
try {
|
|
40
|
+
let input;
|
|
41
|
+
try { input = JSON.parse(raw || '{}'); } catch { process.stdout.write(JSON.stringify({})); return; }
|
|
42
|
+
|
|
43
|
+
const toolName = String(
|
|
44
|
+
input.tool_name || input.toolName || ''
|
|
45
|
+
).toLowerCase();
|
|
46
|
+
|
|
47
|
+
const isWrite = WRITE_TOOL_RE.test(toolName);
|
|
48
|
+
if (!isWrite) { process.stdout.write(JSON.stringify({})); return; }
|
|
49
|
+
|
|
50
|
+
// Collect only PATH-bearing field values (not free-form content).
|
|
51
|
+
const paths = [];
|
|
52
|
+
const walk = (v, depth) => {
|
|
53
|
+
if (depth > 5 || paths.length > 64) return;
|
|
54
|
+
if (Array.isArray(v)) { for (const x of v) walk(x, depth + 1); return; }
|
|
55
|
+
if (v && typeof v === 'object') {
|
|
56
|
+
for (const k of Object.keys(v)) {
|
|
57
|
+
const val = v[k];
|
|
58
|
+
if (typeof val === 'string' && PATH_KEY_RE.test(k)) paths.push(val);
|
|
59
|
+
else walk(val, depth + 1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
walk(input.tool_input || input.toolInput || {}, 0);
|
|
64
|
+
|
|
65
|
+
if (paths.some((p) => PLANNING_PATH_RE.test(p))) {
|
|
66
|
+
process.stdout.write(JSON.stringify({
|
|
67
|
+
additional_context:
|
|
68
|
+
'GSD: .planning/ artifact updated — ensure STATE.md reflects the latest phase and progress.',
|
|
69
|
+
}));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
} catch { /* fall through to empty response */ }
|
|
73
|
+
|
|
74
|
+
process.stdout.write(JSON.stringify({}));
|
|
75
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// gsd-hook-version: {{GSD_VERSION}}
|
|
3
|
+
// gsd-cursor-session-start.js — Cursor sessionStart hook (issue #777)
|
|
4
|
+
//
|
|
5
|
+
// Cursor invokes this script at the start of each agent session.
|
|
6
|
+
// Protocol: JSON from Cursor on stdin; JSON response on stdout.
|
|
7
|
+
//
|
|
8
|
+
// Input schema (cursor sessionStart):
|
|
9
|
+
// { session_id, is_background_agent, composer_mode, conversation_id,
|
|
10
|
+
// generation_id, model, hook_event_name, cursor_version,
|
|
11
|
+
// workspace_roots, user_email, transcript_path }
|
|
12
|
+
//
|
|
13
|
+
// Output schema (cursor sessionStart):
|
|
14
|
+
// { additional_context?: string } ← injected into the session as context
|
|
15
|
+
//
|
|
16
|
+
// Behaviour:
|
|
17
|
+
// - If .planning/STATE.md is present, injects a brief state reminder.
|
|
18
|
+
// - If absent, nudges the user toward /gsd:new-project.
|
|
19
|
+
// - Fails open: any error silently exits 0 so a hook bug never wedges Cursor.
|
|
20
|
+
//
|
|
21
|
+
// Cursor docs: https://cursor.com/docs/hooks
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
|
|
28
|
+
const MSG_PRESENT =
|
|
29
|
+
'GSD: .planning/STATE.md is present — review the current phase and any blockers before acting.';
|
|
30
|
+
const MSG_ABSENT =
|
|
31
|
+
'GSD: no .planning/ workflow found — run /gsd:new-project to start a tracked workflow.';
|
|
32
|
+
|
|
33
|
+
let raw = '';
|
|
34
|
+
const stdinTimeout = setTimeout(() => {
|
|
35
|
+
// Timeout guard: exit silently rather than hanging.
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}, 10000);
|
|
38
|
+
|
|
39
|
+
process.stdin.setEncoding('utf8');
|
|
40
|
+
process.stdin.on('data', (chunk) => { raw += chunk; });
|
|
41
|
+
process.stdin.on('end', () => {
|
|
42
|
+
clearTimeout(stdinTimeout);
|
|
43
|
+
try {
|
|
44
|
+
const statePath = path.join(process.cwd(), '.planning', 'STATE.md');
|
|
45
|
+
const statePresent = fs.existsSync(statePath);
|
|
46
|
+
const msg = statePresent ? MSG_PRESENT : MSG_ABSENT;
|
|
47
|
+
process.stdout.write(JSON.stringify({ additional_context: msg }));
|
|
48
|
+
} catch {
|
|
49
|
+
// Fail open — never block a Cursor session because of a GSD hook error.
|
|
50
|
+
process.stdout.write(JSON.stringify({}));
|
|
51
|
+
}
|
|
52
|
+
});
|