@opengsd/gsd-core 1.9.1 → 1.10.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +27 -3
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-executor.md +7 -5
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-plan-checker.md +9 -0
- package/agents/gsd-planner.md +5 -8
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-verifier.md +14 -70
- package/bin/install.js +453 -289
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/plan-phase.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +579 -66
- package/gsd-core/bin/lib/active-workstream-store.cjs +25 -0
- package/gsd-core/bin/lib/agent-install-check.cjs +38 -6
- package/gsd-core/bin/lib/api-coverage.cjs +120 -0
- package/gsd-core/bin/lib/audit.cjs +89 -1
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- package/gsd-core/bin/lib/capability-registry.cjs +96 -110
- package/gsd-core/bin/lib/capability-validator.cjs +12 -2
- package/gsd-core/bin/lib/check-command-router.cjs +43 -1
- package/gsd-core/bin/lib/command-aliases.cjs +72 -0
- package/gsd-core/bin/lib/commands.cjs +26 -25
- package/gsd-core/bin/lib/commonjs-marker.cjs +136 -0
- package/gsd-core/bin/lib/config-loader.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +12 -1
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +91 -12
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/frontmatter.cjs +84 -12
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +58 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +27 -4
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1021 -57
- package/gsd-core/bin/lib/install-engine.cjs +64 -10
- package/gsd-core/bin/lib/install-profiles.cjs +27 -1
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +87 -1
- package/gsd-core/bin/lib/io.cjs +28 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +6 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone.cjs +75 -47
- package/gsd-core/bin/lib/phase-id.cjs +63 -0
- package/gsd-core/bin/lib/phase-locator.cjs +138 -45
- package/gsd-core/bin/lib/phase.cjs +260 -28
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +232 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +4 -0
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +80 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +99 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +30 -6
- package/gsd-core/bin/lib/roadmap-command-router.cjs +42 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +100 -18
- package/gsd-core/bin/lib/roadmap.cjs +37 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +195 -62
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +15 -3
- package/gsd-core/bin/lib/runtime-homes.cjs +154 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +105 -41
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +113 -27
- package/gsd-core/bin/lib/smart-entry.cjs +12 -0
- package/gsd-core/bin/lib/state-transition.cjs +73 -8
- package/gsd-core/bin/lib/state.cjs +151 -62
- package/gsd-core/bin/lib/surface.cjs +12 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +11 -1
- package/gsd-core/bin/lib/uat.cjs +320 -21
- package/gsd-core/bin/lib/unusable-input.cjs +9 -0
- package/gsd-core/bin/lib/verification.cjs +29 -12
- package/gsd-core/bin/lib/verify.cjs +10 -2
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +181 -18
- package/gsd-core/bin/lib/workstream-inventory.cjs +519 -27
- package/gsd-core/bin/lib/workstream.cjs +6 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +276 -118
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/specless-probe-fallback.md +7 -6
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +32 -69
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +83 -0
- package/gsd-core/workflows/code-review.md +42 -160
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +23 -81
- package/gsd-core/workflows/debug.md +9 -12
- package/gsd-core/workflows/diagnose-issues.md +22 -0
- package/gsd-core/workflows/discovery-phase.md +4 -4
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +5 -16
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +34 -2
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +19 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +65 -137
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/ingest-docs.md +2 -1
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +21 -38
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +13 -226
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +49 -193
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +11 -153
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +46 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +20 -390
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +15 -8
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +17 -14
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +49 -11
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +8 -21
- package/gsd-core/workflows/ui-phase.md +8 -7
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +18 -7
- package/gsd-core/workflows/verify-phase.md +4 -7
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +8 -58
- package/hooks/dist/gsd-agent-isolation-guard.js +428 -0
- package/hooks/dist/gsd-check-update-worker.js +14 -5
- package/hooks/dist/gsd-cursor-subagent-start.js +532 -26
- package/hooks/dist/gsd-read-injection-scanner.js +7 -0
- package/hooks/dist/gsd-statusline.js +72 -6
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/isolation-sentinel.js +268 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +428 -0
- package/hooks/gsd-check-update-worker.js +14 -5
- package/hooks/gsd-cursor-subagent-start.js +532 -26
- package/hooks/gsd-read-injection-scanner.js +7 -0
- package/hooks/gsd-statusline.js +72 -6
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/isolation-sentinel.js +268 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +14 -5
- package/pi/gsd.cjs +57 -12
- package/scripts/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +9 -2
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-context-index.cjs +448 -0
- package/scripts/gen-inventory-manifest.cjs +101 -1
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lint-allow-test-rule-refs.allowlist.json +17 -31
- package/scripts/lint-compiled-artifact-sync.cjs +6 -1
- package/scripts/lint-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-test-file-count.allowlist.json +27 -1
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/prompt-injection-scan.sh +27 -6
- package/scripts/run-tests.cjs +3 -2
- package/skills/gsd-autonomous/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-plan-phase/SKILL.md +2 -2
- package/vscode/package.json +1 -1
- package/scripts/gen-emitted-baseline.cjs +0 -145
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
## 8.5. Chunked Planning Mode
|
|
2
|
+
|
|
3
|
+
**Skip if `CHUNKED_MODE` is `false`.**
|
|
4
|
+
|
|
5
|
+
Chunked mode splits the single planner run into a short outline run + N short per-plan
|
|
6
|
+
runs (~3–5 min each), committing each plan individually for crash resilience. Rerunning
|
|
7
|
+
`/gsd:plan-phase {N} --chunked` resumes from the last committed plan.
|
|
8
|
+
|
|
9
|
+
For recovering plans from a prior *non-chunked* run, use step 6's "Add more plans" or
|
|
10
|
+
proceed to `/gsd:execute-phase` — don't start a fresh chunked run over them.
|
|
11
|
+
|
|
12
|
+
### 8.5.1 Outline Phase (outline-only mode, ~2 min)
|
|
13
|
+
|
|
14
|
+
**Resume detection:** If `${PHASE_DIR}/${PADDED_PHASE}-PLAN-OUTLINE.md` exists and contains
|
|
15
|
+
the `## OUTLINE COMPLETE` marker (written by the outline agent — #2762), skip to 8.5.2.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
OUTLINE_FILE="${PHASE_DIR}/${PADDED_PHASE}-PLAN-OUTLINE.md"
|
|
19
|
+
if [[ -f "$OUTLINE_FILE" ]] && grep -q "^## OUTLINE COMPLETE" "$OUTLINE_FILE"; then
|
|
20
|
+
# reuse existing outline — skip to 8.5.2
|
|
21
|
+
fi
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Display:
|
|
25
|
+
```text
|
|
26
|
+
◆ Chunked mode: spawning outline planner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Spawn the planner in **outline-only** mode — it must write only the outline manifest, not any
|
|
30
|
+
PLAN.md files:
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
Agent(
|
|
34
|
+
prompt="{same planning_context as step 8, plus:}
|
|
35
|
+
|
|
36
|
+
**Chunked mode: outline-only.**
|
|
37
|
+
Do NOT write any PLAN.md files in this Task.
|
|
38
|
+
Write only: {PHASE_DIR}/{PADDED_PHASE}-PLAN-OUTLINE.md
|
|
39
|
+
|
|
40
|
+
The outline must be a markdown table with columns:
|
|
41
|
+
Plan ID | Objective | Wave | Depends On | Requirements
|
|
42
|
+
|
|
43
|
+
End the file with a final line `## OUTLINE COMPLETE` — §8.5.1's resume-check greps
|
|
44
|
+
the file for it, so it MUST be written here, not just returned.
|
|
45
|
+
Return: ## OUTLINE COMPLETE with plan count.",
|
|
46
|
+
subagent_type="gsd-planner",
|
|
47
|
+
model="{planner_model}",
|
|
48
|
+
description="Outline Phase {phase} (chunked)",
|
|
49
|
+
run_in_background=true
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**ORCHESTRATOR RULE — ALL RUNTIMES:** `TS=$(date +%s)`; repeat `PLANNER_STALL_RESULT=$(gsd_stall_watch "$TS" "{outputFile}" "$OUTLINE_FILE" "## OUTLINE COMPLETE")` while waiting/active.
|
|
54
|
+
|
|
55
|
+
Handle return:
|
|
56
|
+
- **`marker_received`:** Read `PLAN-OUTLINE.md`, extract plan list. Continue to 8.5.2.
|
|
57
|
+
- **`stalled` / any other return or empty:** Display error. Offer: 1) Retry outline, 2) Stop.
|
|
58
|
+
|
|
59
|
+
### 8.5.2 Per-Plan Tasks (single-plan mode, ~3-5 min each)
|
|
60
|
+
|
|
61
|
+
For each plan entry extracted from `PLAN-OUTLINE.md`:
|
|
62
|
+
|
|
63
|
+
1. **Resume check:** Skip if `${PHASE_DIR}/{plan_id}-PLAN.md` exists with valid frontmatter
|
|
64
|
+
(resume safety) — UNLESS `--reviews` is set, whose purpose is to REPLAN with review
|
|
65
|
+
feedback (§6), so existing plans are overwritten, not skipped (#2762).
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
PLAN_FILE="${PHASE_DIR}/${plan_id}-PLAN.md"
|
|
69
|
+
if [[ -f "$PLAN_FILE" ]] && head -1 "$PLAN_FILE" | grep -q '^---' && [[ "$ARGUMENTS" != *"--reviews"* ]]; then
|
|
70
|
+
continue # resume safety — NOT under --reviews (replan)
|
|
71
|
+
fi
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
2. Display:
|
|
75
|
+
```text
|
|
76
|
+
◆ Chunked mode: planning {plan_id} ({k}/{N})... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
3. Spawn the planner in **single-plan** mode — it must write exactly one PLAN.md file:
|
|
80
|
+
```javascript
|
|
81
|
+
Agent(
|
|
82
|
+
prompt="{same planning_context as step 8, plus:}
|
|
83
|
+
|
|
84
|
+
**Chunked mode: single-plan.**
|
|
85
|
+
Write exactly ONE plan file: {PHASE_DIR}/{plan_id}-PLAN.md
|
|
86
|
+
Plan to write: {plan_id} — {objective}
|
|
87
|
+
Wave: {wave} | Depends on: {depends_on}
|
|
88
|
+
Phase requirement IDs to cover in this plan: {plan_requirements}
|
|
89
|
+
|
|
90
|
+
Return: ## PLAN COMPLETE with the plan ID.",
|
|
91
|
+
subagent_type="gsd-planner",
|
|
92
|
+
model="{planner_model}",
|
|
93
|
+
description="Plan {plan_id} (chunked {k}/{N})",
|
|
94
|
+
run_in_background=true
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**ORCHESTRATOR RULE — ALL RUNTIMES:** `TS=$(date +%s)`; repeat `PLANNER_STALL_RESULT=$(gsd_stall_watch "$TS" "{outputFile}" "$PLAN_FILE" "## PLAN COMPLETE")` while waiting/active — `stalled` falls into step 4 (preserves prior committed chunks).
|
|
99
|
+
|
|
100
|
+
4. **Verify disk:** Check `${PHASE_DIR}/{plan_id}-PLAN.md` exists. If missing: offer 1) Retry, 2) Stop.
|
|
101
|
+
|
|
102
|
+
5. **Commit per-plan:**
|
|
103
|
+
```bash
|
|
104
|
+
_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 [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/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 "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/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; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
105
|
+
gsd_run query commit "docs(${PADDED_PHASE}): plan ${plan_id} (chunked)" --files "${PHASE_DIR}/${plan_id}-PLAN.md"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
After all N plans are written and committed, treat this as `## PLANNING COMPLETE` and continue
|
|
109
|
+
to step 9.
|
|
110
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## 3.5. Handle PRD Express Path
|
|
2
|
+
|
|
3
|
+
**Skip if:** No `--prd` flag in arguments.
|
|
4
|
+
|
|
5
|
+
**If `--prd <filepath>` provided:**
|
|
6
|
+
|
|
7
|
+
Read and execute `gsd-core/workflows/plan-phase/steps/prd-express-path.md` — it reads the PRD (`$PRD_FILE`), generates `CONTEXT.md` (every PRD requirement/story/criterion → locked decision, uncovered areas → "Claude's Discretion", canonical refs extracted from ROADMAP.md + PRD-referenced specs), commits it, sets `context_content`, and bypasses step 4 (Load CONTEXT.md). The rest of the workflow proceeds normally with the PRD-derived context.
|
|
8
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
### Research-Only Early Exit (`--research-phase`)
|
|
2
|
+
|
|
3
|
+
**Skip if:** `RESEARCH_ONLY` is `false` (the default).
|
|
4
|
+
|
|
5
|
+
**If `RESEARCH_ONLY=true`:** the user invoked `/gsd:plan-phase --research-phase <N>` for research-only mode. Do **not** continue to Section 5.5+ (validation strategy, planner, plan-checker, verification, gaps, bounce, post-planning-gaps). Print the research-complete summary and exit cleanly:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
✓ Research-only mode complete (#3042)
|
|
9
|
+
|
|
10
|
+
Phase: ${PHASE}
|
|
11
|
+
RESEARCH.md: ${research_path}
|
|
12
|
+
|
|
13
|
+
Re-run /gsd:plan-phase ${PHASE} to plan the phase using this research,
|
|
14
|
+
or /gsd:plan-phase ${PHASE} --research to refresh research and plan.
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This exits the workflow. The planner / plan-checker / verifier blocks below are skipped.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
### 5.0. Research-Only Modifiers (`--view`, `--research`)
|
|
2
|
+
|
|
3
|
+
**Skip if:** `RESEARCH_ONLY` is `false`.
|
|
4
|
+
|
|
5
|
+
Three branches in research-only mode (`--research-phase <N>`):
|
|
6
|
+
|
|
7
|
+
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.`
|
|
8
|
+
2. **`--research`** (force-refresh): re-spawn researcher unconditionally — fall through to "Spawn gsd-phase-researcher" below.
|
|
9
|
+
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).
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
if [[ "$VIEW_ONLY" == "true" ]]; then
|
|
13
|
+
[[ -f "$research_path" ]] || { echo "Error: --view requires an existing RESEARCH.md (Phase ${PHASE}). Drop --view to spawn the researcher."; exit 1; }
|
|
14
|
+
cat "$research_path"; exit 0
|
|
15
|
+
fi
|
|
16
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## 2.5. Validate `--reviews` Prerequisite
|
|
2
|
+
|
|
3
|
+
**Skip if:** No `--reviews` flag.
|
|
4
|
+
|
|
5
|
+
**If `--reviews` AND `--gaps`:** Error — cannot combine `--reviews` with `--gaps`. These are conflicting modes.
|
|
6
|
+
|
|
7
|
+
**If `--reviews` AND `has_reviews` is false (no REVIEWS.md in phase dir):**
|
|
8
|
+
|
|
9
|
+
Error:
|
|
10
|
+
```
|
|
11
|
+
No REVIEWS.md found for Phase {N}. Run reviews first:
|
|
12
|
+
|
|
13
|
+
/gsd:review --phase {N}
|
|
14
|
+
|
|
15
|
+
Then re-run /gsd:plan-phase {N} --reviews
|
|
16
|
+
```
|
|
17
|
+
Exit workflow.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Bounded Stall-Detection Helpers (#2650)
|
|
2
|
+
|
|
3
|
+
Every planner/plan-checker spawn in `plan-phase.md` dispatches with
|
|
4
|
+
`run_in_background=true`, records `TS=$(date +%s)`, and then repeatedly
|
|
5
|
+
calls `gsd_stall_watch` until it returns something other than
|
|
6
|
+
`waiting`/`active`. This mirrors the already-shipped `executor.stall_*`
|
|
7
|
+
pattern (`execute-phase.md`, bug #3212, commit `e7942c21b`) but — unlike
|
|
8
|
+
that prose-only surveillance, which cannot run during a *blocking* `Agent()`
|
|
9
|
+
call — each `gsd_stall_watch` call is a real, bounded bash subprocess wait
|
|
10
|
+
issued as its own tool call, so it returns control to the orchestrator on
|
|
11
|
+
its own schedule regardless of whether the backgrounded agent's own
|
|
12
|
+
completion notification ever arrives.
|
|
13
|
+
|
|
14
|
+
**Binding `{outputFile}` (load-bearing, not optional):** every `gsd_stall_watch`
|
|
15
|
+
call below takes `{outputFile}` as its second argument — a literal token the
|
|
16
|
+
orchestrator must substitute with the REAL path from the immediately preceding
|
|
17
|
+
`run_in_background=true` Agent() call's returned `async_launched` result,
|
|
18
|
+
exactly as `docs-update.md:471` already does ("Read tool: file_path: `{outputFile
|
|
19
|
+
from README agent result}`"). This is NOT a bash variable the snippet below
|
|
20
|
+
assigns — there is nothing upstream that assigns one, so a bash variable
|
|
21
|
+
reference here would silently stay empty forever. With `{outputFile}` correctly
|
|
22
|
+
substituted, `[ -f "$output_file" ]` can find the real file and the
|
|
23
|
+
`marker_received` path is reachable; left as a literal (or as an unbound bash
|
|
24
|
+
variable), `marker_found` can never become `true` and every spawn silently
|
|
25
|
+
falls back to the mtime-only path — for the plan-checker spawn specifically,
|
|
26
|
+
that fallback is broken (see next paragraph), so binding this correctly there
|
|
27
|
+
is not a nice-to-have.
|
|
28
|
+
|
|
29
|
+
**Plan-checker's artifact glob needs the marker, not just mtimes:** the
|
|
30
|
+
plan-checker spawn watches `*-PLAN.md` for freshness, but a checker that
|
|
31
|
+
PASSES touches none of those files — no fresh mtime, ever, on a clean run.
|
|
32
|
+
Without `{outputFile}` correctly bound to the real completion output, a
|
|
33
|
+
healthy plan-checker that returns `## VERIFICATION PASSED` in two minutes
|
|
34
|
+
would still be declared `stalled` once `planner.stall_threshold_minutes`
|
|
35
|
+
elapses — reporting a succeeded agent as hung, which is worse than the
|
|
36
|
+
original unbounded wait. The marker path (via `{outputFile}`) is the ONLY
|
|
37
|
+
working completion signal for that spawn; the artifact glob is secondary
|
|
38
|
+
there.
|
|
39
|
+
|
|
40
|
+
**Single-cycle by design, not one long-lived loop:** `gsd_stall_watch` sleeps
|
|
41
|
+
for exactly one `PLANNER_STALL_INTERVAL_MINUTES` and returns — it does NOT
|
|
42
|
+
loop internally for the full `PLANNER_STALL_THRESHOLD_MINUTES`. A single Bash
|
|
43
|
+
tool call blocking for `threshold + interval` minutes (up to 15 min at
|
|
44
|
+
defaults) risks the *host tool's own* timeout killing the call before it ever
|
|
45
|
+
prints a result — silently defeating the fix it exists to ship. Looping at
|
|
46
|
+
the orchestrator-prose level instead means every cycle is a short (default 5
|
|
47
|
+
min), real, bounded call that reliably hands control back — the outer
|
|
48
|
+
threshold is enforced by `dispatch_ts` accumulating across calls, not by one
|
|
49
|
+
call's own duration.
|
|
50
|
+
|
|
51
|
+
**Disclosed tradeoff:** the first cycle always sleeps a full
|
|
52
|
+
`PLANNER_STALL_INTERVAL_MINUTES` before its first check, so a planner that
|
|
53
|
+
completes in seconds is not observed by this path until that interval
|
|
54
|
+
elapses (default 5 min) — slower than a plain blocking call's near-instant
|
|
55
|
+
return on success. This is deliberate: it trades a bounded, at-most-one-
|
|
56
|
+
interval delay on the (common) success path for eliminating the unbounded,
|
|
57
|
+
possibly-indefinite hang on the (rare, previously unrecoverable) stall path
|
|
58
|
+
this issue is about. `PLANNER_STALL_INTERVAL_MINUTES` is the knob for
|
|
59
|
+
projects that want a tighter success-path latency at the cost of more
|
|
60
|
+
config-get calls.
|
|
61
|
+
|
|
62
|
+
This block is independent of, and never gated behind, the `query
|
|
63
|
+
teams-status` / `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` guard used for the
|
|
64
|
+
researcher spawn — the stall path applies on every runtime, teams-active or
|
|
65
|
+
not (AC2).
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
_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 [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/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 "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/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; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
69
|
+
PLANNER_STALL_INTERVAL_MINUTES=$(gsd_run query config-get planner.stall_detect_interval_minutes 2>/dev/null || echo "5")
|
|
70
|
+
PLANNER_STALL_THRESHOLD_MINUTES=$(gsd_run query config-get planner.stall_threshold_minutes 2>/dev/null || echo "10")
|
|
71
|
+
# Both values are config-controlled (.planning/config.json, editable by any repo
|
|
72
|
+
# contributor) and both flow into `$(( ))` arithmetic below. A non-numeric
|
|
73
|
+
# value there is NOT a code-execution risk (empirically verified: bash's
|
|
74
|
+
# arithmetic evaluator hard-errors on a `$(cmd)`-shaped operand instead of
|
|
75
|
+
# invoking it — "syntax error: operand expected", command never runs) but IS
|
|
76
|
+
# a reliability risk this fix cannot afford: a malformed config value would
|
|
77
|
+
# abort the stall-watcher itself with a bash syntax error, silently defeating
|
|
78
|
+
# the exact hang-recovery this issue is about. Reject anything that is not a
|
|
79
|
+
# bare non-negative integer before it is ever used, so a bad config value
|
|
80
|
+
# degrades to the safe default instead of crashing the watcher.
|
|
81
|
+
[[ "$PLANNER_STALL_INTERVAL_MINUTES" =~ ^[0-9]+$ ]] || PLANNER_STALL_INTERVAL_MINUTES=5
|
|
82
|
+
[[ "$PLANNER_STALL_THRESHOLD_MINUTES" =~ ^[0-9]+$ ]] || PLANNER_STALL_THRESHOLD_MINUTES=10
|
|
83
|
+
|
|
84
|
+
# gsd_stall_should_recover — pure decision function, no IO, no sleeping. Given how
|
|
85
|
+
# long the orchestrator has been waiting plus two liveness signals (a completion
|
|
86
|
+
# marker found in the agent's output file, and fresh on-disk artifact activity),
|
|
87
|
+
# decides whether to keep waiting, treat the wait as satisfied, or auto-surface the
|
|
88
|
+
# existing accept/retry/stop recovery menu (9a/11a). Never kills or retries anything
|
|
89
|
+
# itself — it only classifies. Re-validates both numeric args as bare non-negative
|
|
90
|
+
# integers (defense in depth — safe to call with any input, not just the resolved
|
|
91
|
+
# config globals above) before either ever reaches arithmetic expansion.
|
|
92
|
+
gsd_stall_should_recover() {
|
|
93
|
+
local elapsed_seconds="$1" threshold_minutes="$2" marker_found="$3" artifact_fresh="$4"
|
|
94
|
+
[[ "$elapsed_seconds" =~ ^[0-9]+$ ]] || elapsed_seconds=0
|
|
95
|
+
[[ "$threshold_minutes" =~ ^[0-9]+$ ]] || threshold_minutes=10
|
|
96
|
+
local threshold_seconds=$(( threshold_minutes * 60 ))
|
|
97
|
+
if [ "$marker_found" = "true" ]; then
|
|
98
|
+
echo "marker_received"; return 0
|
|
99
|
+
fi
|
|
100
|
+
if [ "$artifact_fresh" = "true" ]; then
|
|
101
|
+
echo "active"; return 0
|
|
102
|
+
fi
|
|
103
|
+
if [ "$elapsed_seconds" -ge "$threshold_seconds" ]; then
|
|
104
|
+
echo "stalled"; return 0
|
|
105
|
+
fi
|
|
106
|
+
echo "waiting"; return 0
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# gsd_stall_watch — ONE bounded, real (non-LLM-side) sleep-and-check cycle, not
|
|
110
|
+
# a long-lived loop (see "Single-cycle by design" above — a single Bash tool
|
|
111
|
+
# call spanning the full threshold risks the host tool's own timeout killing
|
|
112
|
+
# it first). Sleeps exactly one PLANNER_STALL_INTERVAL_MINUTES, then checks for
|
|
113
|
+
# a completion marker in $2 (the outputFile returned by the run_in_background
|
|
114
|
+
# Agent() call) or fresh mtime activity under $3 (an artifact glob), against
|
|
115
|
+
# elapsed time since $1 (an epoch-seconds dispatch_ts the CALLER records once,
|
|
116
|
+
# before the first call, and passes unchanged on every repeat). Remaining args
|
|
117
|
+
# are completion markers. Prints exactly one of: marker_received | active |
|
|
118
|
+
# waiting | stalled. The caller repeats the call while the result is
|
|
119
|
+
# waiting/active; any other result ends the wait.
|
|
120
|
+
gsd_stall_watch() {
|
|
121
|
+
local dispatch_ts="$1" output_file="$2" artifact_glob="$3"; shift 3
|
|
122
|
+
local markers=("$@")
|
|
123
|
+
[[ "$dispatch_ts" =~ ^[0-9]+$ ]] || dispatch_ts=$(date +%s)
|
|
124
|
+
sleep "$(( PLANNER_STALL_INTERVAL_MINUTES * 60 ))"
|
|
125
|
+
local now elapsed marker_found artifact_fresh
|
|
126
|
+
now=$(date +%s)
|
|
127
|
+
elapsed=$(( now - dispatch_ts ))
|
|
128
|
+
marker_found="false"
|
|
129
|
+
if [ -f "$output_file" ]; then
|
|
130
|
+
for m in "${markers[@]}"; do
|
|
131
|
+
if grep -qF "$m" "$output_file" 2>/dev/null; then marker_found="true"; break; fi
|
|
132
|
+
done
|
|
133
|
+
fi
|
|
134
|
+
# -mmin -N ("modified less than N minutes ago"), not -newermt "@<epoch>":
|
|
135
|
+
# -newermt's "@<epoch>" shorthand is a GNU-date convenience the shipped
|
|
136
|
+
# BSD find(1) on macOS does NOT understand ("Can't parse date/time:
|
|
137
|
+
# @<epoch>", verified live) — with the 2>/dev/null below that failed
|
|
138
|
+
# silently and permanently degraded artifact_fresh to false on every
|
|
139
|
+
# macOS run. -mmin -N needs no epoch/date-string conversion at all and is
|
|
140
|
+
# supported identically by GNU find (Linux, Git-for-Windows' bundled
|
|
141
|
+
# findutils) and BSD find (macOS). $artifact_glob stays intentionally
|
|
142
|
+
# unquoted — the shell, not find, expands it into the matching file list.
|
|
143
|
+
artifact_fresh="false"
|
|
144
|
+
if [ -n "$(find $artifact_glob -mmin "-${PLANNER_STALL_INTERVAL_MINUTES}" 2>/dev/null)" ]; then
|
|
145
|
+
artifact_fresh="true"
|
|
146
|
+
fi
|
|
147
|
+
gsd_stall_should_recover "$elapsed" "$PLANNER_STALL_THRESHOLD_MINUTES" "$marker_found" "$artifact_fresh"
|
|
148
|
+
}
|
|
149
|
+
```
|