@opengsd/gsd-core 1.14.0 → 1.15.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 +1 -1
- package/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-code-fixer.compact.md +7 -6
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-debug-session-manager.compact.md +17 -2
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +13 -8
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +8 -7
- package/agents/gsd-planner.md +12 -8
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.md +155 -17
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +10 -9
- package/bin/install.js +642 -95
- package/commands/gsd/autonomous.md +2 -2
- package/commands/gsd/capture.md +1 -1
- package/commands/gsd/mempalace-capture.md +7 -3
- package/commands/gsd/plan-review-convergence.md +6 -6
- package/commands/gsd/progress.md +1 -1
- package/commands/gsd/quick-batch.md +1 -1
- package/commands/gsd/review.md +2 -3
- package/gsd-core/bin/gsd-tools.cjs +335 -22
- package/gsd-core/bin/lib/adr-parser.cjs +3 -1
- package/gsd-core/bin/lib/audit.cjs +81 -13
- package/gsd-core/bin/lib/capability-registry.cjs +82 -187
- package/gsd-core/bin/lib/capability-validator.cjs +0 -1
- package/gsd-core/bin/lib/check-command-router.cjs +101 -14
- package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
- package/gsd-core/bin/lib/commands.cjs +175 -42
- package/gsd-core/bin/lib/config-loader.cjs +65 -4
- package/gsd-core/bin/lib/config.cjs +33 -7
- package/gsd-core/bin/lib/decisions.cjs +30 -14
- package/gsd-core/bin/lib/frontmatter.cjs +13 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +207 -41
- package/gsd-core/bin/lib/install-engine.cjs +13 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
- package/gsd-core/bin/lib/milestone.cjs +18 -5
- package/gsd-core/bin/lib/model-resolver.cjs +159 -50
- package/gsd-core/bin/lib/phase-command-router.cjs +9 -1
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +109 -7
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase.cjs +227 -26
- package/gsd-core/bin/lib/plan-document.cjs +49 -1
- package/gsd-core/bin/lib/planning-document.cjs +459 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +18 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +8 -3
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +163 -3
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
- package/gsd-core/bin/lib/roadmap.cjs +251 -31
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +283 -31
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -33
- package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
- package/gsd-core/bin/lib/state-transition.cjs +39 -2
- package/gsd-core/bin/lib/state.cjs +42 -0
- package/gsd-core/bin/lib/surface.cjs +17 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
- package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
- package/gsd-core/bin/lib/uat.cjs +8 -0
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/verification.cjs +268 -15
- package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
- package/gsd-core/bin/lib/verify.cjs +132 -25
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
- package/gsd-core/bin/shared/config-defaults.manifest.json +3 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +11 -2
- package/gsd-core/references/tdd.md +27 -4
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/worktree-path-safety.md +321 -0
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +2 -2
- package/gsd-core/workflows/add-todo.md +3 -3
- package/gsd-core/workflows/ai-integration-phase.md +11 -3
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +16 -6
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +108 -22
- package/gsd-core/workflows/code-review.md +63 -46
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +1 -1
- package/gsd-core/workflows/complete-milestone.md +2 -2
- package/gsd-core/workflows/debug.md +3 -3
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update.md +3 -3
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +10 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +3 -3
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +36 -26
- package/gsd-core/workflows/execute-plan.md +5 -4
- package/gsd-core/workflows/explore.md +3 -3
- package/gsd-core/workflows/extract-learnings.md +2 -1
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -2
- package/gsd-core/workflows/help/modes/full.compact.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/import.md +2 -2
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +2 -2
- package/gsd-core/workflows/map-codebase.md +2 -2
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +2 -2
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +7 -7
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +24 -7
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +113 -13
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +1 -1
- package/gsd-core/workflows/quick.md +21 -9
- package/gsd-core/workflows/reapply-patches.md +9 -3
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +31 -16
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -2
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +4 -4
- package/gsd-core/workflows/ship.md +3 -2
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +44 -8
- package/gsd-core/workflows/ui-review.md +18 -4
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +7 -7
- package/gsd-core/workflows/validate-phase.md +3 -2
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +81 -16
- package/hooks/dist/gsd-agent-isolation-guard.js +24 -0
- package/hooks/dist/gsd-secret-read-guard.js +27 -1
- package/hooks/dist/gsd-statusline.js +70 -13
- package/hooks/dist/gsd-validate-commit.sh +63 -4
- package/hooks/gsd-agent-isolation-guard.js +24 -0
- package/hooks/gsd-secret-read-guard.js +27 -1
- package/hooks/gsd-statusline.js +70 -13
- package/hooks/gsd-validate-commit.sh +63 -4
- package/package.json +3 -2
- package/scripts/build-hooks.js +15 -6
- package/scripts/check-contract-drift.cjs +127 -11
- package/scripts/command-contract-helpers.cjs +3 -0
- package/scripts/docs-guard-registry.cjs +28 -0
- package/scripts/gen-loop-host-contract.cjs +69 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +14 -0
- package/scripts/lib/ndjson-reporter.cjs +3 -2
- package/scripts/lib/platform-conformance-tier.generated.cjs +11 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +28 -1
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-id-drift.cjs +290 -5
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +28 -9
- package/scripts/lint-workflow-shellcheck-baseline.json +15 -0
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/skills/gsd-autonomous/SKILL.md +2 -2
- package/skills/gsd-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +7 -3
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
- package/skills/gsd-progress/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +1 -1
- package/skills/gsd-review/SKILL.md +2 -3
- package/vscode/package.json +1 -1
|
@@ -16,11 +16,16 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
|
|
|
16
16
|
## 0. Initialize
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
_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}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; };
|
|
19
|
+
_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}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { _gsd_at "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; 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
|
|
20
20
|
RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "" 2>/dev/null || echo "")
|
|
21
|
+
PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
|
|
21
22
|
INIT=$(gsd_run query init.phase-op "${PHASE_ARG}")
|
|
22
23
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
23
24
|
AGENT_SKILLS_UI_REVIEWER=$(gsd_run query agent-skills gsd-ui-auditor)
|
|
25
|
+
# workflow.ui_interaction_capture (default false): read here and handed to the auditor
|
|
26
|
+
# through its <config> block — the agent carries no gsd_run resolver of its own.
|
|
27
|
+
INTERACTION_CAPTURE=$(gsd_run query config-get workflow.ui_interaction_capture --raw 2>/dev/null || echo "false")
|
|
28
|
+
[ "$INTERACTION_CAPTURE" = "true" ] || INTERACTION_CAPTURE="false"
|
|
24
29
|
```
|
|
25
30
|
|
|
26
31
|
**If `response_language` is set:** All user-facing output of this workflow — narration between tool calls, status updates, progress notes, findings, questions, prompts, and explanations — MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
|
|
@@ -46,8 +51,14 @@ UI_REVIEW_FILE=$(ls "${PHASE_DIR}"/*-UI-REVIEW.md 2>/dev/null | head -1)
|
|
|
46
51
|
|
|
47
52
|
**If `SUMMARY_FILES` empty:** Exit — "Phase {N} not executed. Run /gsd:execute-phase {N} first."
|
|
48
53
|
|
|
49
|
-
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** 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 non-Claude runtimes (OpenAI Codex,
|
|
50
|
-
**If `UI_REVIEW_FILE` non-empty:**
|
|
54
|
+
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** 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 non-Claude runtimes (OpenAI Codex, Antigravity, etc.) where `AskUserQuestion` is not available.
|
|
55
|
+
**If `UI_REVIEW_FILE` non-empty:**
|
|
56
|
+
|
|
57
|
+
**If `--auto`:** Auto-select "View" — keep the existing UI-REVIEW.md untouched and exit without
|
|
58
|
+
re-auditing. Log: `[auto] UI-REVIEW.md exists — reusing as-is.` "Re-audit" is the regenerating
|
|
59
|
+
choice; an unattended run reuses an existing artifact instead (#4776).
|
|
60
|
+
|
|
61
|
+
**Otherwise:** Use AskUserQuestion:
|
|
51
62
|
- header: "Existing UI Review"
|
|
52
63
|
- question: "UI-REVIEW.md already exists for Phase {N}."
|
|
53
64
|
- options:
|
|
@@ -94,10 +105,13 @@ ${AGENT_SKILLS_UI_REVIEWER}
|
|
|
94
105
|
<config>
|
|
95
106
|
phase_dir: {phase_dir}
|
|
96
107
|
padded_phase: {padded_phase}
|
|
108
|
+
interaction_capture: {interaction_capture}
|
|
97
109
|
</config>
|
|
98
110
|
```
|
|
99
111
|
|
|
100
|
-
Omit null file paths.
|
|
112
|
+
Omit null file paths. `interaction_capture` is always present — the `$INTERACTION_CAPTURE` value read in step 0, `true` only when
|
|
113
|
+
`workflow.ui_interaction_capture` is on; the auditor's `<screenshot_approach>` branches on it and
|
|
114
|
+
falls back to its Playwright-only static capture when it is `false` or no Chrome binary resolves.
|
|
101
115
|
|
|
102
116
|
<!-- #2508 runtime-aware-dispatch -->
|
|
103
117
|
|
|
@@ -64,7 +64,7 @@ unplanned phase from the roadmap (same logic as /gsd:plan-phase).
|
|
|
64
64
|
Load GSD phase context:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
_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}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; };
|
|
67
|
+
_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}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { _gsd_at "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; 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
|
|
68
68
|
INIT=$(gsd_run query init.plan-phase "$PHASE")
|
|
69
69
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
70
70
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<purpose>
|
|
2
|
-
Safe git revert workflow. Rolls back GSD phase or plan commits
|
|
2
|
+
Safe git revert workflow. Rolls back GSD phase or plan commits selected within the phase directory's own commit window with dependency checks and a confirmation gate. Uses git revert --no-commit (NEVER git reset) to preserve history.
|
|
3
3
|
</purpose>
|
|
4
4
|
|
|
5
5
|
<required_reading>
|
|
@@ -9,7 +9,7 @@ Safe git revert workflow. Rolls back GSD phase or plan commits using the phase m
|
|
|
9
9
|
|
|
10
10
|
<process>
|
|
11
11
|
```bash
|
|
12
|
-
_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}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; };
|
|
12
|
+
_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}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; _gsd_id_ok() { case "$("$1" runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') return 0;; *) return 1;; esac; }; _gsd_homes() { _gsd_at "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif _gsd_homes; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; [ -n "$_G" ] && _gsd_id_ok "$_G"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and no identity-proving gsd_run is on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; _gsd_id_ok gsd_run && GSD_IDENTITY_STATUS=ok; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; 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
|
|
13
13
|
RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "" 2>/dev/null || echo "")
|
|
14
14
|
```
|
|
15
15
|
|
|
@@ -67,7 +67,7 @@ Recent GSD commits:
|
|
|
67
67
|
3. ghi9012 fix(02-03): correct validation logic
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** 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 non-Claude runtimes (OpenAI Codex,
|
|
70
|
+
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** 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 non-Claude runtimes (OpenAI Codex, Antigravity, etc.) where `AskUserQuestion` is not available.
|
|
71
71
|
Use AskUserQuestion to ask:
|
|
72
72
|
- question: "Which commits to revert? Enter numbers (e.g., 1,3) or 'all'"
|
|
73
73
|
- header: "Select"
|
|
@@ -78,30 +78,339 @@ Parse the user's selection into COMMITS list.
|
|
|
78
78
|
|
|
79
79
|
**MODE=phase:**
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
Resolve the phase's own directory, then anchor the selection window on it. `find-phase`
|
|
82
|
+
resolves through `planningDir`, so under an active workstream this is that workstream's
|
|
83
|
+
phase directory — not the root's same-numbered one.
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
```bash
|
|
86
|
+
PHASE_DIR=$(gsd_run query find-phase "${TARGET_PHASE}" --raw 2>/dev/null)
|
|
87
|
+
# find-phase answers relative to the PROJECT ROOT -- gsd-tools resolves it before dispatch --
|
|
88
|
+
# not to this shell's cwd, so from a subdirectory a bare `git log -- "${PHASE_DIR}"` looks in
|
|
89
|
+
# the wrong place and every path-scoped git call below comes back empty. Take the root from the
|
|
90
|
+
# same owner and run those calls there. Unresolved, `.` keeps the behaviour at the root. A root
|
|
91
|
+
# in a DIFFERENT repository from this shell's is refused below, never used.
|
|
92
|
+
PROJECT_ROOT=$(gsd_run query planning inspect --pick generated_from.cwd --raw 2>/dev/null)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If `PHASE_DIR` is empty, the phase does not exist in the active scope:
|
|
96
|
+
```
|
|
97
|
+
Phase ${TARGET_PHASE} not found in the active planning scope. Nothing to revert.
|
|
98
|
+
```
|
|
99
|
+
Exit cleanly — do NOT fall back to an unbounded search.
|
|
100
|
+
|
|
101
|
+
**Refuse an ARCHIVED resolution — it selects the wrong milestone, not merely too few
|
|
102
|
+
commits.** `find-phase` searches the live `phases/` directory first, then every
|
|
103
|
+
`milestones/v<X.Y>-phases/` directory in ascending version order, and its ambiguity check
|
|
104
|
+
is scoped to a *single* directory — it does not span them (`cmdFindPhase`, `src/phase.cts`:
|
|
105
|
+
the `matches.length > 1` test sits inside the per-`searchDir` loop). A phase number that is
|
|
106
|
+
not live therefore resolves **silently to the oldest archived milestone that has one**,
|
|
107
|
+
with no warning. Anchoring there is wrong in both directions at once: the oldest commit
|
|
108
|
+
adding that path is the archival **move**, so the phase's real work predates the window and
|
|
109
|
+
falls outside it, while the window runs forward from that archival through every later
|
|
110
|
+
milestone — where the subject grep matches *their* same-numbered phase. Driven on a
|
|
111
|
+
two-archived-milestone fixture, `--phase 03` selected v2.0's `feat(03-01): add search index`
|
|
112
|
+
and excluded v1.0's own `feat(03-01): implement auth endpoint`. Feeding that to
|
|
113
|
+
`git revert` is the cross-milestone contamination this workflow exists to close, so it
|
|
114
|
+
fails closed.
|
|
115
|
+
|
|
116
|
+
**Two archive layouts exist, and `find-phase` searches only one of them.** The phase locator
|
|
117
|
+
(`listArchiveVersionDirs`, `src/phase-locator.cts`) enumerates both the flat
|
|
118
|
+
`milestones/v<X.Y>-phases/<phase>/` archive and the workstream archive
|
|
119
|
+
`milestones/ws-<name>-<date>/phases/<phase>/` that `workstream complete` writes. `cmdFindPhase`
|
|
120
|
+
builds its own search list and admits only the first (`/^v\d+.*-phases$/`), so a phase that lives
|
|
121
|
+
*only* in a `ws-*` archive resolves to nothing today and the not-found rule above fails closed.
|
|
122
|
+
The refusal below still names both layouts, so it keeps holding if `find-phase` is ever taught the
|
|
123
|
+
second one:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Match the ARCHIVE LAYOUTS, never the bare token `milestones`. A bare `*/milestones/*`
|
|
127
|
+
# REFUSES A LIVE PHASE whenever a workstream or project is itself named `milestones`
|
|
128
|
+
# (driven: GSD_WORKSTREAM=milestones resolves `.planning/workstreams/milestones/phases/03-live`,
|
|
129
|
+
# which that pattern classifies as archived). The two shapes are the locator's two:
|
|
130
|
+
# `v<X.Y>-phases/<phase>` and `ws-<name>-<date>/phases/<phase>`. Blanking PHASE_DIR is
|
|
131
|
+
# deliberate: the fail-closed rule below then also holds, so no path reaches selection even if
|
|
132
|
+
# this refusal's prose is not honored.
|
|
133
|
+
PHASE_DIR_ARCHIVED=""
|
|
134
|
+
case "${PHASE_DIR}" in
|
|
135
|
+
*/milestones/v[0-9]*-phases/*|milestones/v[0-9]*-phases/*|*/milestones/ws-*/phases/*|milestones/ws-*/phases/*)
|
|
136
|
+
PHASE_DIR_ARCHIVED="${PHASE_DIR}"; PHASE_DIR="" ;;
|
|
137
|
+
esac
|
|
138
|
+
# The phase directory must live in the SAME repository as the commits this workflow reverts. In a
|
|
139
|
+
# `sub_repos` project, .planning/ sits in a parent repository and the code in child ones; from a
|
|
140
|
+
# child, PROJECT_ROOT is the parent, and an anchor read there is a commit the child has never seen.
|
|
141
|
+
# Compare the COMMON git directories -- the object database -- physically resolved; a different one
|
|
142
|
+
# refuses. Common, not per-worktree: gsd-tools maps a linked worktree's planning to the MAIN
|
|
143
|
+
# worktree, which is the same repository and holds every commit the linked one does.
|
|
144
|
+
PHASE_DIR_FOREIGN=""
|
|
145
|
+
if [ -n "${PHASE_DIR}" ] && [ -n "${PROJECT_ROOT}" ]; then
|
|
146
|
+
_gd_here=$(_d=$(git rev-parse --git-common-dir 2>/dev/null) && [ -n "$_d" ] && cd "$_d" && pwd -P) || _gd_here=""
|
|
147
|
+
_gd_root=$(cd "${PROJECT_ROOT}" 2>/dev/null && _d=$(git rev-parse --git-common-dir 2>/dev/null) && [ -n "$_d" ] && cd "$_d" && pwd -P) || _gd_root=""
|
|
148
|
+
if [ -z "$_gd_here" ] || [ "$_gd_here" != "$_gd_root" ]; then
|
|
149
|
+
PHASE_DIR_FOREIGN="${PROJECT_ROOT}"; PHASE_DIR=""
|
|
150
|
+
fi
|
|
151
|
+
fi
|
|
152
|
+
# A LIVE path can still be a previous occupant's. `--diff-filter=A` does not follow renames,
|
|
153
|
+
# so re-creating a literal directory that an earlier milestone or workstream used anchors on
|
|
154
|
+
# the EARLIER occupant's add. Nothing is under milestones/ to refuse -- find-phase returned the
|
|
155
|
+
# live dir -- so ask the question the anchor depends on instead: did this exact path go EMPTY
|
|
156
|
+
# somewhere in HEAD's history and come back? Git history owns that answer for every way a path
|
|
157
|
+
# can be vacated -- a flat milestone archive, `workstream complete` moving the workstream into
|
|
158
|
+
# milestones/ws-<name>-<date>/, a removed phase re-added under the same slug -- where a layout
|
|
159
|
+
# glob answers only for the layouts it spells. `--no-renames` so a move-out reads as the deletion
|
|
160
|
+
# it is at this path; `-m` so a merge that emptied it is seen too; `ls-tree` at that commit is
|
|
161
|
+
# what separates "the directory went away" from an ordinary deleted plan file.
|
|
162
|
+
PHASE_DIR_REUSED=""; PHASE_DIR_LIVE=""
|
|
163
|
+
if [ -n "${PHASE_DIR}" ]; then
|
|
164
|
+
for _c in $(git -C "${PROJECT_ROOT:-.}" log -m --no-renames --diff-filter=D --format=%H -- "${PHASE_DIR}" 2>/dev/null); do
|
|
165
|
+
if [ -z "$(git -C "${PROJECT_ROOT:-.}" ls-tree -d "$_c" -- "${PHASE_DIR}" 2>/dev/null)" ]; then
|
|
166
|
+
PHASE_DIR_LIVE="${PHASE_DIR}"; PHASE_DIR_REUSED="$_c"; PHASE_DIR=""; break
|
|
167
|
+
fi
|
|
168
|
+
done
|
|
169
|
+
fi
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If `PHASE_DIR_ARCHIVED` is non-empty, stop — this message, not the not-found one:
|
|
173
|
+
```
|
|
174
|
+
Phase ${TARGET_PHASE} resolves to an ARCHIVED milestone directory (${PHASE_DIR_ARCHIVED}).
|
|
175
|
+
Refusing: the anchor there is the archival commit, so the window would span later
|
|
176
|
+
milestones and select their same-numbered phase instead of this one.
|
|
177
|
+
Use /gsd:undo --last N and select commits explicitly.
|
|
178
|
+
```
|
|
179
|
+
If `PHASE_DIR_FOREIGN` is non-empty, stop with its own message:
|
|
180
|
+
```
|
|
181
|
+
Phase ${TARGET_PHASE} is planned in the repository at ${PHASE_DIR_FOREIGN}, not the one this
|
|
182
|
+
command is running in. Refusing: that repository's history cannot bound commits in this one.
|
|
183
|
+
Use /gsd:undo --last N here and select commits explicitly.
|
|
184
|
+
```
|
|
185
|
+
And if `PHASE_DIR_REUSED` is non-empty, stop with its own message:
|
|
186
|
+
```
|
|
187
|
+
Phase ${TARGET_PHASE} resolves to ${PHASE_DIR_LIVE}, but that path was emptied by commit
|
|
188
|
+
${PHASE_DIR_REUSED} and re-created later. Refusing: the first commit adding this path belongs
|
|
189
|
+
to the earlier occupant, so the window would open there and select its commits too.
|
|
190
|
+
Use /gsd:undo --last N and select commits explicitly.
|
|
191
|
+
```
|
|
192
|
+
Exit cleanly in every case.
|
|
193
|
+
|
|
194
|
+
Derive the selection window from `PHASE_DIR` (the `#3995` anchor, shared with
|
|
195
|
+
`code-review.md`): the base is the parent of the first commit that added anything under
|
|
196
|
+
the phase's own directory, and the tip is `HEAD`.
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
PHASE_START=$(git -C "${PROJECT_ROOT:-.}" log --format="%H" --diff-filter=A -- "${PHASE_DIR}" 2>/dev/null | tail -1)
|
|
200
|
+
# Only a commit in HEAD's own history may anchor. A SHA from another repository has no resolvable
|
|
201
|
+
# parent here, which the root-commit arm below would read as "root commit" and select all of HEAD;
|
|
202
|
+
# one from another worktree's branch that HEAD does not contain bounds nothing on this branch.
|
|
203
|
+
if [ -n "$PHASE_START" ] && ! git merge-base --is-ancestor "$PHASE_START" HEAD 2>/dev/null; then PHASE_START=""; fi
|
|
204
|
+
UNDO_RANGE=""
|
|
205
|
+
if [ -n "$PHASE_START" ]; then
|
|
206
|
+
if git rev-parse "${PHASE_START}^" >/dev/null 2>&1; then
|
|
207
|
+
UNDO_RANGE="${PHASE_START}^..HEAD"
|
|
208
|
+
else
|
|
209
|
+
# PHASE_START is the root commit — it has no parent to exclude. `${PHASE_START}..HEAD`
|
|
210
|
+
# would drop PHASE_START ITSELF, refusing a legitimate revert of the first commit.
|
|
211
|
+
UNDO_RANGE="HEAD"
|
|
212
|
+
fi
|
|
213
|
+
fi
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Fail closed when no anchor resolves.** If `UNDO_RANGE` is empty, stop:
|
|
217
|
+
```
|
|
218
|
+
Cannot determine a reliable commit window for phase ${TARGET_PHASE} (no commit adds ${PHASE_DIR}).
|
|
219
|
+
Re-run with /gsd:undo --last N and select commits explicitly.
|
|
220
|
+
```
|
|
221
|
+
Exit cleanly. An unbounded repository-wide search is never the fallback — that is the
|
|
222
|
+
defect this anchor replaces.
|
|
223
|
+
|
|
224
|
+
Select within the window. **No `--all`:** only commits reachable from `HEAD` may be
|
|
225
|
+
reverted, because reverting a commit that is not in the current branch's history stages a
|
|
226
|
+
change the branch never received.
|
|
227
|
+
|
|
228
|
+
**Selection is a structural parse, not a substring grep (#4661).** The old
|
|
229
|
+
`grep -E "\(0*${TARGET_PHASE}(-[0-9]+)?\):"` interpolated the id straight into a live ERE
|
|
230
|
+
(`.`/`+` in the id became wildcard/quantifier metacharacters), was unanchored (a commit that
|
|
231
|
+
only MENTIONED a scope was wrongly selected as a DECLARATION of it), and disagreed with
|
|
232
|
+
plan mode's own grep on a breaking-change subject (`feat(03-01)!: ...`). `select-revert-commits`
|
|
233
|
+
closes all three: it validates `${TARGET_PHASE}` (refusing before any git command runs on an
|
|
234
|
+
invalid id), then parses each candidate commit's subject through the SAME anchored
|
|
235
|
+
`type(scope)!:` conventional-commit header the changelog/PR-title gate uses and compares the
|
|
236
|
+
DECLARED scope to `${TARGET_PHASE}` by exact string equality — plus a `${TARGET_PHASE}-` prefix,
|
|
237
|
+
so a plan-scoped commit within this phase still selects, matching the old grep's `-NN` tolerance.
|
|
238
|
+
It also zero-pads a plain unpadded `${TARGET_PHASE}` (e.g. `3` → `03`) before comparing, matching
|
|
239
|
+
the old grep's `0*` tolerance for a digit-first id — never for a letter-first custom id
|
|
240
|
+
(`PROJ-42`), where zero-padding would strip the meaningful prefix instead of matching it.
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
COMMITS=$(gsd_run query select-revert-commits --phase "${TARGET_PHASE}" --range "${UNDO_RANGE}" --raw || true)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Use matching commits as COMMITS.
|
|
85
247
|
|
|
86
|
-
If the
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
248
|
+
**Report truncation, never truncate silently.** If the selection exceeds 50 commits, show
|
|
249
|
+
the count and stop rather than capping — a partial phase revert leaves a worse tree state
|
|
250
|
+
than either reverting the phase or not:
|
|
251
|
+
```
|
|
252
|
+
Phase ${TARGET_PHASE} selects ${N} commits (>50). Refusing to revert a partial phase.
|
|
253
|
+
Use /gsd:undo --plan NN-MM per plan, or /gsd:undo --last N.
|
|
254
|
+
```
|
|
93
255
|
|
|
94
256
|
---
|
|
95
257
|
|
|
96
258
|
**MODE=plan:**
|
|
97
259
|
|
|
98
|
-
|
|
260
|
+
Extract the phase number from `TARGET_PLAN` (the `NN` of `NN-MM`) and derive the same
|
|
261
|
+
window from that phase's own directory — a plan number is unique within its phase, and a
|
|
262
|
+
phase number only within its milestone and workstream.
|
|
263
|
+
|
|
99
264
|
```bash
|
|
100
|
-
|
|
265
|
+
PLAN_PHASE="${TARGET_PLAN%%-*}"
|
|
266
|
+
PHASE_DIR=$(gsd_run query find-phase "${PLAN_PHASE}" --raw 2>/dev/null)
|
|
267
|
+
# Project-root-relative, exactly as in MODE=phase: path-scoped git calls run from the root.
|
|
268
|
+
PROJECT_ROOT=$(gsd_run query planning inspect --pick generated_from.cwd --raw 2>/dev/null)
|
|
269
|
+
# Same archived-resolution refusal as MODE=phase, and for the same reason — an archived
|
|
270
|
+
# anchor selects a LATER milestone's same-numbered phase. Blanking PHASE_DIR keeps the
|
|
271
|
+
# fail-closed rule below load-bearing.
|
|
272
|
+
PHASE_DIR_ARCHIVED=""
|
|
273
|
+
case "${PHASE_DIR}" in
|
|
274
|
+
*/milestones/v[0-9]*-phases/*|milestones/v[0-9]*-phases/*|*/milestones/ws-*/phases/*|milestones/ws-*/phases/*)
|
|
275
|
+
PHASE_DIR_ARCHIVED="${PHASE_DIR}"; PHASE_DIR="" ;;
|
|
276
|
+
esac
|
|
277
|
+
# Same-repository refusal as MODE=phase: a phase planned in another repository cannot anchor here.
|
|
278
|
+
PHASE_DIR_FOREIGN=""
|
|
279
|
+
if [ -n "${PHASE_DIR}" ] && [ -n "${PROJECT_ROOT}" ]; then
|
|
280
|
+
_gd_here=$(_d=$(git rev-parse --git-common-dir 2>/dev/null) && [ -n "$_d" ] && cd "$_d" && pwd -P) || _gd_here=""
|
|
281
|
+
_gd_root=$(cd "${PROJECT_ROOT}" 2>/dev/null && _d=$(git rev-parse --git-common-dir 2>/dev/null) && [ -n "$_d" ] && cd "$_d" && pwd -P) || _gd_root=""
|
|
282
|
+
if [ -z "$_gd_here" ] || [ "$_gd_here" != "$_gd_root" ]; then
|
|
283
|
+
PHASE_DIR_FOREIGN="${PROJECT_ROOT}"; PHASE_DIR=""
|
|
284
|
+
fi
|
|
285
|
+
fi
|
|
286
|
+
# A LIVE path can still be a previous occupant's -- same question, same answer as MODE=phase:
|
|
287
|
+
# a path that went EMPTY in HEAD's history and came back anchors on the earlier occupant's add,
|
|
288
|
+
# whatever vacated it. Ask git, not a layout glob. Fail closed; `--last N` is the route.
|
|
289
|
+
PHASE_DIR_REUSED=""; PHASE_DIR_LIVE=""
|
|
290
|
+
if [ -n "${PHASE_DIR}" ]; then
|
|
291
|
+
for _c in $(git -C "${PROJECT_ROOT:-.}" log -m --no-renames --diff-filter=D --format=%H -- "${PHASE_DIR}" 2>/dev/null); do
|
|
292
|
+
if [ -z "$(git -C "${PROJECT_ROOT:-.}" ls-tree -d "$_c" -- "${PHASE_DIR}" 2>/dev/null)" ]; then
|
|
293
|
+
PHASE_DIR_LIVE="${PHASE_DIR}"; PHASE_DIR_REUSED="$_c"; PHASE_DIR=""; break
|
|
294
|
+
fi
|
|
295
|
+
done
|
|
296
|
+
fi
|
|
297
|
+
PHASE_START=$(git -C "${PROJECT_ROOT:-.}" log --format="%H" --diff-filter=A -- "${PHASE_DIR}" 2>/dev/null | tail -1)
|
|
298
|
+
# As in MODE=phase: an anchor outside HEAD's own history never reaches the root-commit arm.
|
|
299
|
+
if [ -n "$PHASE_START" ] && ! git merge-base --is-ancestor "$PHASE_START" HEAD 2>/dev/null; then PHASE_START=""; fi
|
|
300
|
+
UNDO_RANGE=""
|
|
301
|
+
if [ -n "$PHASE_START" ]; then
|
|
302
|
+
if git rev-parse "${PHASE_START}^" >/dev/null 2>&1; then
|
|
303
|
+
UNDO_RANGE="${PHASE_START}^..HEAD"
|
|
304
|
+
else
|
|
305
|
+
# PHASE_START is the root commit — it has no parent to exclude. `${PHASE_START}..HEAD`
|
|
306
|
+
# would drop PHASE_START ITSELF, refusing a legitimate revert of the first commit.
|
|
307
|
+
UNDO_RANGE="HEAD"
|
|
308
|
+
fi
|
|
309
|
+
fi
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Apply the same fail-closed rule as MODE=phase when `PHASE_DIR` or `UNDO_RANGE` is empty —
|
|
313
|
+
and the same three refusals, each with its own message, when `PHASE_DIR_ARCHIVED`,
|
|
314
|
+
`PHASE_DIR_FOREIGN` or `PHASE_DIR_REUSED` is non-empty — then select within the window.
|
|
315
|
+
|
|
316
|
+
**Same structural parse as MODE=phase (#4661), in plan mode:** the DECLARED scope must
|
|
317
|
+
equal `${TARGET_PLAN}` EXACTLY — no phase-prefix tolerance. This is what makes
|
|
318
|
+
`feat(03-01)!: breaking change` select identically in both modes, closing the disagreement
|
|
319
|
+
the old phase-mode/plan-mode grep pair had on a breaking-change subject. `${TARGET_PLAN}`
|
|
320
|
+
is validated (as two phase-number-shaped segments joined by the first `-`) before any git
|
|
321
|
+
command runs, and each segment is zero-padded the same way as `${TARGET_PHASE}` above (e.g.
|
|
322
|
+
`3-1` → `03-01`) — the old plan-mode grep never had this tolerance, so this is a deliberate
|
|
323
|
+
widening, not a preserved behavior; it makes an unpadded `--plan` argument match a
|
|
324
|
+
canonically-padded commit scope instead of silently matching nothing.
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
COMMITS=$(gsd_run query select-revert-commits --plan "${TARGET_PLAN}" --range "${UNDO_RANGE}" --raw || true)
|
|
101
328
|
```
|
|
102
329
|
|
|
103
330
|
Use matching commits as COMMITS.
|
|
104
331
|
|
|
332
|
+
**Report truncation, never truncate silently** — the same rule as MODE=phase. If the
|
|
333
|
+
selection exceeds 50 commits, show the count and stop rather than capping:
|
|
334
|
+
```
|
|
335
|
+
Plan ${TARGET_PLAN} selects ${N} commits (>50). Refusing to revert a partial plan.
|
|
336
|
+
Use /gsd:undo --last N and select commits explicitly.
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
**Known residual — a revision range is ancestry, not chronology.** `PHASE_START^..HEAD`
|
|
342
|
+
excludes everything reachable from `PHASE_START^`, which is the right bound for the
|
|
343
|
+
ordinary linear case. It is not a *chronological* lower bound: a long-lived side branch
|
|
344
|
+
created before the phase, carrying matching scopes, and merged in **after** `PHASE_START`
|
|
345
|
+
is reachable from `HEAD` without being an ancestor of `PHASE_START^`, so it stays
|
|
346
|
+
selectable. This is strictly narrower than the unbounded search it replaces, not a new
|
|
347
|
+
exposure — but it is not zero.
|
|
348
|
+
|
|
349
|
+
**Known residual — a RENAMED phase directory under-selects.** The anchor is
|
|
350
|
+
`--diff-filter=A` on the phase directory's *current* path and does not follow renames, so
|
|
351
|
+
for a phase whose directory has since moved the oldest add at that path is the **move**
|
|
352
|
+
commit, and the phase's real work commits — which predate it — fall outside the window.
|
|
353
|
+
For a rename *within* the live `phases/` tree the failure is under-selection: the undo
|
|
354
|
+
reverts too little or refuses, never too much.
|
|
355
|
+
|
|
356
|
+
The **archival** case is not that case, and is no longer a residual — it is refused above.
|
|
357
|
+
It was previously documented here as under-selection only, which was wrong in the direction
|
|
358
|
+
that matters: the window runs forward from the archival commit, so while the target's own
|
|
359
|
+
work falls outside it, a *later* milestone's same-numbered phase falls inside and matches
|
|
360
|
+
the subject grep. Driven on a two-archived-milestone fixture it selected the wrong
|
|
361
|
+
milestone's commit and none of the right one's. Both modes now refuse an archived
|
|
362
|
+
`PHASE_DIR` outright; `/gsd:undo --last N` is the route for a phase that has been archived.
|
|
363
|
+
|
|
364
|
+
**Known residual — a phase directory introduced by a merge commit resolves no anchor.**
|
|
365
|
+
`git log --diff-filter=A -- "${PHASE_DIR}"` does not walk merge diffs by default. A phase
|
|
366
|
+
directory added on a side branch is still found, because the side-branch commit that added
|
|
367
|
+
it is itself in history; the uncovered case is a directory that first appears *in the merge
|
|
368
|
+
resolution itself*, which a **default** `git log` does not show: it suppresses merge diffs
|
|
369
|
+
unless asked (`-m` prints the add once per parent, so the information exists — the anchor
|
|
370
|
+
command simply does not request it). `PHASE_START` then resolves
|
|
371
|
+
empty and both modes fail closed on a legitimate phase. Safe-direction only — it refuses
|
|
372
|
+
rather than mis-selects — and untested: constructing the evil-merge fixture costs more than
|
|
373
|
+
the branch is worth while the failure mode is a refusal. `/gsd:undo --last N` is the route
|
|
374
|
+
if it is ever hit.
|
|
375
|
+
|
|
376
|
+
**A re-created directory — same number AND same slug — is REFUSED, not a residual.** The
|
|
377
|
+
anchor is the *current path*, and `--diff-filter=A` does not follow renames, so re-creating
|
|
378
|
+
a literal directory an earlier occupant used (`03-auth` again, not merely phase `03` again)
|
|
379
|
+
makes the oldest add at that path the **previous occupant's**. The archived refusal above
|
|
380
|
+
cannot reach it — `find-phase` returns the **live** directory, so nothing is under
|
|
381
|
+
`milestones/` to refuse. Driven before the guard: two milestones both using
|
|
382
|
+
`.planning/phases/03-auth` anchored on the v1 plan commit and selected all four v1+v2 phase-03
|
|
383
|
+
commits; a workstream completed into `milestones/ws-feat-<date>/` and then re-created as `feat`
|
|
384
|
+
with the same `03-auth` did the same across the two workstream generations. The collision check
|
|
385
|
+
closes both without a phase identity a directory name does not carry, and without restating any
|
|
386
|
+
archive layout: a path that went empty in `HEAD`'s history and came back has had a previous
|
|
387
|
+
occupant, so the anchor is untrustworthy and both modes refuse. `code-review.md` carries the
|
|
388
|
+
same weakness on the same anchor, where it is read-only and merely widens a review scope; here
|
|
389
|
+
it reverts, which is why this one is a refusal rather than a note.
|
|
390
|
+
|
|
391
|
+
**Known residual — the collision check reads history, so it sees only what history shows.**
|
|
392
|
+
Two edges, in opposite directions. It **misses** a single commit that both moves the directory
|
|
393
|
+
away *and* re-creates it at the same path: the path is never empty in any commit's tree, so the
|
|
394
|
+
history carries no vacancy to find, and the anchor opens on the earlier occupant. That takes a
|
|
395
|
+
hand-assembled commit — it is not the shape of an archive followed by later planning — and the
|
|
396
|
+
over-selection it allows still has to pass `confirm_revert`. It **over-refuses** when a side
|
|
397
|
+
branch emptied the directory and the merge kept it: the vacancy is real in that branch's
|
|
398
|
+
history, so the path reads as reused. Refusing too often costs a `--last N`; refusing too
|
|
399
|
+
rarely reverts another occupant's work, which is why the check is keyed on the vacancy itself
|
|
400
|
+
rather than on any narrower proof of ownership.
|
|
401
|
+
|
|
402
|
+
**Known residual — concurrent workstreams.** The window above is scoped to the target
|
|
403
|
+
phase's own directory, which is workstream-correct, but the commit subjects it filters
|
|
404
|
+
are not: the executor's scope contract is `type({phase}-{plan})` with no workstream
|
|
405
|
+
token, so two workstreams running the same phase number concurrently emit
|
|
406
|
+
indistinguishable subjects and both fall inside each other's window. Narrowing the window plus the two
|
|
407
|
+
refusals above removes the unreachable-branch class entirely and every previous-milestone
|
|
408
|
+
route this workflow can detect — residual 2 is the one it cannot, since a merged side branch
|
|
409
|
+
is genuinely reachable from `HEAD`. This last class
|
|
410
|
+
needs a discriminator that does not exist in a commit subject today (`#3995`: *"Message
|
|
411
|
+
subjects demonstrably do not carry enough information to identify a phase"*). Until one
|
|
412
|
+
exists, `confirm_revert` is the backstop for it.
|
|
413
|
+
|
|
105
414
|
---
|
|
106
415
|
|
|
107
416
|
**Empty check:**
|
|
@@ -118,11 +427,20 @@ Exit cleanly.
|
|
|
118
427
|
|
|
119
428
|
Skip this step entirely for MODE=last.
|
|
120
429
|
|
|
430
|
+
Resolve the active scope's planning root first — **both** modes below read from it. Under
|
|
431
|
+
an active workstream the roadmap and phase directories describing the target are that
|
|
432
|
+
workstream's, not the root's:
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
PLANNING_DIR=$(gsd_run query planning inspect --pick generated_from.planning_root --raw 2>/dev/null)
|
|
436
|
+
[ -n "$PLANNING_DIR" ] || PLANNING_DIR=".planning"
|
|
437
|
+
```
|
|
438
|
+
|
|
121
439
|
---
|
|
122
440
|
|
|
123
441
|
**MODE=phase:**
|
|
124
442
|
|
|
125
|
-
Read
|
|
443
|
+
Read `${PLANNING_DIR}/ROADMAP.md` inline.
|
|
126
444
|
|
|
127
445
|
Search for phases that list a dependency on the target phase. Look for patterns like:
|
|
128
446
|
- "Depends on: Phase ${TARGET_PHASE}"
|
|
@@ -130,7 +448,7 @@ Search for phases that list a dependency on the target phase. Look for patterns
|
|
|
130
448
|
- "depends_on: [${TARGET_PHASE}]"
|
|
131
449
|
|
|
132
450
|
For each dependent phase N found:
|
|
133
|
-
1. Check if
|
|
451
|
+
1. Check if `${PLANNING_DIR}/phases/${N}-*/` directory exists
|
|
134
452
|
2. If directory exists, check for any PLAN.md or SUMMARY.md files inside it
|
|
135
453
|
|
|
136
454
|
If any downstream phase has started work, collect warnings:
|
|
@@ -145,7 +463,8 @@ If any downstream phase has started work, collect warnings:
|
|
|
145
463
|
|
|
146
464
|
Extract the phase number from TARGET_PLAN (the NN part of NN-MM). Extract the plan number (the MM part).
|
|
147
465
|
|
|
148
|
-
Look for later plans in the same phase directory (
|
|
466
|
+
Look for later plans in the same phase directory (`${PLANNING_DIR}/phases/${NN}-*/`, the
|
|
467
|
+
same workstream-resolved root). For each later plan (plans with number > MM):
|
|
149
468
|
1. Read the later plan's PLAN.md
|
|
150
469
|
2. Check if its `<files>` sections or `consumes` fields reference outputs from the target plan
|
|
151
470
|
|
|
@@ -300,8 +619,8 @@ Show next steps:
|
|
|
300
619
|
|
|
301
620
|
<success_criteria>
|
|
302
621
|
- [ ] Arguments parsed correctly for all three modes
|
|
303
|
-
- [ ] --phase mode
|
|
304
|
-
- [ ] --phase
|
|
622
|
+
- [ ] --phase mode anchors selection on the phase's own directory (find-phase -> PHASE_START), never a repository-wide commit-subject grep
|
|
623
|
+
- [ ] --phase and --plan modes fail closed when no anchor resolves, never widening to an unbounded search
|
|
305
624
|
- [ ] Dependency check warns when downstream phases have started (MODE=phase)
|
|
306
625
|
- [ ] Dependency check warns when later plans reference target plan outputs (MODE=plan)
|
|
307
626
|
- [ ] Dirty-tree guard aborts if working tree has uncommitted changes
|
|
@@ -74,7 +74,7 @@ Parse output:
|
|
|
74
74
|
- Line 1 = installed version (`0.0.0` means unknown version)
|
|
75
75
|
- Line 2 = install scope (`LOCAL`, `GLOBAL`, or `UNKNOWN`)
|
|
76
76
|
- Line 3 = target runtime (`claude`, `opencode`, `kilo`, `codex`, `antigravity`, `windsurf`); empty when no installed target is resolved
|
|
77
|
-
- Line 4 = resolved GSD config dir (e.g. `/Users/me/.claude`, `/Users/me/.gemini`); empty when no installed target is resolved. Capture this as `GSD_DIR` and pass it to subsequent steps so they don't re-derive the runtime path.
|
|
77
|
+
- Line 4 = resolved GSD config dir (e.g. `/Users/me/.claude`, `/Users/me/.gemini/antigravity`); empty when no installed target is resolved. Capture this as `GSD_DIR` and pass it to subsequent steps so they don't re-derive the runtime path.
|
|
78
78
|
|
|
79
79
|
`update-context` reproduces the previous detection cascade — preferred-config-dir fast path, local-over-global with same-path dedup (so `CWD=$HOME` does not misdetect as LOCAL), env-var overrides (`CLAUDE_CONFIG_DIR`, `OPENCODE_CONFIG_DIR`, `KILO_CONFIG`, `XDG_CONFIG_HOME`, `CODEX_HOME`, …), and semver validation — but as a tested projection rather than ~280 lines of inline bash. Branch coverage lives in `tests/update-context.test.cjs`.
|
|
80
80
|
|
|
@@ -132,7 +132,7 @@ Extract `section_manifest` from `INIT_UPDATE` — gates the `channel-banner` sec
|
|
|
132
132
|
<step name="check_latest_version">
|
|
133
133
|
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.)
|
|
134
134
|
|
|
135
|
-
The `GSD_DIR` value emitted by `get_installed_version` (line 4) resolves to the runtime-specific config dir (`~/.claude/`, `~/.gemini/`, `~/.codex/`, etc.), so the script invocation works for every runtime — not just Claude. An unresolved target exits in `get_installed_version` before this step.
|
|
135
|
+
The `GSD_DIR` value emitted by `get_installed_version` (line 4) resolves to the runtime-specific config dir (`~/.claude/`, `~/.gemini/antigravity/`, `~/.codex/`, etc.), so the script invocation works for every runtime — not just Claude. An unresolved target exits in `get_installed_version` before this step.
|
|
136
136
|
|
|
137
137
|
`LATEST_RESULT` is a JSON document with the documented shape `{ ok: bool, version: string, reason: string, detail?: string }`. Parse it with the Node-only `uc_field` helper. When the script cannot run or returns nothing, preserve its failure as a meaningful diagnostic (#2993 CR feedback):
|
|
138
138
|
|
|
@@ -273,8 +273,8 @@ rm -f "$CHANGELOG_TMP"
|
|
|
273
273
|
- `agents/gsd-*` files will be replaced
|
|
274
274
|
|
|
275
275
|
(Paths are relative to detected runtime install location:
|
|
276
|
-
global: `~/.claude/`, `~/.config/opencode/`, `~/.opencode/`, `~/.gemini/`, `~/.config/kilo/`, or `~/.codex/`
|
|
277
|
-
local: `./.claude/`, `./.config/opencode/`, `./.opencode/`, `./.
|
|
276
|
+
global: `~/.claude/`, `~/.config/opencode/`, `~/.opencode/`, `~/.gemini/antigravity/`, `~/.config/kilo/`, or `~/.codex/`
|
|
277
|
+
local: `./.claude/`, `./.config/opencode/`, `./.opencode/`, `./.agents/`, `./.kilo/`, or `./.codex/`)
|
|
278
278
|
|
|
279
279
|
Your custom files in other locations are preserved:
|
|
280
280
|
- Custom commands not in `commands/gsd/` ✓
|
|
@@ -285,7 +285,7 @@ Your custom files in other locations are preserved:
|
|
|
285
285
|
If you've modified any GSD files directly, they'll be automatically backed up to `gsd-local-patches/` and can be reapplied with `/gsd:update --reapply` after the update.
|
|
286
286
|
```
|
|
287
287
|
|
|
288
|
-
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** 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 non-Claude runtimes (OpenAI Codex,
|
|
288
|
+
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** 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 non-Claude runtimes (OpenAI Codex, Antigravity, etc.) where `AskUserQuestion` is not available.
|
|
289
289
|
Use AskUserQuestion:
|
|
290
290
|
- Question: "Proceed with update?"
|
|
291
291
|
- Options:
|
|
@@ -312,7 +312,7 @@ First, resolve the config directory (`RUNTIME_DIR`) from the install scope
|
|
|
312
312
|
detected in `get_installed_version`:
|
|
313
313
|
|
|
314
314
|
```bash
|
|
315
|
-
# RUNTIME_DIR is the resolved config directory (e.g. ~/.config/opencode, ~/.gemini).
|
|
315
|
+
# RUNTIME_DIR is the resolved config directory (e.g. ~/.config/opencode, ~/.gemini/antigravity).
|
|
316
316
|
# get_installed_version emits it as GSD_DIR for a resolved LOCAL or GLOBAL install.
|
|
317
317
|
# The unresolved-target gate exits before this step; the empty guard remains defensive.
|
|
318
318
|
RUNTIME_DIR="$GSD_DIR"
|
|
@@ -526,7 +526,7 @@ already empty). Say nothing and continue — the update flow is unchanged.
|
|
|
526
526
|
|
|
527
527
|
Otherwise, render the report. Each entry carries `path`, `outcome`, and a
|
|
528
528
|
`warnings` array of `{code, detail}` produced by a compatibility pass against
|
|
529
|
-
the just-installed release — a renamed workflow it `@`-references, a
|
|
529
|
+
the just-installed release — a renamed workflow it `@`-references, a slash
|
|
530
530
|
command that no longer exists, missing skill frontmatter. Render each entry's
|
|
531
531
|
warnings under its path. Entries whose `outcome` starts with `skipped_` will
|
|
532
532
|
**not** be restored; list them separately, with their reason, so the user knows
|