@opengsd/gsd-core 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +458 -0
- package/agents/gsd-code-fixer.md +5 -5
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +345 -0
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +63 -35
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +444 -134
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +1 -0
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +2 -1
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/surface.md +18 -8
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +532 -174
- package/gsd-core/bin/lib/adr-parser.cjs +1 -1
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +39 -22
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +528 -116
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +134 -5
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +198 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +981 -79
- package/gsd-core/bin/lib/config-loader.cjs +4 -0
- package/gsd-core/bin/lib/config.cjs +153 -38
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +343 -28
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +539 -60
- package/gsd-core/bin/lib/install-engine.cjs +199 -14
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +36 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +41 -10
- package/gsd-core/bin/lib/model-resolver.cjs +101 -10
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
- package/gsd-core/bin/lib/phase-id.cjs +412 -31
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase.cjs +941 -98
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
- package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
- package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
- package/gsd-core/bin/lib/roadmap.cjs +292 -69
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +159 -32
- package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
- package/gsd-core/bin/lib/state-transition.cjs +465 -62
- package/gsd-core/bin/lib/state.cjs +906 -151
- package/gsd-core/bin/lib/surface.cjs +83 -10
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1420 -516
- package/gsd-core/bin/lib/update-context.cjs +36 -26
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +316 -23
- package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +531 -36
- package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +12 -3
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +5 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +17 -9
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/worktree-path-safety.md +112 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/phase-prompt.md +4 -0
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +15 -10
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +4 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
- package/gsd-core/workflows/code-review-fix.md +5 -3
- package/gsd-core/workflows/code-review.md +211 -43
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +40 -254
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +15 -156
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +78 -159
- package/gsd-core/workflows/execute-plan.md +28 -15
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +3 -1
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +52 -3
- package/gsd-core/workflows/milestone-summary.md +2 -0
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +55 -13
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project.md +37 -205
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +19 -4
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +144 -185
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +30 -10
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +21 -4
- package/gsd-core/workflows/reapply-patches.md +79 -3
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +19 -6
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +48 -43
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work.md +68 -182
- package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +371 -27
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1105 -0
- package/hooks/dist/gsd-statusline.js +18 -10
- package/hooks/dist/gsd-validate-commit.sh +474 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +42 -16
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +371 -27
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1105 -0
- package/hooks/gsd-statusline.js +18 -10
- package/hooks/gsd-validate-commit.sh +474 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -9
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +11 -4
- package/scripts/check-contract-drift.cjs +4 -1
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +133 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +17 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +465 -15
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +530 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-test-file-count.allowlist.json +4 -1
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +22 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +1 -0
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +2 -1
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -13,6 +13,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
14
|
const node_os_1 = __importDefault(require("node:os"));
|
|
15
15
|
const validate_cjs_1 = require("./validate.cjs");
|
|
16
|
+
const security_cjs_1 = require("./security.cjs");
|
|
16
17
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
17
18
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
18
19
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
|
|
@@ -23,14 +24,21 @@ const stateMod = require("./state.cjs");
|
|
|
23
24
|
const modelProfilesMod = require("./model-profiles.cjs");
|
|
24
25
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
|
|
25
26
|
const planScanMod = require("./plan-scan.cjs");
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- verification.cjs is an export= CommonJS module
|
|
28
|
+
const verificationMod = require("./verification.cjs");
|
|
26
29
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core-utils.cjs is an export= CommonJS module
|
|
27
30
|
const coreUtilsMod = require("./core-utils.cjs");
|
|
28
31
|
const { findOrphanSummaries, findUnsummarizedPlans } = coreUtilsMod;
|
|
29
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
|
|
30
33
|
const planningScopeMod = require("./planning-scope.cjs");
|
|
31
34
|
const { SCOPE } = planningScopeMod;
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- worktree-safety.cjs is an export= CommonJS module
|
|
36
|
+
const worktreeSafetyMod = require("./worktree-safety.cjs");
|
|
37
|
+
// Single owner of git C-quoted-path decoding (see #4081 note at the
|
|
38
|
+
// codebase-drift --name-status parse loop).
|
|
39
|
+
const { decodeGitQuotedPath } = worktreeSafetyMod;
|
|
32
40
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
33
|
-
const
|
|
41
|
+
const security_cjs_2 = require("./security.cjs");
|
|
34
42
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
35
43
|
const schema_detect_cjs_1 = require("./schema-detect.cjs");
|
|
36
44
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
@@ -56,7 +64,12 @@ const { SEVERITY: HEALTH_SEVERITY, REMEDY_ACTION, REMEDY_RISK, evaluateRules, ev
|
|
|
56
64
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-snapshot.cjs is an export= CommonJS module
|
|
57
65
|
const planningSnapshotMod = require("./planning-snapshot.cjs");
|
|
58
66
|
const { buildPlanningSnapshot } = planningSnapshotMod;
|
|
59
|
-
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- onboard-projection.cjs is an export= CommonJS module
|
|
68
|
+
const onboardProjectionMod = require("./onboard-projection.cjs");
|
|
69
|
+
const { REQUIRED_CODEBASE_MAP_FILES } = onboardProjectionMod;
|
|
70
|
+
const clock_cjs_1 = require("./clock.cjs");
|
|
71
|
+
const { planningDir, planningRoot, withPlanningLock } = planningWorkspace;
|
|
72
|
+
const { defaultPhaseCleanCommitTimesMs } = verificationMod;
|
|
60
73
|
const { extractFrontmatter, parseMustHavesBlock } = frontmatterMod;
|
|
61
74
|
const { readStateHeadFreshness } = stateMod;
|
|
62
75
|
/**
|
|
@@ -144,9 +157,12 @@ function verifySummaryCore(cwd, summaryPath, checkFileCount, opts) {
|
|
|
144
157
|
if (firstSegment.indexOf('.') > 0)
|
|
145
158
|
return false;
|
|
146
159
|
// Containment guard: a `../`-bearing reference must not turn this advisory
|
|
147
|
-
// into a filesystem existence probe outside the project.
|
|
148
|
-
|
|
149
|
-
|
|
160
|
+
// into a filesystem existence probe outside the project. Lexical (ADR-4650
|
|
161
|
+
// decision 6): the candidate is a string pulled from a SUMMARY document and
|
|
162
|
+
// by construction may not exist yet — existence is what gets probed
|
|
163
|
+
// downstream — and this is a pure string-heuristic filter with no other fs
|
|
164
|
+
// access, so a realpath call would also change its cost profile.
|
|
165
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(candidate, projectRoot) === null)
|
|
150
166
|
return false;
|
|
151
167
|
return true;
|
|
152
168
|
};
|
|
@@ -694,6 +710,189 @@ function scanFileWideNegativeGateConflict(content) {
|
|
|
694
710
|
// This detector is warn-only: it never sets valid=false.
|
|
695
711
|
return { warnings, valid: true };
|
|
696
712
|
}
|
|
713
|
+
/**
|
|
714
|
+
* Issue #4024 — quantitative-criteria trap-shape scanner, the third plan-discipline
|
|
715
|
+
* gate in this family (after #429's comment-echo gate and #968's file-wide
|
|
716
|
+
* negative-gate conflict detector). #429/#968 judge `grep -c … == 0` shapes; this
|
|
717
|
+
* scanner judges the OTHER quantitative criterion shapes that are provably traps
|
|
718
|
+
* at HEAD, so a plan whose criteria are unsatisfiable-or-vacuous before any
|
|
719
|
+
* executor touches them no longer passes `verify plan-structure` clean.
|
|
720
|
+
*
|
|
721
|
+
* Scope: criteria text only — `<acceptance_criteria>`, `<automated>`, and
|
|
722
|
+
* `<verify>` blocks. Prose elsewhere in the plan (e.g. an explanatory
|
|
723
|
+
* `<action>`) is not judged (fail-open; see the negative-space section of the
|
|
724
|
+
* #4024 diagnosis).
|
|
725
|
+
*
|
|
726
|
+
* Ban list, each rule with a corrected arm asserted in tests (a rule that fires
|
|
727
|
+
* on its own fix is a refusal, not a rule):
|
|
728
|
+
* R1 (error) exact count out of `grep -c` with N ≥ 2 — `grep -c` counts LINES,
|
|
729
|
+
* not matches. Hedged counts (`>=`, "at least"), `== 0` (the #429/#968
|
|
730
|
+
* family) and the `== 1` presence idiom stay clean.
|
|
731
|
+
* R2 (error) bulk observed-failing claims — "all N tests … observed failing" /
|
|
732
|
+
* "tests N through M … each … observed failing". A test asserting a
|
|
733
|
+
* non-change cannot go red before the change exists. Per-test
|
|
734
|
+
* "discriminates" wording and subset claims stay clean.
|
|
735
|
+
* R3 (error) `$VAR` unquoted in command position — zsh does not word-split an
|
|
736
|
+
* unquoted expansion, the command exits 127, and any arm reading its
|
|
737
|
+
* status passes vacuously.
|
|
738
|
+
* R4 (warn) a fallible command (`git …`) in a non-final pipeline stage — the
|
|
739
|
+
* pipeline reports the LAST stage's status, so the failure is swallowed.
|
|
740
|
+
* Warn-only: the shape is ambiguous unless the criterion reads the status.
|
|
741
|
+
* R5 (error) `wc` output compared by string equality (`… | wc -l | grep -x 0`)
|
|
742
|
+
* — BSD `wc` pads its output, so the comparison never matches.
|
|
743
|
+
* R6 (error) `git diff`/`git log` with a relative `HEAD~N` anchor — it names
|
|
744
|
+
* whatever commit happened to land (another session's). Bare `git diff`
|
|
745
|
+
* with no range is a WARNING: a committed change produces no output and
|
|
746
|
+
* the check passes, but an uncommitted-tree check is a legitimate idiom.
|
|
747
|
+
*
|
|
748
|
+
* Legitimate exit: `<!-- plan-criteria-allow: R# - reason -->` (house style of
|
|
749
|
+
* `planner-discipline-allow`); the reason must be non-empty. Fail open: no
|
|
750
|
+
* criteria zones → no findings; every parse is text-only, nothing executes.
|
|
751
|
+
*/
|
|
752
|
+
function scanQuantitativeCriteria(content) {
|
|
753
|
+
const errors = [];
|
|
754
|
+
const warnings = [];
|
|
755
|
+
// Criteria zones: <acceptance_criteria>, <automated>, <verify>. A <verify>
|
|
756
|
+
// block contains its <automated> child, so the same segment may be harvested
|
|
757
|
+
// twice — dedupe findings by message below.
|
|
758
|
+
const zones = [];
|
|
759
|
+
for (const tag of ['acceptance_criteria', 'automated', 'verify']) {
|
|
760
|
+
for (const block of (0, markdown_sectionizer_cjs_1.extractTaggedBlocks)(content || '', tag)) {
|
|
761
|
+
zones.push({ text: block, prose: tag === 'acceptance_criteria' });
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (zones.length === 0)
|
|
765
|
+
return { errors, warnings };
|
|
766
|
+
// Allow markers may sit anywhere in the plan (same as the sibling scanners).
|
|
767
|
+
// The reason must be non-empty: `R1 - -->` is not an auditable exit.
|
|
768
|
+
const allow = new Set();
|
|
769
|
+
const allowRe = /<!--\s*plan-criteria-allow:\s*(R[1-6])\s+-\s+([^>]*\S)\s*-->/g;
|
|
770
|
+
let am;
|
|
771
|
+
while ((am = allowRe.exec(content || '')) !== null)
|
|
772
|
+
allow.add(am[1]);
|
|
773
|
+
const record = (bucket, rule, message, seen) => {
|
|
774
|
+
if (allow.has(rule) || seen.has(message))
|
|
775
|
+
return;
|
|
776
|
+
seen.add(message);
|
|
777
|
+
bucket.push(message);
|
|
778
|
+
};
|
|
779
|
+
// A count-grep invocation (grep with -c / --count), per the #429 idiom.
|
|
780
|
+
const countGrepRe = /grep((?:\s+-{1,2}[A-Za-z][A-Za-z-]*)+)\s+(?:'[^']*'|"[^"]*"|[^\s|>&;]+)/g;
|
|
781
|
+
const optsHaveCount = (opts) => /(?:^|\s)-[A-Za-z]*c[A-Za-z]*(?=\s|$)/.test(opts) || /--count\b/.test(opts);
|
|
782
|
+
const hasCountGrep = (s) => {
|
|
783
|
+
countGrepRe.lastIndex = 0;
|
|
784
|
+
let m;
|
|
785
|
+
while ((m = countGrepRe.exec(s)) !== null) {
|
|
786
|
+
if (optsHaveCount(m[1]))
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
return false;
|
|
790
|
+
};
|
|
791
|
+
const seenErr = new Set();
|
|
792
|
+
const seenWarn = new Set();
|
|
793
|
+
for (const zone of zones) {
|
|
794
|
+
// Same normalization as the sibling scanners (#3611): newline join,
|
|
795
|
+
// backslash continuation join, entity-amp decode.
|
|
796
|
+
const text = decodeEntityAmps(zone.text
|
|
797
|
+
.replace(/\r\n/g, '\n')
|
|
798
|
+
.replace(/\r/g, '\n')
|
|
799
|
+
.replace(/\\\n/g, ' '));
|
|
800
|
+
// R2 — bulk observed-failing prose (acceptance_criteria only). The gap
|
|
801
|
+
// [^\n.]{0,120} keeps the subject and the claim adjacent within one
|
|
802
|
+
// sentence, so the corrected arm (a subset claim, or "each test
|
|
803
|
+
// discriminates") never fires.
|
|
804
|
+
if (zone.prose) {
|
|
805
|
+
const bulkRedRes = [
|
|
806
|
+
/\ball\s+(?:\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+tests\b[^\n.]{0,120}?observed\s+fail/i,
|
|
807
|
+
/\btests\s+\d+\s+through\s+\d+\b[^\n.]{0,120}?observed\s+fail/i,
|
|
808
|
+
];
|
|
809
|
+
for (const re of bulkRedRes) {
|
|
810
|
+
if (re.test(text)) {
|
|
811
|
+
record(errors, 'R2', '[plan-criteria R2] A bulk "all N tests / tests N through M were observed failing" criterion is ' +
|
|
812
|
+
'unsatisfiable whenever any of the N tests asserts a non-change, which cannot go red before the ' +
|
|
813
|
+
'change exists. Say "each test discriminates" (fails before the change, passes after), and attach a ' +
|
|
814
|
+
'mutation record for any test that cannot be seen red.', seenErr);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
for (const seg of text.split('\n').flatMap(splitShellSegments)) {
|
|
819
|
+
// R1 — exact count out of `grep -c` (N ≥ 2). Spaced `==`/`-eq` (the
|
|
820
|
+
// #429 zeroCmp idiom, excluding assignments and >=/<=/!=), or the prose
|
|
821
|
+
// "returns exactly N".
|
|
822
|
+
if (hasCountGrep(seg)) {
|
|
823
|
+
const exact = [];
|
|
824
|
+
let m;
|
|
825
|
+
const shellCmp = /\s==?\s*(\d+)\b/g;
|
|
826
|
+
while ((m = shellCmp.exec(seg)) !== null)
|
|
827
|
+
exact.push(parseInt(m[1], 10));
|
|
828
|
+
const eqCmp = /-eq\s+(\d+)\b/g;
|
|
829
|
+
while ((m = eqCmp.exec(seg)) !== null)
|
|
830
|
+
exact.push(parseInt(m[1], 10));
|
|
831
|
+
const proseCmp = /\breturns?\s+exactly\s+(\d+)\b/gi;
|
|
832
|
+
while ((m = proseCmp.exec(seg)) !== null)
|
|
833
|
+
exact.push(parseInt(m[1], 10));
|
|
834
|
+
if (exact.some((n) => n >= 2)) {
|
|
835
|
+
record(errors, 'R1', '[plan-criteria R1] An exact count out of `grep -c` is a trap: `grep -c` counts LINES, not matches, ' +
|
|
836
|
+
'so the criterion can be false at HEAD before any executor touches it. Use `>= 1`, `== 0`, or ' +
|
|
837
|
+
'`grep -n` and read the line numbers.', seenErr);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
// R3 — unquoted $VAR in command position (zsh does not word-split; the
|
|
841
|
+
// command exits 127 and any arm reading its status passes vacuously).
|
|
842
|
+
// The anchor tolerates a markdown list bullet and/or an inline-code
|
|
843
|
+
// backtick before the command — criteria are prose lines like
|
|
844
|
+
// "- `$NOKEY npx tsx x.ts` exits 0" — but nothing else: the expansion
|
|
845
|
+
// must still be the FIRST token of the command itself.
|
|
846
|
+
if (/^(?:[-*]\s*)?\u0060?\$[A-Z_][A-Z0-9_]*\s/.test(seg)) {
|
|
847
|
+
record(errors, 'R3', '[plan-criteria R3] `$VAR` unquoted in command position does not word-split under zsh — the shell ' +
|
|
848
|
+
'looks for a command literally named by the whole expansion and exits 127, so any arm reading its ' +
|
|
849
|
+
'status passes vacuously. Write the prefix inline or use a shell function.', seenErr);
|
|
850
|
+
}
|
|
851
|
+
// R4 — a fallible `git …` in a non-final pipeline stage: the pipeline
|
|
852
|
+
// reports the LAST stage's status, so git's failure is swallowed.
|
|
853
|
+
// Warn-only: without a status assertion the shape is ambiguous.
|
|
854
|
+
// Not a markdown table: the negated-pipe class matches a SHELL pipeline
|
|
855
|
+
// stage boundary (git before the next `|`), the same shape the
|
|
856
|
+
// #429/#968 scanners use; there is no table row to parse.
|
|
857
|
+
// allow-adhoc-markdown: shell pipeline stage boundary, not a table cell (#4024)
|
|
858
|
+
if (/\bgit\s+[a-z][^\n|]*\|/.test(seg)) {
|
|
859
|
+
record(warnings, 'R4', '[plan-criteria R4] A fallible `git` in a non-final pipeline stage is swallowed — the pipeline reports ' +
|
|
860
|
+
'the last stage\'s status, so a broken command reads as clean. Capture the status first.', seenWarn);
|
|
861
|
+
}
|
|
862
|
+
// R5 — `wc` output compared by string equality: BSD `wc` pads its output
|
|
863
|
+
// (seven spaces then the number), so `grep -x 0` can never match.
|
|
864
|
+
if (/\bwc\b/.test(seg) && /\bgrep\s+(?:-{1,2}[A-Za-z]*x[A-Za-z]*|--line-regexp)\s+['"]?\d+['"]?/.test(seg)) {
|
|
865
|
+
record(errors, 'R5', '[plan-criteria R5] `wc` output compared by string equality is unsatisfiable on BSD `wc`, which pads ' +
|
|
866
|
+
'its output before the number. Compare numerically: `test "$n" -eq 0`, or `tr -d \' \'` first.', seenErr);
|
|
867
|
+
}
|
|
868
|
+
// R6 — relative HEAD~N anchors name whatever commit happened to land;
|
|
869
|
+
// a bare `git diff` with no range makes a committed change invisible.
|
|
870
|
+
if (/\bgit\s+(?:diff|log)\b[^\n|;&]*\bHEAD~\d+/.test(seg)) {
|
|
871
|
+
record(errors, 'R6', '[plan-criteria R6] `git diff`/`git log` with a relative `HEAD~N` anchor names whatever commit ' +
|
|
872
|
+
'happened to land last — in a repository with parallel sessions that is another session\'s commit. ' +
|
|
873
|
+
'Pin an explicit range: `<sha>^..<sha>`.', seenErr);
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
// The argument span ends at the closing backtick of an inline-code
|
|
877
|
+
// span ("`git diff` shows ..."), at a redirection, or at a chain
|
|
878
|
+
// operator — trailing prose must never read as diff arguments.
|
|
879
|
+
const bareDiff = /\bgit\s+diff\b([^\u0060\n|;&<>]*)/.exec(seg);
|
|
880
|
+
if (bareDiff) {
|
|
881
|
+
const rest = bareDiff[1].trim();
|
|
882
|
+
// Bare or flags-only (no rev, range, or path): ambiguous between an
|
|
883
|
+
// uncommitted-tree check (legitimate) and a committed-change check
|
|
884
|
+
// (vacuously clean) — warn, never error.
|
|
885
|
+
if (rest === '' || /^-{1,2}[A-Za-z-]+$/.test(rest)) {
|
|
886
|
+
record(warnings, 'R6', '[plan-criteria R6] Bare `git diff` with no revision range produces no output for a COMMITTED ' +
|
|
887
|
+
'change, so the criterion passes vacuously. Pin an explicit range (`<sha>^..<sha>`) unless the ' +
|
|
888
|
+
'check is intentionally about the uncommitted tree.', seenWarn);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return { errors, warnings };
|
|
895
|
+
}
|
|
697
896
|
/**
|
|
698
897
|
* Single pass over `<task ...>…</task>` blocks. The body pattern is
|
|
699
898
|
* ReDoS-safe stop-at-next-open (mirrors `taggedBlockPattern` in
|
|
@@ -904,6 +1103,12 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
|
904
1103
|
warnings.push(...echoScan.warnings);
|
|
905
1104
|
const conflictScan = scanFileWideNegativeGateConflict(content);
|
|
906
1105
|
warnings.push(...conflictScan.warnings);
|
|
1106
|
+
// #4024: quantitative-criteria trap shapes (exact grep -c counts, bulk
|
|
1107
|
+
// observed-failing claims, $VAR command position, swallowed pipeline
|
|
1108
|
+
// stages, wc string-equality, relative git anchors).
|
|
1109
|
+
const quantScan = scanQuantitativeCriteria(content);
|
|
1110
|
+
errors.push(...quantScan.errors);
|
|
1111
|
+
warnings.push(...quantScan.warnings);
|
|
907
1112
|
output({
|
|
908
1113
|
valid: errors.length === 0,
|
|
909
1114
|
errors,
|
|
@@ -1080,12 +1285,19 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
|
|
|
1080
1285
|
results.push(check);
|
|
1081
1286
|
}
|
|
1082
1287
|
const passed = results.filter((r) => r['passed']).length;
|
|
1288
|
+
// Positive-evidence floor (#3956): a non-empty artifacts block whose items are
|
|
1289
|
+
// all bare strings / path-less objects is item-by-item skipped, leaving results
|
|
1290
|
+
// empty; `passed === results.length` would then be `0 === 0` → a vacuous GREEN
|
|
1291
|
+
// over zero checks. Require at least one checked artifact, mirroring the
|
|
1292
|
+
// no-vacuous-pass rule at src/uat-predicate.cts. The fully-empty block is still
|
|
1293
|
+
// caught earlier by the `artifacts.length === 0` guard and returns its error.
|
|
1294
|
+
const allPassed = results.length > 0 && passed === results.length;
|
|
1083
1295
|
output({
|
|
1084
|
-
all_passed:
|
|
1296
|
+
all_passed: allPassed,
|
|
1085
1297
|
passed,
|
|
1086
1298
|
total: results.length,
|
|
1087
1299
|
artifacts: results,
|
|
1088
|
-
}, raw,
|
|
1300
|
+
}, raw, allPassed ? 'valid' : 'invalid');
|
|
1089
1301
|
}
|
|
1090
1302
|
/**
|
|
1091
1303
|
* Returns a Set of file paths (relative to cwd) that are promised by plans in
|
|
@@ -1174,11 +1386,11 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1174
1386
|
// project. Leave sourceContent as null so the existing not-found /
|
|
1175
1387
|
// pending classification below runs unchanged. Note this guard is
|
|
1176
1388
|
// narrower than it may look: `from: "."` is a non-empty string, so it
|
|
1177
|
-
// still reaches
|
|
1389
|
+
// still reaches tryWithinRoot and safeReadFile below, and DOES read the
|
|
1178
1390
|
// cwd directory (yielding "Source read failed: EISDIR") — this branch
|
|
1179
1391
|
// only short-circuits the true empty-string case.
|
|
1180
|
-
const
|
|
1181
|
-
if (
|
|
1392
|
+
const fromContained = (0, security_cjs_2.tryWithinRoot)(fromPath, cwd);
|
|
1393
|
+
if (fromContained === null) {
|
|
1182
1394
|
// Do not echo result.error — it embeds absolute host paths.
|
|
1183
1395
|
check['path_rejected'] = 'from';
|
|
1184
1396
|
check['detail'] = 'Source path rejected — resolves outside the project directory';
|
|
@@ -1186,7 +1398,7 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1186
1398
|
continue;
|
|
1187
1399
|
}
|
|
1188
1400
|
try {
|
|
1189
|
-
sourceContent = (0, shell_command_projection_cjs_1.platformReadSync)(
|
|
1401
|
+
sourceContent = (0, shell_command_projection_cjs_1.platformReadSync)(fromContained);
|
|
1190
1402
|
}
|
|
1191
1403
|
catch (err) {
|
|
1192
1404
|
// Report the errno only — never the message or path (untrusted `from:`
|
|
@@ -1254,8 +1466,8 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1254
1466
|
// An empty/missing `to:` is a malformed plan, not a
|
|
1255
1467
|
// path-confinement violation — only a non-empty path that
|
|
1256
1468
|
// actually resolves outside the project is path_rejected.
|
|
1257
|
-
const
|
|
1258
|
-
if (
|
|
1469
|
+
const toContained = (0, security_cjs_2.tryWithinRoot)(toPath, cwd);
|
|
1470
|
+
if (toContained === null) {
|
|
1259
1471
|
// Do not read a rejected `to:` — treat as no target content
|
|
1260
1472
|
// and do not echo result.error, which embeds absolute host
|
|
1261
1473
|
// paths.
|
|
@@ -1263,7 +1475,7 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1263
1475
|
check['detail'] = `Pattern "${link['pattern']}" not found in source; target path rejected — resolves outside the project directory`;
|
|
1264
1476
|
}
|
|
1265
1477
|
else {
|
|
1266
|
-
targetContent = (0, shell_command_projection_cjs_1.platformReadSync)(
|
|
1478
|
+
targetContent = (0, shell_command_projection_cjs_1.platformReadSync)(toContained);
|
|
1267
1479
|
}
|
|
1268
1480
|
}
|
|
1269
1481
|
if (targetContent && pat.test(targetContent)) {
|
|
@@ -1299,7 +1511,17 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1299
1511
|
// A pending link (from: file promised by a same-or-later-wave plan) is not a
|
|
1300
1512
|
// hard failure — it should not count against the all_verified gate (#1202).
|
|
1301
1513
|
const hardFailed = results.filter((r) => !r['verified'] && !r['pending']).length;
|
|
1302
|
-
|
|
1514
|
+
// Positive-evidence floor (#3956): an all-bare-string key_links block skips
|
|
1515
|
+
// every item (only `typeof link === 'string'` items are continue-skipped
|
|
1516
|
+
// above), leaving results empty; `hardFailed === 0` would then be a vacuous
|
|
1517
|
+
// GREEN over zero checks. Require at least one checked link. (A `from:`-less
|
|
1518
|
+
// object is NOT skipped, unlike a path-less object on the artifacts side — it
|
|
1519
|
+
// falls through to a `verified: false` result and hard-fails, so it was never
|
|
1520
|
+
// part of the vacuous-pass surface; only the all-bare-string case is.) A
|
|
1521
|
+
// pending link IS pushed to results (with pending: true), so an all-pending
|
|
1522
|
+
// block still satisfies results.length > 0 and its #1202 non-hard-failing
|
|
1523
|
+
// semantics are unchanged — the floor only rejects the zero-result case.
|
|
1524
|
+
const allVerified = results.length > 0 && hardFailed === 0;
|
|
1303
1525
|
output({
|
|
1304
1526
|
all_verified: allVerified,
|
|
1305
1527
|
verified,
|
|
@@ -1593,6 +1815,121 @@ function cmdValidateAgents(cwd, raw) {
|
|
|
1593
1815
|
sandbox_posture: sandboxPosture,
|
|
1594
1816
|
}, raw);
|
|
1595
1817
|
}
|
|
1818
|
+
// ─── Context drift (#3348) ───────────────────────────────────────────────────
|
|
1819
|
+
/**
|
|
1820
|
+
* Resolve a phase directory under `phasesDir` from a user-supplied `phaseArg`,
|
|
1821
|
+
* via the canonical phase-directory matcher (phase-id.cjs::matchPhaseDirs) rather
|
|
1822
|
+
* than a naive substring test — a bare `.includes(phaseArg)` lets a non-existent
|
|
1823
|
+
* phase silently match a different phase whose directory name merely contains the
|
|
1824
|
+
* requested token (e.g. "1" matching "11-expansion"). Falls back to an exact
|
|
1825
|
+
* directory-name match. Returns null if neither resolves. (#1571, #2528)
|
|
1826
|
+
*/
|
|
1827
|
+
function resolvePhaseDirByToken(phasesDir, phaseArg) {
|
|
1828
|
+
const normalizedPhase = normalizePhaseName(phaseArg);
|
|
1829
|
+
const dirEntries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
|
|
1830
|
+
const dirNames = dirEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1831
|
+
const matched = matchPhaseDirs(dirNames, normalizedPhase).matches[0];
|
|
1832
|
+
if (matched)
|
|
1833
|
+
return node_path_1.default.join(phasesDir, matched);
|
|
1834
|
+
const contained = (0, security_cjs_2.tryWithinRoot)(phaseArg, phasesDir);
|
|
1835
|
+
if (contained !== null && node_fs_1.default.existsSync(contained))
|
|
1836
|
+
return contained;
|
|
1837
|
+
return null;
|
|
1838
|
+
}
|
|
1839
|
+
/**
|
|
1840
|
+
* Pure comparator: which of `entries` have an effective last-changed time
|
|
1841
|
+
* STRICTLY BEFORE `contextEffectiveMs` (CONTEXT.md's own effective time)? Strict
|
|
1842
|
+
* `<` is "stale" (matches findStaleVerificationSummary's own strict `>` convention
|
|
1843
|
+
* for "newer than" elsewhere in this codebase — an artifact committed in the SAME
|
|
1844
|
+
* commit/second as CONTEXT.md is in sync, not stale).
|
|
1845
|
+
*/
|
|
1846
|
+
function computeContextDrift(contextEffectiveMs, entries) {
|
|
1847
|
+
return entries.filter((e) => e.effectiveMs < contextEffectiveMs).map((e) => e.file);
|
|
1848
|
+
}
|
|
1849
|
+
function buildContextDriftMessage(staleArtifacts, phaseArg) {
|
|
1850
|
+
const parts = [`CONTEXT.md decisions are newer than: ${staleArtifacts.join(', ')}.`];
|
|
1851
|
+
if (staleArtifacts.some((f) => f.endsWith('-RESEARCH.md'))) {
|
|
1852
|
+
parts.push(`Regenerate research: /gsd:plan-phase ${phaseArg} --research.`);
|
|
1853
|
+
}
|
|
1854
|
+
if (staleArtifacts.some((f) => f.endsWith('-PATTERNS.md'))) {
|
|
1855
|
+
parts.push('Regenerate patterns: delete the PATTERNS.md file, then re-run /gsd:plan-phase.');
|
|
1856
|
+
}
|
|
1857
|
+
if (staleArtifacts.some((f) => f.endsWith('-VALIDATION.md') || (f.endsWith('-SPEC.md') && !f.endsWith('-AI-SPEC.md') && !f.endsWith('-UI-SPEC.md')))) {
|
|
1858
|
+
parts.push('Regenerate or manually reconcile VALIDATION.md / SPEC.md against the current decisions.');
|
|
1859
|
+
}
|
|
1860
|
+
parts.push('Do not hand-inject the newer decisions into a prompt as a substitute for regenerating — that carries the staleness forward.');
|
|
1861
|
+
return parts.join(' ');
|
|
1862
|
+
}
|
|
1863
|
+
function cmdVerifyContextDrift(cwd, phaseArg, raw) {
|
|
1864
|
+
if (!phaseArg) {
|
|
1865
|
+
error('Usage: verify context-drift <phase>');
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
const pDir = planningDir(cwd);
|
|
1869
|
+
const phasesDir = node_path_1.default.join(pDir, 'phases');
|
|
1870
|
+
const emitSkip = (reason, message = '') => {
|
|
1871
|
+
output({ block: false, skipped: true, reason, stale_artifacts: [], message }, raw);
|
|
1872
|
+
};
|
|
1873
|
+
if (!node_fs_1.default.existsSync(phasesDir)) {
|
|
1874
|
+
emitSkip('phase-not-found', `Phase directory not found: ${phaseArg}`);
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
// Same phase-directory resolution rule cmdVerifySchemaDrift uses (#1571, #2528):
|
|
1878
|
+
// matchPhaseDirs, never a naive substring test.
|
|
1879
|
+
const phaseDir = resolvePhaseDirByToken(phasesDir, phaseArg);
|
|
1880
|
+
if (!phaseDir) {
|
|
1881
|
+
emitSkip('phase-not-found', `Phase directory not found: ${phaseArg}`);
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
let phaseFiles;
|
|
1885
|
+
try {
|
|
1886
|
+
phaseFiles = node_fs_1.default.readdirSync(phaseDir).slice().sort();
|
|
1887
|
+
}
|
|
1888
|
+
catch {
|
|
1889
|
+
emitSkip('phase-not-found', `Phase directory not found: ${phaseArg}`);
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
const contextFile = phaseFiles.find((f) => f.endsWith('-CONTEXT.md'));
|
|
1893
|
+
if (!contextFile) {
|
|
1894
|
+
emitSkip('no-context-md');
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
const researchFile = phaseFiles.find((f) => f.endsWith('-RESEARCH.md'));
|
|
1898
|
+
const patternsFile = phaseFiles.find((f) => f.endsWith('-PATTERNS.md'));
|
|
1899
|
+
const validationFile = phaseFiles.find((f) => f.endsWith('-VALIDATION.md'));
|
|
1900
|
+
const specFile = phaseFiles.find((f) => f.endsWith('-SPEC.md') && !f.endsWith('-AI-SPEC.md') && !f.endsWith('-UI-SPEC.md'));
|
|
1901
|
+
const upstreamFiles = [researchFile, patternsFile, validationFile, specFile].filter((f) => !!f);
|
|
1902
|
+
if (upstreamFiles.length === 0) {
|
|
1903
|
+
emitSkip('no-upstream-artifacts');
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
const allFiles = [contextFile, ...upstreamFiles];
|
|
1907
|
+
const cleanCommitMs = defaultPhaseCleanCommitTimesMs(phaseDir, allFiles);
|
|
1908
|
+
const effectiveTimeMs = (file) => cleanCommitMs.has(file)
|
|
1909
|
+
? cleanCommitMs.get(file)
|
|
1910
|
+
: node_fs_1.default.statSync(node_path_1.default.join(phaseDir, file)).mtimeMs;
|
|
1911
|
+
const contextMs = effectiveTimeMs(contextFile);
|
|
1912
|
+
const driftEntries = upstreamFiles.map((f) => ({ file: f, effectiveMs: effectiveTimeMs(f) }));
|
|
1913
|
+
const staleArtifacts = computeContextDrift(contextMs, driftEntries);
|
|
1914
|
+
let wf;
|
|
1915
|
+
try {
|
|
1916
|
+
const rawCfg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(pDir, 'config.json'), 'utf-8'));
|
|
1917
|
+
wf = rawCfg['workflow'];
|
|
1918
|
+
}
|
|
1919
|
+
catch {
|
|
1920
|
+
wf = undefined;
|
|
1921
|
+
}
|
|
1922
|
+
const action = wf?.context_drift_action === 'block' ? 'block' : 'warn';
|
|
1923
|
+
const block = staleArtifacts.length > 0 && action === 'block';
|
|
1924
|
+
const message = staleArtifacts.length > 0 ? buildContextDriftMessage(staleArtifacts, phaseArg) : '';
|
|
1925
|
+
output({
|
|
1926
|
+
block,
|
|
1927
|
+
skipped: false,
|
|
1928
|
+
stale_artifacts: staleArtifacts,
|
|
1929
|
+
action,
|
|
1930
|
+
message,
|
|
1931
|
+
}, raw);
|
|
1932
|
+
}
|
|
1596
1933
|
function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
|
1597
1934
|
if (!phaseArg) {
|
|
1598
1935
|
error('Usage: verify schema-drift <phase> [--skip]');
|
|
@@ -1611,18 +1948,7 @@ function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
|
|
1611
1948
|
// matching "11-expansion"), making the drift gate inspect the wrong phase.
|
|
1612
1949
|
// This shares the one selection rule with find-phase / verify
|
|
1613
1950
|
// phase-completeness rather than restating it. (#1571, #2528)
|
|
1614
|
-
|
|
1615
|
-
const normalizedPhase = normalizePhaseName(phaseArg);
|
|
1616
|
-
const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
|
|
1617
|
-
const dirNames = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1618
|
-
const drift = matchPhaseDirs(dirNames, normalizedPhase).matches[0];
|
|
1619
|
-
if (drift)
|
|
1620
|
-
phaseDir = node_path_1.default.join(phasesDir, drift);
|
|
1621
|
-
if (!phaseDir) {
|
|
1622
|
-
const exact = node_path_1.default.join(phasesDir, phaseArg);
|
|
1623
|
-
if (node_fs_1.default.existsSync(exact))
|
|
1624
|
-
phaseDir = exact;
|
|
1625
|
-
}
|
|
1951
|
+
const phaseDir = resolvePhaseDirByToken(phasesDir, phaseArg);
|
|
1626
1952
|
if (!phaseDir) {
|
|
1627
1953
|
output({ block: false, drift_detected: false, blocking: false, message: `Phase directory not found: ${phaseArg}` }, raw);
|
|
1628
1954
|
return;
|
|
@@ -1668,6 +1994,110 @@ function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
|
|
1668
1994
|
skipped: isSkipped,
|
|
1669
1995
|
}, raw);
|
|
1670
1996
|
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Stamp `last_mapped_commit` (plus `last_mapped_at`) into the frontmatter of
|
|
1999
|
+
* every codebase-map document that exists on disk, using the current HEAD sha.
|
|
2000
|
+
*
|
|
2001
|
+
* #3418: `drift.cjs` shipped a correct `writeMappedCommit` with no production
|
|
2002
|
+
* caller, so no full `/gsd:map-codebase` run ever wrote the machine-readable
|
|
2003
|
+
* baseline that `cmdVerifyCodebaseDrift` reads. The stamp lives in CODE rather
|
|
2004
|
+
* than in a prose instruction to the mapper agent on purpose: an agent that
|
|
2005
|
+
* decides its work is already done skips a prose step silently, which is the
|
|
2006
|
+
* exact class of failure the stamp exists to detect.
|
|
2007
|
+
*
|
|
2008
|
+
* Only documents that already exist are stamped -- a `--fast` map produces four
|
|
2009
|
+
* of the seven, and this must not conjure the missing three as frontmatter-only
|
|
2010
|
+
* stubs that would then satisfy the seven-file completeness probe. `only`
|
|
2011
|
+
* (`--files a.md,b.md`) narrows further, for a caller that refreshed a subset.
|
|
2012
|
+
*
|
|
2013
|
+
* Non-blocking by contract, like every other drift surface: any failure emits
|
|
2014
|
+
* `skipped` with a reason and exits 0.
|
|
2015
|
+
*/
|
|
2016
|
+
function cmdStampCodebaseMap(cwd, raw, only) {
|
|
2017
|
+
// Non-hoisted: load-order matters for circular dep guard
|
|
2018
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- drift.cjs is an export= CommonJS module
|
|
2019
|
+
const drift = require('./drift.cjs');
|
|
2020
|
+
const emit = (payload) => output(payload, raw);
|
|
2021
|
+
const skip = (reason) => ({
|
|
2022
|
+
stamped: [],
|
|
2023
|
+
commit: null,
|
|
2024
|
+
stamped_at: null,
|
|
2025
|
+
skipped: true,
|
|
2026
|
+
reason,
|
|
2027
|
+
});
|
|
2028
|
+
try {
|
|
2029
|
+
// Probed outside the lock on purpose: taking the lock creates `.planning/`
|
|
2030
|
+
// if absent, and a stamp against a project that has no codebase map at all
|
|
2031
|
+
// must not conjure the directory. The per-document existence check inside
|
|
2032
|
+
// the lock is the one that matters -- a map deleted after this probe lands
|
|
2033
|
+
// on `no-codebase-map` there rather than being recreated as stubs.
|
|
2034
|
+
const codebaseDir = node_path_1.default.join(planningDir(cwd), 'codebase');
|
|
2035
|
+
if (!node_fs_1.default.existsSync(codebaseDir)) {
|
|
2036
|
+
emit(skip('no-codebase-dir'));
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
// `--files` restricts the stamp to the documents a caller actually
|
|
2040
|
+
// refreshed. The execute-phase auto-remap path rewrites STRUCTURE.md and
|
|
2041
|
+
// ARCHITECTURE.md only; stamping the other five at HEAD there would claim a
|
|
2042
|
+
// currency they do not have. Membership is checked against the closed
|
|
2043
|
+
// seven-document set, so an unknown name is a fail-loud non-answer rather
|
|
2044
|
+
// than a path this function tries to resolve. An empty value is refused
|
|
2045
|
+
// rather than read as "no filter": a caller that meant to narrow the scope
|
|
2046
|
+
// must not silently widen it to all seven.
|
|
2047
|
+
let candidates = REQUIRED_CODEBASE_MAP_FILES;
|
|
2048
|
+
if (only) {
|
|
2049
|
+
if (only.length === 0) {
|
|
2050
|
+
emit(skip('empty-codebase-map-file-filter'));
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
const unknown = only.filter((file) => !candidates.includes(file));
|
|
2054
|
+
if (unknown.length > 0) {
|
|
2055
|
+
emit(skip('unknown-codebase-map-file: ' + unknown.join(',')));
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
candidates = candidates.filter((file) => only.includes(file));
|
|
2059
|
+
}
|
|
2060
|
+
// Everything the stamp reads is read under the lock it writes under, the
|
|
2061
|
+
// same lock the rest of the .planning/ writers take. Two stampers can run
|
|
2062
|
+
// at once (the full map-codebase run and the execute-phase auto-remap);
|
|
2063
|
+
// one that resolved HEAD or listed the present documents before waiting on
|
|
2064
|
+
// the lock would write its now-stale sha over the newer one, or recreate a
|
|
2065
|
+
// document deleted while it waited as a frontmatter-only stub.
|
|
2066
|
+
const result = withPlanningLock(cwd, () => {
|
|
2067
|
+
const revProbe = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', 'HEAD'], { cwd });
|
|
2068
|
+
if (revProbe.exitCode !== 0)
|
|
2069
|
+
return skip('not-a-git-repo');
|
|
2070
|
+
const commit = revProbe.stdout.trim();
|
|
2071
|
+
if (!/^[0-9a-f]{7,40}$/.test(commit))
|
|
2072
|
+
return skip('unreadable-head');
|
|
2073
|
+
const present = candidates.filter((file) => node_fs_1.default.existsSync(node_path_1.default.join(codebaseDir, file)));
|
|
2074
|
+
if (present.length === 0)
|
|
2075
|
+
return skip('no-codebase-map');
|
|
2076
|
+
// Host-local calendar day, matching the `**Analysis Date:**` line the
|
|
2077
|
+
// mapper agent writes -- the two freshness markers must not disagree by
|
|
2078
|
+
// a timezone.
|
|
2079
|
+
const stampedAt = clock_cjs_1.realClock.localToday();
|
|
2080
|
+
const write = drift['writeMappedCommit'];
|
|
2081
|
+
const stamped = [];
|
|
2082
|
+
const failed = [];
|
|
2083
|
+
for (const file of present) {
|
|
2084
|
+
try {
|
|
2085
|
+
write(node_path_1.default.join(codebaseDir, file), commit, stampedAt);
|
|
2086
|
+
stamped.push(file);
|
|
2087
|
+
}
|
|
2088
|
+
catch (err) {
|
|
2089
|
+
// One unwritable document must not cost the stamp on the other six.
|
|
2090
|
+
failed.push({ file, reason: err instanceof Error ? err.message : String(err) });
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
return { stamped, failed, commit, stamped_at: stampedAt, skipped: false, reason: null };
|
|
2094
|
+
});
|
|
2095
|
+
emit(result);
|
|
2096
|
+
}
|
|
2097
|
+
catch (err) {
|
|
2098
|
+
emit(skip('exception: ' + (err instanceof Error ? err.message : String(err))));
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
1671
2101
|
function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
1672
2102
|
// Non-hoisted: load-order matters for circular dep guard
|
|
1673
2103
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- drift.cjs is an export= CommonJS module
|
|
@@ -1716,16 +2146,45 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1716
2146
|
});
|
|
1717
2147
|
return;
|
|
1718
2148
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
2149
|
+
// #3418: an absent or unresolvable baseline means NO COMPARISON IS POSSIBLE.
|
|
2150
|
+
// It is neither zero drift nor total drift, and reporting it as either is a
|
|
2151
|
+
// lie the consumer cannot detect. The former fallback diffed HEAD against
|
|
2152
|
+
// the empty tree, so every tracked file read as newly added and the gate
|
|
2153
|
+
// reported maximum drift identically on every run -- which made a genuinely
|
|
2154
|
+
// stale map indistinguishable from a fresh one, and let `spawn_mapper` fire
|
|
2155
|
+
// a whole-repo remap while presenting itself as an incremental one.
|
|
2156
|
+
if (!lastMapped) {
|
|
2157
|
+
emit({
|
|
2158
|
+
block: false,
|
|
2159
|
+
skipped: true,
|
|
2160
|
+
reason: 'no-mapped-commit',
|
|
2161
|
+
action_required: false,
|
|
2162
|
+
directive: 'none',
|
|
2163
|
+
elements: [],
|
|
2164
|
+
last_mapped_commit: null,
|
|
2165
|
+
});
|
|
2166
|
+
return;
|
|
1723
2167
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
2168
|
+
const baseProbe = (0, shell_command_projection_cjs_1.execGit)(['cat-file', '-t', lastMapped], { cwd });
|
|
2169
|
+
if (baseProbe.exitCode !== 0 || baseProbe.stdout.trim() !== 'commit') {
|
|
2170
|
+
// A stamp git cannot resolve: history rewrite, GC, or a shallow clone.
|
|
2171
|
+
// Distinct reason from 'no-mapped-commit' -- the map claims a baseline,
|
|
2172
|
+
// this repository just cannot see it, which is an operator-actionable
|
|
2173
|
+
// difference (re-map vs. unshallow). A resolvable non-commit (a tree or
|
|
2174
|
+
// blob sha, a ref name) is the same class of bad baseline: git would
|
|
2175
|
+
// happily diff against it and report drift against the wrong object.
|
|
2176
|
+
emit({
|
|
2177
|
+
block: false,
|
|
2178
|
+
skipped: true,
|
|
2179
|
+
reason: 'unresolvable-mapped-commit',
|
|
2180
|
+
action_required: false,
|
|
2181
|
+
directive: 'none',
|
|
2182
|
+
elements: [],
|
|
2183
|
+
last_mapped_commit: lastMapped,
|
|
2184
|
+
});
|
|
2185
|
+
return;
|
|
1728
2186
|
}
|
|
2187
|
+
const base = lastMapped;
|
|
1729
2188
|
const diff = (0, shell_command_projection_cjs_1.execGit)(['diff', '--name-status', base, 'HEAD'], { cwd });
|
|
1730
2189
|
if (diff.exitCode !== 0) {
|
|
1731
2190
|
emit({
|
|
@@ -1738,6 +2197,27 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1738
2197
|
});
|
|
1739
2198
|
return;
|
|
1740
2199
|
}
|
|
2200
|
+
// #3418: GSD's own planning artifacts are not codebase structure. A
|
|
2201
|
+
// map-codebase run commits `.planning/codebase/*.md`, so a correctly
|
|
2202
|
+
// stamped baseline would be re-poisoned by the very commit that carries
|
|
2203
|
+
// the stamp -- the next gate invocation would report the map's own seven
|
|
2204
|
+
// documents as seven new directories, back over the default threshold of
|
|
2205
|
+
// three. Derived from planningRoot() rather than a hardcoded literal so a
|
|
2206
|
+
// repoint of the planning root cannot leave this filter behind.
|
|
2207
|
+
//
|
|
2208
|
+
// `git diff --name-status` always prints repo-root-relative paths, so a cwd
|
|
2209
|
+
// below the root needs the `sub/` prefix or the filter matches nothing.
|
|
2210
|
+
// That prefix comes from git (`--show-prefix`: root-relative, forward
|
|
2211
|
+
// slashes, trailing slash, empty at the root). The rejected alternative was
|
|
2212
|
+
// path.relative(`--show-toplevel`, cwd), which mixes two path producers: on
|
|
2213
|
+
// Windows os.tmpdir() hands back the 8.3 short form while git resolves the
|
|
2214
|
+
// long one, so relative() between them yields a `../..` chain that matches
|
|
2215
|
+
// nothing. The `.planning` half below is safe to compute with relative()
|
|
2216
|
+
// because both of its sides are the same cwd string.
|
|
2217
|
+
const prefixProbe = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--show-prefix'], { cwd });
|
|
2218
|
+
const repoPrefix = prefixProbe.exitCode === 0 ? prefixProbe.stdout.trim() : '';
|
|
2219
|
+
const planningPrefix = repoPrefix + node_path_1.default.relative(cwd, planningRoot(cwd)).split(node_path_1.default.sep).join('/') + '/';
|
|
2220
|
+
const isPlanningArtifact = (file) => file.split('\\').join('/').startsWith(planningPrefix);
|
|
1741
2221
|
const added = [];
|
|
1742
2222
|
const modified = [];
|
|
1743
2223
|
const deleted = [];
|
|
@@ -1748,7 +2228,18 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1748
2228
|
if (!m)
|
|
1749
2229
|
continue;
|
|
1750
2230
|
const status = m[1];
|
|
1751
|
-
|
|
2231
|
+
// execGit sets no core.quotepath config, so git's default `true` applies:
|
|
2232
|
+
// any path containing non-ASCII bytes (or `"`, `\`, control bytes) is
|
|
2233
|
+
// C-quoted — `"docs/\350\256\276…/overview.md"`. Capturing that verbatim
|
|
2234
|
+
// garbles affected_paths/elements and makes isPathMapped compare the
|
|
2235
|
+
// quoted prefix (`"docs`) against STRUCTURE.md, misclassifying DOCUMENTED
|
|
2236
|
+
// directories as new_dir (#4081). Decode with the single owner of the
|
|
2237
|
+
// git C-quote seam (worktree-safety.cjs); a non-quoted value — the plain
|
|
2238
|
+
// ASCII common case — passes through untouched. Both capture groups are
|
|
2239
|
+
// decoded: R/C lines carry old AND new paths, either may be quoted.
|
|
2240
|
+
const file = decodeGitQuotedPath(m[3] || m[2]);
|
|
2241
|
+
if (isPlanningArtifact(file))
|
|
2242
|
+
continue;
|
|
1752
2243
|
if (status === 'A' || status === 'R' || status === 'C')
|
|
1753
2244
|
added.push(file);
|
|
1754
2245
|
else if (status === 'M')
|
|
@@ -1811,6 +2302,7 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1811
2302
|
module.exports = {
|
|
1812
2303
|
scanNegativeGrepCommentEcho,
|
|
1813
2304
|
scanFileWideNegativeGateConflict,
|
|
2305
|
+
scanQuantitativeCriteria,
|
|
1814
2306
|
cmdVerifySummary,
|
|
1815
2307
|
verifySummaryCore,
|
|
1816
2308
|
cmdVerifyPlanStructure,
|
|
@@ -1824,5 +2316,8 @@ module.exports = {
|
|
|
1824
2316
|
cmdValidateAgents,
|
|
1825
2317
|
cmdVerifySchemaDrift,
|
|
1826
2318
|
cmdVerifyCodebaseDrift,
|
|
2319
|
+
computeContextDrift,
|
|
2320
|
+
cmdVerifyContextDrift,
|
|
2321
|
+
cmdStampCodebaseMap,
|
|
1827
2322
|
STATE_HEAD_ADVISORY_COMMITS,
|
|
1828
2323
|
};
|