@opengsd/gsd-core 1.10.0 → 1.12.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/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +12 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +22 -14
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +5 -3
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +23 -2
- package/agents/gsd-plan-checker.md +50 -53
- package/agents/gsd-planner.md +50 -50
- 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 +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +13 -4
- package/bin/install.js +1448 -1103
- package/commands/gsd/code-review.md +1 -1
- 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/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +9 -5
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1035 -138
- package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
- package/gsd-core/bin/lib/api-coverage.cjs +33 -14
- package/gsd-core/bin/lib/artifacts.cjs +5 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +1026 -268
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +845 -130
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +700 -40
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +216 -42
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +1172 -108
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
- package/gsd-core/bin/lib/config-loader.cjs +187 -23
- package/gsd-core/bin/lib/config.cjs +102 -3
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +208 -33
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- 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/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +899 -229
- package/gsd-core/bin/lib/gap-checker.cjs +95 -10
- package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +439 -168
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +811 -259
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
- package/gsd-core/bin/lib/install-profiles.cjs +212 -61
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +170 -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/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +183 -22
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +842 -73
- package/gsd-core/bin/lib/model-catalog.cjs +232 -16
- package/gsd-core/bin/lib/model-resolver.cjs +193 -68
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
- package/gsd-core/bin/lib/phase-id.cjs +514 -40
- package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
- package/gsd-core/bin/lib/phase-locator.cjs +262 -34
- package/gsd-core/bin/lib/phase.cjs +1038 -214
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +98 -3
- 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-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
- package/gsd-core/bin/lib/probe-core.cjs +5 -2
- package/gsd-core/bin/lib/profile-output.cjs +1 -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/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
- package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +442 -96
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
- package/gsd-core/bin/lib/smart-entry.cjs +133 -23
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +52 -19
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +338 -8
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +846 -176
- package/gsd-core/bin/lib/state.cjs +2589 -369
- package/gsd-core/bin/lib/surface.cjs +33 -11
- 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/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
- package/gsd-core/bin/lib/uat.cjs +1761 -167
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
- package/gsd-core/bin/lib/unusable-input.cjs +37 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +75 -0
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +272 -9
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +453 -918
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
- 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/references/agent-contracts.md +44 -26
- 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 +39 -21
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +4 -6
- package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +17 -11
- package/gsd-core/references/failing-direction.md +78 -0
- 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 +5 -4
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +61 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- 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/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +44 -13
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- 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 +5 -5
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -17
- 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/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +5 -4
- package/gsd-core/templates/state.md +11 -4
- package/gsd-core/templates/verification-report.md +9 -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 +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +13 -20
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +11 -27
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +64 -5
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
- package/gsd-core/workflows/code-review-fix.md +38 -11
- package/gsd-core/workflows/code-review.md +159 -52
- package/gsd-core/workflows/complete-milestone.md +151 -23
- package/gsd-core/workflows/debug.md +12 -8
- package/gsd-core/workflows/diagnose-issues.md +47 -15
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +27 -2
- package/gsd-core/workflows/eval-review.md +7 -14
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
- 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 +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
- 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 +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +72 -100
- package/gsd-core/workflows/execute-plan.md +52 -15
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +76 -10
- package/gsd-core/workflows/import.md +18 -15
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +49 -16
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +8 -5
- package/gsd-core/workflows/new-milestone.md +22 -29
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +26 -40
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +14 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- 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 +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +162 -59
- package/gsd-core/workflows/plan-review-convergence.md +96 -11
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +61 -18
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
- package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
- package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +55 -44
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +219 -20
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +69 -7
- package/gsd-core/workflows/ship.md +116 -50
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +53 -13
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +64 -9
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +49 -14
- package/gsd-core/workflows/ui-phase.md +15 -21
- package/gsd-core/workflows/ui-review.md +8 -12
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +7 -11
- 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 +66 -25
- package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
- 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 +83 -3
- package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
- 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 +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +37 -27
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +55 -32
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +231 -24
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- 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 +162 -46
- 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 +92 -59
- 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 +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +158 -30
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +2 -3
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +83 -3
- package/hooks/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +37 -27
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +55 -32
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +231 -24
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +162 -46
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +28 -11
- package/pi/gsd.cjs +19 -5
- package/scripts/base64-scan.sh +74 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +5 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -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 +64 -14
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +11 -21
- package/scripts/gen-context-index.cjs +2 -11
- 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-health-docs.cjs +390 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +138 -25
- package/scripts/gen-registry.cjs +3 -14
- 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/alias-drift-families.cjs +46 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +308 -0
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +492 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +471 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +488 -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-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +950 -0
- package/scripts/lint-test-file-count.allowlist.json +137 -8
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +830 -0
- package/scripts/lint-vendored-deps.cjs +297 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +100 -14
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +24 -7
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +9 -5
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -107
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
|
|
24
24
|
const path = require('path');
|
|
25
25
|
const fs = require('fs');
|
|
26
|
+
const { HOOK_ON_CRASH, allow, crash } = require('./lib/hook-exit.js');
|
|
27
|
+
|
|
28
|
+
// This is a PostToolUse advisory scanner over content the tool call already
|
|
29
|
+
// returned; a crash while scanning must not retroactively block that Read/
|
|
30
|
+
// WebFetch/WebSearch result from reaching the agent — losing the injection
|
|
31
|
+
// check is safer than failing the tool call it is only observing (#3911).
|
|
32
|
+
const ON_CRASH = HOOK_ON_CRASH.ALLOW;
|
|
26
33
|
|
|
27
34
|
// Summarisation-specific patterns (novel — not in gsd-prompt-guard.js).
|
|
28
35
|
// These target instructions specifically designed to survive context compression.
|
|
@@ -72,23 +79,10 @@ const MARKDOWN_LINK_PATTERNS = [
|
|
|
72
79
|
},
|
|
73
80
|
];
|
|
74
81
|
|
|
75
|
-
// Standard injection patterns —
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
/disregard\s+(all\s+)?previous/i,
|
|
80
|
-
/forget\s+(all\s+)?(your\s+)?instructions/i,
|
|
81
|
-
/override\s+(system|previous)\s+(prompt|instructions)/i,
|
|
82
|
-
/you\s+are\s+now\s+(?:a|an|the)\s+/i,
|
|
83
|
-
/act\s+as\s+(?:a|an|the)\s+(?!plan|phase|wave)/i,
|
|
84
|
-
/pretend\s+(?:you(?:'re| are)\s+|to\s+be\s+)/i,
|
|
85
|
-
/from\s+now\s+on,?\s+you\s+(?:are|will|should|must)/i,
|
|
86
|
-
/(?:print|output|reveal|show|display|repeat)\s+(?:your\s+)?(?:system\s+)?(?:prompt|instructions)/i,
|
|
87
|
-
/<\/?(?:system|assistant|human)>/i,
|
|
88
|
-
/\[SYSTEM\]/i,
|
|
89
|
-
/\[INST\]/i,
|
|
90
|
-
/<<\s*SYS\s*>>/i,
|
|
91
|
-
];
|
|
82
|
+
// Standard injection patterns — shared with gsd-prompt-guard.js via
|
|
83
|
+
// hooks/lib/injection-patterns.js so the two surfaces cannot drift (#3504).
|
|
84
|
+
// Staging of the lib helper is allowlisted in GSD_HOOK_LIB_FILES (bin/install.js).
|
|
85
|
+
const { INJECTION_PATTERNS } = require('./lib/injection-patterns.js');
|
|
92
86
|
|
|
93
87
|
const ALL_PATTERNS = [...INJECTION_PATTERNS, ...SUMMARISATION_PATTERNS];
|
|
94
88
|
|
|
@@ -98,6 +92,16 @@ const ALL_PATTERNS = [...INJECTION_PATTERNS, ...SUMMARISATION_PATTERNS];
|
|
|
98
92
|
// construction. Normalized to forward slashes to match `p` below.
|
|
99
93
|
const OWN_BUNDLE_PREFIX = __dirname.replace(/\\/g, '/').replace(/\/+$/, '') + '/';
|
|
100
94
|
|
|
95
|
+
// Synthetic rule ids for the finding classes that have no entry in
|
|
96
|
+
// MARKDOWN_LINK_PATTERNS. Frozen and referenced from BOTH the push sites and
|
|
97
|
+
// renderFinding so the two can never drift — a bare literal repeated at each
|
|
98
|
+
// site is how a rename silently falls through to the generic render branch.
|
|
99
|
+
const RULE_IDS = Object.freeze({
|
|
100
|
+
INJECTION_PATTERN: 'INJECTION-PATTERN',
|
|
101
|
+
INVISIBLE_UNICODE: 'INVISIBLE-UNICODE',
|
|
102
|
+
UNICODE_TAG_BLOCK: 'UNICODE-TAG-BLOCK',
|
|
103
|
+
});
|
|
104
|
+
|
|
101
105
|
function isExcludedPath(filePath) {
|
|
102
106
|
const p = filePath.replace(/\\/g, '/');
|
|
103
107
|
return (
|
|
@@ -216,7 +220,7 @@ function normalizeKimiPayload(data) {
|
|
|
216
220
|
}
|
|
217
221
|
|
|
218
222
|
let inputBuf = '';
|
|
219
|
-
const stdinTimeout = setTimeout(() =>
|
|
223
|
+
const stdinTimeout = setTimeout(() => allow(undefined), 5000);
|
|
220
224
|
process.stdin.setEncoding('utf8');
|
|
221
225
|
process.stdin.on('data', chunk => { inputBuf += chunk; });
|
|
222
226
|
process.stdin.on('end', () => {
|
|
@@ -227,7 +231,7 @@ process.stdin.on('end', () => {
|
|
|
227
231
|
const toolName = data.tool_name;
|
|
228
232
|
const SCANNED_TOOLS = new Set(['Read', 'WebFetch', 'WebSearch']);
|
|
229
233
|
if (!SCANNED_TOOLS.has(toolName)) {
|
|
230
|
-
|
|
234
|
+
allow(undefined);
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
// Source label + path-exclusion (path-exclusion applies to file reads only)
|
|
@@ -238,8 +242,8 @@ process.stdin.on('end', () => {
|
|
|
238
242
|
source = typeof data.tool_input?.file_path === 'string'
|
|
239
243
|
? data.tool_input.file_path
|
|
240
244
|
: '';
|
|
241
|
-
if (!source)
|
|
242
|
-
if (isExcludedPath(source))
|
|
245
|
+
if (!source) allow(undefined);
|
|
246
|
+
if (isExcludedPath(source)) allow(undefined);
|
|
243
247
|
} else if (toolName === 'WebFetch') {
|
|
244
248
|
source = data.tool_input?.url || 'web';
|
|
245
249
|
} else { // WebSearch
|
|
@@ -264,15 +268,22 @@ process.stdin.on('end', () => {
|
|
|
264
268
|
}
|
|
265
269
|
|
|
266
270
|
if (!content || content.length < 20) {
|
|
267
|
-
|
|
271
|
+
allow(undefined);
|
|
268
272
|
}
|
|
269
273
|
|
|
274
|
+
// Typed findings IR — single source of truth for both the machine-readable
|
|
275
|
+
// `findings` array and the rendered advisory prose. Never build these as two
|
|
276
|
+
// parallel arrays: that invites the generative-fix-divergence defect class
|
|
277
|
+
// where the rendered text and the structured data silently drift apart.
|
|
270
278
|
const findings = [];
|
|
271
279
|
|
|
272
280
|
for (const pattern of ALL_PATTERNS) {
|
|
273
281
|
if (pattern.test(content)) {
|
|
274
282
|
// Trim pattern source for readable output
|
|
275
|
-
findings.push(
|
|
283
|
+
findings.push({
|
|
284
|
+
ruleId: RULE_IDS.INJECTION_PATTERN,
|
|
285
|
+
match: pattern.source.replace(/\\s\+/g, '-').replace(/[()\\]/g, '').substring(0, 50),
|
|
286
|
+
});
|
|
276
287
|
}
|
|
277
288
|
}
|
|
278
289
|
|
|
@@ -284,26 +295,36 @@ process.stdin.on('end', () => {
|
|
|
284
295
|
const m = line.match(entry.pattern);
|
|
285
296
|
if (!m) continue;
|
|
286
297
|
if (entry.safePredicate && entry.safePredicate(line)) continue;
|
|
287
|
-
findings.push(
|
|
298
|
+
findings.push({ ruleId: entry.ruleId, match: m[0].substring(0, 40) });
|
|
288
299
|
}
|
|
289
300
|
}
|
|
290
301
|
|
|
291
302
|
// Invisible Unicode (zero-width, RTL override, soft hyphen, BOM)
|
|
292
303
|
if (/[\u200B-\u200F\u2028-\u202F\uFEFF\u00AD\u2060-\u2069]/.test(content)) {
|
|
293
|
-
findings.push(
|
|
304
|
+
findings.push({ ruleId: RULE_IDS.INVISIBLE_UNICODE, match: null });
|
|
294
305
|
}
|
|
295
306
|
|
|
296
307
|
// Unicode tag block U+E0000–E007F (invisible instruction injection vector)
|
|
297
308
|
try {
|
|
298
309
|
if (/[\u{E0000}-\u{E007F}]/u.test(content)) {
|
|
299
|
-
findings.push(
|
|
310
|
+
findings.push({ ruleId: RULE_IDS.UNICODE_TAG_BLOCK, match: null });
|
|
300
311
|
}
|
|
301
312
|
} catch {
|
|
302
313
|
// Engine does not support Unicode property escapes — skip this check
|
|
303
314
|
}
|
|
304
315
|
|
|
305
316
|
if (findings.length === 0) {
|
|
306
|
-
|
|
317
|
+
allow(undefined);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Renders one finding back into the exact prose fragment the advisory has
|
|
321
|
+
// always embedded. Kept as the ONLY place that maps IR -> text, so the
|
|
322
|
+
// `additionalContext` string and the `findings` array can never diverge.
|
|
323
|
+
function renderFinding(f) {
|
|
324
|
+
if (f.ruleId === RULE_IDS.INVISIBLE_UNICODE) return 'invisible-unicode';
|
|
325
|
+
if (f.ruleId === RULE_IDS.UNICODE_TAG_BLOCK) return 'unicode-tag-block';
|
|
326
|
+
if (f.ruleId === RULE_IDS.INJECTION_PATTERN) return f.match;
|
|
327
|
+
return `${f.ruleId}:${f.match}`;
|
|
307
328
|
}
|
|
308
329
|
|
|
309
330
|
const severity = findings.length >= 3 ? 'HIGH' : 'LOW';
|
|
@@ -313,7 +334,7 @@ process.stdin.on('end', () => {
|
|
|
313
334
|
: 'Single pattern match may be a false positive (e.g., documentation). Proceed with awareness.';
|
|
314
335
|
const advisory =
|
|
315
336
|
`\u26a0\ufe0f INJECTION SCAN [${severity}] (${toolName}): "${label}" triggered ` +
|
|
316
|
-
`${findings.length} pattern(s): ${findings.join(', ')}. ` +
|
|
337
|
+
`${findings.length} pattern(s): ${findings.map(renderFinding).join(', ')}. ` +
|
|
317
338
|
`This content is now in your conversation context. ${detail} Source: ${source}`;
|
|
318
339
|
|
|
319
340
|
// Opt-in blocking: only when configured AND high-confidence
|
|
@@ -330,12 +351,14 @@ process.stdin.on('end', () => {
|
|
|
330
351
|
const output = blocking
|
|
331
352
|
? { decision: 'block',
|
|
332
353
|
reason: `Prompt-injection blocked (${toolName}). ${advisory}`,
|
|
333
|
-
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory } }
|
|
334
|
-
: { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory } };
|
|
354
|
+
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory, findings } }
|
|
355
|
+
: { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory, findings } };
|
|
335
356
|
|
|
336
357
|
process.stdout.write(JSON.stringify(output));
|
|
337
358
|
} catch {
|
|
338
|
-
// Silent fail — never block tool execution
|
|
339
|
-
|
|
359
|
+
// Silent fail — never block tool execution.
|
|
360
|
+
// ON_CRASH is declared ALLOW at module top: this preserves today's
|
|
361
|
+
// exit(0) fail-open behavior exactly (#3911).
|
|
362
|
+
crash(ON_CRASH, undefined);
|
|
340
363
|
}
|
|
341
364
|
});
|
|
@@ -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
|
|
@@ -9,6 +9,36 @@ 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;
|
|
20
|
+
// #3582: gsd-core/bin/lib/*.cjs (semver-compare.cjs, state-document.cjs,
|
|
21
|
+
// active-workstream-store.cjs, planning-workspace.cjs — required below) and
|
|
22
|
+
// package-identity.cjs are tsc build artifacts (ADR-457), gitignored and
|
|
23
|
+
// absent on a raw plugin-marketplace / git-clone install that never ran
|
|
24
|
+
// `npm run build:lib`. The statusline renders on EVERY prompt, so a build
|
|
25
|
+
// failure here must DEGRADE (print nothing, exit 0) rather than crash
|
|
26
|
+
// Claude Code's per-render statusline hook. Scoped to the spawned-as-a-script
|
|
27
|
+
// path (`require.main === module`) — a test `require()` of this module for
|
|
28
|
+
// its pure helpers assumes a built tree, same as every other hook test.
|
|
29
|
+
if (require.main === module) {
|
|
30
|
+
try {
|
|
31
|
+
const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
|
|
32
|
+
ensureRuntimeBuild();
|
|
33
|
+
} catch (e) {
|
|
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);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
12
42
|
const { isSemverNewer } = require('../gsd-core/bin/lib/semver-compare.cjs');
|
|
13
43
|
const { PACKAGE_NAME, updateCacheFileName } = require('../gsd-core/bin/lib/package-identity.cjs');
|
|
14
44
|
const { normalizeStateStatus } = require('../gsd-core/bin/lib/state-document.cjs');
|
|
@@ -151,13 +181,27 @@ function readStateFileOrNull(statePath) {
|
|
|
151
181
|
* - null when no .planning marker is found at all (GSD not present), or
|
|
152
182
|
* when a workstream DOES resolve but its STATE.md doesn't exist yet
|
|
153
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.
|
|
154
192
|
*/
|
|
155
|
-
function readGsdState(dir) {
|
|
193
|
+
function readGsdState(dir, opts = {}) {
|
|
194
|
+
const { stateFreshness = false } = opts;
|
|
156
195
|
const home = os.homedir();
|
|
157
196
|
let current = dir;
|
|
158
197
|
for (let i = 0; i < 10; i++) {
|
|
159
198
|
const flatState = readStateFileOrNull(path.join(current, '.planning', 'STATE.md'));
|
|
160
|
-
if (flatState !== null)
|
|
199
|
+
if (flatState !== null) {
|
|
200
|
+
if (stateFreshness && flatState.stateHead) {
|
|
201
|
+
flatState.freshness = deriveStateFreshness(current, flatState.stateHead);
|
|
202
|
+
}
|
|
203
|
+
return flatState;
|
|
204
|
+
}
|
|
161
205
|
|
|
162
206
|
if (listAvailableWorkstreams(current).length > 0) {
|
|
163
207
|
let resolvedWs = null;
|
|
@@ -169,7 +213,11 @@ function readGsdState(dir) {
|
|
|
169
213
|
|
|
170
214
|
if (!resolvedWs) return { noActiveWorkstream: true };
|
|
171
215
|
|
|
172
|
-
|
|
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;
|
|
173
221
|
}
|
|
174
222
|
|
|
175
223
|
const parent = path.dirname(current);
|
|
@@ -220,6 +268,10 @@ function parseStateMd(content) {
|
|
|
220
268
|
if (key === 'active_phase') state.activePhase = (v === 'null' || v === '') ? null : v;
|
|
221
269
|
// next_action: recommended command when idle (discuss-phase / plan-phase / execute-phase / verify-phase)
|
|
222
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;
|
|
223
275
|
}
|
|
224
276
|
// next_phases supports both flow array and block-list YAML forms.
|
|
225
277
|
const npFlowMatch = fm.match(/^next_phases:\s*\[([^\]]*)\]/m);
|
|
@@ -353,6 +405,10 @@ function formatGsdState(s) {
|
|
|
353
405
|
}
|
|
354
406
|
}
|
|
355
407
|
|
|
408
|
+
// #2734: STATE.md freshness marker — opt-in, appended last.
|
|
409
|
+
const fresh = formatStateFreshness(s.freshness);
|
|
410
|
+
if (fresh) parts.push(fresh);
|
|
411
|
+
|
|
356
412
|
return parts.join(' · ');
|
|
357
413
|
}
|
|
358
414
|
|
|
@@ -458,6 +514,10 @@ function formatGsdStateCompact(s) {
|
|
|
458
514
|
}
|
|
459
515
|
}
|
|
460
516
|
|
|
517
|
+
// #2734: STATE.md freshness marker \u2014 opt-in, appended last.
|
|
518
|
+
const fresh = formatStateFreshness(s.freshness);
|
|
519
|
+
if (fresh) parts.push(fresh);
|
|
520
|
+
|
|
461
521
|
return parts.join(' \u00b7 ');
|
|
462
522
|
}
|
|
463
523
|
|
|
@@ -554,13 +614,153 @@ function buildGitSegment(info) {
|
|
|
554
614
|
return ` │ \x1b[2m${info.branch}\x1b[0m${state}`;
|
|
555
615
|
}
|
|
556
616
|
|
|
617
|
+
// --- STATE.md freshness marker (opt-in, #2734) --------------------------------
|
|
618
|
+
//
|
|
619
|
+
// Opt-in via `statusline.show_state_freshness: true`. Renders `state ~N
|
|
620
|
+
// commits back` inside the GSD-state segment when STATE.md's `state_head`
|
|
621
|
+
// stamp (#2573) is at least STATE_HEAD_ADVISORY_COMMITS commits behind HEAD.
|
|
622
|
+
// Same impure-reader -> pure-IR -> pure-formatter shape as the git segment
|
|
623
|
+
// above. See .gsd/phase/feat-2734-statusline-state-freshness/40-design.md.
|
|
624
|
+
|
|
625
|
+
// Deliberate mirror of the fence in src/state.cts (STATE_HEAD_HASH_RE) — kept
|
|
626
|
+
// hook-side rather than requiring state.cjs on the per-render path (measured
|
|
627
|
+
// ~20ms; see design doc "Laws that apply"). tests/gsd-statusline.test.cjs
|
|
628
|
+
// asserts behavioral parity against readStateHeadFreshness rather than
|
|
629
|
+
// comparing source (local/no-source-grep forbids the latter anyway).
|
|
630
|
+
const STATE_HEAD_HASH_RE = /^[0-9a-f]{4,40}$/i;
|
|
631
|
+
|
|
632
|
+
// Mirror of the constant verify.cts's W024 health check thresholds on
|
|
633
|
+
// (STATE_HEAD_ADVISORY_COMMITS). A test asserts equality with verify.cjs's
|
|
634
|
+
// export so the two copies can't drift.
|
|
635
|
+
const STATE_HEAD_ADVISORY_COMMITS = 20;
|
|
636
|
+
|
|
637
|
+
// Same bound class as GIT_STATUS_TIMEOUT_MS above.
|
|
638
|
+
const STATE_FRESHNESS_GIT_TIMEOUT_MS = 1500;
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Pure function: does raw pass the state_head hash fence? Must run BEFORE any
|
|
642
|
+
* value from STATE.md reaches a git argv slot.
|
|
643
|
+
*/
|
|
644
|
+
function isValidStateHeadStamp(raw) {
|
|
645
|
+
return typeof raw === 'string' && STATE_HEAD_HASH_RE.test(raw.trim());
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Run `git rev-list --left-right --count <stamp>...HEAD` in root. Returns raw
|
|
650
|
+
* stdout, or null when git is missing, root isn't a repo, the stamp is
|
|
651
|
+
* unknown, or the call times out. Never throws. Only call with a stamp that
|
|
652
|
+
* already passed isValidStateHeadStamp/the hash fence above.
|
|
653
|
+
*/
|
|
654
|
+
function readStateHeadCommits(root, stamp) {
|
|
655
|
+
try {
|
|
656
|
+
return childProcess.execFileSync('git',
|
|
657
|
+
['-C', root, 'rev-list', '--left-right', '--count', `${stamp}...HEAD`],
|
|
658
|
+
{ encoding: 'utf8', timeout: STATE_FRESHNESS_GIT_TIMEOUT_MS,
|
|
659
|
+
stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
|
|
660
|
+
} catch (e) {
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Pure function: parse `git rev-list --left-right --count A...B` output
|
|
667
|
+
* ("<left>\t<right>"). Returns { left, right } as non-negative integers, or
|
|
668
|
+
* null when text isn't a matching string (covers null, '', 'garbage', '1',
|
|
669
|
+
* 'a\tb', '\t', and any other unparseable shape).
|
|
670
|
+
*/
|
|
671
|
+
function parseRevListCounts(text) {
|
|
672
|
+
if (typeof text !== 'string') return null;
|
|
673
|
+
const m = text.match(/^(\d+)\s+(\d+)\s*$/);
|
|
674
|
+
if (!m) return null;
|
|
675
|
+
return { left: parseInt(m[1], 10), right: parseInt(m[2], 10) };
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Impure -> pure IR: derive the freshness signal for a recorded state_head
|
|
680
|
+
* stamp. Returns { state_head, commits_behind, commit_stale } — never throws,
|
|
681
|
+
* every unresolvable input degrades to the all-null-but-state_head shape.
|
|
682
|
+
*
|
|
683
|
+
* Order (each failure returns immediately, no further work):
|
|
684
|
+
* a. hash fence — malformed/absent stamp never reaches a spawn
|
|
685
|
+
* b. repo pinning — root must own its own .git (mirrors projectOwnsItsRepo
|
|
686
|
+
* in src/state.cts: a filesystem-identity check, not a --show-toplevel
|
|
687
|
+
* string compare, which is unreliable on macOS /private/var and Windows
|
|
688
|
+
* 8.3 paths). Costs no subprocess.
|
|
689
|
+
* c. sub_repos guard — a planning.sub_repos workspace's outer HEAD never
|
|
690
|
+
* advances when code lands in nested children, so a "fresh" answer here
|
|
691
|
+
* would be a confident lie. Costs no subprocess.
|
|
692
|
+
* d. one bounded git spawn: rev-list --left-right --count answers ancestry
|
|
693
|
+
* and distance together. left > 0 means the stamp is not an ancestor of
|
|
694
|
+
* HEAD (reset/rebase/force-push) -> unknown, never "fresh".
|
|
695
|
+
*/
|
|
696
|
+
function deriveStateFreshness(root, stamp, deps = {}) {
|
|
697
|
+
const { existsSync = fs.existsSync, readConfig = readGsdConfig, readCounts = readStateHeadCommits } = deps;
|
|
698
|
+
|
|
699
|
+
const raw = typeof stamp === 'string' ? stamp.trim() : '';
|
|
700
|
+
const valid = STATE_HEAD_HASH_RE.test(raw);
|
|
701
|
+
const state_head = valid ? raw.slice(0, 7) : null;
|
|
702
|
+
const nullResult = { state_head, commits_behind: null, commit_stale: null };
|
|
703
|
+
if (!valid || !root) return nullResult;
|
|
704
|
+
|
|
705
|
+
try {
|
|
706
|
+
if (!existsSync(path.join(root, '.git'))) return nullResult;
|
|
707
|
+
} catch (e) {
|
|
708
|
+
return nullResult;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
try {
|
|
712
|
+
const cfg = readConfig(root);
|
|
713
|
+
const sub = getConfigValue(cfg, 'planning.sub_repos') ?? getConfigValue(cfg, 'sub_repos');
|
|
714
|
+
if (Array.isArray(sub) && sub.length > 0) return nullResult;
|
|
715
|
+
} catch (e) {
|
|
716
|
+
return nullResult;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const counts = parseRevListCounts(readCounts(root, raw));
|
|
720
|
+
if (!counts || counts.left > 0) return nullResult;
|
|
721
|
+
|
|
722
|
+
return { state_head, commits_behind: counts.right, commit_stale: counts.right > 0 };
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Pure function: format the freshness IR into the marker text, or '' below
|
|
727
|
+
* STATE_HEAD_ADVISORY_COMMITS (including when commits_behind is absent/null —
|
|
728
|
+
* the unknown case must never render, never mind alarm on it).
|
|
729
|
+
*/
|
|
730
|
+
function formatStateFreshness(fresh) {
|
|
731
|
+
if (!fresh || typeof fresh.commits_behind !== 'number' || fresh.commits_behind < STATE_HEAD_ADVISORY_COMMITS) return '';
|
|
732
|
+
return `state ~${fresh.commits_behind} commits back`;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Pure function: single source of truth for statusline config resolution.
|
|
737
|
+
* `runStatusline()` and `renderStatusline()` previously read this config
|
|
738
|
+
* independently, which had drifted into a live divergence between the two
|
|
739
|
+
* entry points — this collapses both onto one resolver.
|
|
740
|
+
*
|
|
741
|
+
* @param {object} cfg — parsed .planning/config.json (readGsdConfig())
|
|
742
|
+
* @returns {{ showLastCommand: boolean, position: 'end'|'front', stateFormat: 'full'|'compact', showGit: boolean, showStateFreshness: boolean }}
|
|
743
|
+
*/
|
|
744
|
+
function resolveStatuslineOptions(cfg) {
|
|
745
|
+
const showLastCommand = getConfigValue(cfg, 'statusline.show_last_command') === true;
|
|
746
|
+
const cfgPos = getConfigValue(cfg, 'statusline.context_position');
|
|
747
|
+
// Clamp any non-'front' value (including absent/null) to 'end' — the single
|
|
748
|
+
// source of truth for this default; composeStatusline's own coercion stays
|
|
749
|
+
// as belt-and-suspenders defense for direct callers.
|
|
750
|
+
const position = cfgPos === 'front' ? 'front' : 'end';
|
|
751
|
+
const stateFormat = getConfigValue(cfg, 'statusline.state_format') === 'compact' ? 'compact' : 'full';
|
|
752
|
+
const showGit = getConfigValue(cfg, 'statusline.show_git') === true;
|
|
753
|
+
const showStateFreshness = getConfigValue(cfg, 'statusline.show_state_freshness') === true;
|
|
754
|
+
return { showLastCommand, position, stateFormat, showGit, showStateFreshness };
|
|
755
|
+
}
|
|
756
|
+
|
|
557
757
|
// --- stdin ------------------------------------------------------------------
|
|
558
758
|
|
|
559
759
|
function runStatusline() {
|
|
560
760
|
let input = '';
|
|
561
761
|
// Timeout guard: if stdin doesn't close within 3s (e.g. pipe issues on
|
|
562
762
|
// Windows/Git Bash), exit silently instead of hanging. See #775.
|
|
563
|
-
const stdinTimeout = setTimeout(() =>
|
|
763
|
+
const stdinTimeout = setTimeout(() => allow(undefined), 3000);
|
|
564
764
|
process.stdin.setEncoding('utf8');
|
|
565
765
|
process.stdin.on('data', chunk => input += chunk);
|
|
566
766
|
process.stdin.on('end', () => {
|
|
@@ -699,31 +899,34 @@ function runStatusline() {
|
|
|
699
899
|
// Last-slash-command suffix and context_position config (#2538, #2937).
|
|
700
900
|
// Reads the active session transcript for the most recent <command-name> tag.
|
|
701
901
|
// Failure here must never break the statusline — wrap the entire lookup.
|
|
902
|
+
// #2734: config resolution moved to resolveStatuslineOptions() — the single
|
|
903
|
+
// source of truth shared with renderStatusline() below. The two entry
|
|
904
|
+
// points duplicated this resolution byte-for-byte; one copy is what keeps
|
|
905
|
+
// a new key from reaching only one of them.
|
|
702
906
|
let lastCmdSuffix = '';
|
|
703
|
-
let position = 'end';
|
|
704
|
-
let stateFormat = 'full';
|
|
705
907
|
let gitSuffix = '';
|
|
908
|
+
const options = resolveStatuslineOptions(cfg);
|
|
706
909
|
try {
|
|
707
|
-
if (
|
|
910
|
+
if (options.showLastCommand) {
|
|
708
911
|
const transcriptPath = data.transcript_path;
|
|
709
912
|
const lastCmd = readLastSlashCommand(transcriptPath);
|
|
710
913
|
if (lastCmd) {
|
|
711
914
|
lastCmdSuffix = ` │ \x1b[2mlast: /${lastCmd}\x1b[0m`;
|
|
712
915
|
}
|
|
713
916
|
}
|
|
714
|
-
|
|
715
|
-
if (cfgPos != null) position = cfgPos;
|
|
716
|
-
if (getConfigValue(cfg, 'statusline.state_format') === 'compact') stateFormat = 'compact';
|
|
717
|
-
if (getConfigValue(cfg, 'statusline.show_git') === true) {
|
|
917
|
+
if (options.showGit) {
|
|
718
918
|
gitSuffix = buildGitSegment(parseGitStatus(readGitStatus(dir)));
|
|
719
919
|
}
|
|
720
920
|
} catch (e) {
|
|
721
921
|
// Never break the statusline on config/transcript/git errors
|
|
722
922
|
}
|
|
723
923
|
|
|
924
|
+
// #2734: readGsdState is inside `if (!task)` deliberately — when a todo
|
|
925
|
+
// task is in flight the GSD-state segment is not rendered, so spending a
|
|
926
|
+
// freshness git spawn here would spend a subprocess on discarded output.
|
|
724
927
|
if (!task) {
|
|
725
|
-
const state = readGsdState(dir) || {};
|
|
726
|
-
gsdStateStr = stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
|
|
928
|
+
const state = readGsdState(dir, { stateFreshness: options.showStateFreshness }) || {};
|
|
929
|
+
gsdStateStr = options.stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
|
|
727
930
|
}
|
|
728
931
|
|
|
729
932
|
// Output
|
|
@@ -734,7 +937,7 @@ function runStatusline() {
|
|
|
734
937
|
? `\x1b[2m${gsdStateStr}\x1b[0m`
|
|
735
938
|
: null;
|
|
736
939
|
|
|
737
|
-
process.stdout.write(composeStatusline({ gsdUpdate, model, ctx, middle, dirname, lastCmdSuffix, gitSuffix, position }));
|
|
940
|
+
process.stdout.write(composeStatusline({ gsdUpdate, model, ctx, middle, dirname, lastCmdSuffix, gitSuffix, position: options.position }));
|
|
738
941
|
} catch (e) {
|
|
739
942
|
// Silent fail - don't break statusline on parse errors
|
|
740
943
|
}
|
|
@@ -828,6 +1031,9 @@ module.exports = {
|
|
|
828
1031
|
shortGsdStatus, formatGsdStateCompact,
|
|
829
1032
|
compactModelName,
|
|
830
1033
|
readGitStatus, parseGitStatus, buildGitSegment,
|
|
1034
|
+
STATE_HEAD_ADVISORY_COMMITS, isValidStateHeadStamp,
|
|
1035
|
+
readStateHeadCommits, parseRevListCounts, deriveStateFreshness,
|
|
1036
|
+
formatStateFreshness, resolveStatuslineOptions,
|
|
831
1037
|
};
|
|
832
1038
|
|
|
833
1039
|
/**
|
|
@@ -839,30 +1045,31 @@ function renderStatusline(data) {
|
|
|
839
1045
|
const dir = data.workspace?.current_dir || process.cwd();
|
|
840
1046
|
const dirname = path.basename(dir);
|
|
841
1047
|
|
|
1048
|
+
// #2734: config resolution moved to resolveStatuslineOptions() — the single
|
|
1049
|
+
// source of truth shared with runStatusline() above. The two entry points
|
|
1050
|
+
// duplicated this resolution byte-for-byte; one copy is what keeps a new
|
|
1051
|
+
// key from reaching only one of them.
|
|
842
1052
|
let lastCmdSuffix = '';
|
|
843
|
-
let position = 'end';
|
|
844
|
-
let stateFormat = 'full';
|
|
845
1053
|
let gitSuffix = '';
|
|
1054
|
+
let options = { showLastCommand: false, position: 'end', stateFormat: 'full', showGit: false, showStateFreshness: false };
|
|
846
1055
|
try {
|
|
847
1056
|
const cfg = readGsdConfig(dir);
|
|
848
|
-
|
|
1057
|
+
options = resolveStatuslineOptions(cfg);
|
|
1058
|
+
if (options.showLastCommand) {
|
|
849
1059
|
const lastCmd = readLastSlashCommand(data.transcript_path);
|
|
850
1060
|
if (lastCmd) {
|
|
851
1061
|
lastCmdSuffix = ` │ \x1b[2mlast: /${lastCmd}\x1b[0m`;
|
|
852
1062
|
}
|
|
853
1063
|
}
|
|
854
|
-
|
|
855
|
-
if (cfgPos != null) position = cfgPos;
|
|
856
|
-
if (getConfigValue(cfg, 'statusline.state_format') === 'compact') stateFormat = 'compact';
|
|
857
|
-
if (getConfigValue(cfg, 'statusline.show_git') === true) {
|
|
1064
|
+
if (options.showGit) {
|
|
858
1065
|
gitSuffix = buildGitSegment(parseGitStatus(readGitStatus(dir)));
|
|
859
1066
|
}
|
|
860
1067
|
} catch (e) { /* swallow */ }
|
|
861
1068
|
|
|
862
|
-
const state = readGsdState(dir) || {};
|
|
863
|
-
const gsdStateStr = stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
|
|
1069
|
+
const state = readGsdState(dir, { stateFreshness: options.showStateFreshness }) || {};
|
|
1070
|
+
const gsdStateStr = options.stateFormat === 'compact' ? formatGsdStateCompact(state) : formatGsdState(state);
|
|
864
1071
|
const middle = gsdStateStr ? `\x1b[2m${gsdStateStr}\x1b[0m` : null;
|
|
865
|
-
return composeStatusline({ model, ctx: '', middle, dirname, lastCmdSuffix, gitSuffix, position });
|
|
1072
|
+
return composeStatusline({ model, ctx: '', middle, dirname, lastCmdSuffix, gitSuffix, position: options.position });
|
|
866
1073
|
}
|
|
867
1074
|
|
|
868
1075
|
module.exports.renderStatusline = renderStatusline;
|
|
@@ -15,7 +15,28 @@
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const os = require('os');
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
// #3582: gsd-core/bin/lib/package-identity.cjs is a tsc build artifact
|
|
20
|
+
// (ADR-457), gitignored and absent on a raw plugin-marketplace / git-clone
|
|
21
|
+
// install that never ran `npm run build:lib`. This is an opt-in SessionStart
|
|
22
|
+
// hook — a build failure here must DEGRADE, not crash session start. With
|
|
23
|
+
// PACKAGE_NAME left null, buildBannerOutput's own lineage guard
|
|
24
|
+
// (`!cache.package_name || cache.package_name !== PACKAGE_NAME`) always
|
|
25
|
+
// treats the cache as untrusted, so main() falls through to its existing
|
|
26
|
+
// silent "print nothing" path below — no separate degrade branch needed.
|
|
27
|
+
// This try/require/ensureRuntimeBuild/require/catch shape is deliberately
|
|
28
|
+
// duplicated (not extracted to hooks/lib/) — see
|
|
29
|
+
// gsd-check-update-worker.js's identical #3582 comment for why.
|
|
30
|
+
let PACKAGE_NAME = null;
|
|
31
|
+
let updateCacheFileName = 'gsd-update-check.json';
|
|
32
|
+
try {
|
|
33
|
+
const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
|
|
34
|
+
ensureRuntimeBuild();
|
|
35
|
+
({ PACKAGE_NAME, updateCacheFileName } = require('../gsd-core/bin/lib/package-identity.cjs'));
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// Runtime library missing/broken and could not self-build — degrade to the
|
|
38
|
+
// fallbacks above rather than crash the SessionStart hook.
|
|
39
|
+
}
|
|
19
40
|
|
|
20
41
|
// Suppress repeat parse-error banners for 24 hours so a genuinely broken
|
|
21
42
|
// cache file doesn't nag the user every session.
|