@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
|
@@ -22,10 +22,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
22
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
23
|
};
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.MARKDOWN_LINK_PATTERNS = exports.INJECTION_PATTERNS = void 0;
|
|
26
|
-
exports.
|
|
25
|
+
exports.MARKDOWN_LINK_PATTERNS = exports.INJECTION_PATTERNS = exports.PathAcceptance = void 0;
|
|
26
|
+
exports.isContainedIn = isContainedIn;
|
|
27
27
|
exports.loadTrustedGlobalRoots = loadTrustedGlobalRoots;
|
|
28
|
+
exports.assertWithinRoot = assertWithinRoot;
|
|
29
|
+
exports.tryWithinRoot = tryWithinRoot;
|
|
28
30
|
exports.requireSafePath = requireSafePath;
|
|
31
|
+
exports.tryWithinRootLexical = tryWithinRootLexical;
|
|
32
|
+
exports.assertWithinRootLexical = assertWithinRootLexical;
|
|
29
33
|
exports.scanForInjection = scanForInjection;
|
|
30
34
|
exports.sanitizeForPrompt = sanitizeForPrompt;
|
|
31
35
|
exports.sanitizeForDisplay = sanitizeForDisplay;
|
|
@@ -39,6 +43,31 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
39
43
|
const node_os_1 = __importDefault(require("node:os"));
|
|
40
44
|
const node_path_1 = __importDefault(require("node:path"));
|
|
41
45
|
// ─── Path Traversal Prevention ──────────────────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* THE containment comparison — the single place this repo decides whether an
|
|
48
|
+
* already-resolved path lies inside an already-resolved root (ADR-4650).
|
|
49
|
+
*
|
|
50
|
+
* Separator-aware on purpose: comparing the bare strings would accept a
|
|
51
|
+
* sibling that merely shares a prefix (`<root>-evil` against `<root>`), so both
|
|
52
|
+
* sides get a trailing separator before the prefix test. `target === root` is
|
|
53
|
+
* contained.
|
|
54
|
+
*
|
|
55
|
+
* `pathImpl` lets a caller supply `path.win32` / `path.posix` instead of the
|
|
56
|
+
* ambient module, so win32 separator semantics are testable off Windows.
|
|
57
|
+
*
|
|
58
|
+
* Exported for callers that have ALREADY resolved both operands themselves
|
|
59
|
+
* and need only this comparison step (e.g. a caller that owns its own
|
|
60
|
+
* `fs.realpathSync` calls to preserve an exists-vs-escaped tri-state). A
|
|
61
|
+
* caller that has NOT resolved its operands must NOT reach for this function
|
|
62
|
+
* directly — the comparison alone is not a containment check — and should use
|
|
63
|
+
* `assertWithinRoot` / `tryWithinRoot` (or the `assertWithinRootLexical` /
|
|
64
|
+
* `tryWithinRootLexical` pair) instead.
|
|
65
|
+
*/
|
|
66
|
+
function isContainedIn(resolvedTarget, resolvedRoot, pathImpl = node_path_1.default) {
|
|
67
|
+
if (resolvedTarget === resolvedRoot)
|
|
68
|
+
return true;
|
|
69
|
+
return (resolvedTarget + pathImpl.sep).startsWith(resolvedRoot + pathImpl.sep); // allow-handrolled-containment: this IS the canonical comparison every other site routes through
|
|
70
|
+
}
|
|
42
71
|
/**
|
|
43
72
|
* Validate that a file path resolves within an allowed base directory.
|
|
44
73
|
* Prevents path traversal attacks via ../ sequences, symlinks, or absolute paths.
|
|
@@ -122,9 +151,7 @@ function validatePath(filePath, baseDir, opts = {}) {
|
|
|
122
151
|
}
|
|
123
152
|
}
|
|
124
153
|
}
|
|
125
|
-
|
|
126
|
-
const normalizedPath = resolvedPath + node_path_1.default.sep;
|
|
127
|
-
if (resolvedPath !== resolvedBase && !normalizedPath.startsWith(normalizedBase)) {
|
|
154
|
+
if (!isContainedIn(resolvedPath, resolvedBase)) {
|
|
128
155
|
return {
|
|
129
156
|
safe: false,
|
|
130
157
|
resolved: resolvedPath,
|
|
@@ -204,17 +231,109 @@ function loadTrustedGlobalRoots(config) {
|
|
|
204
231
|
}
|
|
205
232
|
return result;
|
|
206
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Named acceptance policy for what kind of candidate path is even considered.
|
|
236
|
+
*
|
|
237
|
+
* This replaces the old per-call-site `{ allowAbsolute: true }` boolean flag.
|
|
238
|
+
* At a call site, `{ allowAbsolute: true }` reads as "containment is relaxed
|
|
239
|
+
* here" — which is FALSE. An absolute path that resolves OUTSIDE the root is
|
|
240
|
+
* still rejected; the flag only ever controlled whether an absolute candidate
|
|
241
|
+
* was considered at all. `AbsoluteInsideRoot` states the real contract: an
|
|
242
|
+
* absolute candidate is accepted for consideration, but containment is
|
|
243
|
+
* enforced exactly as it is for a relative one.
|
|
244
|
+
*/
|
|
245
|
+
exports.PathAcceptance = {
|
|
246
|
+
/** Relative candidates only; an absolute candidate is rejected outright. */
|
|
247
|
+
RelativeOnly: 'relative-only',
|
|
248
|
+
/**
|
|
249
|
+
* An absolute candidate is accepted — but ONLY if it still resolves inside the
|
|
250
|
+
* root. Containment is NOT relaxed by this policy; an absolute path outside the
|
|
251
|
+
* root is rejected exactly as a traversal is. This is the distinction the old
|
|
252
|
+
* `{ allowAbsolute: true }` flag failed to make at its call sites.
|
|
253
|
+
*/
|
|
254
|
+
AbsoluteInsideRoot: 'absolute-inside-root',
|
|
255
|
+
};
|
|
207
256
|
/**
|
|
208
257
|
* Validate a file path and throw on traversal attempt.
|
|
209
258
|
* Convenience wrapper around validatePath for use in CLI commands.
|
|
210
259
|
*/
|
|
211
|
-
function
|
|
212
|
-
const result = validatePath(
|
|
260
|
+
function assertWithinRoot(candidate, root, label, policy = exports.PathAcceptance.RelativeOnly) {
|
|
261
|
+
const result = validatePath(candidate, root, { allowAbsolute: policy === exports.PathAcceptance.AbsoluteInsideRoot });
|
|
213
262
|
if (!result.safe) {
|
|
214
263
|
throw new Error(`${label || 'Path'} validation failed: ${result.error}`);
|
|
215
264
|
}
|
|
216
265
|
return result.resolved;
|
|
217
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Validate a file path and return null on traversal attempt (no throw).
|
|
269
|
+
*
|
|
270
|
+
* Returns exactly `null` when unsafe — never `''`, never `result.resolved`.
|
|
271
|
+
* `validatePath` populates `resolved` with the ESCAPING path on the
|
|
272
|
+
* traversal branch, so returning it here would reproduce the defect this
|
|
273
|
+
* narrowing exists to remove.
|
|
274
|
+
*/
|
|
275
|
+
function tryWithinRoot(candidate, root, policy = exports.PathAcceptance.RelativeOnly) {
|
|
276
|
+
const result = validatePath(candidate, root, { allowAbsolute: policy === exports.PathAcceptance.AbsoluteInsideRoot });
|
|
277
|
+
if (!result.safe) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
return result.resolved;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Validate a file path and throw on traversal attempt.
|
|
284
|
+
* Convenience wrapper around validatePath for use in CLI commands.
|
|
285
|
+
*
|
|
286
|
+
* Delegates to assertWithinRoot so there is one implementation beneath both
|
|
287
|
+
* names; its declared return type is ContainedPath (a branded string, still
|
|
288
|
+
* assignable to string) so existing callers keep compiling untouched.
|
|
289
|
+
*/
|
|
290
|
+
function requireSafePath(filePath, baseDir, label, policy = exports.PathAcceptance.RelativeOnly) {
|
|
291
|
+
return assertWithinRoot(filePath, baseDir, label, policy);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* LEXICAL containment — `path.resolve` only, never any filesystem access.
|
|
295
|
+
*
|
|
296
|
+
* Shares `isContainedIn` with the realpath-based predicate, so there is ONE
|
|
297
|
+
* containment decision in this repo; these differ only in how a path is
|
|
298
|
+
* RESOLVED before that decision, never in the decision itself (ADR-4650
|
|
299
|
+
* decisions 1 and 6).
|
|
300
|
+
*
|
|
301
|
+
* Use this — and say why at the call site — only where a symlink must be
|
|
302
|
+
* PRESERVED rather than resolved, or where the target legitimately does not
|
|
303
|
+
* exist yet. Three such cases exist: a destination validated before the
|
|
304
|
+
* `mkdirSync` that creates it, a migration that snapshots and restores a
|
|
305
|
+
* symlinked path AS A LINK, and a restore gate that refuses links outright.
|
|
306
|
+
* Everywhere else the realpath-based `assertWithinRoot` / `tryWithinRoot` is
|
|
307
|
+
* the correct predicate, because a lexical check CANNOT SEE A SYMLINK: a
|
|
308
|
+
* caller relying on one for a write-confinement guarantee must pair it with
|
|
309
|
+
* its own symlink refusal.
|
|
310
|
+
*
|
|
311
|
+
* `candidate` is resolved RELATIVE TO `root` (so an absolute candidate is
|
|
312
|
+
* taken as-is, matching `path.resolve` semantics). `target === root` is
|
|
313
|
+
* contained.
|
|
314
|
+
*
|
|
315
|
+
* DELIBERATELY ABSENT: no NUL-byte rejection here. The existing lexical
|
|
316
|
+
* callers do not reject NUL at this layer (one of them checks NUL itself,
|
|
317
|
+
* separately), and adding it here would change their behavior. Callers that
|
|
318
|
+
* need it keep their own check.
|
|
319
|
+
*/
|
|
320
|
+
function tryWithinRootLexical(candidate, root, opts = {}) {
|
|
321
|
+
const p = opts.pathImpl || node_path_1.default;
|
|
322
|
+
if (typeof candidate !== 'string' || candidate === '')
|
|
323
|
+
return null;
|
|
324
|
+
if (typeof root !== 'string' || root === '')
|
|
325
|
+
return null;
|
|
326
|
+
const rootResolved = p.resolve(root);
|
|
327
|
+
const targetResolved = p.resolve(root, candidate);
|
|
328
|
+
return isContainedIn(targetResolved, rootResolved, p) ? targetResolved : null;
|
|
329
|
+
}
|
|
330
|
+
function assertWithinRootLexical(candidate, root, label, opts = {}) {
|
|
331
|
+
const contained = tryWithinRootLexical(candidate, root, opts);
|
|
332
|
+
if (contained === null) {
|
|
333
|
+
throw new Error(`${label || 'Path'} validation failed: lexical containment check failed`);
|
|
334
|
+
}
|
|
335
|
+
return contained;
|
|
336
|
+
}
|
|
218
337
|
// ─── Prompt Injection Detection ────────────────────────────────────────────────────
|
|
219
338
|
/**
|
|
220
339
|
* Patterns that indicate prompt injection attempts in user-supplied text.
|
|
@@ -242,6 +242,8 @@ const MANAGED_HOOK_BASENAMES_BY_SURFACE = {
|
|
|
242
242
|
'gsd-write-guard.js',
|
|
243
243
|
'gsd-agent-isolation-guard.js',
|
|
244
244
|
'gsd-worktree-path-guard.js',
|
|
245
|
+
// #4221: secret-file read guard (Read|Grep|Bash).
|
|
246
|
+
'gsd-secret-read-guard.js',
|
|
245
247
|
]),
|
|
246
248
|
'codex-toml': new Set([
|
|
247
249
|
'gsd-check-update.js',
|
|
@@ -269,6 +271,8 @@ const MANAGED_HOOK_COMMAND_BASENAMES_BY_SURFACE = {
|
|
|
269
271
|
'gsd-write-guard.js',
|
|
270
272
|
'gsd-agent-isolation-guard.js',
|
|
271
273
|
'gsd-worktree-path-guard.js',
|
|
274
|
+
// #4221: secret-file read guard (Read|Grep|Bash).
|
|
275
|
+
'gsd-secret-read-guard.js',
|
|
272
276
|
]),
|
|
273
277
|
'codex-toml': new Set([
|
|
274
278
|
'gsd-check-update.js',
|
|
@@ -64,7 +64,7 @@ const stateDocument = require("./state-document.cjs");
|
|
|
64
64
|
const { stateFieldValue } = stateDocument;
|
|
65
65
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
66
66
|
const phaseId = require("./phase-id.cjs");
|
|
67
|
-
const { comparePhaseNum, extractPhaseToken, matchPhaseDirs, normalizePhaseName, stripProjectCodePrefix } = phaseId;
|
|
67
|
+
const { comparePhaseNum, extractPhaseToken, matchPhaseDirs, normalizePhaseName, parsePhaseFromProse, stripProjectCodePrefix } = phaseId;
|
|
68
68
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
69
69
|
const stateMod = require("./state.cjs");
|
|
70
70
|
const { readStateHeadFreshness } = stateMod;
|
|
@@ -233,10 +233,7 @@ function readGitSignals(cwd) {
|
|
|
233
233
|
}
|
|
234
234
|
/** Leading numeric phase token from a STATE.md scalar or body `Phase:` value. */
|
|
235
235
|
function phaseTokenFromState(raw) {
|
|
236
|
-
|
|
237
|
-
return null;
|
|
238
|
-
const match = raw.trim().match(/^(\d+(?:[A-Z])?(?:\.\d+)*)/i);
|
|
239
|
-
return match ? match[1] : null;
|
|
236
|
+
return parsePhaseFromProse(raw).phase;
|
|
240
237
|
}
|
|
241
238
|
/**
|
|
242
239
|
* Detect whether the current phase's verify report indicates failure. The
|
|
@@ -429,7 +426,7 @@ function detectSignals(cwd, now = Date.now) {
|
|
|
429
426
|
const stateHeadRaw = stateFieldValue(fm, body, 'state_head', 'State Head').value;
|
|
430
427
|
const freshness = readStateHeadFreshness(cwd, stateHeadRaw);
|
|
431
428
|
return {
|
|
432
|
-
current_phase:
|
|
429
|
+
current_phase: phaseTokenFromState(currentPhaseRaw),
|
|
433
430
|
total_phases: parseIntOrNull(totalPhasesRaw),
|
|
434
431
|
status: (statusRaw || '').toLowerCase(),
|
|
435
432
|
progress: parseIntOrNull(progressRaw),
|
|
@@ -472,11 +469,12 @@ function isComplete(s) {
|
|
|
472
469
|
return false;
|
|
473
470
|
}
|
|
474
471
|
else {
|
|
475
|
-
// Legacy path: STATE.md comparison.
|
|
476
|
-
//
|
|
472
|
+
// Legacy path: STATE.md comparison. It only fires when ROADMAP.md is
|
|
473
|
+
// absent or has no Progress table, and uses canonical phase-id ordering
|
|
474
|
+
// so dotted phase ids are never coerced to JavaScript numbers.
|
|
477
475
|
if (s.total_phases === null || s.current_phase === null)
|
|
478
476
|
return false;
|
|
479
|
-
if (s.current_phase
|
|
477
|
+
if (comparePhaseNum(s.current_phase, s.total_phases) < 0)
|
|
480
478
|
return false;
|
|
481
479
|
}
|
|
482
480
|
// Status regex: require milestone-level completion language. The pre-fix
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* from the prior hand-written .cjs; only types are added.
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.KNOWN_STATUS_PATTERNS = exports.KNOWN_TEMPLATE_DEFAULTS = void 0;
|
|
11
|
+
exports.KNOWN_STATUS_PATTERNS = exports.KNOWN_TEMPLATE_DEFAULTS = exports.STATUS_ANCHORED_PATTERNS = exports.STATUS_EXACT_TOKENS = void 0;
|
|
12
12
|
exports.toFiniteNumber = toFiniteNumber;
|
|
13
13
|
exports.isUnfilledFieldValue = isUnfilledFieldValue;
|
|
14
14
|
exports.leadingCalendarDate = leadingCalendarDate;
|
|
@@ -364,7 +364,7 @@ function stateFieldContinuation(content, fieldName) {
|
|
|
364
364
|
// this locates exactly the line whose value it returned. The pipe-table rung
|
|
365
365
|
// is deliberately absent: a `| Field | value |` row is bounded by its closing
|
|
366
366
|
// pipe and cannot wrap.
|
|
367
|
-
const match = new RegExp(
|
|
367
|
+
const match = new RegExp(`^[ \\t]*\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'im').exec(content) ??
|
|
368
368
|
new RegExp(`^${escaped}:[ \\t]*(.+)`, 'im').exec(content);
|
|
369
369
|
if (!match)
|
|
370
370
|
return null;
|
|
@@ -396,8 +396,9 @@ function stateFieldContinuation(content, fieldName) {
|
|
|
396
396
|
}
|
|
397
397
|
function stateExtractField(content, fieldName) {
|
|
398
398
|
const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
|
|
399
|
-
// Bold
|
|
400
|
-
|
|
399
|
+
// Bold line-start format: **FieldName:** value. Leading same-line whitespace
|
|
400
|
+
// matches the writer's established indented-field tolerance.
|
|
401
|
+
const boldPattern = new RegExp(`^[ \\t]*\\*\\*${escaped}:\\*\\*[ \\t]*(.+)`, 'im');
|
|
401
402
|
const boldMatch = content.match(boldPattern);
|
|
402
403
|
if (boldMatch)
|
|
403
404
|
return boldMatch[1].trim();
|
|
@@ -512,17 +513,57 @@ function stateCurrentPositionSlice(body) {
|
|
|
512
513
|
const section = (0, markdown_sectionizer_cjs_1.collectSection)(body, isCurrentPosition, { levelBounded: true });
|
|
513
514
|
return section ? section.body : null;
|
|
514
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* Join a matched `**Field:**`/`Field:` label prefix to its new value, inserting a
|
|
518
|
+
* single space when the prefix does not already end in same-line whitespace.
|
|
519
|
+
*
|
|
520
|
+
* On an empty field the same-line `[ \t]*` gap (below) captures nothing, so the
|
|
521
|
+
* bare `prefix + value` would glue the value to the label (`**Status:**value`);
|
|
522
|
+
* this inserts the missing separator. A non-empty field whose label-to-value
|
|
523
|
+
* separator is ordinary space/tab keeps that separator in the prefix, so `[ \t]$`
|
|
524
|
+
* is true and the output stays byte-identical to prior behaviour. The one
|
|
525
|
+
* exception is a non-empty field written with NO separator at all (a hand-edited
|
|
526
|
+
* `**Status:**value`): the narrowed gap captures nothing, `[ \t]$` is false, and a
|
|
527
|
+
* single space is inserted — an intentional normalization, not byte-identical, and
|
|
528
|
+
* with no GSD-template trigger. An empty new value inserts no separator, avoiding a
|
|
529
|
+
* dangling trailing space. See #4010.
|
|
530
|
+
*/
|
|
531
|
+
function joinFieldReplacement(prefix, newValue) {
|
|
532
|
+
const value = `${newValue}`;
|
|
533
|
+
const needsSeparator = value.length > 0 && !/[ \t]$/.test(prefix);
|
|
534
|
+
return `${prefix}${needsSeparator ? ' ' : ''}${value}`;
|
|
535
|
+
}
|
|
515
536
|
function stateReplaceField(content, fieldName, newValue) {
|
|
516
537
|
const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
|
|
517
538
|
// Bold inline format: **FieldName:** value
|
|
518
|
-
|
|
539
|
+
// The label-to-value gap is same-line whitespace only (`[ \t]*`, mirroring the
|
|
540
|
+
// read side at stateExtractField). `\s*` here matched `\n`, so on an empty field
|
|
541
|
+
// `(.*)` captured the following line and the rebuild discarded it — the #4010
|
|
542
|
+
// data-loss. ADR-3180 §7.7 makes stateExtractField the same-line-confined owner;
|
|
543
|
+
// this aligns the writer to it.
|
|
544
|
+
//
|
|
545
|
+
// #4243: the bold form is also ANCHORED to line start, with same-line leading
|
|
546
|
+
// whitespace only. The pre-fix pattern carried no `^` and no `m` flag, so a
|
|
547
|
+
// bold label quoted MID-SENTENCE inside prose — an Accumulated Context bullet
|
|
548
|
+
// mentioning `**Status:**` — captured the rewrite and destroyed the rest of
|
|
549
|
+
// its line, silently, whenever a whole-body caller fed this function every
|
|
550
|
+
// section (beginPhaseCore's tryField, advancePlanCore's Status/Current Plan
|
|
551
|
+
// writes). The plain branch below was always line-anchored; only the bold
|
|
552
|
+
// branch lagged. Anchoring reuses #4010's same-line confinement idiom (the
|
|
553
|
+
// leading class is `[ \t]*`, deliberately NOT the `\s*` the issue suggested —
|
|
554
|
+
// `^\s*\*\*` can consume the newlines before the label into the match and
|
|
555
|
+
// drop them on rebuild) and #4186's recognition-by-anchoring discipline: a
|
|
556
|
+
// write target must BE the whole declared line shape, never a substring
|
|
557
|
+
// guess inside prose. `$` is explicit-and-inert (`.` never crosses line
|
|
558
|
+
// terminators) and documents that the match ends at end-of-line.
|
|
559
|
+
const boldPattern = new RegExp(`^([ \\t]*\\*\\*${escaped}:\\*\\*[ \\t]*)(.*)$`, 'im');
|
|
519
560
|
if (boldPattern.test(content)) {
|
|
520
|
-
return content.replace(boldPattern, (_match, prefix) =>
|
|
561
|
+
return content.replace(boldPattern, (_match, prefix) => joinFieldReplacement(prefix, newValue));
|
|
521
562
|
}
|
|
522
|
-
// Plain line-start format: FieldName: value
|
|
523
|
-
const plainPattern = new RegExp(`(^${escaped}
|
|
563
|
+
// Plain line-start format: FieldName: value (same same-line confinement as above)
|
|
564
|
+
const plainPattern = new RegExp(`(^${escaped}:[ \\t]*)(.*)`, 'im');
|
|
524
565
|
if (plainPattern.test(content)) {
|
|
525
|
-
return content.replace(plainPattern, (_match, prefix) =>
|
|
566
|
+
return content.replace(plainPattern, (_match, prefix) => joinFieldReplacement(prefix, newValue));
|
|
526
567
|
}
|
|
527
568
|
// Pipe-table format: | FieldName | value |
|
|
528
569
|
// Preserve the surrounding pipe/whitespace structure; only swap the value cell.
|
|
@@ -570,31 +611,117 @@ function stateReplaceFieldInSession(content, primary, fallback, value) {
|
|
|
570
611
|
const target = hasCanonicalSession ? isSession : isSessionContinuity;
|
|
571
612
|
return (0, markdown_sectionizer_cjs_1.withSection)(content, target, (sectionBody) => stateReplaceFieldWithFallback(sectionBody, primary, fallback, value));
|
|
572
613
|
}
|
|
614
|
+
/**
|
|
615
|
+
* #4186: the DECLARED raw-status vocabulary `normalizeStateStatus` recognizes.
|
|
616
|
+
* Keys are whole-field values, compared against the caller's input after
|
|
617
|
+
* lowercasing, trimming, and collapsing internal whitespace runs to single
|
|
618
|
+
* spaces — so case and whitespace variants the vocabulary documents
|
|
619
|
+
* (`EXECUTING PHASE 5`, ` Paused `, `In progress`) keep normalizing.
|
|
620
|
+
* Values are members of `STATUS_LIFECYCLE_ENUM` (`src/state-md-schema.cts`)
|
|
621
|
+
* — the set the normalizer maps recognized input ONTO.
|
|
622
|
+
*
|
|
623
|
+
* The mapping preserves the PRE-#4186 branch ORDER's observable artifacts for
|
|
624
|
+
* every value the old substring chain recognized: `Planning complete` →
|
|
625
|
+
* `planning` (the `planning` branch outranked `complete`) and
|
|
626
|
+
* `Phase complete — ready for verification` → `verifying` (`verif` outranked
|
|
627
|
+
* `complete`; pinned by tests/state.test.cjs's advance-plan case-5 comment).
|
|
628
|
+
*
|
|
629
|
+
* Everything else — prose that merely CONTAINS a status word — falls through
|
|
630
|
+
* to the caller's raw value (the recorded lenient fallback, #3873 phase-3
|
|
631
|
+
* row 26). A token guessed from a substring inside a sentence is worse than
|
|
632
|
+
* a visible paragraph: the paragraph is visibly prose, the wrong token is
|
|
633
|
+
* not (a `.planning/` path in Italian prose silently produced
|
|
634
|
+
* `status: planning`; `verificata` produced `verifying`; `completezza`
|
|
635
|
+
* produced `completed`).
|
|
636
|
+
*/
|
|
637
|
+
exports.STATUS_EXACT_TOKENS = Object.freeze({
|
|
638
|
+
paused: 'paused',
|
|
639
|
+
stopped: 'paused',
|
|
640
|
+
executing: 'executing',
|
|
641
|
+
'in progress': 'executing',
|
|
642
|
+
'ready to execute': 'executing',
|
|
643
|
+
planning: 'planning',
|
|
644
|
+
'ready to plan': 'planning',
|
|
645
|
+
'planning complete': 'planning',
|
|
646
|
+
discussing: 'discussing',
|
|
647
|
+
verifying: 'verifying',
|
|
648
|
+
completed: 'completed',
|
|
649
|
+
done: 'completed',
|
|
650
|
+
complete: 'completed',
|
|
651
|
+
'phase complete': 'completed',
|
|
652
|
+
// advance-plan's phase-complete write (state-transition.cts:1812) — maps
|
|
653
|
+
// to `verifying`, preserving the pre-#4186 branch order where `verif`
|
|
654
|
+
// outranked `complete`.
|
|
655
|
+
'phase complete — ready for verification': 'verifying',
|
|
656
|
+
'all phases complete': 'completed',
|
|
657
|
+
// Legacy bare terminal form. ADR-2207/#2204 removed it from every WRITER
|
|
658
|
+
// (phase verbs write `All phases complete`; milestone close writes
|
|
659
|
+
// `<version> milestone complete`) — kept here as READER recognition so a
|
|
660
|
+
// legacy STATE.md still normalizes, exactly the way KNOWN_TEMPLATE_DEFAULTS
|
|
661
|
+
// keeps the other legacy Status strings.
|
|
662
|
+
'milestone complete': 'completed',
|
|
663
|
+
unknown: 'unknown',
|
|
664
|
+
});
|
|
665
|
+
/**
|
|
666
|
+
* #4186: ANCHORED patterns for handler-written raw statuses whose text
|
|
667
|
+
* carries a variable component (a phase number, a milestone version, a
|
|
668
|
+
* #1070 completion glyph). Each pattern is matched against the same
|
|
669
|
+
* normalized key as `STATUS_EXACT_TOKENS` (lowercased, trimmed,
|
|
670
|
+
* whitespace-collapsed) and must match the WHOLE value — never a substring —
|
|
671
|
+
* mirroring how `KNOWN_STATUS_PATTERNS` anchors its template-default checks.
|
|
672
|
+
* `Executing Phase 5 — final stretch` (executor-appended prose) matches
|
|
673
|
+
* NOTHING and passes through verbatim, the same discipline #1070 applies to
|
|
674
|
+
* "Complete but needs manual QA".
|
|
675
|
+
*/
|
|
676
|
+
exports.STATUS_ANCHORED_PATTERNS = Object.freeze([
|
|
677
|
+
// begin-phase / planned transitions write `Executing Phase ${N}`.
|
|
678
|
+
[/^executing phase\s+\S+$/, 'executing'],
|
|
679
|
+
[/^planning phase\s+\S+$/, 'planning'],
|
|
680
|
+
[/^verifying phase\s+\S+$/, 'verifying'],
|
|
681
|
+
// phase-complete verbs write `Phase ${N} complete` (state.cts) — the exact
|
|
682
|
+
// shape the #3578 demote guard then re-checks against the disk counters.
|
|
683
|
+
[/^phase\s+\S+\s+complete$/, 'completed'],
|
|
684
|
+
// milestoneCompleteCore writes `${version} milestone complete` (terminal,
|
|
685
|
+
// ADR-2207); the version label is a single token (milestone.cts's charset
|
|
686
|
+
// validation admits letters, digits, '.', '-', '_').
|
|
687
|
+
[/^\S+\s+milestone complete$/, 'completed'],
|
|
688
|
+
// #1070: LLM executors may write "Complete ✓" or bare "Complete" when
|
|
689
|
+
// finishing a phase.
|
|
690
|
+
[/^complete\s*[✓✔✅☑]?$/, 'completed'],
|
|
691
|
+
]);
|
|
692
|
+
/**
|
|
693
|
+
* Normalize a raw `Status` body-field value to the canonical status token.
|
|
694
|
+
*
|
|
695
|
+
* #4186: recognition is ANCHORED — the whole field value (lowercased,
|
|
696
|
+
* trimmed, whitespace-collapsed) must be a member of the declared vocabulary
|
|
697
|
+
* (`STATUS_EXACT_TOKENS` / `STATUS_ANCHORED_PATTERNS` above). The pre-#4186
|
|
698
|
+
* implementation ran a first-match-wins chain of SUBSTRING tests over the
|
|
699
|
+
* free-prose field, so any prose merely CONTAINING a trigger word was
|
|
700
|
+
* silently rewritten to a credible wrong token: a `.planning/...` path
|
|
701
|
+
* mentioned in a non-English status line landed on `planning` (the trigger
|
|
702
|
+
* word lives in the directory name and outranked the `verif`/`complete`
|
|
703
|
+
* branches), Italian `verifica*` landed on `verifying`, `completezza` and
|
|
704
|
+
* `fasi complete` landed on `completed`. The lenient FALLBACK is unchanged
|
|
705
|
+
* and recorded (#3873 phase-3 row 26): an unrecognized value passes through
|
|
706
|
+
* verbatim — visible prose, never a guessed token.
|
|
707
|
+
*
|
|
708
|
+
* `pausedAt` keeps its documented force (issue #4186: intended behavior): a
|
|
709
|
+
* truthy value yields `paused` regardless of the prose.
|
|
710
|
+
*/
|
|
573
711
|
function normalizeStateStatus(status, pausedAt) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
if (
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
else if (statusLower.includes('discussing')) {
|
|
586
|
-
normalizedStatus = 'discussing';
|
|
587
|
-
}
|
|
588
|
-
else if (statusLower.includes('verif')) {
|
|
589
|
-
normalizedStatus = 'verifying';
|
|
590
|
-
}
|
|
591
|
-
else if (statusLower.includes('complete') || statusLower.includes('done')) {
|
|
592
|
-
normalizedStatus = 'completed';
|
|
593
|
-
}
|
|
594
|
-
else if (statusLower.includes('ready to execute')) {
|
|
595
|
-
normalizedStatus = 'executing';
|
|
712
|
+
if (pausedAt)
|
|
713
|
+
return 'paused';
|
|
714
|
+
if (!status)
|
|
715
|
+
return 'unknown';
|
|
716
|
+
const key = status.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
717
|
+
const exact = exports.STATUS_EXACT_TOKENS[key];
|
|
718
|
+
if (exact)
|
|
719
|
+
return exact;
|
|
720
|
+
for (const [pattern, token] of exports.STATUS_ANCHORED_PATTERNS) {
|
|
721
|
+
if (pattern.test(key))
|
|
722
|
+
return token;
|
|
596
723
|
}
|
|
597
|
-
return
|
|
724
|
+
return status;
|
|
598
725
|
}
|
|
599
726
|
/**
|
|
600
727
|
* ADR-3180 §7.6 rule 4 (#3217): `scope` is the `listMilestonePhaseDirs`-owner
|
|
@@ -43,24 +43,31 @@ exports.STATE_FIELD_SCHEMA = exports.STATUS_LIFECYCLE_ENUM = void 0;
|
|
|
43
43
|
/**
|
|
44
44
|
* The seven CANONICAL values `normalizeStateStatus` (`src/state-document.cts`)
|
|
45
45
|
* maps recognized raw status prose TO — the function's default fallback plus
|
|
46
|
-
* each
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
46
|
+
* each vocabulary entry's literal output. This is NOT the raw body prose
|
|
47
|
+
* vocabulary `CONTEXT.md`'s "STATE.md Status Lifecycle (ADR-2207)" entry
|
|
48
|
+
* documents (`Ready to plan` → `All phases complete` → `<version> milestone
|
|
49
|
+
* complete` → `Awaiting next milestone`, plus the handler-authored strings
|
|
50
|
+
* in `KNOWN_TEMPLATE_DEFAULTS['Status']`) — that is free-form prose
|
|
51
|
+
* `normalizeStateStatus` READS.
|
|
52
52
|
*
|
|
53
53
|
* CORRECTED (#3873 phase-3 test-matrix row 26 — verified by executing
|
|
54
54
|
* `normalizeStateStatus`, not by reading this docstring's prior claim):
|
|
55
55
|
* this is NOT a closed set the `status` frontmatter key is restricted to at
|
|
56
|
-
* runtime. `normalizeStateStatus` is deliberately LENIENT: its fallback
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
56
|
+
* runtime. `normalizeStateStatus` is deliberately LENIENT: its fallback
|
|
57
|
+
* returns the caller's raw, UNRECOGNIZED prose unchanged — when none of its
|
|
58
|
+
* vocabulary entries recognize the whole-field input, that raw value is what
|
|
59
|
+
* the function returns. A status value outside this seven-member set is not
|
|
60
|
+
* rejected, coerced, or normalized; it passes straight through into the
|
|
61
|
+
* frontmatter. `STATUS_LIFECYCLE_ENUM` is therefore the set of values the
|
|
62
|
+
* normalizer maps recognized input ONTO, not a runtime-enforced closed
|
|
63
|
+
* vocabulary for the field.
|
|
64
|
+
*
|
|
65
|
+
* #4186: recognition is ANCHORED (whole-field match against the declared
|
|
66
|
+
* `STATUS_EXACT_TOKENS` / `STATUS_ANCHORED_PATTERNS` tables in
|
|
67
|
+
* `src/state-document.cts`), never a substring scan of the prose — prose
|
|
68
|
+
* merely CONTAINING a status word (a `.planning/` path, `verifica*`,
|
|
69
|
+
* `completezza`) passes through verbatim instead of being rewritten to a
|
|
70
|
+
* credible wrong token.
|
|
64
71
|
*/
|
|
65
72
|
exports.STATUS_LIFECYCLE_ENUM = Object.freeze([
|
|
66
73
|
'unknown',
|
|
@@ -128,19 +135,29 @@ exports.STATE_FIELD_SCHEMA = Object.freeze(Object.assign(Object.create(null), {
|
|
|
128
135
|
// (verified: it calls `stateExtractField(bodyContent, 'Current Plan')`
|
|
129
136
|
// only), so that shape is out of scope for this row regardless.
|
|
130
137
|
//
|
|
131
|
-
// #3784
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
|
|
138
|
+
// #3784/#3791 WIDENED THIS ROW. The paragraph above describes the
|
|
139
|
+
// pre-#3791 parser and is kept as the record of what the shape was
|
|
140
|
+
// before, because row 25 exists to stop exactly that reading from
|
|
141
|
+
// being re-asserted by accident.
|
|
142
|
+
//
|
|
143
|
+
// `advancePlanCore` now accepts the hybrid shape, so the declared set
|
|
144
|
+
// is `['N', 'N of M']`. Two properties of the widening matter to a
|
|
145
|
+
// future reader:
|
|
146
|
+
//
|
|
147
|
+
// - It is ANCHORED. The parser matches `/^(\d+)\s+of\s+(\d+)\s*$/`
|
|
148
|
+
// against the whole value, so `4 — blocked on review of 2 PRs`
|
|
149
|
+
// is REJECTED rather than yielding a total of 2 out of prose.
|
|
150
|
+
// Declaring `'N of M'` is a claim about that grammar, not about
|
|
151
|
+
// "contains the word of".
|
|
152
|
+
// - `'N/M'` stays UNDECLARED and must keep failing. Row 23 probes
|
|
153
|
+
// the undeclared remainder of `SHAPE_EXAMPLES`, so it needs at
|
|
154
|
+
// least one member outside the declared set to stay non-vacuous.
|
|
155
|
+
//
|
|
156
|
+
// Widening this row without widening the parser (or the reverse) goes
|
|
157
|
+
// RED on rows 23/24/25. That coupling is the forcing function, and it
|
|
158
|
+
// is the reason this row is data rather than a predicate: §8.8's
|
|
159
|
+
// "parsers are checked, not generated".
|
|
160
|
+
acceptedShapes: Object.freeze(['N', 'N of M']),
|
|
144
161
|
emitted: 'when-present',
|
|
145
162
|
},
|
|
146
163
|
// Status / lifecycle (body-derived; #1230 delta heuristic applies)
|