@opengsd/gsd-core 1.6.0-rc.1 → 1.6.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 +1 -1
- package/agents/gsd-roadmapper.md +6 -0
- package/bin/install.js +92 -332
- package/commands/gsd/capture.md +5 -1
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +18 -3
- package/gsd-core/bin/lib/adr-parser.cjs +21 -6
- package/gsd-core/bin/lib/capability-registry.cjs +48 -48
- package/gsd-core/bin/lib/commands.cjs +247 -0
- package/gsd-core/bin/lib/config-loader.cjs +6 -0
- package/gsd-core/bin/lib/config.cjs +6 -0
- package/gsd-core/bin/lib/frontmatter.cjs +7 -3
- package/gsd-core/bin/lib/phase-id.cjs +25 -11
- package/gsd-core/bin/lib/phase.cjs +4 -4
- package/gsd-core/bin/lib/probe-core.cjs +7 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +59 -26
- package/gsd-core/bin/lib/roadmap-command-router.cjs +16 -3
- package/gsd-core/bin/lib/roadmap-parser.cjs +29 -8
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -17
- package/gsd-core/bin/lib/roadmap.cjs +5 -2
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +423 -3
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +77 -0
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +1 -28
- package/gsd-core/bin/lib/runtime-name-policy.cjs +44 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +55 -1
- package/gsd-core/bin/lib/surface.cjs +12 -19
- package/gsd-core/bin/lib/validate.cjs +5 -2
- package/gsd-core/bin/lib/verify.cjs +11 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +202 -0
- package/gsd-core/bin/shared/config-defaults.manifest.json +2 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/context-budget.md +8 -8
- package/gsd-core/references/execute-phase-context-guard.md +16 -0
- package/gsd-core/references/planning-config.md +1 -0
- package/gsd-core/references/prohibition-probe.md +15 -9
- package/gsd-core/workflows/autonomous.md +33 -33
- package/gsd-core/workflows/diagnose-issues.md +6 -1
- package/gsd-core/workflows/execute-phase.md +8 -6
- package/gsd-core/workflows/help/modes/full.md +10 -0
- package/gsd-core/workflows/list-seeds.md +63 -0
- package/gsd-core/workflows/manager.md +37 -37
- package/gsd-core/workflows/pr-branch.md +156 -0
- package/gsd-core/workflows/quick.md +6 -1
- package/gsd-core/workflows/review.md +10 -2
- package/gsd-core/workflows/spec-phase.md +8 -3
- package/gsd-core/workflows/verify-phase.md +2 -2
- package/package.json +4 -1
- package/scripts/prompt-injection-scan.sh +1 -0
|
@@ -61,7 +61,7 @@ fi
|
|
|
61
61
|
|
|
62
62
|
When `--only` is set, also set `FROM_PHASE` to the same value so existing filter logic applies.
|
|
63
63
|
|
|
64
|
-
When `--interactive` is set, discuss runs inline with questions (not auto-answered). On
|
|
64
|
+
When `--interactive` is set, discuss runs inline with questions (not auto-answered). On Codex, where a backgrounded agent can still spawn subagents, plan and execute are dispatched as background agents — keeping the main context lean (only discuss conversations accumulate) and enabling overlap. On every other runtime (Claude Code and all other non-Codex runtimes), backgrounded agents cannot reliably nest subagents, so plan and execute run inline to preserve worktree isolation and independent verification, and phases run sequentially with their work accumulating in the main context. Either way, user input is preserved on all design decisions.
|
|
65
65
|
|
|
66
66
|
When `PLAN_STRATEGY=converge`, the planning step MUST invoke the plan-review convergence workflow instead of `gsd-plan-phase`. `--cross-ai` is an alias for `--converge`. Forward `CONVERGENCE_ARGS` exactly as parsed so reviewer flags and `--max-cycles N` retain the same meaning as they have on `/gsd:plan-review-convergence`.
|
|
67
67
|
|
|
@@ -111,7 +111,7 @@ Display startup banner:
|
|
|
111
111
|
If `ONLY_PHASE` is set, display: `Single phase mode: Phase ${ONLY_PHASE}`
|
|
112
112
|
Else if `FROM_PHASE` is set, display: `Starting from phase ${FROM_PHASE}`
|
|
113
113
|
If `TO_PHASE` is set, display: `Stopping after phase ${TO_PHASE}`
|
|
114
|
-
If `INTERACTIVE` is set, display: `Mode: Interactive (discuss inline, plan+execute
|
|
114
|
+
If `INTERACTIVE` is set, display: `Mode: Interactive (discuss inline, plan+execute inline — background on Codex only)`
|
|
115
115
|
If `PLAN_STRATEGY` is `converge`, display: `Planning: Plan-review convergence enabled`
|
|
116
116
|
|
|
117
117
|
</step>
|
|
@@ -357,27 +357,13 @@ UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
|
|
|
357
357
|
|
|
358
358
|
**3b. Plan**
|
|
359
359
|
|
|
360
|
-
**If `INTERACTIVE` is set:** Background dispatch is only safe where a backgrounded agent can still
|
|
360
|
+
**If `INTERACTIVE` is set:** Background dispatch is only safe on a runtime where a backgrounded agent can still nest the pipeline's subagents (plan-checker / worktree executors / verifier). Among supported runtimes only **Codex** (`spawn_agent`) can do this; Claude Code's backgrounded agents have no `Agent`/`Task` tool, and every other runtime either prohibits nested subagents or disables them by default. So run **inline** everywhere except Codex, which is dispatched in the background. Resolve the runtime first:
|
|
361
361
|
|
|
362
362
|
```bash
|
|
363
|
-
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
|
|
363
|
+
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
|
364
364
|
```
|
|
365
365
|
|
|
366
|
-
- **
|
|
367
|
-
|
|
368
|
-
- If `PLAN_STRATEGY=converge`:
|
|
369
|
-
|
|
370
|
-
```
|
|
371
|
-
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
- Otherwise (local planning):
|
|
375
|
-
|
|
376
|
-
```
|
|
377
|
-
Skill(skill="gsd-plan-phase", args="${PHASE_NUM}")
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
- **On other runtimes:** Dispatch plan as a background agent to keep the main context lean. While plan runs, the workflow can immediately start discussing the next phase (see step 4).
|
|
366
|
+
- **If `RUNTIME` is `codex`:** Dispatch plan as a background agent to keep the main context lean. While plan runs, the workflow can immediately start discussing the next phase (see step 4).
|
|
381
367
|
|
|
382
368
|
- If `PLAN_STRATEGY=converge`, print: `◆ Spawning background plan-convergence loop for phase ${PHASE_NUM}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
|
|
383
369
|
|
|
@@ -401,6 +387,20 @@ RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo
|
|
|
401
387
|
|
|
402
388
|
Store the agent task_id. After discuss for the next phase completes (or if no next phase), wait for the plan agent to finish before proceeding to execute.
|
|
403
389
|
|
|
390
|
+
- **Otherwise (Claude Code or any other non-Codex runtime):** Run plan **inline** (do NOT background) so the plan-checker runs. The next phase's discuss does not overlap planning here — correctness over overlap.
|
|
391
|
+
|
|
392
|
+
- If `PLAN_STRATEGY=converge`:
|
|
393
|
+
|
|
394
|
+
```
|
|
395
|
+
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
- Otherwise (local planning):
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
Skill(skill="gsd-plan-phase", args="${PHASE_NUM}")
|
|
402
|
+
```
|
|
403
|
+
|
|
404
404
|
**If `INTERACTIVE` is NOT set (default):** Run plan inline.
|
|
405
405
|
|
|
406
406
|
If `PLAN_STRATEGY=converge`, run the convergence loop:
|
|
@@ -419,19 +419,13 @@ Verify plan produced output — re-run `init phase-op` and check `has_plans`. If
|
|
|
419
419
|
|
|
420
420
|
**3c. Execute**
|
|
421
421
|
|
|
422
|
-
**If `INTERACTIVE` is set:** Wait for the plan agent to complete (if not already) and verify plans exist. Background dispatch is only safe where a backgrounded agent can still
|
|
422
|
+
**If `INTERACTIVE` is set:** Wait for the plan agent to complete (if not already) and verify plans exist. Background dispatch is only safe on a runtime where a backgrounded agent can still nest the pipeline's subagents (plan-checker / worktree executors / verifier). Among supported runtimes only **Codex** (`spawn_agent`) can do this; Claude Code's backgrounded agents have no `Agent`/`Task` tool, and every other runtime either prohibits nested subagents or disables them by default. So run **inline** everywhere except Codex, which is dispatched in the background. Resolve the runtime first:
|
|
423
423
|
|
|
424
424
|
```bash
|
|
425
|
-
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
- **On Claude Code (`RUNTIME` is `claude`):** Run execute **inline** (do NOT background) so worktree isolation and verification run:
|
|
429
|
-
|
|
430
|
-
```
|
|
431
|
-
Skill(skill="gsd-execute-phase", args="${PHASE_NUM} --no-transition")
|
|
425
|
+
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
|
432
426
|
```
|
|
433
427
|
|
|
434
|
-
- **
|
|
428
|
+
- **If `RUNTIME` is `codex`:** Dispatch execute as a background agent:
|
|
435
429
|
|
|
436
430
|
```
|
|
437
431
|
Agent(
|
|
@@ -443,6 +437,12 @@ Agent(
|
|
|
443
437
|
|
|
444
438
|
Store the agent task_id. The workflow can now start discussing the next phase while this phase executes in the background. Before starting post-execution routing for this phase, wait for the execute agent to complete.
|
|
445
439
|
|
|
440
|
+
- **Otherwise (Claude Code or any other non-Codex runtime):** Run execute **inline** (do NOT background) so worktree isolation and verification run:
|
|
441
|
+
|
|
442
|
+
```
|
|
443
|
+
Skill(skill="gsd-execute-phase", args="${PHASE_NUM} --no-transition")
|
|
444
|
+
```
|
|
445
|
+
|
|
446
446
|
**If `INTERACTIVE` is NOT set (default):** Run execute inline as before.
|
|
447
447
|
|
|
448
448
|
```
|
|
@@ -656,12 +656,12 @@ Check for blockers in the Blockers/Concerns section. If blockers are found, go t
|
|
|
656
656
|
|
|
657
657
|
If incomplete phases remain: proceed to next phase, loop back to execute_phase.
|
|
658
658
|
|
|
659
|
-
**Interactive mode overlap:** When `INTERACTIVE` is set, the iterate step enables pipeline parallelism **on
|
|
659
|
+
**Interactive mode overlap:** When `INTERACTIVE` is set, the iterate step enables pipeline parallelism **on Codex** (on every other runtime, plan/execute run inline — see 3b/3c — so there is no overlap and phases run sequentially):
|
|
660
660
|
1. After discuss completes for Phase N, dispatch plan+execute as background agents
|
|
661
661
|
2. Immediately start discuss for Phase N+1 (the next incomplete phase) while Phase N builds
|
|
662
662
|
3. Before starting plan for Phase N+1, wait for Phase N's execute agent to complete and handle its post-execution routing (verification, gap closure, etc.)
|
|
663
663
|
|
|
664
|
-
This means the user is always answering discuss questions (lightweight, interactive) while the heavy work (planning, code generation) runs in the background. The main context only accumulates discuss conversations — plan and execute contexts are isolated in their agents. (On Claude Code, plan and execute run inline, so they run sequentially and their work accumulates in the main context.)
|
|
664
|
+
This means the user is always answering discuss questions (lightweight, interactive) while the heavy work (planning, code generation) runs in the background. The main context only accumulates discuss conversations — plan and execute contexts are isolated in their agents. (On Claude Code and all other non-Codex runtimes, plan and execute run inline, so they run sequentially and their work accumulates in the main context.)
|
|
665
665
|
|
|
666
666
|
If all phases complete, proceed to lifecycle step.
|
|
667
667
|
|
|
@@ -873,9 +873,9 @@ When any phase operation fails or a blocker is detected, present 3 options via A
|
|
|
873
873
|
- [ ] `--to N` handle_blocker resume message preserves --to flag
|
|
874
874
|
- [ ] `--to N` skips lifecycle when not all milestone phases complete
|
|
875
875
|
- [ ] `--interactive` runs discuss inline via gsd-discuss-phase (asks questions, waits for user)
|
|
876
|
-
- [ ] `--interactive` dispatches plan and execute as background agents on
|
|
877
|
-
- [ ] `--interactive` enables pipeline parallelism (discuss Phase N+1 while Phase N builds) on
|
|
878
|
-
- [ ] `--interactive` main context only accumulates discuss conversations on
|
|
876
|
+
- [ ] `--interactive` dispatches plan and execute as background agents on Codex (the only runtime where a backgrounded agent can nest subagents); runs them inline on all other runtimes
|
|
877
|
+
- [ ] `--interactive` enables pipeline parallelism (discuss Phase N+1 while Phase N builds) on Codex; phases run sequentially on all other runtimes
|
|
878
|
+
- [ ] `--interactive` main context only accumulates discuss conversations on Codex (on all other runtimes, inline plan/execute also accumulate)
|
|
879
879
|
- [ ] `--interactive` waits for background agents before post-execution routing
|
|
880
880
|
- [ ] `--interactive` compatible with `--only`, `--from`, and `--to` flags
|
|
881
881
|
- [ ] `--converge` routes planning through `gsd-plan-review-convergence`
|
|
@@ -59,7 +59,12 @@ gaps = [
|
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
_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 "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$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
|
|
62
|
-
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees 2>/dev/null || echo "true")
|
|
62
|
+
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
|
|
63
|
+
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
|
64
|
+
if [ "$RUNTIME" != "claude" ] && [ "$USE_WORKTREES" != "false" ]; then
|
|
65
|
+
echo "FATAL: git worktree isolation (isolation=\"worktree\") is unsupported on runtime '$RUNTIME' — it would run executor agents unisolated against the main checkout. Set workflow.use_worktrees=false." >&2
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
63
68
|
```
|
|
64
69
|
|
|
65
70
|
**Report diagnosis plan to user:**
|
|
@@ -91,13 +91,13 @@ Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelizat
|
|
|
91
91
|
Read runtime/worktree config and fail closed before any executor dispatch:
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
|
-
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
|
|
95
|
-
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees 2>/dev/null || echo "true")
|
|
94
|
+
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
|
95
|
+
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
|
|
96
96
|
EXECUTOR_STALL_INTERVAL_MINUTES=$(gsd_run query config-get executor.stall_detect_interval_minutes 2>/dev/null || echo "5")
|
|
97
97
|
EXECUTOR_STALL_THRESHOLD_MINUTES=$(gsd_run query config-get executor.stall_threshold_minutes 2>/dev/null || echo "10")
|
|
98
98
|
|
|
99
|
-
if [ "$RUNTIME"
|
|
100
|
-
echo "FATAL:
|
|
99
|
+
if [ "$RUNTIME" != "claude" ] && [ "$USE_WORKTREES" != "false" ]; then
|
|
100
|
+
echo "FATAL: git worktree isolation (isolation=\"worktree\") is unsupported on runtime '$RUNTIME' — it would run executor agents unisolated against the main checkout. Set workflow.use_worktrees=false." >&2
|
|
101
101
|
exit 1
|
|
102
102
|
fi
|
|
103
103
|
# Sweep orphaned locked worktrees from prior crashed sessions before spawning executors (#3707).
|
|
@@ -113,7 +113,7 @@ if [ "$RUNTIME" = "claude" ] && [ "$USE_WORKTREES" != "false" ]; then
|
|
|
113
113
|
fi
|
|
114
114
|
fi
|
|
115
115
|
```
|
|
116
|
-
Codex maps subagents to `spawn_agent`,
|
|
116
|
+
`isolation="worktree"` is a Claude-Code-specific agent primitive; no other runtime can honor it (Codex maps subagents to `spawn_agent`, others prohibit or omit worktree binding). Failing closed prevents main-checkout edits while the workflow believes agents are isolated.
|
|
117
117
|
|
|
118
118
|
If the project uses git submodules, worktree isolation is unsafe **only when a plan touches a submodule path** — the executor commit protocol cannot correctly handle submodule commits inside isolated worktrees. The previous behavior unconditionally disabled worktree isolation whenever `.gitmodules` existed, which penalised every plan in a submodule project even when the plan was nowhere near a submodule. Compute submodule paths once and intersect them per-plan with the plan's declared `files_modified` frontmatter.
|
|
119
119
|
|
|
@@ -493,6 +493,8 @@ increases monotonically across waves. `{status}` is `complete` (success),
|
|
|
493
493
|
|
|
494
494
|
@~/.claude/gsd-core/references/execute-phase-wave-guard.md
|
|
495
495
|
|
|
496
|
+
@~/.claude/gsd-core/references/execute-phase-context-guard.md
|
|
497
|
+
|
|
496
498
|
1. **Intra-wave files_modified overlap check (BEFORE spawning):**
|
|
497
499
|
|
|
498
500
|
Before spawning any agents for this wave, inspect the `files_modified` list of all plans
|
|
@@ -685,7 +687,7 @@ increases monotonically across waves. `{status}` is `complete` (success),
|
|
|
685
687
|
)
|
|
686
688
|
```
|
|
687
689
|
|
|
688
|
-
After each `Agent()` returns, parse executor-returned worktree metadata (`<worktree_metadata>`) before harness metadata, then
|
|
690
|
+
After each `Agent()` returns, parse executor-returned worktree metadata (`<worktree_metadata>`) before harness metadata, then record the `{agent_id, worktree_path, branch, expected_base}` entry with `gsd_run query worktree.record-agent --manifest "$WAVE_WORKTREE_MANIFEST" --agent-id … --path … --branch … --base …`. The verb validates every field at write time using the same rules the `cleanup-wave` reader enforces (write-strict `--agent-id`), failing loudly with a non-zero exit and recovery hint rather than appending an under-populated entry the reader would later drop silently. On a non-zero exit or any missing field: stop and ask for recovery instead of scanning worktrees.
|
|
689
691
|
|
|
690
692
|
> **Worktree recovery policy (#48 + #1292):** See `execute-phase/steps/worktree-recovery-policy.md` — FAIL-CLOSED rule for base/HEAD-namespace mismatches AND isolated-run fail-safe recovery.
|
|
691
693
|
|
|
@@ -394,6 +394,16 @@ List pending todos and select one to work on.
|
|
|
394
394
|
Usage: `/gsd:capture --list`
|
|
395
395
|
Usage: `/gsd:capture --list api`
|
|
396
396
|
|
|
397
|
+
**`/gsd:capture --list-seeds [status]`**
|
|
398
|
+
List and audit captured seeds (read-only).
|
|
399
|
+
|
|
400
|
+
- Lists all seeds with ID, status, scope, trigger, and title
|
|
401
|
+
- Optional status filter (e.g., `/gsd:capture --list-seeds dormant`)
|
|
402
|
+
- Does not modify any seed — enrich with `/gsd:capture --seed --enrich SEED-NNN`
|
|
403
|
+
|
|
404
|
+
Usage: `/gsd:capture --list-seeds`
|
|
405
|
+
Usage: `/gsd:capture --list-seeds dormant`
|
|
406
|
+
|
|
397
407
|
### User Acceptance Testing
|
|
398
408
|
|
|
399
409
|
**`/gsd:verify-work [phase]`**
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<purpose>
|
|
2
|
+
List captured seeds for browsing and audit, with an optional status filter. Read-only — never mutates seeds.
|
|
3
|
+
</purpose>
|
|
4
|
+
|
|
5
|
+
<required_reading>
|
|
6
|
+
Read all files referenced by the invoking prompt's execution_context before starting.
|
|
7
|
+
</required_reading>
|
|
8
|
+
|
|
9
|
+
<process>
|
|
10
|
+
|
|
11
|
+
<step name="load_seeds">
|
|
12
|
+
Load seed context. An optional status filter (e.g. `dormant`, `active`, `triggered`) may follow `--list-seeds`.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
_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 "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$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
|
|
16
|
+
SEEDS=$(gsd_run list-seeds "$STATUS_FILTER")
|
|
17
|
+
if [[ "$SEEDS" == @file:* ]]; then SEEDS=$(cat "${SEEDS#@file:}"); fi
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Replace `$STATUS_FILTER` with the filter token from `$ARGUMENTS` if one was given, otherwise omit it.
|
|
21
|
+
|
|
22
|
+
Extract from the JSON: `count`, `seeds[]` (each has `seed_id`, `status`, `scope`, `trigger_when`, `planted`, `title`), and `summary` (a `{ status: count }` map).
|
|
23
|
+
</step>
|
|
24
|
+
|
|
25
|
+
<step name="empty_case">
|
|
26
|
+
If `count` is 0:
|
|
27
|
+
```
|
|
28
|
+
No seeds found.
|
|
29
|
+
|
|
30
|
+
Plant one with /gsd:capture --seed "<forward-looking idea>".
|
|
31
|
+
```
|
|
32
|
+
(If a status filter was given and nothing matched, say so: `No seeds with status "<filter>".`) Exit.
|
|
33
|
+
</step>
|
|
34
|
+
|
|
35
|
+
<step name="render_table">
|
|
36
|
+
Render the seeds as a table, sorted by `seed_id` (already sorted by the tool). Truncate `trigger_when` and `title` to keep the table readable.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Seeds
|
|
40
|
+
─────────────────────────────────────────────────────────────────────
|
|
41
|
+
ID Status Scope Trigger Title
|
|
42
|
+
SEED-001 dormant large when websockets land Real-time collaboration
|
|
43
|
+
SEED-006 triggered medium MILE-04 planning Remove legacy auth crates
|
|
44
|
+
─────────────────────────────────────────────────────────────────────
|
|
45
|
+
<count> seeds (<summary rendered as "N status" pairs, e.g. "1 dormant, 1 triggered">)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then offer next actions as plain text (no mutation here):
|
|
49
|
+
```
|
|
50
|
+
- /gsd:capture --seed --enrich <ID> enrich a seed with trigger, why, and scope
|
|
51
|
+
- /gsd:capture --list-seeds <status> filter by status
|
|
52
|
+
```
|
|
53
|
+
</step>
|
|
54
|
+
|
|
55
|
+
</process>
|
|
56
|
+
|
|
57
|
+
<success_criteria>
|
|
58
|
+
- [ ] Seeds listed with ID, status, scope, trigger, and title
|
|
59
|
+
- [ ] Status filter applied when provided
|
|
60
|
+
- [ ] Empty / no-match case handled with guidance
|
|
61
|
+
- [ ] Summary line shows total and per-status counts
|
|
62
|
+
- [ ] No seed files were modified (read-only)
|
|
63
|
+
</success_criteria>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<purpose>
|
|
2
2
|
|
|
3
|
-
Interactive command center for managing a milestone from a single terminal. Shows a dashboard of all phases with visual status, dispatches discuss inline and plan/execute
|
|
3
|
+
Interactive command center for managing a milestone from a single terminal. Shows a dashboard of all phases with visual status, dispatches discuss inline and runs plan/execute inline (backgrounded only on Codex), and loops back to the dashboard after each action. Enables parallel phase work from one terminal.
|
|
4
4
|
|
|
5
5
|
</purpose>
|
|
6
6
|
|
|
@@ -45,7 +45,7 @@ Display startup banner:
|
|
|
45
45
|
{milestone_version} — {milestone_name}
|
|
46
46
|
{phase_count} phases · {completed_count} complete
|
|
47
47
|
|
|
48
|
-
✓ Discuss → inline ◆ Plan/Execute → background
|
|
48
|
+
✓ Discuss → inline ◆ Plan/Execute → inline (background on Codex)
|
|
49
49
|
Dashboard auto-refreshes when background work is active.
|
|
50
50
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
51
51
|
```
|
|
@@ -221,8 +221,8 @@ Go to exit step.
|
|
|
221
221
|
|
|
222
222
|
When the user selects a compound option, behavior depends on the runtime — the Plan Phase N / Execute Phase N handlers below resolve it via `gsd_run query config-get runtime`:
|
|
223
223
|
|
|
224
|
-
- **On
|
|
225
|
-
- **
|
|
224
|
+
- **On Codex:** **Spawn all background agents first** (plan/execute) — dispatch them in parallel using the Plan Phase N / Execute Phase N handlers below — then run the inline discuss; the background agents continue while you discuss.
|
|
225
|
+
- **Otherwise (Claude Code or any other non-Codex runtime):** a backgrounded agent cannot reliably nest the pipeline's subagents, so run the chosen plan/execute step(s) **inline** via their handlers below (in order), then run the inline discuss. There is no overlap.
|
|
226
226
|
|
|
227
227
|
Inline discuss:
|
|
228
228
|
|
|
@@ -244,27 +244,13 @@ After discuss completes, loop back to dashboard step.
|
|
|
244
244
|
|
|
245
245
|
### Plan Phase N
|
|
246
246
|
|
|
247
|
-
Planning runs autonomously. **First resolve the runtime.**
|
|
247
|
+
Planning runs autonomously. **First resolve the runtime.** Background dispatch is only safe on a runtime where a backgrounded agent can still nest the pipeline's subagents (plan-checker / worktree executors / verifier). Among supported runtimes only **Codex** (`spawn_agent`) can do this; Claude Code's backgrounded agents have no `Agent`/`Task` tool, and every other runtime either prohibits nested subagents or disables them by default. So run **inline** everywhere except Codex, which is dispatched in the background.
|
|
248
248
|
|
|
249
249
|
```bash
|
|
250
|
-
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
|
|
250
|
+
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
|
251
251
|
```
|
|
252
252
|
|
|
253
|
-
**If `RUNTIME` is `
|
|
254
|
-
|
|
255
|
-
```
|
|
256
|
-
Skill(skill="gsd-plan-phase", args="{N} --auto {manager_flags.plan}")
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
Display while it runs:
|
|
260
|
-
|
|
261
|
-
```
|
|
262
|
-
◆ Planning Phase {N}: {phase_name}... (runs inline so the plan-checker runs — the dashboard resumes when it returns, ~1–5 min; expected, not a freeze)
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
Then loop back to dashboard step.
|
|
266
|
-
|
|
267
|
-
**If `RUNTIME` is not `claude` (e.g. Codex):** Spawn a background agent that delegates to the Skill pipeline with any configured flags:
|
|
253
|
+
**If `RUNTIME` is `codex`:** Spawn a background agent that delegates to the Skill pipeline with any configured flags:
|
|
268
254
|
|
|
269
255
|
```
|
|
270
256
|
Agent(
|
|
@@ -286,7 +272,7 @@ Important: You are running in the background. Do NOT use AskUserQuestion — mak
|
|
|
286
272
|
)
|
|
287
273
|
```
|
|
288
274
|
|
|
289
|
-
> **ORCHESTRATOR RULE —
|
|
275
|
+
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above with `run_in_background=true`, do NOT do any planning work for this phase independently. Return to the dashboard immediately and wait for the background agent to report back. Only resume planning-related work when the subagent result is available.
|
|
290
276
|
|
|
291
277
|
Display:
|
|
292
278
|
|
|
@@ -296,29 +282,29 @@ Display:
|
|
|
296
282
|
|
|
297
283
|
Loop back to dashboard step.
|
|
298
284
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
Execution runs autonomously. **First resolve the runtime.** On Claude Code a backgrounded agent has no `Agent`/`Task` tool, so it cannot spawn the per-plan worktree-isolated executors or the verifier — backgrounding it there silently disables `workflow.use_worktrees` isolation and `workflow.verifier`. So run execute **inline** on Claude Code, and **background** it only on runtimes where a backgrounded agent can still nest subagents.
|
|
302
|
-
|
|
303
|
-
```bash
|
|
304
|
-
RUNTIME=$(gsd_run query config-get runtime --default claude 2>/dev/null || echo "claude")
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
**If `RUNTIME` is `claude` (Claude Code):** Run execute inline so worktree isolation and the verifier actually run — do NOT wrap it in `Agent(run_in_background=true, …)`:
|
|
285
|
+
**Otherwise (Claude Code or any other non-Codex runtime):** Run plan inline so the plan-checker and quality gates actually run — do NOT wrap it in `Agent(run_in_background=true, …)`:
|
|
308
286
|
|
|
309
287
|
```
|
|
310
|
-
Skill(skill="gsd-
|
|
288
|
+
Skill(skill="gsd-plan-phase", args="{N} --auto {manager_flags.plan}")
|
|
311
289
|
```
|
|
312
290
|
|
|
313
291
|
Display while it runs:
|
|
314
292
|
|
|
315
293
|
```
|
|
316
|
-
◆
|
|
294
|
+
◆ Planning Phase {N}: {phase_name}... (runs inline so the plan-checker runs — the dashboard resumes when it returns, ~1–5 min; expected, not a freeze)
|
|
317
295
|
```
|
|
318
296
|
|
|
319
297
|
Then loop back to dashboard step.
|
|
320
298
|
|
|
321
|
-
|
|
299
|
+
### Execute Phase N
|
|
300
|
+
|
|
301
|
+
Execution runs autonomously. **First resolve the runtime.** Background dispatch is only safe on a runtime where a backgrounded agent can still nest the pipeline's subagents (plan-checker / worktree executors / verifier). Among supported runtimes only **Codex** (`spawn_agent`) can do this; Claude Code's backgrounded agents have no `Agent`/`Task` tool, and every other runtime either prohibits nested subagents or disables them by default. So run **inline** everywhere except Codex, which is dispatched in the background.
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**If `RUNTIME` is `codex`:** Spawn a background agent that delegates to the Skill pipeline with any configured flags:
|
|
322
308
|
|
|
323
309
|
```
|
|
324
310
|
Agent(
|
|
@@ -340,7 +326,7 @@ Important: You are running in the background. Do NOT use AskUserQuestion — mak
|
|
|
340
326
|
)
|
|
341
327
|
```
|
|
342
328
|
|
|
343
|
-
> **ORCHESTRATOR RULE —
|
|
329
|
+
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above with `run_in_background=true`, do NOT do any execution work for this phase independently. Return to the dashboard immediately and wait for the background agent to report back. Only resume execution-related work when the subagent result is available.
|
|
344
330
|
|
|
345
331
|
Display:
|
|
346
332
|
|
|
@@ -350,6 +336,20 @@ Display:
|
|
|
350
336
|
|
|
351
337
|
Loop back to dashboard step.
|
|
352
338
|
|
|
339
|
+
**Otherwise (Claude Code or any other non-Codex runtime):** Run execute inline so worktree isolation and the verifier actually run — do NOT wrap it in `Agent(run_in_background=true, …)`:
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
Skill(skill="gsd-execute-phase", args="{N} {manager_flags.execute}")
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Display while it runs:
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
◆ Executing Phase {N}: {phase_name}... (runs inline so worktree isolation and verification run — the dashboard resumes when it returns; expected, not a freeze)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Then loop back to dashboard step.
|
|
352
|
+
|
|
353
353
|
</step>
|
|
354
354
|
|
|
355
355
|
<step name="background_completion">
|
|
@@ -422,8 +422,8 @@ Display final status with progress bar:
|
|
|
422
422
|
- [ ] Dependency resolution: blocked phases show which deps are missing
|
|
423
423
|
- [ ] Recommendations prioritize: execute > plan > discuss
|
|
424
424
|
- [ ] Discuss phases run inline via Skill() — interactive questions work
|
|
425
|
-
- [ ] Plan phases
|
|
426
|
-
- [ ] Execute phases
|
|
425
|
+
- [ ] Plan phases run inline (or as background Task agents on Codex) — dashboard resumes when complete
|
|
426
|
+
- [ ] Execute phases run inline (or as background Task agents on Codex) — dashboard resumes when complete
|
|
427
427
|
- [ ] Dashboard refreshes pick up changes from background agents via disk state
|
|
428
428
|
- [ ] Background agent completion triggers notification and dashboard refresh
|
|
429
429
|
- [ ] Background agent errors present retry/skip options
|
|
@@ -43,6 +43,162 @@ Commits: {AHEAD} ahead
|
|
|
43
43
|
```
|
|
44
44
|
</step>
|
|
45
45
|
|
|
46
|
+
<step name="handle_sub_repos">
|
|
47
|
+
Read the sub-repo list from config using the canonical key path — `planning.sub_repos`.
|
|
48
|
+
A non-zero exit code means the key is absent; treat that as "no sub-repos configured".
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
SUB_REPOS_JSON=$(gsd_run query config-get planning.sub_repos 2>/dev/null)
|
|
52
|
+
if [ $? -ne 0 ] || [ -z "$SUB_REPOS_JSON" ] || [ "$SUB_REPOS_JSON" = "null" ] || [ "$SUB_REPOS_JSON" = "[]" ]; then
|
|
53
|
+
: # Not configured or empty — skip to analyze_commits
|
|
54
|
+
fi
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Scan each sub-repo for uncommitted changes using node (always available — avoids undeclared
|
|
58
|
+
jq dependency). Write dirty repo names to a temp file so the list survives across
|
|
59
|
+
subsequent command executions:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
ROOT=$(git rev-parse --show-toplevel)
|
|
63
|
+
DIRTY_FILE=$(mktemp)
|
|
64
|
+
|
|
65
|
+
node -e "
|
|
66
|
+
const repos = JSON.parse(process.argv[1]);
|
|
67
|
+
const { execFileSync } = require('child_process');
|
|
68
|
+
const path = require('path');
|
|
69
|
+
const fs = require('fs');
|
|
70
|
+
const root = process.argv[2];
|
|
71
|
+
// realpath parity with the pr-subrepo seam's validatePath: resolve $ROOT through
|
|
72
|
+
// symlinks once so the containment check below compares real paths, not text.
|
|
73
|
+
let realRoot;
|
|
74
|
+
try { realRoot = fs.realpathSync(root); } catch (_) { realRoot = path.resolve(root); }
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const r of repos) {
|
|
77
|
+
// Reject before any git invocation: this scan runs on raw config values,
|
|
78
|
+
// ahead of the pr-subrepo seam's own validatePath guard. A traversal,
|
|
79
|
+
// embedded-newline, or symlink entry here would run git outside the
|
|
80
|
+
// workspace, or inject a spurious record into the dirty-file output.
|
|
81
|
+
if (typeof r !== 'string' || !/^[A-Za-z0-9._\/-]+$/.test(r)) continue;
|
|
82
|
+
// realpathSync follows symlinks — path.resolve only normalizes '..' textually,
|
|
83
|
+
// so an in-tree symlink pointing outside root would otherwise smuggle git out.
|
|
84
|
+
let resolved;
|
|
85
|
+
try { resolved = fs.realpathSync(path.resolve(realRoot, r)); } catch (_) { continue; }
|
|
86
|
+
if (resolved !== realRoot && !resolved.startsWith(realRoot + path.sep)) continue;
|
|
87
|
+
try {
|
|
88
|
+
const res = execFileSync('git', ['-C', resolved, 'status', '--porcelain'],
|
|
89
|
+
{ encoding: 'utf8', timeout: 10_000 });
|
|
90
|
+
// Exclude untracked-only repos: seam filters ?? lines, so detection must match.
|
|
91
|
+
const tracked = res.split('\n').filter(l => l.length > 0 && !l.startsWith('??'));
|
|
92
|
+
if (tracked.length > 0) out.push(r);
|
|
93
|
+
} catch (_) {}
|
|
94
|
+
}
|
|
95
|
+
fs.writeFileSync(process.argv[3], out.join('\n'));
|
|
96
|
+
" "$SUB_REPOS_JSON" "$ROOT" "$DIRTY_FILE"
|
|
97
|
+
|
|
98
|
+
DIRTY_REPOS=$(cat "$DIRTY_FILE")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If `$DIRTY_REPOS` is empty, remove the temp file and continue to `analyze_commits`.
|
|
102
|
+
|
|
103
|
+
Display dirty repos and prompt the user:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
Sub-repos with uncommitted changes:
|
|
107
|
+
backend
|
|
108
|
+
frontend
|
|
109
|
+
|
|
110
|
+
How should sub-repo changes be handled?
|
|
111
|
+
1. all — branch, commit (explicit files only), push -u, open companion PR per repo
|
|
112
|
+
2. select — choose which sub-repos to process
|
|
113
|
+
3. skip — ignore sub-repos, continue with root repo only
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
If the user chooses **skip**, remove the temp file and continue to `analyze_commits`.
|
|
117
|
+
|
|
118
|
+
For each selected sub-repo `$REPO_REL`, delegate all git work to the `pr-subrepo` query
|
|
119
|
+
seam — it stages explicit changed files (never `git add -A`), creates the branch,
|
|
120
|
+
commits, and pushes with `--set-upstream`. Branch names include the repo slug to avoid
|
|
121
|
+
colliding with the root `PR_BRANCH` that `create_pr_branch` creates later:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Replace path separators to make the name safe as a branch component
|
|
125
|
+
REPO_SAFE="${REPO_REL//\//-}"
|
|
126
|
+
SUB_BRANCH="${CURRENT_BRANCH}-${REPO_SAFE}-pr"
|
|
127
|
+
COMMIT_MSG="fix(${REPO_REL}): sync uncommitted changes for PR"
|
|
128
|
+
|
|
129
|
+
RESULT=$(gsd_run query pr-subrepo "$COMMIT_MSG" \
|
|
130
|
+
--repo "$REPO_REL" \
|
|
131
|
+
--branch "$SUB_BRANCH")
|
|
132
|
+
SUBREPO_EXIT=$?
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If the seam exited non-zero (stage/commit/push failure), report its error and move on to
|
|
136
|
+
the next selected sub-repo. **Do not run the companion-PR step below for this repo** —
|
|
137
|
+
the seam's stderr already explains the failure, and the "branch pushed" path would
|
|
138
|
+
otherwise contradict it:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
if [ "$SUBREPO_EXIT" -ne 0 ]; then
|
|
142
|
+
echo "pr-subrepo failed for $REPO_REL — see error above; skipping companion PR." >&2
|
|
143
|
+
fi
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Only when `$SUBREPO_EXIT` is `0`, parse the structured result with node and open the
|
|
147
|
+
companion PR. If `remote_slug` is null (non-GitHub remote), skip `gh pr create` and show
|
|
148
|
+
the push URL instead:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
REMOTE_SLUG=$(node -e "
|
|
152
|
+
try { console.log(JSON.parse(process.argv[1]).remote_slug || ''); } catch(_) {}
|
|
153
|
+
" "$RESULT")
|
|
154
|
+
|
|
155
|
+
if [ -n "$REMOTE_SLUG" ]; then
|
|
156
|
+
# Defense-in-depth: $REPO_REL was already validated by the dirty-scan filter and
|
|
157
|
+
# the pr-subrepo seam's validatePath, but these are separate, independent git -C
|
|
158
|
+
# invocations on the same value. Resolve it through symlinks with the SAME realpath
|
|
159
|
+
# containment the seam uses (path.resolve alone would not catch a symlink escape),
|
|
160
|
+
# and run git against the validated absolute path rather than re-concatenating.
|
|
161
|
+
SUB_REPO_DIR=$(node -e "
|
|
162
|
+
const fs = require('fs'), path = require('path');
|
|
163
|
+
try {
|
|
164
|
+
const realRoot = fs.realpathSync(process.argv[1]);
|
|
165
|
+
const resolved = fs.realpathSync(path.resolve(realRoot, process.argv[2]));
|
|
166
|
+
if (resolved !== realRoot && !resolved.startsWith(realRoot + path.sep)) process.exit(1);
|
|
167
|
+
process.stdout.write(resolved);
|
|
168
|
+
} catch (_) { process.exit(1); }
|
|
169
|
+
" "$ROOT" "$REPO_REL" 2>/dev/null)
|
|
170
|
+
|
|
171
|
+
if [ -z "$SUB_REPO_DIR" ]; then
|
|
172
|
+
echo "Refusing unsafe sub-repo path: $REPO_REL" >&2
|
|
173
|
+
SUB_TARGET="$TARGET"
|
|
174
|
+
else
|
|
175
|
+
# Resolve base branch: use $TARGET if it exists in sub-repo, else fall back to
|
|
176
|
+
# the sub-repo's own default branch
|
|
177
|
+
if git -C "$SUB_REPO_DIR" ls-remote --exit-code --heads origin "$TARGET" \
|
|
178
|
+
> /dev/null 2>&1; then
|
|
179
|
+
SUB_TARGET="$TARGET"
|
|
180
|
+
else
|
|
181
|
+
SUB_TARGET=$(git -C "$SUB_REPO_DIR" remote show origin 2>/dev/null \
|
|
182
|
+
| awk '/HEAD branch/ {print $NF}')
|
|
183
|
+
SUB_TARGET="${SUB_TARGET:-main}"
|
|
184
|
+
fi
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
gh pr create \
|
|
188
|
+
--repo "$REMOTE_SLUG" \
|
|
189
|
+
--base "$SUB_TARGET" \
|
|
190
|
+
--head "$SUB_BRANCH" \
|
|
191
|
+
--title "$COMMIT_MSG" \
|
|
192
|
+
--body "Companion PR for root repo branch \`$CURRENT_BRANCH\`."
|
|
193
|
+
else
|
|
194
|
+
echo "No GitHub remote detected for $REPO_REL — branch pushed, open PR manually."
|
|
195
|
+
fi
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
After processing all selected sub-repos, remove the temp file and continue to
|
|
199
|
+
`analyze_commits` for the root repo.
|
|
200
|
+
</step>
|
|
201
|
+
|
|
46
202
|
<step name="analyze_commits">
|
|
47
203
|
Classify commits:
|
|
48
204
|
|
|
@@ -137,7 +137,12 @@ AGENT_SKILLS_VERIFIER=$(gsd_run query agent-skills gsd-verifier)
|
|
|
137
137
|
Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`.
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
|
-
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees 2>/dev/null || echo "true")
|
|
140
|
+
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
|
|
141
|
+
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
|
|
142
|
+
if [ "$RUNTIME" != "claude" ] && [ "$USE_WORKTREES" != "false" ]; then
|
|
143
|
+
echo "FATAL: git worktree isolation (isolation=\"worktree\") is unsupported on runtime '$RUNTIME' — it would run executor agents unisolated against the main checkout. Set workflow.use_worktrees=false." >&2
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
141
146
|
```
|
|
142
147
|
|
|
143
148
|
If `USE_WORKTREES` is not `"false"`, run a startup orphan sweep before spawning any executors. This reaps locked worktrees whose lock-owner process is dead, whose branch is merged into the default branch, and whose lock file mtime is older than 5 minutes. Running it at startup prevents accumulation of orphaned worktrees from prior sessions that exited without cleanup (#3707).
|