@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,8 +16,9 @@ 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_AUDITOR=$(gsd_run query agent-skills gsd-nyquist-auditor)
|
|
@@ -89,7 +90,7 @@ No gaps → skip to Step 6, set `nyquist_compliant: true`.
|
|
|
89
90
|
|
|
90
91
|
## 4. Present Gap Plan
|
|
91
92
|
|
|
92
|
-
**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,
|
|
93
|
+
**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.
|
|
93
94
|
Call AskUserQuestion with gap table and options:
|
|
94
95
|
1. "Fix all gaps" → Step 5
|
|
95
96
|
2. "Skip — mark manual-only" → add to Manual-Only, Step 6
|
|
@@ -31,7 +31,7 @@ For each UI checkpoint listed in the phase's UI-SPEC.md (or inferred from SUMMAR
|
|
|
31
31
|
Run the same checkpoint loop above using that server. Resolve the key first:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; };
|
|
34
|
+
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _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
|
|
35
35
|
LIVE_DOM_UAT=$(gsd_run query config-get workflow.live_dom_uat --raw 2>/dev/null || echo "false")
|
|
36
36
|
```
|
|
37
37
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
**User-story format guard.** When `MVP_MODE=true`, also verify the phase's goal is in User Story format via the centralized validator:
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
_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; };
|
|
11
|
+
_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
|
|
12
12
|
PHASE_GOAL=$(gsd_run query roadmap.get-phase "${phase_number}" ${GSD_WS} --pick goal)
|
|
13
13
|
USER_STORY_VALID=$(gsd_run query user-story.validate --story "$PHASE_GOAL" --pick valid)
|
|
14
14
|
if [ "$USER_STORY_VALID" != "true" ]; then
|
|
@@ -39,7 +39,7 @@ No Pass/Fail buttons. No severity questions. Just: "Here's what should happen. D
|
|
|
39
39
|
If $ARGUMENTS contains a phase number, load context:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
_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; };
|
|
42
|
+
_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
|
|
43
43
|
GSD_WS=""
|
|
44
44
|
echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
|
|
45
45
|
PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
|
|
@@ -183,18 +183,31 @@ instrument — the executor's own narration is never the last word. For each `*-
|
|
|
183
183
|
BASE=$(grep -oE '^plan_head_before: [0-9a-f]{7,40}' "$SUMMARY_FILE" | awk '{print $2}')
|
|
184
184
|
CLAIMED=$(grep -oE '^commits: [0-9]+' "$SUMMARY_FILE" | grep -oE '[0-9]+' || echo absent)
|
|
185
185
|
ACTUAL=$(git rev-list --count "${BASE}"..HEAD)
|
|
186
|
+
AFTER=$(grep -oE '^plan_head_after: [0-9a-f]{7,40}' "$SUMMARY_FILE" | awk '{print $2}')
|
|
186
187
|
```
|
|
187
188
|
- A `commits: absent` or `plan_head_before: absent` SUMMARY (pre-#3968 legacy) is reported as
|
|
188
189
|
a WARNING with the measured git state, not a mismatch.
|
|
189
|
-
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
190
|
+
- **Bounded reconciliation (#4670).** A SUMMARY carrying `plan_head_after:` (the executor's
|
|
191
|
+
HEAD at its measurement moment — after the last task commit, before the SUMMARY commit) is
|
|
192
|
+
reconciled against the plan's OWN window:
|
|
193
|
+
```bash
|
|
194
|
+
if git merge-base --is-ancestor "$AFTER" HEAD 2>/dev/null \
|
|
195
|
+
&& [ "$(git rev-list --count "${BASE}..${AFTER}")" = "$CLAIMED" ]; then
|
|
196
|
+
: # consistent
|
|
197
|
+
fi
|
|
198
|
+
```
|
|
199
|
+
Consistent → done. Anything else is a **BLOCKER** — `commit_claim_mismatch` with both
|
|
200
|
+
numbers and the SUMMARY path: commits claimed but never made (#3968), task commits lost
|
|
201
|
+
after the fact, or the plan's recorded window rewritten afterwards (a rebase/amend/cherry-pick
|
|
202
|
+
of those commits makes `$AFTER` a non-ancestor — recount that plan's commits manually
|
|
203
|
+
before treating it as a genuine mismatch). The unbounded `${BASE}..HEAD` count is NOT
|
|
204
|
+
evidence either way: it grows with every later plan's commits and execute-phase's own
|
|
205
|
+
phase-completion commit, so an honest plan would read as a mismatch (#4670).
|
|
206
|
+
- **Legacy fallback (#4670).** A SUMMARY with a base but no `plan_head_after:` (pre-#4670)
|
|
207
|
+
cannot be bounded to its own window — report the measured `${BASE}..HEAD` count as a
|
|
208
|
+
**WARNING** with the SUMMARY's task-commit list for manual counting. The old
|
|
209
|
+
`ACTUAL == CLAIMED` / `ACTUAL == CLAIMED + 1` tolerance was a guess that later plans'
|
|
210
|
+
commits defeat; it must never produce a BLOCKER on the unsound window.
|
|
198
211
|
</step>
|
|
199
212
|
|
|
200
213
|
<step name="extract_tests">
|
|
@@ -377,7 +390,7 @@ Display the returned checkpoint EXACTLY as-is:
|
|
|
377
390
|
- Do NOT add commentary before or after the block.
|
|
378
391
|
- If you notice protocol/meta markers such as `to=all:`, role-routing text, XML system tags, hidden instruction markers, ad copy, or any unrelated suffix, discard the draft and output `{CHECKPOINT}` only.
|
|
379
392
|
|
|
380
|
-
**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,
|
|
393
|
+
**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.
|
|
381
394
|
Wait for user response (plain text, no AskUserQuestion).
|
|
382
395
|
</step>
|
|
383
396
|
|
|
@@ -542,6 +555,42 @@ Commit the UAT file:
|
|
|
542
555
|
gsd_run query commit "test({phase_num}): complete UAT - {passed} passed, {issues} issues" --files ".planning/phases/XX-name/{phase_num}-UAT.md"
|
|
543
556
|
```
|
|
544
557
|
|
|
558
|
+
**If the UAT file has a non-empty `## Deferred Follow-Ups` section,** those items are currently visible only inside this phase's `*-UAT.md` — offer to promote them to the roadmap backlog so they stay visible at the project level (#4546; reuses the exact entry mechanism `next.md`'s `prior_phase_completeness` step uses for plans-without-summaries):
|
|
559
|
+
|
|
560
|
+
```
|
|
561
|
+
Deferred follow-ups recorded: {N}
|
|
562
|
+
|
|
563
|
+
They currently live only in {phase_num}-UAT.md. Promote them to the ROADMAP.md backlog?
|
|
564
|
+
|
|
565
|
+
[P] Promote to ROADMAP.md 999.x backlog
|
|
566
|
+
[K] Keep them in the UAT file only
|
|
567
|
+
|
|
568
|
+
Choice [K]:
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
(TEXT_MODE: present this as a plain-text numbered list per the text-mode convention and wait for the typed choice.)
|
|
572
|
+
|
|
573
|
+
**If the user chooses [P]:**
|
|
574
|
+
1. Compute the next backlog number: `{backlog_number}` = the smallest positive integer not already used by an existing `### Phase 999.{n}` heading in `.planning/ROADMAP.md` — scan the headings rather than counting them, since numbering may be non-contiguous. If `.planning/ROADMAP.md` does not exist, create it containing only a `## Backlog` section and use `1`.
|
|
575
|
+
2. Append to that `## Backlog` section one backlog entry per deferred follow-up (each with its own `999.{backlog_number}` heading, incrementing per entry), with `test`/`idea`/`deferred_at` verbatim from the section's YAML and `{idea}` flattened to a single line (newlines → spaces — a multi-line response would corrupt the single-line entry; this mirrors `next.md`'s use of a slug for the same reason):
|
|
576
|
+
|
|
577
|
+
```markdown
|
|
578
|
+
### Phase 999.{backlog_number}: Follow-up — Phase {phase_num} deferred UAT follow-up: Test {test} (BACKLOG)
|
|
579
|
+
|
|
580
|
+
**Goal:** Resolve the UAT checkpoint deferred during Phase {phase_num} verification
|
|
581
|
+
**Source phase:** {phase_num}
|
|
582
|
+
**Deferred at:** {date} during /gsd:verify-work {phase} session completion
|
|
583
|
+
**Follow-ups:**
|
|
584
|
+
- [ ] Test {test}: {idea} (deferred {deferred_at})
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
3. Commit the deferral record:
|
|
588
|
+
```bash
|
|
589
|
+
gsd_run query commit "docs: defer Phase {phase_num} UAT follow-ups to backlog" --files .planning/ROADMAP.md
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
**If the user chooses [K]:** continue to the summary unchanged — the deferred items remain in the UAT file's `## Deferred Follow-Ups` section.
|
|
593
|
+
|
|
545
594
|
Present summary:
|
|
546
595
|
```
|
|
547
596
|
## UAT Complete: Phase {phase}
|
|
@@ -603,7 +652,7 @@ If an active secure-phase step hook exists AND `SECURITY_FILE` exists: check fro
|
|
|
603
652
|
|
|
604
653
|
If no active secure-phase step hook exists OR (`SECURITY_FILE` exists AND `threats_open` is `0`):
|
|
605
654
|
|
|
606
|
-
If execution verification is waiting only on human UAT and this session recorded zero issues, canonicalize the report before the shared completion predicate:
|
|
655
|
+
If execution verification is waiting only on human UAT and this session recorded zero issues, canonicalize the report before the shared completion predicate. (#4663) Zero issues is NOT pass evidence on its own — blocked rows are not issues by this workflow's own rule, so a session that observed nothing (0 passed / 0 issues / N blocked) must NOT flip the report. The flip runs the SAME UAT-row predicate the phase-close uses, in its `--uat-only` form: it skips the verification-status blockers (the report still reads `human_needed` at this point — the full predicate could never pass here), and `passed` means at least one UAT check passed with no row pending/blocked/failed or skipped without a reason. The flagged transition-gate call below stays the final say on canonical verification:
|
|
607
656
|
|
|
608
657
|
```bash
|
|
609
658
|
PHASE_DIR=$(printf '%s' "$INIT" | jq -r '.phase_dir // empty')
|
|
@@ -612,19 +661,35 @@ VERIFICATION_STATUS=$(gsd_run query verification.status "$PHASE_DIR" 2>/dev/null
|
|
|
612
661
|
VERIFICATION_STATUS_VALUE=$(printf '%s' "$VERIFICATION_STATUS" | jq -r '.status // empty' 2>/dev/null || echo "")
|
|
613
662
|
PHASE_VERIFICATION_STATUS="$VERIFICATION_STATUS_VALUE"
|
|
614
663
|
if [ "$VERIFICATION_STATUS_VALUE" = "human_needed" ]; then
|
|
615
|
-
gsd_run
|
|
664
|
+
UAT_PRECHECK=$(gsd_run phase uat-passed "{phase}" --uat-only 2>/dev/null)
|
|
665
|
+
UAT_PRECHECK_PASSED=$(printf '%s' "$UAT_PRECHECK" | jq -r '.passed // false' 2>/dev/null || echo "false")
|
|
666
|
+
if [ "$UAT_PRECHECK_PASSED" = "true" ]; then
|
|
667
|
+
gsd_run query frontmatter.set "$VERIFICATION_FILE" --field status --value passed
|
|
668
|
+
else
|
|
669
|
+
UAT_BLOCKERS=$(printf '%s' "$UAT_PRECHECK" | jq -r '.blockers | length' 2>/dev/null)
|
|
670
|
+
[ -n "$UAT_BLOCKERS" ] || UAT_BLOCKERS="?"
|
|
671
|
+
echo "NOT canonicalizing: ${UAT_BLOCKERS} UAT row(s) blocked or not passing; verification stays human_needed. Resolve or pass them, then re-run /gsd:verify-work {phase}." >&2
|
|
672
|
+
fi
|
|
616
673
|
fi
|
|
617
674
|
```
|
|
618
675
|
|
|
619
|
-
If `PHASE_VERIFICATION_STATUS` is `stale`,
|
|
676
|
+
If `PHASE_VERIFICATION_STATUS` is `stale`, the covered source files changed after the verifier
|
|
677
|
+
last ran — re-run the VERIFIER, not this workflow (`/gsd:verify-work` never rewrites
|
|
678
|
+
VERIFICATION.md; its only write is the human_needed canonicalization, #4663). Spawn the
|
|
679
|
+
verifier for this phase exactly as execute-phase's `verify_phase_goal` step does (subagent
|
|
680
|
+
`gsd-verifier`; phase directory, goal, requirement IDs, and all SUMMARYs in
|
|
681
|
+
`<required_reading>`), then re-read `verification.status` and continue at the fresh/passed
|
|
682
|
+
case below. (#4682)
|
|
620
683
|
|
|
621
684
|
```
|
|
622
|
-
|
|
685
|
+
Verification is stale: covered source files changed after the verifier last ran.
|
|
623
686
|
|
|
624
687
|
Blocking completion:
|
|
625
688
|
verification is stale
|
|
626
689
|
|
|
627
|
-
-
|
|
690
|
+
- Re-run the verifier for phase {phase} (dispatch `gsd-verifier` as in execute-phase's
|
|
691
|
+
verify_phase_goal step) to regenerate VERIFICATION.md with a fresh digest, then re-run
|
|
692
|
+
`/gsd:verify-work {phase}`
|
|
628
693
|
```
|
|
629
694
|
|
|
630
695
|
Otherwise, check the shared UAT-plus-verification completion predicate before transition:
|
|
@@ -337,6 +337,30 @@ function resolveRegistryIsolation(cwd, configPath) {
|
|
|
337
337
|
if (!useWorktrees) isolation = 'none';
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
// #4734: a harness worktree can never be created in a directory that is not
|
|
341
|
+
// a git repository (or a repository with no commits) — git's definitive
|
|
342
|
+
// exit-128 answer on `rev-parse HEAD`. Demanding the isolation flag there
|
|
343
|
+
// blocked every flag-less dispatch the moment the sentinel went stale, on a
|
|
344
|
+
// root where no worktree can exist. Degrade to 'none' exactly like the
|
|
345
|
+
// use_worktrees opt-out above; the classification is owned by
|
|
346
|
+
// `classifyGitHead` (worktree-base-ref.cjs) — the same single owner the
|
|
347
|
+
// base-check's degrade decision uses — and ambiguous or failed resolutions
|
|
348
|
+
// keep the conservative (enforce) default, matching that check's own
|
|
349
|
+
// fail-closed treatment of the same classes.
|
|
350
|
+
if (isolation === 'harness-worktree') {
|
|
351
|
+
try {
|
|
352
|
+
ensureRuntimeBuild();
|
|
353
|
+
const { classifyGitHead } = require('../gsd-core/bin/lib/worktree-base-ref.cjs');
|
|
354
|
+
if (classifyGitHead({ cwd }).status === 'definitive-absence') {
|
|
355
|
+
isolation = 'none';
|
|
356
|
+
}
|
|
357
|
+
} catch {
|
|
358
|
+
// Unbuilt or unreadable runtime library — keep the conservative
|
|
359
|
+
// (enforce) default rather than silently disabling the guard, same
|
|
360
|
+
// posture as the ladder fallback above.
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
340
364
|
return { isolation, harnessFlag };
|
|
341
365
|
}
|
|
342
366
|
|
|
@@ -86,7 +86,12 @@
|
|
|
86
86
|
// on plain commands, without arming the compound-`cd` prompt". Writes to
|
|
87
87
|
// secret files are out of scope (Write/Edit were never gated). Commands
|
|
88
88
|
// over 1 MiB are denied outright (`command-too-large`) rather than
|
|
89
|
-
// scanned partially or waved through.
|
|
89
|
+
// scanned partially or waved through. (#4639 adds one more, by design: the
|
|
90
|
+
// value of `--env-file` under a container runtime is exempt, so the
|
|
91
|
+
// container's own command can print the interpolated environment
|
|
92
|
+
// (`alpine printenv`, `docker compose config`) — the same exposure class
|
|
93
|
+
// as the pre-existing volume-mount gap (`-v .env:/s`); the flag's value
|
|
94
|
+
// itself is a name, never contents.)
|
|
90
95
|
//
|
|
91
96
|
// Triggers on: Read, Grep, Bash tool calls (Kimi: ReadFile, Grep, Shell)
|
|
92
97
|
// Action: BLOCK (decision: 'block', exit 2) — codes secret-read |
|
|
@@ -127,6 +132,15 @@ const NON_READING_COMMANDS = new Set([
|
|
|
127
132
|
'basename', 'dirname', 'realpath', 'file', 'echo', 'printf',
|
|
128
133
|
]);
|
|
129
134
|
|
|
135
|
+
// #4639: container runtimes take `--env-file <file>` — the runtime opens the
|
|
136
|
+
// file itself, interpolates it into the container environment, and returns
|
|
137
|
+
// nothing to the agent, so the flag's VALUE is a name, never contents. The
|
|
138
|
+
// same category NON_READING_COMMANDS encodes, expressed as a flag value. Only
|
|
139
|
+
// the flag's value under these runtimes is exempt; every other operand in the
|
|
140
|
+
// segment is still checked, so the carve-out cannot launder a read.
|
|
141
|
+
const CONTAINER_RUNTIMES = new Set(['docker', 'docker-compose', 'podman', 'nerdctl']);
|
|
142
|
+
const ENV_FILE_FLAG_RE = /^--env-file(=|$)/;
|
|
143
|
+
|
|
130
144
|
// Shell interpreters that run a script from `-c`, a file operand, or stdin
|
|
131
145
|
// (heredoc / here-string / piped `echo`|`printf`). `su` is here for its `-c`
|
|
132
146
|
// form (`su [user] -c 'cmd'`); a bare `su user` resolves to file mode, which
|
|
@@ -840,7 +854,19 @@ function findSecretRead(command, depth) {
|
|
|
840
854
|
|
|
841
855
|
if (NON_READING_COMMANDS.has(base)) continue;
|
|
842
856
|
|
|
857
|
+
// #4639: exempt ONLY the value of `--env-file`, and only when the
|
|
858
|
+
// segment's command word is a container runtime. A bare `--env-file`
|
|
859
|
+
// consumes the next operand; `--env-file=<value>` is a single word.
|
|
860
|
+
const envFileExempt = CONTAINER_RUNTIMES.has(base);
|
|
861
|
+
let skipNext = false;
|
|
843
862
|
for (const w of operands) {
|
|
863
|
+
if (envFileExempt) {
|
|
864
|
+
if (skipNext) { skipNext = false; continue; }
|
|
865
|
+
if (ENV_FILE_FLAG_RE.test(w.text)) {
|
|
866
|
+
if (!w.text.includes('=')) skipNext = true; // bare flag consumes the next operand
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
844
870
|
if (namesSecret(normalizeOperand(w.text))) return w.text;
|
|
845
871
|
}
|
|
846
872
|
}
|
|
@@ -42,6 +42,10 @@ if (require.main === module) {
|
|
|
42
42
|
const { isSemverNewer } = require('../gsd-core/bin/lib/semver-compare.cjs');
|
|
43
43
|
const { PACKAGE_NAME, updateCacheFileName } = require('../gsd-core/bin/lib/package-identity.cjs');
|
|
44
44
|
const { normalizeStateStatus } = require('../gsd-core/bin/lib/state-document.cjs');
|
|
45
|
+
const {
|
|
46
|
+
renderBracketPhaseDisplay,
|
|
47
|
+
renderBracketMilestoneDisplay,
|
|
48
|
+
} = require('../gsd-core/bin/lib/phase-id-display.cjs');
|
|
45
49
|
// #2850: reuse the existing workstream resolution seams rather than
|
|
46
50
|
// re-implementing CLI>env>store precedence or path construction inline.
|
|
47
51
|
// peekActiveWorkstream is the read-only sibling of the store-tier lookup
|
|
@@ -359,16 +363,26 @@ function renderProgressBar(percent) {
|
|
|
359
363
|
* Progress bar is opt-in: appended to the milestone segment only when
|
|
360
364
|
* progress.percent is present in frontmatter; absent → empty string.
|
|
361
365
|
*/
|
|
362
|
-
function formatGsdState(s) {
|
|
366
|
+
function formatGsdState(s, opts = {}) {
|
|
363
367
|
// #2850: workstream mode with nothing resolvable — an observable signal,
|
|
364
368
|
// never silent emptiness (distinguishes from "GSD isn't installed here").
|
|
365
369
|
if (s.noActiveWorkstream) return NO_ACTIVE_WORKSTREAM_LABEL;
|
|
366
370
|
|
|
367
371
|
const parts = [];
|
|
372
|
+
const bracket = opts.convention === 'bracket';
|
|
373
|
+
const bracketMilestone = bracket
|
|
374
|
+
? renderBracketMilestoneDisplay(s.milestone, opts.projectCode)
|
|
375
|
+
: null;
|
|
376
|
+
const phaseLabel = (phase) => {
|
|
377
|
+
const display = bracket
|
|
378
|
+
? renderBracketPhaseDisplay(s.milestone, phase, opts.projectCode)
|
|
379
|
+
: null;
|
|
380
|
+
return display ?? `Phase ${phase}`;
|
|
381
|
+
};
|
|
368
382
|
|
|
369
383
|
// Milestone segment: version + name + (opt-in) progress bar
|
|
370
384
|
if (s.milestone || s.milestoneName) {
|
|
371
|
-
const ver = s.milestone
|
|
385
|
+
const ver = bracketMilestone ?? s.milestone ?? '';
|
|
372
386
|
const name = (s.milestoneName && s.milestoneName !== 'milestone') ? s.milestoneName : '';
|
|
373
387
|
const bar = renderProgressBar(s.percent);
|
|
374
388
|
const pieces = [ver, name, bar].filter(Boolean);
|
|
@@ -384,7 +398,8 @@ function formatGsdState(s) {
|
|
|
384
398
|
// stage = whichever lifecycle status was written by the orchestrator
|
|
385
399
|
// (discussing / planning / executing / verifying)
|
|
386
400
|
const stage = s.status || '';
|
|
387
|
-
|
|
401
|
+
const phase = phaseLabel(s.activePhase);
|
|
402
|
+
parts.push(stage ? `${phase} ${stage}` : phase);
|
|
388
403
|
} else if (s.nextAction && phasesStr) {
|
|
389
404
|
// Scene 2: idle + a recommended next command is visible to the user.
|
|
390
405
|
// Surfaces "what to run next" without the user opening STATE.md.
|
|
@@ -402,9 +417,16 @@ function formatGsdState(s) {
|
|
|
402
417
|
// earlier so no existing project's status-line changes shape.
|
|
403
418
|
if (s.status) parts.push(s.status);
|
|
404
419
|
if (s.phaseNum && s.phaseTotal) {
|
|
420
|
+
const bracketPhase = bracket
|
|
421
|
+
? renderBracketPhaseDisplay(s.milestone, s.phaseNum, opts.projectCode)
|
|
422
|
+
: null;
|
|
405
423
|
const phase = s.phaseName
|
|
406
|
-
?
|
|
407
|
-
|
|
424
|
+
? bracketPhase
|
|
425
|
+
? `${bracketPhase} ${s.phaseName} (${s.phaseNum}/${s.phaseTotal})`
|
|
426
|
+
: `${s.phaseName} (${s.phaseNum}/${s.phaseTotal})`
|
|
427
|
+
: bracketPhase
|
|
428
|
+
? `${bracketPhase} (${s.phaseNum}/${s.phaseTotal})`
|
|
429
|
+
: `ph ${s.phaseNum}/${s.phaseTotal}`;
|
|
408
430
|
parts.push(phase);
|
|
409
431
|
}
|
|
410
432
|
}
|
|
@@ -490,17 +512,24 @@ function shortGsdStatus(status) {
|
|
|
490
512
|
* default format — and collapses narrative statuses via shortGsdStatus().
|
|
491
513
|
* The default "full" format is untouched.
|
|
492
514
|
*/
|
|
493
|
-
function formatGsdStateCompact(s) {
|
|
515
|
+
function formatGsdStateCompact(s, opts = {}) {
|
|
494
516
|
// #2850: mirrors formatGsdState's observable "nothing resolvable" signal.
|
|
495
517
|
if (s.noActiveWorkstream) return NO_ACTIVE_WORKSTREAM_LABEL;
|
|
496
518
|
|
|
497
519
|
const parts = [];
|
|
520
|
+
const bracket = opts.convention === 'bracket';
|
|
521
|
+
const bracketMilestone = bracket
|
|
522
|
+
? renderBracketMilestoneDisplay(s.milestone, opts.projectCode)
|
|
523
|
+
: null;
|
|
498
524
|
|
|
499
|
-
if (s.milestone) parts.push(s.milestone);
|
|
525
|
+
if (s.milestone) parts.push(bracketMilestone ?? s.milestone);
|
|
500
526
|
|
|
501
527
|
const phaseId = s.activePhase || s.phaseNum;
|
|
502
528
|
if (phaseId) {
|
|
503
|
-
|
|
529
|
+
const bracketPhase = bracket
|
|
530
|
+
? renderBracketPhaseDisplay(s.milestone, phaseId, opts.projectCode)
|
|
531
|
+
: null;
|
|
532
|
+
parts.push(bracketPhase ?? (s.phaseTotal ? `P${phaseId}/${s.phaseTotal}` : `P${phaseId}`));
|
|
504
533
|
}
|
|
505
534
|
|
|
506
535
|
// Scene exclusivity mirrors formatGsdState's if/else chain: an in-flight
|
|
@@ -747,7 +776,7 @@ function formatStateFreshness(fresh) {
|
|
|
747
776
|
* entry points — this collapses both onto one resolver.
|
|
748
777
|
*
|
|
749
778
|
* @param {object} cfg — parsed .planning/config.json (readGsdConfig())
|
|
750
|
-
* @returns {{ showLastCommand: boolean, position: 'end'|'front', stateFormat: 'full'|'compact', showGit: boolean, showStateFreshness: boolean }}
|
|
779
|
+
* @returns {{ showLastCommand: boolean, position: 'end'|'front', stateFormat: 'full'|'compact', showGit: boolean, showStateFreshness: boolean, convention: string|null, projectCode: string|null }}
|
|
751
780
|
*/
|
|
752
781
|
function resolveStatuslineOptions(cfg) {
|
|
753
782
|
const showLastCommand = getConfigValue(cfg, 'statusline.show_last_command') === true;
|
|
@@ -759,7 +788,22 @@ function resolveStatuslineOptions(cfg) {
|
|
|
759
788
|
const stateFormat = getConfigValue(cfg, 'statusline.state_format') === 'compact' ? 'compact' : 'full';
|
|
760
789
|
const showGit = getConfigValue(cfg, 'statusline.show_git') === true;
|
|
761
790
|
const showStateFreshness = getConfigValue(cfg, 'statusline.show_state_freshness') === true;
|
|
762
|
-
|
|
791
|
+
const convention = getConfigValue(cfg, 'phase_id_convention') === 'bracket'
|
|
792
|
+
? 'bracket'
|
|
793
|
+
: null;
|
|
794
|
+
const projectCodeValue = getConfigValue(cfg, 'project_code');
|
|
795
|
+
const projectCode = typeof projectCodeValue === 'string' && projectCodeValue !== ''
|
|
796
|
+
? projectCodeValue
|
|
797
|
+
: null;
|
|
798
|
+
return {
|
|
799
|
+
showLastCommand,
|
|
800
|
+
position,
|
|
801
|
+
stateFormat,
|
|
802
|
+
showGit,
|
|
803
|
+
showStateFreshness,
|
|
804
|
+
convention,
|
|
805
|
+
projectCode,
|
|
806
|
+
};
|
|
763
807
|
}
|
|
764
808
|
|
|
765
809
|
// --- stdin ------------------------------------------------------------------
|
|
@@ -934,7 +978,9 @@ function runStatusline() {
|
|
|
934
978
|
// freshness git spawn here would spend a subprocess on discarded output.
|
|
935
979
|
if (!task) {
|
|
936
980
|
const state = readGsdState(dir, { stateFreshness: options.showStateFreshness }) || {};
|
|
937
|
-
gsdStateStr = options.stateFormat === 'compact'
|
|
981
|
+
gsdStateStr = options.stateFormat === 'compact'
|
|
982
|
+
? formatGsdStateCompact(state, options)
|
|
983
|
+
: formatGsdState(state, options);
|
|
938
984
|
}
|
|
939
985
|
|
|
940
986
|
// Output
|
|
@@ -1042,6 +1088,7 @@ module.exports = {
|
|
|
1042
1088
|
STATE_HEAD_ADVISORY_COMMITS, isValidStateHeadStamp,
|
|
1043
1089
|
readStateHeadCommits, parseRevListCounts, deriveStateFreshness,
|
|
1044
1090
|
formatStateFreshness, resolveStatuslineOptions,
|
|
1091
|
+
renderBracketPhaseDisplay, renderBracketMilestoneDisplay,
|
|
1045
1092
|
};
|
|
1046
1093
|
|
|
1047
1094
|
/**
|
|
@@ -1059,7 +1106,15 @@ function renderStatusline(data) {
|
|
|
1059
1106
|
// key from reaching only one of them.
|
|
1060
1107
|
let lastCmdSuffix = '';
|
|
1061
1108
|
let gitSuffix = '';
|
|
1062
|
-
let options = {
|
|
1109
|
+
let options = {
|
|
1110
|
+
showLastCommand: false,
|
|
1111
|
+
position: 'end',
|
|
1112
|
+
stateFormat: 'full',
|
|
1113
|
+
showGit: false,
|
|
1114
|
+
showStateFreshness: false,
|
|
1115
|
+
convention: null,
|
|
1116
|
+
projectCode: null,
|
|
1117
|
+
};
|
|
1063
1118
|
try {
|
|
1064
1119
|
const cfg = readGsdConfig(dir);
|
|
1065
1120
|
options = resolveStatuslineOptions(cfg);
|
|
@@ -1075,7 +1130,9 @@ function renderStatusline(data) {
|
|
|
1075
1130
|
} catch (e) { /* swallow */ }
|
|
1076
1131
|
|
|
1077
1132
|
const state = readGsdState(dir, { stateFreshness: options.showStateFreshness }) || {};
|
|
1078
|
-
const gsdStateStr = options.stateFormat === 'compact'
|
|
1133
|
+
const gsdStateStr = options.stateFormat === 'compact'
|
|
1134
|
+
? formatGsdStateCompact(state, options)
|
|
1135
|
+
: formatGsdState(state, options);
|
|
1079
1136
|
const middle = gsdStateStr ? `\x1b[2m${gsdStateStr}\x1b[0m` : null;
|
|
1080
1137
|
return composeStatusline({ model, ctx: '', middle, dirname, lastCmdSuffix, gitSuffix, position: options.position });
|
|
1081
1138
|
}
|
|
@@ -16,6 +16,21 @@ set -euo pipefail
|
|
|
16
16
|
# Idempotent and failure-proof by construction: unset vars expand to "" (a
|
|
17
17
|
# no-op rm -f target), and `|| true` guarantees the trap itself never changes
|
|
18
18
|
# the script's exit status.
|
|
19
|
+
# Subprocess exit statuses, pre-initialised so they can never be inherited from
|
|
20
|
+
# the ambient environment. Each is captured as `... || VAR=$?`, which assigns
|
|
21
|
+
# ONLY on the failure branch; on success the variable keeps whatever it already
|
|
22
|
+
# held, and `${VAR:-0}` defaults only when unset or empty. So an EXPORTED
|
|
23
|
+
# CONFIG_STATUS / CMD_STATUS / CLASSIFY_STATUS — from a CI wrapper, a .envrc, or
|
|
24
|
+
# another hook — survived into the success path and was read as "the subprocess
|
|
25
|
+
# failed". Measured: `CLASSIFY_STATUS=3 git commit -m "nope: bad"` printed
|
|
26
|
+
# "validator disabled for this call" and exited 0, silently accepting a
|
|
27
|
+
# non-conforming commit. Same for CONFIG_STATUS and CMD_STATUS. Found by the
|
|
28
|
+
# security review of #4429; the gate is fail-open by design on a genuine
|
|
29
|
+
# subprocess failure (#3838), which is exactly what made this bypass quiet.
|
|
30
|
+
CONFIG_STATUS=0
|
|
31
|
+
CMD_STATUS=0
|
|
32
|
+
CLASSIFY_STATUS=0
|
|
33
|
+
|
|
19
34
|
ENABLED_ERR=""
|
|
20
35
|
CMD_ERR=""
|
|
21
36
|
CLASSIFY_ERR=""
|
|
@@ -61,6 +76,12 @@ if [ -f .planning/config.json ]; then
|
|
|
61
76
|
process.exit(3);
|
|
62
77
|
}
|
|
63
78
|
" 2>"$ENABLED_ERR") || CONFIG_STATUS=$?
|
|
79
|
+
# Pre-initialised, NOT left to `${...:-0}` alone: the capture below only
|
|
80
|
+
# assigns on the `||` branch, so on SUCCESS the variable keeps whatever it
|
|
81
|
+
# already held — and an EXPORTED variable of this name is inherited from the
|
|
82
|
+
# ambient environment. `${VAR:-0}` defaults only when unset/empty, so
|
|
83
|
+
# `CONFIG_STATUS=3 git commit …` made this hook print "validator disabled" and exit 0,
|
|
84
|
+
# silently accepting a non-conforming commit. Found by review of #4429.
|
|
64
85
|
CONFIG_STATUS=${CONFIG_STATUS:-0}
|
|
65
86
|
if [ "$CONFIG_STATUS" != "0" ]; then
|
|
66
87
|
# Could not determine the opt-in flag at all (node missing, JSON parse
|
|
@@ -103,6 +124,12 @@ CMD=$(echo "$INPUT" | node -e "
|
|
|
103
124
|
}
|
|
104
125
|
});
|
|
105
126
|
" 2>"$CMD_ERR") || CMD_STATUS=$?
|
|
127
|
+
# Pre-initialised, NOT left to `${...:-0}` alone: the capture below only
|
|
128
|
+
# assigns on the `||` branch, so on SUCCESS the variable keeps whatever it
|
|
129
|
+
# already held — and an EXPORTED variable of this name is inherited from the
|
|
130
|
+
# ambient environment. `${VAR:-0}` defaults only when unset/empty, so
|
|
131
|
+
# `CMD_STATUS=3 git commit …` made this hook print "validator disabled" and exit 0,
|
|
132
|
+
# silently accepting a non-conforming commit. Found by review of #4429.
|
|
106
133
|
CMD_STATUS=${CMD_STATUS:-0}
|
|
107
134
|
if [ "$CMD_STATUS" != "0" ]; then
|
|
108
135
|
# Could not extract tool_input.command at all (node missing, malformed
|
|
@@ -127,6 +154,12 @@ GIT_CMD_LIB="$HOOK_DIR/lib/git-cmd.js" node -e "
|
|
|
127
154
|
process.exit(3);
|
|
128
155
|
}
|
|
129
156
|
" "$CMD" 2>"$CLASSIFY_ERR" || CLASSIFY_STATUS=$?
|
|
157
|
+
# Pre-initialised, NOT left to `${...:-0}` alone: the capture below only
|
|
158
|
+
# assigns on the `||` branch, so on SUCCESS the variable keeps whatever it
|
|
159
|
+
# already held — and an EXPORTED variable of this name is inherited from the
|
|
160
|
+
# ambient environment. `${VAR:-0}` defaults only when unset/empty, so
|
|
161
|
+
# `CLASSIFY_STATUS=3 git commit …` made this hook print "validator disabled" and exit 0,
|
|
162
|
+
# silently accepting a non-conforming commit. Found by review of #4429.
|
|
130
163
|
CLASSIFY_STATUS=${CLASSIFY_STATUS:-0}
|
|
131
164
|
if [ "$CLASSIFY_STATUS" != "0" ] && [ "$CLASSIFY_STATUS" != "1" ]; then
|
|
132
165
|
# 0 = is a git commit (validate below); 1 = genuinely not a git commit
|
|
@@ -557,7 +590,7 @@ if [ "$CLASSIFY_STATUS" = "0" ]; then
|
|
|
557
590
|
fi
|
|
558
591
|
# Single source of truth for the accepted commit-type list (#3811): the
|
|
559
592
|
# 10 built-ins plus whatever passed the safe-token filter above. Both the
|
|
560
|
-
#
|
|
593
|
+
# membership test and the human-readable error text below are derived
|
|
561
594
|
# from this ONE array — no hand-synced second copy.
|
|
562
595
|
#
|
|
563
596
|
# The `"${EXTRA_COMMIT_TYPES[@]+"${EXTRA_COMMIT_TYPES[@]}"}"` form (not
|
|
@@ -568,7 +601,6 @@ if [ "$CLASSIFY_STATUS" = "0" ]; then
|
|
|
568
601
|
# /bin/bash 3.2.57 on macOS. The `${arr[@]+word}` form is the
|
|
569
602
|
# nounset-safe idiom for "expand if set, empty otherwise" on empty arrays.
|
|
570
603
|
COMMIT_TYPES=("${BUILTIN_COMMIT_TYPES[@]}" "${EXTRA_COMMIT_TYPES[@]+"${EXTRA_COMMIT_TYPES[@]}"}")
|
|
571
|
-
COMMIT_TYPE_ALT=$(IFS='|'; echo "${COMMIT_TYPES[*]}")
|
|
572
604
|
COMMIT_TYPE_LIST=$(printf '%s, ' "${COMMIT_TYPES[@]}")
|
|
573
605
|
COMMIT_TYPE_LIST="${COMMIT_TYPE_LIST%, }"
|
|
574
606
|
# Typed `valid_types` array (#3811 review finding): CONTRIBUTING.md bans
|
|
@@ -580,8 +612,35 @@ if [ "$CLASSIFY_STATUS" = "0" ]; then
|
|
|
580
612
|
# or `\`.
|
|
581
613
|
COMMIT_TYPES_JSON=$(printf '"%s",' "${COMMIT_TYPES[@]}")
|
|
582
614
|
COMMIT_TYPES_JSON="[${COMMIT_TYPES_JSON%,}]"
|
|
583
|
-
# Validate Conventional Commits format
|
|
584
|
-
|
|
615
|
+
# Validate Conventional Commits format.
|
|
616
|
+
#
|
|
617
|
+
# #4429: do NOT build `^(type1|type2|...)` out of COMMIT_TYPES. That
|
|
618
|
+
# alternation grows with the CONFIGURED list, and how large a pattern can be
|
|
619
|
+
# compiled is a property of the platform's regex engine. bash 3.2.57 / BSD
|
|
620
|
+
# libc (macOS, this file's stated target) caps it at 64 KiB - bisected: a
|
|
621
|
+
# 65504-byte alternation compiles, 65515 fails. bash 5.2 / glibc has no
|
|
622
|
+
# reachable cap, so this half never bit Linux. Past a cap `[[ =~ ]]`
|
|
623
|
+
# returns 2, and `if !` cannot tell a COMPILE ERROR from "the subject does
|
|
624
|
+
# not conform" - so a valid `feat(auth): ...` was blocked with
|
|
625
|
+
# CONVENTIONAL_COMMITS_VIOLATION while `feat` sat in its own valid_types.
|
|
626
|
+
#
|
|
627
|
+
# Match the SHAPE with a fixed-size pattern, then test membership against
|
|
628
|
+
# the array. The regex no longer depends on how many types are configured,
|
|
629
|
+
# and the loop adds no subprocess or pipe (the #4429 hazard this file
|
|
630
|
+
# already avoids elsewhere). The character class is exactly the safe-token
|
|
631
|
+
# filter `^[a-z][a-z0-9-]*$` applied above, so it captures every type that
|
|
632
|
+
# can legally reach COMMIT_TYPES and no token that cannot.
|
|
633
|
+
SUBJECT_TYPE=''
|
|
634
|
+
if [[ "$SUBJECT" =~ ^([a-z][a-z0-9-]*)(\(.+\))?:[[:space:]].+ ]]; then
|
|
635
|
+
SUBJECT_TYPE="${BASH_REMATCH[1]}"
|
|
636
|
+
fi
|
|
637
|
+
COMMIT_TYPE_OK=0
|
|
638
|
+
if [ -n "$SUBJECT_TYPE" ]; then
|
|
639
|
+
for _known_type in "${COMMIT_TYPES[@]}"; do
|
|
640
|
+
if [ "$_known_type" = "$SUBJECT_TYPE" ]; then COMMIT_TYPE_OK=1; break; fi
|
|
641
|
+
done
|
|
642
|
+
fi
|
|
643
|
+
if [ "$COMMIT_TYPE_OK" -ne 1 ]; then
|
|
585
644
|
# Emit typed `code` and `valid_types` fields alongside `reason` (#2974,
|
|
586
645
|
# #3811). Tests assert on the stable code string and the typed array;
|
|
587
646
|
# the reason is the human-readable copy, never grepped by tests.
|