@opengsd/gsd-core 1.9.1 → 1.11.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 +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +27 -3
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +12 -10
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +46 -0
- package/agents/gsd-planner.md +49 -54
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +26 -73
- package/bin/install.js +1272 -1238
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +2 -2
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/plan-phase.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1009 -115
- package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
- package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
- package/gsd-core/bin/lib/api-coverage.cjs +123 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +926 -202
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- 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-registry.cjs +608 -148
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +507 -24
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +114 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +94 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +665 -99
- package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +76 -0
- package/gsd-core/bin/lib/config.cjs +22 -2
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +217 -40
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +239 -32
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
- 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 +145 -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 +265 -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 +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1325 -169
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +805 -264
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +160 -57
- 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-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
- package/gsd-core/bin/lib/io.cjs +38 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +821 -109
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +507 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +258 -58
- package/gsd-core/bin/lib/phase.cjs +891 -156
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +405 -84
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
- package/gsd-core/bin/lib/smart-entry.cjs +154 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +424 -105
- package/gsd-core/bin/lib/state.cjs +1927 -401
- package/gsd-core/bin/lib/surface.cjs +35 -10
- 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 +20 -4
- package/gsd-core/bin/lib/uat.cjs +706 -64
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +33 -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.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -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 +287 -20
- package/gsd-core/bin/lib/verify.cjs +368 -880
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
- package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
- package/gsd-core/bin/lib/workstream.cjs +8 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- 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 +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +8 -7
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +33 -70
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +74 -166
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +160 -95
- package/gsd-core/workflows/debug.md +16 -17
- package/gsd-core/workflows/diagnose-issues.md +56 -8
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- 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 +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +103 -187
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +7 -6
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +35 -47
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +27 -240
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +89 -209
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +45 -159
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +55 -405
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +41 -13
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +41 -20
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +106 -13
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +53 -31
- package/gsd-core/workflows/ui-phase.md +13 -12
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +19 -8
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +17 -65
- package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
- package/hooks/dist/gsd-check-update-worker.js +64 -12
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +45 -24
- package/hooks/dist/gsd-statusline.js +90 -6
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- 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 +277 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +517 -0
- package/hooks/gsd-check-update-worker.js +64 -12
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +607 -26
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +45 -24
- package/hooks/gsd-statusline.js +90 -6
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +277 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +31 -10
- package/pi/gsd.cjs +71 -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 +9 -0
- package/scripts/changeset/lint.cjs +68 -6
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +439 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +150 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
- 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-compiled-artifact-sync.cjs +6 -1
- 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-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -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 +434 -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 +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +40 -3
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +52 -6
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +2 -2
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +2 -2
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +1 -2
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- 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/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 -577
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/gen-emitted-baseline.cjs +0 -145
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1050
|
@@ -20,13 +20,14 @@
|
|
|
20
20
|
|
|
21
21
|
'use strict';
|
|
22
22
|
|
|
23
|
-
let raw = '';
|
|
24
23
|
const stdinTimeout = setTimeout(() => {
|
|
25
24
|
process.exit(0);
|
|
26
25
|
}, 10000);
|
|
27
26
|
|
|
28
27
|
process.stdin.setEncoding('utf8');
|
|
29
|
-
|
|
28
|
+
// Drain stdin (puts the stream into flowing mode so 'end' fires); the
|
|
29
|
+
// payload itself is unused — this hook responds unconditionally.
|
|
30
|
+
process.stdin.on('data', () => {});
|
|
30
31
|
process.stdin.on('end', () => {
|
|
31
32
|
clearTimeout(stdinTimeout);
|
|
32
33
|
try {
|
|
@@ -11,26 +11,14 @@
|
|
|
11
11
|
// The goal is to surface suspicious content so the orchestrator can inspect it,
|
|
12
12
|
// not to create false-positive deadlocks.
|
|
13
13
|
|
|
14
|
-
const fs = require('fs');
|
|
15
14
|
const path = require('path');
|
|
16
15
|
|
|
17
|
-
// Prompt injection patterns
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/override\s+(system|previous)\s+(prompt|instructions)/i,
|
|
24
|
-
/you\s+are\s+now\s+(?:a|an|the)\s+/i,
|
|
25
|
-
/act\s+as\s+(?:a|an|the)\s+(?!plan|phase|wave)/i,
|
|
26
|
-
/pretend\s+(?:you(?:'re| are)\s+|to\s+be\s+)/i,
|
|
27
|
-
/from\s+now\s+on,?\s+you\s+(?:are|will|should|must)/i,
|
|
28
|
-
/(?:print|output|reveal|show|display|repeat)\s+(?:your\s+)?(?:system\s+)?(?:prompt|instructions)/i,
|
|
29
|
-
/<\/?(?:system|assistant|human)>/i,
|
|
30
|
-
/\[SYSTEM\]/i,
|
|
31
|
-
/\[INST\]/i,
|
|
32
|
-
/<<\s*SYS\s*>>/i,
|
|
33
|
-
];
|
|
16
|
+
// Prompt injection patterns — shared with gsd-read-injection-scanner.js via
|
|
17
|
+
// hooks/lib/injection-patterns.js so the two surfaces cannot drift (#3504).
|
|
18
|
+
// Deliberately a subset of security.cjs's set: hooks stay loadable without the
|
|
19
|
+
// compiled lib tree. Staging of the lib helper is allowlisted in
|
|
20
|
+
// GSD_HOOK_LIB_FILES (bin/install.js).
|
|
21
|
+
const { INJECTION_PATTERNS } = require('./lib/injection-patterns.js');
|
|
34
22
|
|
|
35
23
|
// #2304: Kimi's native hook bus delivers Kimi's tool vocabulary in the payload
|
|
36
24
|
// (Write → WriteFile, Edit/MultiEdit → StrReplaceFile) while the [[hooks]]
|
|
@@ -153,8 +141,21 @@ process.stdin.on('end', () => {
|
|
|
153
141
|
process.exit(0);
|
|
154
142
|
}
|
|
155
143
|
|
|
156
|
-
// Get the content being written
|
|
157
|
-
|
|
144
|
+
// Get the content being written. #3504 (isolated review finding 3): the
|
|
145
|
+
// bare `||` chain handed a NON-STRING truthy `content` straight to
|
|
146
|
+
// pattern.test(), where ToString can throw ("Cannot convert object to a
|
|
147
|
+
// primitive value" for `{"toString": null}`) into the outer catch — the
|
|
148
|
+
// exact crash-to-allow class #2547/#2595 hardened inside
|
|
149
|
+
// normalizeKimiPayload, unreached on this read. Guarded selection: take
|
|
150
|
+
// the first field that is a string, or String-coerces without throwing,
|
|
151
|
+
// so a poisoned `content` no longer shadows a real `new_string`.
|
|
152
|
+
let content = '';
|
|
153
|
+
for (const candidate of [data.tool_input?.content, data.tool_input?.new_string]) {
|
|
154
|
+
if (typeof candidate === 'string' && candidate) { content = candidate; break; }
|
|
155
|
+
if (candidate && typeof candidate !== 'string') {
|
|
156
|
+
try { const s = String(candidate); if (s) { content = s; break; } } catch { /* keep looking */ }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
158
159
|
if (!content) {
|
|
159
160
|
process.exit(0);
|
|
160
161
|
}
|
|
@@ -72,26 +72,29 @@ const MARKDOWN_LINK_PATTERNS = [
|
|
|
72
72
|
},
|
|
73
73
|
];
|
|
74
74
|
|
|
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
|
-
];
|
|
75
|
+
// Standard injection patterns — shared with gsd-prompt-guard.js via
|
|
76
|
+
// hooks/lib/injection-patterns.js so the two surfaces cannot drift (#3504).
|
|
77
|
+
// Staging of the lib helper is allowlisted in GSD_HOOK_LIB_FILES (bin/install.js).
|
|
78
|
+
const { INJECTION_PATTERNS } = require('./lib/injection-patterns.js');
|
|
92
79
|
|
|
93
80
|
const ALL_PATTERNS = [...INJECTION_PATTERNS, ...SUMMARISATION_PATTERNS];
|
|
94
81
|
|
|
82
|
+
// #3023: the staged bundle's directory name is runtime-descriptor-driven, so a
|
|
83
|
+
// literal `/<config>/hooks/` fragment cannot reliably identify GSD's own hook
|
|
84
|
+
// scripts. This module lives inside the bundle, so __dirname identifies it by
|
|
85
|
+
// construction. Normalized to forward slashes to match `p` below.
|
|
86
|
+
const OWN_BUNDLE_PREFIX = __dirname.replace(/\\/g, '/').replace(/\/+$/, '') + '/';
|
|
87
|
+
|
|
88
|
+
// Synthetic rule ids for the finding classes that have no entry in
|
|
89
|
+
// MARKDOWN_LINK_PATTERNS. Frozen and referenced from BOTH the push sites and
|
|
90
|
+
// renderFinding so the two can never drift — a bare literal repeated at each
|
|
91
|
+
// site is how a rename silently falls through to the generic render branch.
|
|
92
|
+
const RULE_IDS = Object.freeze({
|
|
93
|
+
INJECTION_PATTERN: 'INJECTION-PATTERN',
|
|
94
|
+
INVISIBLE_UNICODE: 'INVISIBLE-UNICODE',
|
|
95
|
+
UNICODE_TAG_BLOCK: 'UNICODE-TAG-BLOCK',
|
|
96
|
+
});
|
|
97
|
+
|
|
95
98
|
function isExcludedPath(filePath) {
|
|
96
99
|
const p = filePath.replace(/\\/g, '/');
|
|
97
100
|
return (
|
|
@@ -101,6 +104,7 @@ function isExcludedPath(filePath) {
|
|
|
101
104
|
/CHECKPOINT/i.test(path.basename(p)) ||
|
|
102
105
|
/[/\\](?:security|techsec|injection)[/\\.]/i.test(p) ||
|
|
103
106
|
/security\.cjs$/.test(p) ||
|
|
107
|
+
p.startsWith(OWN_BUNDLE_PREFIX) ||
|
|
104
108
|
p.includes('/.claude/hooks/')
|
|
105
109
|
);
|
|
106
110
|
}
|
|
@@ -260,12 +264,19 @@ process.stdin.on('end', () => {
|
|
|
260
264
|
process.exit(0);
|
|
261
265
|
}
|
|
262
266
|
|
|
267
|
+
// Typed findings IR — single source of truth for both the machine-readable
|
|
268
|
+
// `findings` array and the rendered advisory prose. Never build these as two
|
|
269
|
+
// parallel arrays: that invites the generative-fix-divergence defect class
|
|
270
|
+
// where the rendered text and the structured data silently drift apart.
|
|
263
271
|
const findings = [];
|
|
264
272
|
|
|
265
273
|
for (const pattern of ALL_PATTERNS) {
|
|
266
274
|
if (pattern.test(content)) {
|
|
267
275
|
// Trim pattern source for readable output
|
|
268
|
-
findings.push(
|
|
276
|
+
findings.push({
|
|
277
|
+
ruleId: RULE_IDS.INJECTION_PATTERN,
|
|
278
|
+
match: pattern.source.replace(/\\s\+/g, '-').replace(/[()\\]/g, '').substring(0, 50),
|
|
279
|
+
});
|
|
269
280
|
}
|
|
270
281
|
}
|
|
271
282
|
|
|
@@ -277,19 +288,19 @@ process.stdin.on('end', () => {
|
|
|
277
288
|
const m = line.match(entry.pattern);
|
|
278
289
|
if (!m) continue;
|
|
279
290
|
if (entry.safePredicate && entry.safePredicate(line)) continue;
|
|
280
|
-
findings.push(
|
|
291
|
+
findings.push({ ruleId: entry.ruleId, match: m[0].substring(0, 40) });
|
|
281
292
|
}
|
|
282
293
|
}
|
|
283
294
|
|
|
284
295
|
// Invisible Unicode (zero-width, RTL override, soft hyphen, BOM)
|
|
285
296
|
if (/[\u200B-\u200F\u2028-\u202F\uFEFF\u00AD\u2060-\u2069]/.test(content)) {
|
|
286
|
-
findings.push(
|
|
297
|
+
findings.push({ ruleId: RULE_IDS.INVISIBLE_UNICODE, match: null });
|
|
287
298
|
}
|
|
288
299
|
|
|
289
300
|
// Unicode tag block U+E0000–E007F (invisible instruction injection vector)
|
|
290
301
|
try {
|
|
291
302
|
if (/[\u{E0000}-\u{E007F}]/u.test(content)) {
|
|
292
|
-
findings.push(
|
|
303
|
+
findings.push({ ruleId: RULE_IDS.UNICODE_TAG_BLOCK, match: null });
|
|
293
304
|
}
|
|
294
305
|
} catch {
|
|
295
306
|
// Engine does not support Unicode property escapes — skip this check
|
|
@@ -299,6 +310,16 @@ process.stdin.on('end', () => {
|
|
|
299
310
|
process.exit(0);
|
|
300
311
|
}
|
|
301
312
|
|
|
313
|
+
// Renders one finding back into the exact prose fragment the advisory has
|
|
314
|
+
// always embedded. Kept as the ONLY place that maps IR -> text, so the
|
|
315
|
+
// `additionalContext` string and the `findings` array can never diverge.
|
|
316
|
+
function renderFinding(f) {
|
|
317
|
+
if (f.ruleId === RULE_IDS.INVISIBLE_UNICODE) return 'invisible-unicode';
|
|
318
|
+
if (f.ruleId === RULE_IDS.UNICODE_TAG_BLOCK) return 'unicode-tag-block';
|
|
319
|
+
if (f.ruleId === RULE_IDS.INJECTION_PATTERN) return f.match;
|
|
320
|
+
return `${f.ruleId}:${f.match}`;
|
|
321
|
+
}
|
|
322
|
+
|
|
302
323
|
const severity = findings.length >= 3 ? 'HIGH' : 'LOW';
|
|
303
324
|
const label = toolName === 'Read' ? path.basename(source) : source;
|
|
304
325
|
const detail = severity === 'HIGH'
|
|
@@ -306,7 +327,7 @@ process.stdin.on('end', () => {
|
|
|
306
327
|
: 'Single pattern match may be a false positive (e.g., documentation). Proceed with awareness.';
|
|
307
328
|
const advisory =
|
|
308
329
|
`\u26a0\ufe0f INJECTION SCAN [${severity}] (${toolName}): "${label}" triggered ` +
|
|
309
|
-
`${findings.length} pattern(s): ${findings.join(', ')}. ` +
|
|
330
|
+
`${findings.length} pattern(s): ${findings.map(renderFinding).join(', ')}. ` +
|
|
310
331
|
`This content is now in your conversation context. ${detail} Source: ${source}`;
|
|
311
332
|
|
|
312
333
|
// Opt-in blocking: only when configured AND high-confidence
|
|
@@ -323,8 +344,8 @@ process.stdin.on('end', () => {
|
|
|
323
344
|
const output = blocking
|
|
324
345
|
? { decision: 'block',
|
|
325
346
|
reason: `Prompt-injection blocked (${toolName}). ${advisory}`,
|
|
326
|
-
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory } }
|
|
327
|
-
: { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory } };
|
|
347
|
+
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory, findings } }
|
|
348
|
+
: { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory, findings } };
|
|
328
349
|
|
|
329
350
|
process.stdout.write(JSON.stringify(output));
|
|
330
351
|
} catch {
|
package/hooks/gsd-statusline.js
CHANGED
|
@@ -9,9 +9,38 @@ 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
|
+
// #3582: gsd-core/bin/lib/*.cjs (semver-compare.cjs, state-document.cjs,
|
|
13
|
+
// active-workstream-store.cjs, planning-workspace.cjs — required below) and
|
|
14
|
+
// package-identity.cjs are tsc build artifacts (ADR-457), gitignored and
|
|
15
|
+
// absent on a raw plugin-marketplace / git-clone install that never ran
|
|
16
|
+
// `npm run build:lib`. The statusline renders on EVERY prompt, so a build
|
|
17
|
+
// failure here must DEGRADE (print nothing, exit 0) rather than crash
|
|
18
|
+
// Claude Code's per-render statusline hook. Scoped to the spawned-as-a-script
|
|
19
|
+
// path (`require.main === module`) — a test `require()` of this module for
|
|
20
|
+
// its pure helpers assumes a built tree, same as every other hook test.
|
|
21
|
+
if (require.main === module) {
|
|
22
|
+
try {
|
|
23
|
+
const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
|
|
24
|
+
ensureRuntimeBuild();
|
|
25
|
+
} catch (e) {
|
|
26
|
+
process.stdout.write('');
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
12
30
|
const { isSemverNewer } = require('../gsd-core/bin/lib/semver-compare.cjs');
|
|
13
31
|
const { PACKAGE_NAME, updateCacheFileName } = require('../gsd-core/bin/lib/package-identity.cjs');
|
|
14
32
|
const { normalizeStateStatus } = require('../gsd-core/bin/lib/state-document.cjs');
|
|
33
|
+
// #2850: reuse the existing workstream resolution seams rather than
|
|
34
|
+
// re-implementing CLI>env>store precedence or path construction inline.
|
|
35
|
+
// peekActiveWorkstream is the read-only sibling of the store-tier lookup
|
|
36
|
+
// resolveActiveWorkstream defaults to (getActiveWorkstream) — that default
|
|
37
|
+
// self-heals a stale/invalid pointer by deleting it, which is correct for a
|
|
38
|
+
// command but not for a renderer invoked on every prompt. Injecting it via
|
|
39
|
+
// resolveActiveWorkstream's own `getStored` override keeps the CLI>env>store
|
|
40
|
+
// precedence itself fully reused (untouched); only the store tier's *write*
|
|
41
|
+
// side effect is removed.
|
|
42
|
+
const { resolveActiveWorkstream, peekActiveWorkstream } = require('../gsd-core/bin/lib/active-workstream-store.cjs');
|
|
43
|
+
const { listAvailableWorkstreams, planningPaths } = require('../gsd-core/bin/lib/planning-workspace.cjs');
|
|
15
44
|
|
|
16
45
|
// --- Config + last-command readers ------------------------------------------
|
|
17
46
|
|
|
@@ -102,21 +131,65 @@ function readLastSlashCommand(transcriptPath) {
|
|
|
102
131
|
// --- GSD state reader -------------------------------------------------------
|
|
103
132
|
|
|
104
133
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
134
|
+
* Read and parse a STATE.md if it exists. Returns the parsed state object,
|
|
135
|
+
* `null` when the file is absent, or `null` on any read/parse failure (never
|
|
136
|
+
* throws) — the single shared shape for both the flat and workstream reads
|
|
137
|
+
* in readGsdState() below.
|
|
138
|
+
*/
|
|
139
|
+
function readStateFileOrNull(statePath) {
|
|
140
|
+
if (!fs.existsSync(statePath)) return null;
|
|
141
|
+
try {
|
|
142
|
+
return parseStateMd(fs.readFileSync(statePath, 'utf8'));
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Walk up from dir looking for .planning/STATE.md (flat mode). If an ancestor
|
|
150
|
+
* has no flat STATE.md but IS in workstream mode (.planning/workstreams/
|
|
151
|
+
* present — the single-source-of-truth check `listAvailableWorkstreams`
|
|
152
|
+
* shares with the init.progress/phase.complete #1912/#2028 guards, so this
|
|
153
|
+
* can't drift from how every other GSD command detects the mode), resolve
|
|
154
|
+
* the active workstream and read that workstream's STATE.md instead (#2850).
|
|
155
|
+
*
|
|
156
|
+
* Resolution reuses `resolveActiveWorkstream` (active-workstream-store.cjs)
|
|
157
|
+
* called with an empty args array, so only its env>store precedence applies
|
|
158
|
+
* here — the CLI leg is inert for this renderer, which never receives argv.
|
|
159
|
+
* The store tier is `peekActiveWorkstream`, a READ-ONLY sibling of the
|
|
160
|
+
* default `getActiveWorkstream`: the default self-heals a stale/invalid
|
|
161
|
+
* pointer by deleting it, which is correct for a command but not for a
|
|
162
|
+
* renderer invoked on every prompt — a render must never write or delete.
|
|
163
|
+
*
|
|
164
|
+
* Returns:
|
|
165
|
+
* - the parsed state object when a flat or workstream STATE.md is found
|
|
166
|
+
* - { noActiveWorkstream: true } when workstream mode is active at an
|
|
167
|
+
* ancestor but no workstream can be resolved — an observable signal so
|
|
168
|
+
* this is distinguishable from "GSD isn't installed here" (#2850)
|
|
169
|
+
* - null when no .planning marker is found at all (GSD not present), or
|
|
170
|
+
* when a workstream DOES resolve but its STATE.md doesn't exist yet
|
|
171
|
+
* (negative space: mirrors flat-mode's own silent pre-STATE.md window)
|
|
107
172
|
*/
|
|
108
173
|
function readGsdState(dir) {
|
|
109
174
|
const home = os.homedir();
|
|
110
175
|
let current = dir;
|
|
111
176
|
for (let i = 0; i < 10; i++) {
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
177
|
+
const flatState = readStateFileOrNull(path.join(current, '.planning', 'STATE.md'));
|
|
178
|
+
if (flatState !== null) return flatState;
|
|
179
|
+
|
|
180
|
+
if (listAvailableWorkstreams(current).length > 0) {
|
|
181
|
+
let resolvedWs = null;
|
|
114
182
|
try {
|
|
115
|
-
|
|
183
|
+
resolvedWs = resolveActiveWorkstream(current, [], process.env, { getStored: peekActiveWorkstream }).ws;
|
|
116
184
|
} catch (e) {
|
|
117
|
-
|
|
185
|
+
resolvedWs = null;
|
|
118
186
|
}
|
|
187
|
+
|
|
188
|
+
if (!resolvedWs) return { noActiveWorkstream: true };
|
|
189
|
+
|
|
190
|
+
return readStateFileOrNull(planningPaths(current, resolvedWs).state);
|
|
119
191
|
}
|
|
192
|
+
|
|
120
193
|
const parent = path.dirname(current);
|
|
121
194
|
if (parent === current || current === home) break;
|
|
122
195
|
current = parent;
|
|
@@ -217,6 +290,10 @@ function parseStateMd(content) {
|
|
|
217
290
|
return state;
|
|
218
291
|
}
|
|
219
292
|
|
|
293
|
+
// #2850: shared literal for formatGsdState/formatGsdStateCompact's "nothing
|
|
294
|
+
// resolvable" signal — one source of truth so the two renderers can't drift.
|
|
295
|
+
const NO_ACTIVE_WORKSTREAM_LABEL = 'no active workstream';
|
|
296
|
+
|
|
220
297
|
/**
|
|
221
298
|
* Render a 10-segment milestone progress bar (matches the context meter style).
|
|
222
299
|
*
|
|
@@ -249,6 +326,10 @@ function renderProgressBar(percent) {
|
|
|
249
326
|
* progress.percent is present in frontmatter; absent → empty string.
|
|
250
327
|
*/
|
|
251
328
|
function formatGsdState(s) {
|
|
329
|
+
// #2850: workstream mode with nothing resolvable — an observable signal,
|
|
330
|
+
// never silent emptiness (distinguishes from "GSD isn't installed here").
|
|
331
|
+
if (s.noActiveWorkstream) return NO_ACTIVE_WORKSTREAM_LABEL;
|
|
332
|
+
|
|
252
333
|
const parts = [];
|
|
253
334
|
|
|
254
335
|
// Milestone segment: version + name + (opt-in) progress bar
|
|
@@ -364,6 +445,9 @@ function shortGsdStatus(status) {
|
|
|
364
445
|
* The default "full" format is untouched.
|
|
365
446
|
*/
|
|
366
447
|
function formatGsdStateCompact(s) {
|
|
448
|
+
// #2850: mirrors formatGsdState's observable "nothing resolvable" signal.
|
|
449
|
+
if (s.noActiveWorkstream) return NO_ACTIVE_WORKSTREAM_LABEL;
|
|
450
|
+
|
|
367
451
|
const parts = [];
|
|
368
452
|
|
|
369
453
|
if (s.milestone) parts.push(s.milestone);
|
|
@@ -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.
|
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
// Detects when Claude attempts file edits outside a GSD workflow context
|
|
5
5
|
// (no active /gsd- skill or Task subagent) and injects an advisory warning.
|
|
6
6
|
//
|
|
7
|
-
// This is a SOFT guard — it advises, not blocks. The edit still
|
|
8
|
-
// The warning nudges Claude to use /gsd:quick or /gsd:fast instead
|
|
9
|
-
// making direct edits that bypass state tracking.
|
|
7
|
+
// This is a SOFT guard for edits — it advises, not blocks. The edit still
|
|
8
|
+
// proceeds. The warning nudges Claude to use /gsd:quick or /gsd:fast instead
|
|
9
|
+
// of making direct edits that bypass state tracking.
|
|
10
|
+
//
|
|
11
|
+
// ONE hard block lives here: `git add -f` on an agent/worktree-agent branch
|
|
12
|
+
// (WORKTREE_AGENT_FORCE_ADD_FORBIDDEN) — and that block leg fails CLOSED on
|
|
13
|
+
// internal error (#3504): when the guard is enabled and the blocking context
|
|
14
|
+
// holds, a thrown error exits 2 (block), not 0. The advisory legs keep the
|
|
15
|
+
// fail-open posture — a broken advisory must never wedge every tool call.
|
|
10
16
|
//
|
|
11
17
|
// Enable via config: hooks.workflow_guard: true (default: false)
|
|
12
18
|
// Only triggers on Write/Edit tool calls to non-.planning/ files.
|
|
@@ -14,40 +20,46 @@
|
|
|
14
20
|
const fs = require('fs');
|
|
15
21
|
const path = require('path');
|
|
16
22
|
const { spawnSync } = require('child_process');
|
|
17
|
-
const { tokenize } = require('./lib/git-cmd.js');
|
|
23
|
+
const { tokenize, skipToSubcommand } = require('./lib/git-cmd.js');
|
|
18
24
|
|
|
19
25
|
function forceGitAddCwds(command, defaultCwd) {
|
|
20
26
|
const tokens = tokenize(command || '');
|
|
21
27
|
const separators = new Set(['&&', '||', ';', '|']);
|
|
22
28
|
const cwdList = [];
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
// #3504: per-segment walk driven by git-cmd.js's canonical skipToSubcommand.
|
|
30
|
+
// The previous inline walk knew six global flags and silently missed the
|
|
31
|
+
// rest — `git -c core.hooksPath=/tmp/x add -f x`, `git --no-optional-locks
|
|
32
|
+
// add -f x`, `--literal-pathspecs`, `--namespace=…` and friends all fell
|
|
33
|
+
// through to a silent exit 0, a no-crash bypass the fail-closed catch is
|
|
34
|
+
// structurally blind to (it only fires on throws). Sharing the classifier
|
|
35
|
+
// makes the block's flag knowledge exactly the classifier's.
|
|
36
|
+
const segments = [];
|
|
37
|
+
let start = 0;
|
|
38
|
+
for (let i = 0; i <= tokens.length; i++) {
|
|
39
|
+
if (i === tokens.length || separators.has(tokens[i])) {
|
|
40
|
+
if (i > start) segments.push(tokens.slice(start, i));
|
|
41
|
+
start = i + 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const seg of segments) {
|
|
45
|
+
const subIdx = skipToSubcommand(seg);
|
|
46
|
+
if (subIdx === -1 || subIdx >= seg.length || seg[subIdx] !== 'add') continue;
|
|
25
47
|
|
|
26
|
-
|
|
48
|
+
// Resolve a `-C <dir>` (separate-arg form) preceding the subcommand so
|
|
49
|
+
// the branch is probed at the repository the add targets.
|
|
27
50
|
let gitCwd = defaultCwd;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
gitCwd = path.resolve(gitCwd, tokens[j + 1]);
|
|
33
|
-
j += 2;
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (['-C', '--git-dir', '--work-tree'].includes(flagName) && !token.includes('=')) {
|
|
37
|
-
j += 2;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
if (['--git-dir', '--work-tree', '--no-pager', '-p', '-P'].includes(flagName)) {
|
|
41
|
-
j++;
|
|
51
|
+
for (let k = 0; k < subIdx; ) {
|
|
52
|
+
if (seg[k] === '-C' && k + 1 < subIdx) {
|
|
53
|
+
gitCwd = path.resolve(gitCwd, seg[k + 1]);
|
|
54
|
+
k += 2;
|
|
42
55
|
continue;
|
|
43
56
|
}
|
|
44
|
-
|
|
57
|
+
k++;
|
|
45
58
|
}
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
if (tokens[k] === '--force' || tokens[k] === '-f' || /^-[A-Za-z]*f[A-Za-z]*$/.test(tokens[k])) {
|
|
60
|
+
for (let k = subIdx + 1; k < seg.length; k++) {
|
|
61
|
+
if (seg[k] === '--') break;
|
|
62
|
+
if (seg[k] === '--force' || seg[k] === '-f' || /^-[A-Za-z]*f[A-Za-z]*$/.test(seg[k])) {
|
|
51
63
|
cwdList.push(gitCwd);
|
|
52
64
|
break;
|
|
53
65
|
}
|
|
@@ -62,11 +74,86 @@ function currentBranch(cwd) {
|
|
|
62
74
|
encoding: 'utf8',
|
|
63
75
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
64
76
|
windowsHide: true,
|
|
77
|
+
// #3504: bounded — this ran unbounded before, an indefinite hang under a
|
|
78
|
+
// wedged git would hang every PreToolUse call. Host wiring allows a 5s
|
|
79
|
+
// budget for the whole hook, so the probe gets 2s of it; a timeout
|
|
80
|
+
// returns '' (branch unknown), which both the block decision and the
|
|
81
|
+
// fail-closed re-check treat as "cannot establish agent branch".
|
|
82
|
+
timeout: 2000,
|
|
83
|
+
killSignal: 'SIGTERM',
|
|
65
84
|
});
|
|
66
85
|
if (result.status !== 0) return '';
|
|
67
86
|
return result.stdout.trim();
|
|
68
87
|
}
|
|
69
88
|
|
|
89
|
+
// Agent-branch predicate, shared by the happy-path detector and the fail-closed
|
|
90
|
+
// re-check so the block's scope cannot drift between the two paths (#3504).
|
|
91
|
+
function isAgentBranch(branch) {
|
|
92
|
+
return /^(worktree-)?agent-/.test(branch);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Typed cwd read, shared by both paths for the same reason: a non-string cwd
|
|
96
|
+
// degrades to process.cwd() instead of throwing inside path.join().
|
|
97
|
+
function payloadCwd(data) {
|
|
98
|
+
return typeof data?.cwd === 'string' && data.cwd ? data.cwd : process.cwd();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// The force-add block payload, emitted by the happy-path detector and the
|
|
102
|
+
// fail-closed catch — one source so the two exits cannot drift. `origin`
|
|
103
|
+
// distinguishes them structurally ('force-add-detected' vs 'fail-closed') so a
|
|
104
|
+
// consumer — or the model reading Kimi's stderr feedback — is never told a
|
|
105
|
+
// force-add was detected when the call was in fact blocked unanalyzed.
|
|
106
|
+
function emitForceAddBlock(origin) {
|
|
107
|
+
const failClosed = origin === 'fail-closed';
|
|
108
|
+
const reason = failClosed
|
|
109
|
+
? 'workflow guard internal error on an agent branch - failing closed. The command was NOT analyzed and was NOT confirmed to be a force-add. Retry the call or inspect the guard.'
|
|
110
|
+
: 'agent/worktree-agent branches must not run git add -f or git add --force. Respect the SDK skipped_gitignored/skipped_commit_docs_false contract and leave gitignored files untracked.';
|
|
111
|
+
const output = {
|
|
112
|
+
decision: 'block',
|
|
113
|
+
code: 'WORKTREE_AGENT_FORCE_ADD_FORBIDDEN',
|
|
114
|
+
origin: failClosed ? 'fail-closed' : 'force-add-detected',
|
|
115
|
+
reason,
|
|
116
|
+
};
|
|
117
|
+
process.stdout.write(JSON.stringify(output));
|
|
118
|
+
// Kimi CLI's exit-2 protocol feeds stderr back to the model (#2304)
|
|
119
|
+
process.stderr.write(output.reason);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// #3504 fail-closed context re-derivation. Runs INSIDE the outer catch, after
|
|
123
|
+
// an internal error, and answers exactly one question: does the blocking
|
|
124
|
+
// context of the force-add guard hold for this payload? Each stage is guarded —
|
|
125
|
+
// a re-derivation that itself throws must degrade to "cannot establish" (false),
|
|
126
|
+
// never take down the catch. Deliberately does NOT re-detect the force-add: the
|
|
127
|
+
// error may live in the detector itself, and on an agent branch with the guard
|
|
128
|
+
// enabled, a Bash call under an internal error is conservative-correct to block.
|
|
129
|
+
// Anything it cannot establish (unparseable payload, non-Bash tool, guard
|
|
130
|
+
// disabled, branch not determinably agent-*) fails open, preserving the
|
|
131
|
+
// advisory legs' fail-open posture.
|
|
132
|
+
function failClosedBlockContext(rawInput) {
|
|
133
|
+
let data;
|
|
134
|
+
try {
|
|
135
|
+
data = normalizeKimiPayload(JSON.parse(rawInput));
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (data === null || typeof data !== 'object' || data.tool_name !== 'Bash') return false;
|
|
140
|
+
const cwd = payloadCwd(data);
|
|
141
|
+
let enabled;
|
|
142
|
+
try {
|
|
143
|
+
enabled = workflowGuardEnabled(cwd);
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (!enabled) return false;
|
|
148
|
+
let branch;
|
|
149
|
+
try {
|
|
150
|
+
branch = currentBranch(cwd);
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return isAgentBranch(branch);
|
|
155
|
+
}
|
|
156
|
+
|
|
70
157
|
function workflowGuardEnabled(cwd) {
|
|
71
158
|
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
72
159
|
if (!fs.existsSync(configPath)) return false;
|
|
@@ -180,6 +267,15 @@ process.stdin.on('end', () => {
|
|
|
180
267
|
clearTimeout(stdinTimeout);
|
|
181
268
|
try {
|
|
182
269
|
const data = normalizeKimiPayload(JSON.parse(input));
|
|
270
|
+
// #3504 test-only fault seam: throws right after parse so the fail-closed
|
|
271
|
+
// posture of the outer catch is exercisable — no JSON-expressible input
|
|
272
|
+
// throws in this handler today (#2547/#2595 hardened every read). Gated on
|
|
273
|
+
// GSD_TEST_MODE as well so a leaked GSD_TEST_WORKFLOW_GUARD_FAULT in a real
|
|
274
|
+
// shell cannot wedge a production session. The fault's failure direction
|
|
275
|
+
// is CLOSED: the catch below may block, never bypass a block.
|
|
276
|
+
if (process.env.GSD_TEST_MODE === '1' && process.env.GSD_TEST_WORKFLOW_GUARD_FAULT === '1') {
|
|
277
|
+
throw new Error('GSD_TEST_WORKFLOW_GUARD_FAULT: injected fault');
|
|
278
|
+
}
|
|
183
279
|
const toolName = data.tool_name;
|
|
184
280
|
const cwd = data.cwd || process.cwd();
|
|
185
281
|
const isWorkflowGuardEnabled = workflowGuardEnabled(cwd);
|
|
@@ -192,14 +288,7 @@ process.stdin.on('end', () => {
|
|
|
192
288
|
for (const gitCwd of forceGitAddCwds(command, cwd)) {
|
|
193
289
|
const branch = currentBranch(gitCwd);
|
|
194
290
|
if (/^(worktree-)?agent-/.test(branch)) {
|
|
195
|
-
|
|
196
|
-
decision: 'block',
|
|
197
|
-
code: 'WORKTREE_AGENT_FORCE_ADD_FORBIDDEN',
|
|
198
|
-
reason: 'agent/worktree-agent branches must not run git add -f or git add --force. Respect the SDK skipped_gitignored/skipped_commit_docs_false contract and leave gitignored files untracked.',
|
|
199
|
-
};
|
|
200
|
-
process.stdout.write(JSON.stringify(output));
|
|
201
|
-
// Kimi CLI's exit-2 protocol feeds stderr back to the model (#2304)
|
|
202
|
-
process.stderr.write(output.reason);
|
|
291
|
+
emitForceAddBlock();
|
|
203
292
|
process.exit(2);
|
|
204
293
|
}
|
|
205
294
|
}
|
|
@@ -264,8 +353,17 @@ process.stdin.on('end', () => {
|
|
|
264
353
|
};
|
|
265
354
|
|
|
266
355
|
process.stdout.write(JSON.stringify(output));
|
|
267
|
-
} catch
|
|
268
|
-
//
|
|
356
|
+
} catch {
|
|
357
|
+
// #3504: split posture on internal error. The ONE hard block in this hook
|
|
358
|
+
// (force-add on agent branches) fails CLOSED — if the blocking context can
|
|
359
|
+
// be re-derived from the payload (Bash tool + guard enabled + determinably
|
|
360
|
+
// an agent branch), exit 2 rather than silently allowing. Everything else
|
|
361
|
+
// keeps the historical fail-open posture: a broken advisory guard must
|
|
362
|
+
// never wedge the session's tool calls.
|
|
363
|
+
if (failClosedBlockContext(input)) {
|
|
364
|
+
emitForceAddBlock('fail-closed');
|
|
365
|
+
process.exit(2);
|
|
366
|
+
}
|
|
269
367
|
process.exit(0);
|
|
270
368
|
}
|
|
271
369
|
});
|
|
@@ -173,7 +173,8 @@ process.stdin.on('end', () => {
|
|
|
173
173
|
// / error → not GSD-managed → no-op.
|
|
174
174
|
const branchResult = git(['symbolic-ref', '--short', 'HEAD'], cwd);
|
|
175
175
|
const branch = branchResult.status === 0 && branchResult.stdout ? branchResult.stdout.trim() : '';
|
|
176
|
-
|
|
176
|
+
// #3021: accept worktree-wf_<runid>-<n> branches (Workflow backend's naming).
|
|
177
|
+
if (!/^((worktree-)?agent-|worktree-wf_)[A-Za-z0-9._/-]+$/.test(branch)) {
|
|
177
178
|
process.exit(0); // not a GSD-managed executor worktree — no-op
|
|
178
179
|
}
|
|
179
180
|
|