@opengsd/gsd-core 1.11.0 → 1.13.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/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +78 -42
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +91 -112
- package/agents/gsd-planner.md +20 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +82 -7
- package/agents/gsd-ui-researcher.md +70 -3
- package/agents/gsd-verifier.md +24 -2
- package/bin/install.js +847 -200
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/quick.md +8 -4
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +761 -100
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +785 -144
- package/gsd-core/bin/lib/capability-state.cjs +25 -4
- package/gsd-core/bin/lib/capability-validator.cjs +321 -18
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +229 -6
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +877 -54
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +121 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +118 -14
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +23 -2
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +975 -326
- package/gsd-core/bin/lib/gap-checker.cjs +41 -8
- package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +96 -11
- package/gsd-core/bin/lib/init-command-router.cjs +132 -21
- package/gsd-core/bin/lib/init.cjs +252 -56
- package/gsd-core/bin/lib/install-engine.cjs +252 -15
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
- package/gsd-core/bin/lib/install-profiles.cjs +100 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +195 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +175 -4
- package/gsd-core/bin/lib/milestone.cjs +112 -7
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +321 -13
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +1175 -115
- package/gsd-core/bin/lib/plan-document.cjs +273 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
- package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
- package/gsd-core/bin/lib/roadmap.cjs +248 -64
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -31
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +216 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
- package/gsd-core/bin/lib/state-transition.cjs +850 -145
- package/gsd-core/bin/lib/state.cjs +1629 -287
- package/gsd-core/bin/lib/surface.cjs +33 -10
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +2542 -387
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -13
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +441 -56
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +6 -5
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +17 -5
- package/gsd-core/references/execute-mvp-tdd.md +18 -18
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +76 -3
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +39 -9
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +15 -12
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +7 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +3 -1
- package/gsd-core/workflows/add-phase.md +5 -3
- package/gsd-core/workflows/add-tests.md +4 -9
- package/gsd-core/workflows/add-todo.md +2 -2
- package/gsd-core/workflows/ai-integration-phase.md +5 -10
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +14 -3
- package/gsd-core/workflows/audit-milestone.md +11 -9
- package/gsd-core/workflows/audit-uat.md +19 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +12 -26
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +3 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
- package/gsd-core/workflows/code-review-fix.md +3 -1
- package/gsd-core/workflows/code-review.md +192 -69
- package/gsd-core/workflows/complete-milestone.md +28 -14
- package/gsd-core/workflows/debug.md +6 -4
- package/gsd-core/workflows/diagnose-issues.md +17 -7
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +46 -19
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +3 -1
- package/gsd-core/workflows/eval-review.md +5 -10
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
- 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 +29 -5
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +68 -66
- package/gsd-core/workflows/execute-plan.md +25 -20
- package/gsd-core/workflows/explore.md +3 -1
- package/gsd-core/workflows/extract-learnings.md +3 -1
- package/gsd-core/workflows/fast.md +8 -2
- package/gsd-core/workflows/forensics.md +3 -1
- package/gsd-core/workflows/graduation.md +6 -6
- package/gsd-core/workflows/health.md +4 -7
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +17 -14
- package/gsd-core/workflows/inbox.md +5 -6
- package/gsd-core/workflows/ingest-docs.md +45 -12
- package/gsd-core/workflows/insert-phase.md +7 -5
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +7 -3
- package/gsd-core/workflows/list-workspaces.md +3 -1
- package/gsd-core/workflows/manager.md +15 -26
- package/gsd-core/workflows/map-codebase.md +3 -1
- package/gsd-core/workflows/milestone-summary.md +3 -1
- package/gsd-core/workflows/mvp-phase.md +3 -3
- package/gsd-core/workflows/new-milestone.md +10 -22
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +17 -29
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +4 -2
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +20 -5
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
- package/gsd-core/workflows/plan-phase.md +251 -54
- package/gsd-core/workflows/plan-review-convergence.md +148 -19
- package/gsd-core/workflows/plant-seed.md +3 -3
- package/gsd-core/workflows/pr-branch.md +195 -51
- package/gsd-core/workflows/profile-user.md +17 -15
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +52 -15
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +33 -32
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +6 -4
- package/gsd-core/workflows/remove-workspace.md +3 -3
- package/gsd-core/workflows/resume-project.md +14 -14
- package/gsd-core/workflows/review.md +404 -21
- package/gsd-core/workflows/scan.md +3 -1
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +9 -9
- package/gsd-core/workflows/settings-integrations.md +66 -32
- package/gsd-core/workflows/settings.md +4 -6
- package/gsd-core/workflows/ship.md +22 -16
- package/gsd-core/workflows/sketch-wrap-up.md +13 -17
- package/gsd-core/workflows/sketch.md +13 -19
- package/gsd-core/workflows/smart-entry.md +4 -6
- package/gsd-core/workflows/spec-phase.md +31 -4
- package/gsd-core/workflows/spike-wrap-up.md +9 -11
- package/gsd-core/workflows/spike.md +21 -32
- package/gsd-core/workflows/stats.md +4 -2
- package/gsd-core/workflows/sync-skills.md +13 -5
- package/gsd-core/workflows/thread.md +13 -7
- package/gsd-core/workflows/transition.md +7 -5
- package/gsd-core/workflows/ui-phase.md +36 -21
- package/gsd-core/workflows/ui-review.md +7 -11
- package/gsd-core/workflows/ultraplan-phase.md +7 -13
- package/gsd-core/workflows/undo.md +9 -17
- package/gsd-core/workflows/update.md +47 -48
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +106 -21
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +302 -22
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +77 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +46 -12
- package/hooks/dist/gsd-read-guard.js +18 -7
- package/hooks/dist/gsd-read-injection-scanner.js +22 -13
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +222 -29
- package/hooks/dist/gsd-validate-commit.sh +523 -12
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +36 -17
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +4 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +302 -22
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +77 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +46 -12
- package/hooks/gsd-read-guard.js +18 -7
- package/hooks/gsd-read-injection-scanner.js +22 -13
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +222 -29
- package/hooks/gsd-validate-commit.sh +523 -12
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +36 -17
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/hooks.json +6 -0
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +4 -0
- package/package.json +14 -9
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +12 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +52 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +406 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +189 -4
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +45 -14
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +124 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +83 -14
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#
|
|
6
6
|
# OPT-IN: This hook is a no-op unless config.json has hooks.community: true.
|
|
7
7
|
# Enable with: "hooks": { "community": true } in .planning/config.json
|
|
8
|
+
set -euo pipefail
|
|
8
9
|
|
|
9
10
|
# Check opt-in config — exit silently if not enabled
|
|
10
11
|
if [ -f .planning/config.json ]; then
|
package/hooks/gsd-statusline.js
CHANGED
|
@@ -9,6 +9,14 @@ const os = require('os');
|
|
|
9
9
|
// Namespace (not destructured) so tests can inject spawn failures by
|
|
10
10
|
// monkeypatching childProcess.execFileSync.
|
|
11
11
|
const childProcess = require('child_process');
|
|
12
|
+
const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
|
|
13
|
+
|
|
14
|
+
// This hook's build-seam outer catch (require.main guard just below) has
|
|
15
|
+
// always exited 0 (fail open — the statusline renders on EVERY prompt, so a
|
|
16
|
+
// build failure must degrade to a blank line rather than break Claude Code's
|
|
17
|
+
// per-render hook). Declared ONCE so that catch's crash() call states its
|
|
18
|
+
// policy explicitly rather than inheriting a default (#3911).
|
|
19
|
+
const ON_CRASH = HOOK_ON_CRASH.ALLOW;
|
|
12
20
|
// #3582: gsd-core/bin/lib/*.cjs (semver-compare.cjs, state-document.cjs,
|
|
13
21
|
// active-workstream-store.cjs, planning-workspace.cjs — required below) and
|
|
14
22
|
// package-identity.cjs are tsc build artifacts (ADR-457), gitignored and
|
|
@@ -23,8 +31,12 @@ if (require.main === module) {
|
|
|
23
31
|
const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
|
|
24
32
|
ensureRuntimeBuild();
|
|
25
33
|
} catch (e) {
|
|
26
|
-
|
|
27
|
-
process.exit(0)
|
|
34
|
+
// #3911: crash(ON_CRASH, ...) with an undefined payload preserves the
|
|
35
|
+
// pre-migration `process.stdout.write(''); process.exit(0);` byte-for-
|
|
36
|
+
// byte — undefined makes terminateNow's stdout JSON.stringify throw
|
|
37
|
+
// internally (swallowed there), so fd 1 stays untouched, same as writing
|
|
38
|
+
// an explicit empty string did.
|
|
39
|
+
crash(ON_CRASH, undefined);
|
|
28
40
|
}
|
|
29
41
|
}
|
|
30
42
|
const { isSemverNewer } = require('../gsd-core/bin/lib/semver-compare.cjs');
|
|
@@ -169,13 +181,27 @@ function readStateFileOrNull(statePath) {
|
|
|
169
181
|
* - null when no .planning marker is found at all (GSD not present), or
|
|
170
182
|
* when a workstream DOES resolve but its STATE.md doesn't exist yet
|
|
171
183
|
* (negative space: mirrors flat-mode's own silent pre-STATE.md window)
|
|
184
|
+
*
|
|
185
|
+
* @param {string} dir
|
|
186
|
+
* @param {{ stateFreshness?: boolean }} [opts] — #2734, additive/default-off.
|
|
187
|
+
* When true and the resolved state carries a truthy `.stateHead`, attaches
|
|
188
|
+
* `state.freshness` (deriveStateFreshness) before returning — `current` at
|
|
189
|
+
* that point is the project root the walk resolved, which is what AC-4's
|
|
190
|
+
* repo-pinning check needs. Never derived when false or the stamp is
|
|
191
|
+
* absent, so existing callers (default opts) spend zero extra spawns.
|
|
172
192
|
*/
|
|
173
|
-
function readGsdState(dir) {
|
|
193
|
+
function readGsdState(dir, opts = {}) {
|
|
194
|
+
const { stateFreshness = false } = opts;
|
|
174
195
|
const home = os.homedir();
|
|
175
196
|
let current = dir;
|
|
176
197
|
for (let i = 0; i < 10; i++) {
|
|
177
198
|
const flatState = readStateFileOrNull(path.join(current, '.planning', 'STATE.md'));
|
|
178
|
-
if (flatState !== null)
|
|
199
|
+
if (flatState !== null) {
|
|
200
|
+
if (stateFreshness && flatState.stateHead) {
|
|
201
|
+
flatState.freshness = deriveStateFreshness(current, flatState.stateHead);
|
|
202
|
+
}
|
|
203
|
+
return flatState;
|
|
204
|
+
}
|
|
179
205
|
|
|
180
206
|
if (listAvailableWorkstreams(current).length > 0) {
|
|
181
207
|
let resolvedWs = null;
|
|
@@ -187,7 +213,11 @@ function readGsdState(dir) {
|
|
|
187
213
|
|
|
188
214
|
if (!resolvedWs) return { noActiveWorkstream: true };
|
|
189
215
|
|
|
190
|
-
|
|
216
|
+
const wsState = readStateFileOrNull(planningPaths(current, resolvedWs).state);
|
|
217
|
+
if (wsState !== null && stateFreshness && wsState.stateHead) {
|
|
218
|
+
wsState.freshness = deriveStateFreshness(current, wsState.stateHead);
|
|
219
|
+
}
|
|
220
|
+
return wsState;
|
|
191
221
|
}
|
|
192
222
|
|
|
193
223
|
const parent = path.dirname(current);
|
|
@@ -238,6 +268,10 @@ function parseStateMd(content) {
|
|
|
238
268
|
if (key === 'active_phase') state.activePhase = (v === 'null' || v === '') ? null : v;
|
|
239
269
|
// next_action: recommended command when idle (discuss-phase / plan-phase / execute-phase / verify-phase)
|
|
240
270
|
if (key === 'next_action') state.nextAction = (v === 'null' || v === '') ? null : v;
|
|
271
|
+
// #2734: state_head — the commit STATE.md was written against, consumed
|
|
272
|
+
// by deriveStateFreshness() below. Mirrors active_phase/next_action's
|
|
273
|
+
// null/empty handling exactly.
|
|
274
|
+
if (key === 'state_head') state.stateHead = (v === 'null' || v === '') ? null : v;
|
|
241
275
|
}
|
|
242
276
|
// next_phases supports both flow array and block-list YAML forms.
|
|
243
277
|
const npFlowMatch = fm.match(/^next_phases:\s*\[([^\]]*)\]/m);
|
|
@@ -355,8 +389,12 @@ function formatGsdState(s) {
|
|
|
355
389
|
// Scene 2: idle + a recommended next command is visible to the user.
|
|
356
390
|
// Surfaces "what to run next" without the user opening STATE.md.
|
|
357
391
|
parts.push(`next ${s.nextAction} ${phasesStr}`);
|
|
358
|
-
} else if (Number(s.percent) === 100 || (s.
|
|
359
|
-
// Scene 3: milestone complete (every phase done).
|
|
392
|
+
} else if (Number(s.percent) === 100 || (Number(s.totalPhases) > 0 && Number(s.completedPhases) === Number(s.totalPhases))) {
|
|
393
|
+
// Scene 3: milestone complete (every phase done). #3945: the counters are
|
|
394
|
+
// regex-captured STRINGS, so the old `cp && tp && cp === tp` guard fired on
|
|
395
|
+
// the empty set ('0' is truthy, '0' === '0') — "0% · milestone complete".
|
|
396
|
+
// Numeric coercion + a non-empty denominator makes "nothing to measure"
|
|
397
|
+
// stop meaning "everything is done".
|
|
360
398
|
parts.push('milestone complete');
|
|
361
399
|
} else {
|
|
362
400
|
// Backward-compatible default — preserved EXACTLY for STATE.md files that
|
|
@@ -371,6 +409,10 @@ function formatGsdState(s) {
|
|
|
371
409
|
}
|
|
372
410
|
}
|
|
373
411
|
|
|
412
|
+
// #2734: STATE.md freshness marker — opt-in, appended last.
|
|
413
|
+
const fresh = formatStateFreshness(s.freshness);
|
|
414
|
+
if (fresh) parts.push(fresh);
|
|
415
|
+
|
|
374
416
|
return parts.join(' · ');
|
|
375
417
|
}
|
|
376
418
|
|
|
@@ -462,7 +504,7 @@ function formatGsdStateCompact(s) {
|
|
|
462
504
|
// still completes) wins over milestone-complete (Scene 3), even if a
|
|
463
505
|
// non-atomic STATE.md edit leaves percent=100 alongside a lifecycle phase.
|
|
464
506
|
const done = !s.activePhase && (Number(s.percent) === 100 ||
|
|
465
|
-
(s.
|
|
507
|
+
(Number(s.totalPhases) > 0 && Number(s.completedPhases) === Number(s.totalPhases)));
|
|
466
508
|
|
|
467
509
|
if (done) {
|
|
468
510
|
parts.push('complete');
|
|
@@ -476,6 +518,10 @@ function formatGsdStateCompact(s) {
|
|
|
476
518
|
}
|
|
477
519
|
}
|
|
478
520
|
|
|
521
|
+
// #2734: STATE.md freshness marker \u2014 opt-in, appended last.
|
|
522
|
+
const fresh = formatStateFreshness(s.freshness);
|
|
523
|
+
if (fresh) parts.push(fresh);
|
|
524
|
+
|
|
479
525
|
return parts.join(' \u00b7 ');
|
|
480
526
|
}
|
|
481
527
|
|
|
@@ -572,13 +618,153 @@ function buildGitSegment(info) {
|
|
|
572
618
|
return ` │ \x1b[2m${info.branch}\x1b[0m${state}`;
|
|
573
619
|
}
|
|
574
620
|
|
|
621
|
+
// --- STATE.md freshness marker (opt-in, #2734) --------------------------------
|
|
622
|
+
//
|
|
623
|
+
// Opt-in via `statusline.show_state_freshness: true`. Renders `state ~N
|
|
624
|
+
// commits back` inside the GSD-state segment when STATE.md's `state_head`
|
|
625
|
+
// stamp (#2573) is at least STATE_HEAD_ADVISORY_COMMITS commits behind HEAD.
|
|
626
|
+
// Same impure-reader -> pure-IR -> pure-formatter shape as the git segment
|
|
627
|
+
// above. See .gsd/phase/feat-2734-statusline-state-freshness/40-design.md.
|
|
628
|
+
|
|
629
|
+
// Deliberate mirror of the fence in src/state.cts (STATE_HEAD_HASH_RE) — kept
|
|
630
|
+
// hook-side rather than requiring state.cjs on the per-render path (measured
|
|
631
|
+
// ~20ms; see design doc "Laws that apply"). tests/gsd-statusline.test.cjs
|
|
632
|
+
// asserts behavioral parity against readStateHeadFreshness rather than
|
|
633
|
+
// comparing source (local/no-source-grep forbids the latter anyway).
|
|
634
|
+
const STATE_HEAD_HASH_RE = /^[0-9a-f]{4,40}$/i;
|
|
635
|
+
|
|
636
|
+
// Mirror of the constant verify.cts's W024 health check thresholds on
|
|
637
|
+
// (STATE_HEAD_ADVISORY_COMMITS). A test asserts equality with verify.cjs's
|
|
638
|
+
// export so the two copies can't drift.
|
|
639
|
+
const STATE_HEAD_ADVISORY_COMMITS = 20;
|
|
640
|
+
|
|
641
|
+
// Same bound class as GIT_STATUS_TIMEOUT_MS above.
|
|
642
|
+
const STATE_FRESHNESS_GIT_TIMEOUT_MS = 1500;
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Pure function: does raw pass the state_head hash fence? Must run BEFORE any
|
|
646
|
+
* value from STATE.md reaches a git argv slot.
|
|
647
|
+
*/
|
|
648
|
+
function isValidStateHeadStamp(raw) {
|
|
649
|
+
return typeof raw === 'string' && STATE_HEAD_HASH_RE.test(raw.trim());
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Run `git rev-list --left-right --count <stamp>...HEAD` in root. Returns raw
|
|
654
|
+
* stdout, or null when git is missing, root isn't a repo, the stamp is
|
|
655
|
+
* unknown, or the call times out. Never throws. Only call with a stamp that
|
|
656
|
+
* already passed isValidStateHeadStamp/the hash fence above.
|
|
657
|
+
*/
|
|
658
|
+
function readStateHeadCommits(root, stamp) {
|
|
659
|
+
try {
|
|
660
|
+
return childProcess.execFileSync('git',
|
|
661
|
+
['-C', root, 'rev-list', '--left-right', '--count', `${stamp}...HEAD`],
|
|
662
|
+
{ encoding: 'utf8', timeout: STATE_FRESHNESS_GIT_TIMEOUT_MS,
|
|
663
|
+
stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
|
|
664
|
+
} catch (e) {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Pure function: parse `git rev-list --left-right --count A...B` output
|
|
671
|
+
* ("<left>\t<right>"). Returns { left, right } as non-negative integers, or
|
|
672
|
+
* null when text isn't a matching string (covers null, '', 'garbage', '1',
|
|
673
|
+
* 'a\tb', '\t', and any other unparseable shape).
|
|
674
|
+
*/
|
|
675
|
+
function parseRevListCounts(text) {
|
|
676
|
+
if (typeof text !== 'string') return null;
|
|
677
|
+
const m = text.match(/^(\d+)\s+(\d+)\s*$/);
|
|
678
|
+
if (!m) return null;
|
|
679
|
+
return { left: parseInt(m[1], 10), right: parseInt(m[2], 10) };
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Impure -> pure IR: derive the freshness signal for a recorded state_head
|
|
684
|
+
* stamp. Returns { state_head, commits_behind, commit_stale } — never throws,
|
|
685
|
+
* every unresolvable input degrades to the all-null-but-state_head shape.
|
|
686
|
+
*
|
|
687
|
+
* Order (each failure returns immediately, no further work):
|
|
688
|
+
* a. hash fence — malformed/absent stamp never reaches a spawn
|
|
689
|
+
* b. repo pinning — root must own its own .git (mirrors projectOwnsItsRepo
|
|
690
|
+
* in src/state.cts: a filesystem-identity check, not a --show-toplevel
|
|
691
|
+
* string compare, which is unreliable on macOS /private/var and Windows
|
|
692
|
+
* 8.3 paths). Costs no subprocess.
|
|
693
|
+
* c. sub_repos guard — a planning.sub_repos workspace's outer HEAD never
|
|
694
|
+
* advances when code lands in nested children, so a "fresh" answer here
|
|
695
|
+
* would be a confident lie. Costs no subprocess.
|
|
696
|
+
* d. one bounded git spawn: rev-list --left-right --count answers ancestry
|
|
697
|
+
* and distance together. left > 0 means the stamp is not an ancestor of
|
|
698
|
+
* HEAD (reset/rebase/force-push) -> unknown, never "fresh".
|
|
699
|
+
*/
|
|
700
|
+
function deriveStateFreshness(root, stamp, deps = {}) {
|
|
701
|
+
const { existsSync = fs.existsSync, readConfig = readGsdConfig, readCounts = readStateHeadCommits } = deps;
|
|
702
|
+
|
|
703
|
+
const raw = typeof stamp === 'string' ? stamp.trim() : '';
|
|
704
|
+
const valid = STATE_HEAD_HASH_RE.test(raw);
|
|
705
|
+
const state_head = valid ? raw.slice(0, 7) : null;
|
|
706
|
+
const nullResult = { state_head, commits_behind: null, commit_stale: null };
|
|
707
|
+
if (!valid || !root) return nullResult;
|
|
708
|
+
|
|
709
|
+
try {
|
|
710
|
+
if (!existsSync(path.join(root, '.git'))) return nullResult;
|
|
711
|
+
} catch (e) {
|
|
712
|
+
return nullResult;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
try {
|
|
716
|
+
const cfg = readConfig(root);
|
|
717
|
+
const sub = getConfigValue(cfg, 'planning.sub_repos') ?? getConfigValue(cfg, 'sub_repos');
|
|
718
|
+
if (Array.isArray(sub) && sub.length > 0) return nullResult;
|
|
719
|
+
} catch (e) {
|
|
720
|
+
return nullResult;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const counts = parseRevListCounts(readCounts(root, raw));
|
|
724
|
+
if (!counts || counts.left > 0) return nullResult;
|
|
725
|
+
|
|
726
|
+
return { state_head, commits_behind: counts.right, commit_stale: counts.right > 0 };
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Pure function: format the freshness IR into the marker text, or '' below
|
|
731
|
+
* STATE_HEAD_ADVISORY_COMMITS (including when commits_behind is absent/null —
|
|
732
|
+
* the unknown case must never render, never mind alarm on it).
|
|
733
|
+
*/
|
|
734
|
+
function formatStateFreshness(fresh) {
|
|
735
|
+
if (!fresh || typeof fresh.commits_behind !== 'number' || fresh.commits_behind < STATE_HEAD_ADVISORY_COMMITS) return '';
|
|
736
|
+
return `state ~${fresh.commits_behind} commits back`;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Pure function: single source of truth for statusline config resolution.
|
|
741
|
+
* `runStatusline()` and `renderStatusline()` previously read this config
|
|
742
|
+
* independently, which had drifted into a live divergence between the two
|
|
743
|
+
* entry points — this collapses both onto one resolver.
|
|
744
|
+
*
|
|
745
|
+
* @param {object} cfg — parsed .planning/config.json (readGsdConfig())
|
|
746
|
+
* @returns {{ showLastCommand: boolean, position: 'end'|'front', stateFormat: 'full'|'compact', showGit: boolean, showStateFreshness: boolean }}
|
|
747
|
+
*/
|
|
748
|
+
function resolveStatuslineOptions(cfg) {
|
|
749
|
+
const showLastCommand = getConfigValue(cfg, 'statusline.show_last_command') === true;
|
|
750
|
+
const cfgPos = getConfigValue(cfg, 'statusline.context_position');
|
|
751
|
+
// Clamp any non-'front' value (including absent/null) to 'end' — the single
|
|
752
|
+
// source of truth for this default; composeStatusline's own coercion stays
|
|
753
|
+
// as belt-and-suspenders defense for direct callers.
|
|
754
|
+
const position = cfgPos === 'front' ? 'front' : 'end';
|
|
755
|
+
const stateFormat = getConfigValue(cfg, 'statusline.state_format') === 'compact' ? 'compact' : 'full';
|
|
756
|
+
const showGit = getConfigValue(cfg, 'statusline.show_git') === true;
|
|
757
|
+
const showStateFreshness = getConfigValue(cfg, 'statusline.show_state_freshness') === true;
|
|
758
|
+
return { showLastCommand, position, stateFormat, showGit, showStateFreshness };
|
|
759
|
+
}
|
|
760
|
+
|
|
575
761
|
// --- stdin ------------------------------------------------------------------
|
|
576
762
|
|
|
577
763
|
function runStatusline() {
|
|
578
764
|
let input = '';
|
|
579
765
|
// Timeout guard: if stdin doesn't close within 3s (e.g. pipe issues on
|
|
580
766
|
// Windows/Git Bash), exit silently instead of hanging. See #775.
|
|
581
|
-
const stdinTimeout = setTimeout(() =>
|
|
767
|
+
const stdinTimeout = setTimeout(() => allow(undefined), 3000);
|
|
582
768
|
process.stdin.setEncoding('utf8');
|
|
583
769
|
process.stdin.on('data', chunk => input += chunk);
|
|
584
770
|
process.stdin.on('end', () => {
|
|
@@ -717,31 +903,34 @@ function runStatusline() {
|
|
|
717
903
|
// Last-slash-command suffix and context_position config (#2538, #2937).
|
|
718
904
|
// Reads the active session transcript for the most recent <command-name> tag.
|
|
719
905
|
// Failure here must never break the statusline — wrap the entire lookup.
|
|
906
|
+
// #2734: config resolution moved to resolveStatuslineOptions() — the single
|
|
907
|
+
// source of truth shared with renderStatusline() below. The two entry
|
|
908
|
+
// points duplicated this resolution byte-for-byte; one copy is what keeps
|
|
909
|
+
// a new key from reaching only one of them.
|
|
720
910
|
let lastCmdSuffix = '';
|
|
721
|
-
let position = 'end';
|
|
722
|
-
let stateFormat = 'full';
|
|
723
911
|
let gitSuffix = '';
|
|
912
|
+
const options = resolveStatuslineOptions(cfg);
|
|
724
913
|
try {
|
|
725
|
-
if (
|
|
914
|
+
if (options.showLastCommand) {
|
|
726
915
|
const transcriptPath = data.transcript_path;
|
|
727
916
|
const lastCmd = readLastSlashCommand(transcriptPath);
|
|
728
917
|
if (lastCmd) {
|
|
729
918
|
lastCmdSuffix = ` │ \x1b[2mlast: /${lastCmd}\x1b[0m`;
|
|
730
919
|
}
|
|
731
920
|
}
|
|
732
|
-
|
|
733
|
-
if (cfgPos != null) position = cfgPos;
|
|
734
|
-
if (getConfigValue(cfg, 'statusline.state_format') === 'compact') stateFormat = 'compact';
|
|
735
|
-
if (getConfigValue(cfg, 'statusline.show_git') === true) {
|
|
921
|
+
if (options.showGit) {
|
|
736
922
|
gitSuffix = buildGitSegment(parseGitStatus(readGitStatus(dir)));
|
|
737
923
|
}
|
|
738
924
|
} catch (e) {
|
|
739
925
|
// Never break the statusline on config/transcript/git errors
|
|
740
926
|
}
|
|
741
927
|
|
|
928
|
+
// #2734: readGsdState is inside `if (!task)` deliberately — when a todo
|
|
929
|
+
// task is in flight the GSD-state segment is not rendered, so spending a
|
|
930
|
+
// freshness git spawn here would spend a subprocess on discarded output.
|
|
742
931
|
if (!task) {
|
|
743
|
-
const state = readGsdState(dir) || {};
|
|
744
|
-
gsdStateStr = stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
|
|
932
|
+
const state = readGsdState(dir, { stateFreshness: options.showStateFreshness }) || {};
|
|
933
|
+
gsdStateStr = options.stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
|
|
745
934
|
}
|
|
746
935
|
|
|
747
936
|
// Output
|
|
@@ -752,7 +941,7 @@ function runStatusline() {
|
|
|
752
941
|
? `\x1b[2m${gsdStateStr}\x1b[0m`
|
|
753
942
|
: null;
|
|
754
943
|
|
|
755
|
-
process.stdout.write(composeStatusline({ gsdUpdate, model, ctx, middle, dirname, lastCmdSuffix, gitSuffix, position }));
|
|
944
|
+
process.stdout.write(composeStatusline({ gsdUpdate, model, ctx, middle, dirname, lastCmdSuffix, gitSuffix, position: options.position }));
|
|
756
945
|
} catch (e) {
|
|
757
946
|
// Silent fail - don't break statusline on parse errors
|
|
758
947
|
}
|
|
@@ -846,6 +1035,9 @@ module.exports = {
|
|
|
846
1035
|
shortGsdStatus, formatGsdStateCompact,
|
|
847
1036
|
compactModelName,
|
|
848
1037
|
readGitStatus, parseGitStatus, buildGitSegment,
|
|
1038
|
+
STATE_HEAD_ADVISORY_COMMITS, isValidStateHeadStamp,
|
|
1039
|
+
readStateHeadCommits, parseRevListCounts, deriveStateFreshness,
|
|
1040
|
+
formatStateFreshness, resolveStatuslineOptions,
|
|
849
1041
|
};
|
|
850
1042
|
|
|
851
1043
|
/**
|
|
@@ -857,30 +1049,31 @@ function renderStatusline(data) {
|
|
|
857
1049
|
const dir = data.workspace?.current_dir || process.cwd();
|
|
858
1050
|
const dirname = path.basename(dir);
|
|
859
1051
|
|
|
1052
|
+
// #2734: config resolution moved to resolveStatuslineOptions() — the single
|
|
1053
|
+
// source of truth shared with runStatusline() above. The two entry points
|
|
1054
|
+
// duplicated this resolution byte-for-byte; one copy is what keeps a new
|
|
1055
|
+
// key from reaching only one of them.
|
|
860
1056
|
let lastCmdSuffix = '';
|
|
861
|
-
let position = 'end';
|
|
862
|
-
let stateFormat = 'full';
|
|
863
1057
|
let gitSuffix = '';
|
|
1058
|
+
let options = { showLastCommand: false, position: 'end', stateFormat: 'full', showGit: false, showStateFreshness: false };
|
|
864
1059
|
try {
|
|
865
1060
|
const cfg = readGsdConfig(dir);
|
|
866
|
-
|
|
1061
|
+
options = resolveStatuslineOptions(cfg);
|
|
1062
|
+
if (options.showLastCommand) {
|
|
867
1063
|
const lastCmd = readLastSlashCommand(data.transcript_path);
|
|
868
1064
|
if (lastCmd) {
|
|
869
1065
|
lastCmdSuffix = ` │ \x1b[2mlast: /${lastCmd}\x1b[0m`;
|
|
870
1066
|
}
|
|
871
1067
|
}
|
|
872
|
-
|
|
873
|
-
if (cfgPos != null) position = cfgPos;
|
|
874
|
-
if (getConfigValue(cfg, 'statusline.state_format') === 'compact') stateFormat = 'compact';
|
|
875
|
-
if (getConfigValue(cfg, 'statusline.show_git') === true) {
|
|
1068
|
+
if (options.showGit) {
|
|
876
1069
|
gitSuffix = buildGitSegment(parseGitStatus(readGitStatus(dir)));
|
|
877
1070
|
}
|
|
878
1071
|
} catch (e) { /* swallow */ }
|
|
879
1072
|
|
|
880
|
-
const state = readGsdState(dir) || {};
|
|
881
|
-
const gsdStateStr = stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
|
|
1073
|
+
const state = readGsdState(dir, { stateFreshness: options.showStateFreshness }) || {};
|
|
1074
|
+
const gsdStateStr = options.stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
|
|
882
1075
|
const middle = gsdStateStr ? `\x1b[2m${gsdStateStr}\x1b[0m` : null;
|
|
883
|
-
return composeStatusline({ model, ctx: '', middle, dirname, lastCmdSuffix, gitSuffix, position });
|
|
1076
|
+
return composeStatusline({ model, ctx: '', middle, dirname, lastCmdSuffix, gitSuffix, position: options.position });
|
|
884
1077
|
}
|
|
885
1078
|
|
|
886
1079
|
module.exports.renderStatusline = renderStatusline;
|