@opengsd/gsd-core 1.15.0 → 1.16.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 +85 -5
- package/agents/gsd-code-fixer.compact.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.compact.md +5 -3
- package/agents/gsd-code-reviewer.md +8 -6
- package/agents/gsd-debug-session-manager.compact.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +4 -4
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +2 -2
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +7 -2
- package/agents/gsd-planner.md +3 -3
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +21 -30
- package/agents/gsd-ui-auditor.md +42 -51
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +31 -9
- package/bin/install.js +126 -196
- package/commands/gsd/add-tests.md +6 -1
- package/commands/gsd/ai-integration-phase.md +6 -1
- package/commands/gsd/audit-fix.md +5 -0
- package/commands/gsd/audit-milestone.md +6 -1
- package/commands/gsd/autonomous.md +5 -0
- package/commands/gsd/capture.md +8 -4
- package/commands/gsd/code-review.md +7 -2
- package/commands/gsd/complete-milestone.md +4 -0
- package/commands/gsd/config.md +7 -3
- package/commands/gsd/debug.md +11 -7
- package/commands/gsd/discuss-phase.md +7 -3
- package/commands/gsd/docs-update.md +12 -7
- package/commands/gsd/eval-review.md +6 -1
- package/commands/gsd/execute-phase.md +12 -7
- package/commands/gsd/extract-learnings.md +5 -0
- package/commands/gsd/fast.md +4 -0
- package/commands/gsd/forensics.md +5 -1
- package/commands/gsd/graphify.md +10 -6
- package/commands/gsd/health.md +5 -0
- package/commands/gsd/help.md +7 -2
- package/commands/gsd/import.md +7 -3
- package/commands/gsd/inbox.md +5 -0
- package/commands/gsd/ingest-docs.md +5 -1
- package/commands/gsd/manager.md +6 -1
- package/commands/gsd/map-codebase.md +7 -3
- package/commands/gsd/mempalace-capture.md +5 -1
- package/commands/gsd/mempalace-recall.md +5 -1
- package/commands/gsd/milestone-summary.md +5 -1
- package/commands/gsd/mvp-phase.md +8 -3
- package/commands/gsd/new-milestone.md +6 -1
- package/commands/gsd/new-project.md +5 -0
- package/commands/gsd/next.md +6 -1
- package/commands/gsd/ns-context.md +4 -0
- package/commands/gsd/ns-ideate.md +4 -0
- package/commands/gsd/ns-manage.md +4 -0
- package/commands/gsd/ns-project.md +4 -0
- package/commands/gsd/ns-review.md +4 -0
- package/commands/gsd/ns-workflow.md +4 -0
- package/commands/gsd/onboard.md +6 -1
- package/commands/gsd/pause-work.md +5 -1
- package/commands/gsd/phase.md +8 -4
- package/commands/gsd/plan-phase.md +6 -1
- package/commands/gsd/plan-review-convergence.md +5 -1
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +6 -1
- package/commands/gsd/quick-batch.md +20 -8
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +5 -1
- package/commands/gsd/secure-phase.md +6 -1
- package/commands/gsd/ship.md +5 -0
- package/commands/gsd/sketch.md +7 -2
- package/commands/gsd/spec-phase.md +5 -1
- package/commands/gsd/spike.md +8 -3
- package/commands/gsd/surface.md +5 -1
- package/commands/gsd/thread.md +4 -0
- package/commands/gsd/ui-phase.md +6 -1
- package/commands/gsd/ui-review.md +6 -1
- package/commands/gsd/ultraplan-phase.md +5 -1
- package/commands/gsd/undo.md +5 -1
- package/commands/gsd/update.md +6 -2
- package/commands/gsd/validate-phase.md +6 -1
- package/commands/gsd/verify-work.md +6 -1
- package/commands/gsd/workspace.md +7 -3
- package/gsd-core/bin/gsd-tools.cjs +142 -56
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +78 -44
- package/gsd-core/bin/lib/broken-windows.cjs +13 -13
- package/gsd-core/bin/lib/capability-activation.cjs +9 -4
- package/gsd-core/bin/lib/capability-registry.cjs +183 -103
- package/gsd-core/bin/lib/capability-validator.cjs +16 -0
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
- package/gsd-core/bin/lib/cli-exit.cjs +12 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +11 -8
- package/gsd-core/bin/lib/command-aliases.cjs +7 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
- package/gsd-core/bin/lib/commands.cjs +173 -170
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config.cjs +43 -12
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/coverage.cjs +4 -8
- package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
- package/gsd-core/bin/lib/drift.cjs +177 -42
- package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
- package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
- package/gsd-core/bin/lib/frontmatter.cjs +413 -234
- package/gsd-core/bin/lib/gap-checker.cjs +72 -29
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
- package/gsd-core/bin/lib/gate-args.cjs +53 -0
- package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
- package/gsd-core/bin/lib/gate-config.cjs +46 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
- package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
- package/gsd-core/bin/lib/gate-exit.cjs +27 -0
- package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
- package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
- package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
- package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
- package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
- package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
- package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
- package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
- package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
- package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
- package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
- package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
- package/gsd-core/bin/lib/init.cjs +162 -96
- package/gsd-core/bin/lib/install-engine.cjs +19 -35
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
- package/gsd-core/bin/lib/milestone.cjs +19 -1
- package/gsd-core/bin/lib/model-resolver.cjs +18 -18
- package/gsd-core/bin/lib/observability/event.cjs +1 -1
- package/gsd-core/bin/lib/observability/logger.cjs +46 -1
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +11 -4
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id.cjs +1 -1
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +280 -80
- package/gsd-core/bin/lib/plan-document.cjs +93 -19
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +273 -40
- package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- package/gsd-core/bin/lib/profile-output.cjs +6 -3
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
- package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
- package/gsd-core/bin/lib/report-parser.cjs +269 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +13 -15
- package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
- package/gsd-core/bin/lib/roadmap.cjs +169 -72
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +91 -157
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +307 -591
- package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
- package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
- package/gsd-core/bin/lib/smart-entry.cjs +19 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
- package/gsd-core/bin/lib/state-contract.cjs +15 -18
- package/gsd-core/bin/lib/state-document.cjs +100 -22
- package/gsd-core/bin/lib/state.cjs +214 -105
- package/gsd-core/bin/lib/surface.cjs +2 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
- package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
- package/gsd-core/bin/lib/uat.cjs +424 -7
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
- package/gsd-core/bin/lib/vendor/README.md +31 -9
- package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
- package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +1165 -314
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +286 -567
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
- package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
- package/gsd-core/bin/lib/workstream.cjs +11 -2
- package/gsd-core/bin/shared/config-defaults.manifest.json +5 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +4 -0
- package/gsd-core/references/autonomous-smart-discuss.md +2 -1
- package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
- package/gsd-core/references/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/planning-config.md +1 -1
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +10 -4
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/UAT.md +17 -1
- package/gsd-core/templates/config.json +2 -11
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +8 -7
- package/gsd-core/workflows/add-tests.md +3 -2
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +3 -2
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +4 -3
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous.md +28 -18
- package/gsd-core/workflows/check-todos.md +6 -5
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +5 -4
- package/gsd-core/workflows/code-review.md +84 -90
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +12 -7
- package/gsd-core/workflows/debug.md +30 -5
- package/gsd-core/workflows/diagnose-issues.md +3 -2
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +4 -3
- package/gsd-core/workflows/do.md +1 -1
- package/gsd-core/workflows/docs-update.md +4 -3
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +5 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
- package/gsd-core/workflows/execute-phase.md +78 -170
- package/gsd-core/workflows/execute-plan.md +13 -19
- package/gsd-core/workflows/explore.md +2 -2
- package/gsd-core/workflows/extract-learnings.md +3 -2
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +3 -2
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +4 -3
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +5 -3
- package/gsd-core/workflows/map-codebase.md +4 -3
- package/gsd-core/workflows/milestone-summary.md +3 -2
- package/gsd-core/workflows/mvp-phase.md +14 -14
- package/gsd-core/workflows/new-milestone.md +10 -10
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/pause-work.md +2 -2
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +1 -1
- package/gsd-core/workflows/plan-phase.md +27 -18
- package/gsd-core/workflows/plan-review-convergence.md +1 -1
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
- package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +15 -10
- package/gsd-core/workflows/quick.md +42 -31
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +3 -2
- package/gsd-core/workflows/review.md +3 -2
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +11 -12
- package/gsd-core/workflows/settings-advanced.md +1 -1
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +12 -12
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +1 -1
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +5 -4
- package/gsd-core/workflows/ui-review.md +4 -3
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/validate-phase.md +10 -12
- package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
- package/gsd-core/workflows/verify-work.md +68 -59
- package/hooks/dist/gsd-agent-isolation-guard.js +8 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +8 -0
- package/hooks/dist/gsd-context-monitor.js +31 -8
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +147 -16
- package/hooks/dist/gsd-statusline.js +15 -7
- package/hooks/dist/gsd-update-banner.js +8 -0
- package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
- package/hooks/dist/gsd-workflow-guard.js +5 -4
- package/hooks/dist/gsd-worktree-path-guard.js +6 -2
- package/hooks/dist/lib/cli-exit.js +12 -0
- package/hooks/dist/lib/git-probe.js +17 -1
- package/hooks/dist/lib/isolation-sentinel.js +2 -2
- package/hooks/gsd-agent-isolation-guard.js +8 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +8 -0
- package/hooks/gsd-context-monitor.js +31 -8
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +147 -16
- package/hooks/gsd-statusline.js +15 -7
- package/hooks/gsd-update-banner.js +8 -0
- package/hooks/gsd-windsurf-pre-write.js +11 -2
- package/hooks/gsd-workflow-guard.js +5 -4
- package/hooks/gsd-worktree-path-guard.js +6 -2
- package/hooks/hooks.json +5 -5
- package/hooks/lib/cli-exit.js +12 -0
- package/hooks/lib/git-probe.js +17 -1
- package/hooks/lib/isolation-sentinel.js +2 -2
- package/package.json +21 -4
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +12 -8
- package/scripts/docs-guard-registry.cjs +6 -0
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +10 -1
- package/scripts/gen-platform-conformance-tier.cjs +187 -1
- package/scripts/gen-plugin-skills.cjs +87 -1
- package/scripts/gen-research-agents.cjs +24 -31
- package/scripts/gen-scripts-cli-exit.cjs +30 -3
- package/scripts/gen-test-timings.cjs +32 -7
- package/scripts/lib/cli-exit.cjs +12 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +20 -2
- package/scripts/lib/ndjson-reporter.cjs +28 -3
- package/scripts/lib/platform-conformance-tier.generated.cjs +34 -5
- package/scripts/lib/registration-ledger-preload.cjs +155 -0
- package/scripts/lib/vendor-bundle.cjs +59 -0
- package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
- 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-completion-predicate-drift.cjs +18 -19
- package/scripts/lint-descriptions.cjs +7 -3
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +10 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
- package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
- package/scripts/lint-phase-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +29 -0
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-test-file-count.allowlist.json +11 -1
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
- package/scripts/mutation-matrix.cjs +50 -5
- package/scripts/require-issue-link-policy.cjs +6 -2
- package/scripts/sync-runtime-launcher.cjs +184 -2
- package/scripts/verify-npm-publish.cjs +76 -20
- package/skills/gsd-add-tests/SKILL.md +6 -1
- package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
- package/skills/gsd-audit-fix/SKILL.md +5 -0
- package/skills/gsd-audit-milestone/SKILL.md +6 -1
- package/skills/gsd-autonomous/SKILL.md +5 -0
- package/skills/gsd-capture/SKILL.md +8 -4
- package/skills/gsd-code-review/SKILL.md +7 -2
- package/skills/gsd-complete-milestone/SKILL.md +4 -0
- package/skills/gsd-config/SKILL.md +8 -4
- package/skills/gsd-debug/SKILL.md +11 -7
- package/skills/gsd-discuss-phase/SKILL.md +7 -3
- package/skills/gsd-docs-update/SKILL.md +12 -7
- package/skills/gsd-eval-review/SKILL.md +6 -1
- package/skills/gsd-execute-phase/SKILL.md +12 -7
- package/skills/gsd-extract-learnings/SKILL.md +5 -0
- package/skills/gsd-fast/SKILL.md +4 -0
- package/skills/gsd-forensics/SKILL.md +5 -1
- package/skills/gsd-graphify/SKILL.md +10 -6
- package/skills/gsd-health/SKILL.md +5 -0
- package/skills/gsd-help/SKILL.md +7 -2
- package/skills/gsd-import/SKILL.md +7 -3
- package/skills/gsd-inbox/SKILL.md +5 -0
- package/skills/gsd-ingest-docs/SKILL.md +5 -1
- package/skills/gsd-manager/SKILL.md +6 -1
- package/skills/gsd-map-codebase/SKILL.md +7 -3
- package/skills/gsd-mempalace-capture/SKILL.md +5 -1
- package/skills/gsd-mempalace-recall/SKILL.md +5 -1
- package/skills/gsd-milestone-summary/SKILL.md +5 -1
- package/skills/gsd-mvp-phase/SKILL.md +8 -3
- package/skills/gsd-new-milestone/SKILL.md +6 -1
- package/skills/gsd-new-project/SKILL.md +5 -0
- package/skills/gsd-next/SKILL.md +6 -1
- package/skills/gsd-ns-context/SKILL.md +4 -0
- package/skills/gsd-ns-ideate/SKILL.md +4 -0
- package/skills/gsd-ns-manage/SKILL.md +4 -0
- package/skills/gsd-ns-project/SKILL.md +4 -0
- package/skills/gsd-ns-review/SKILL.md +4 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -0
- package/skills/gsd-onboard/SKILL.md +6 -1
- package/skills/gsd-pause-work/SKILL.md +5 -1
- package/skills/gsd-phase/SKILL.md +8 -4
- package/skills/gsd-plan-phase/SKILL.md +6 -1
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -1
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +6 -1
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +20 -8
- package/skills/gsd-review/SKILL.md +5 -1
- package/skills/gsd-review-backlog/SKILL.md +3 -2
- package/skills/gsd-secure-phase/SKILL.md +6 -1
- package/skills/gsd-ship/SKILL.md +5 -0
- package/skills/gsd-sketch/SKILL.md +7 -2
- package/skills/gsd-spec-phase/SKILL.md +5 -1
- package/skills/gsd-spike/SKILL.md +8 -3
- package/skills/gsd-surface/SKILL.md +5 -1
- package/skills/gsd-thread/SKILL.md +4 -0
- package/skills/gsd-ui-phase/SKILL.md +6 -1
- package/skills/gsd-ui-review/SKILL.md +6 -1
- package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
- package/skills/gsd-undo/SKILL.md +5 -1
- package/skills/gsd-update/SKILL.md +6 -2
- package/skills/gsd-validate-phase/SKILL.md +6 -1
- package/skills/gsd-verify-work/SKILL.md +6 -1
- package/skills/gsd-workspace/SKILL.md +7 -3
- package/skills/gsd-workstreams/SKILL.md +6 -6
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +0 -24
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the legacy `## Task N` heading fallback — including the optional `tracker-id`
|
|
7
7
|
* attribute, ADR-3646 Phase 1, read verbatim and never split here), planned-file
|
|
8
8
|
* extraction, and the frontmatter-derived scheduling metadata (`wave`,
|
|
9
|
-
* `depends_on`, `autonomous`, `agent_hint`, `files_modified`).
|
|
9
|
+
* `depends_on`, `autonomous`, `agent_hint`, `files_modified`, `gap_closure`).
|
|
10
10
|
*
|
|
11
11
|
* WHY THIS IS A LEAF MODULE. This logic was written inline inside
|
|
12
12
|
* `cmdPhasePlanIndex` (`src/phase.cts`). Two commands in two different families
|
|
@@ -32,9 +32,23 @@
|
|
|
32
32
|
* ADR-457 build-at-publish: source in src/plan-document.cts, compiled to
|
|
33
33
|
* gsd-core/bin/lib/plan-document.cjs (gitignored).
|
|
34
34
|
*/
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
// #5026 / ADR-4910 §1 absorption: the 7 frontmatter-derived scheduling fields
|
|
36
|
+
// below, plus `objective` as an 8th (see `frontmatterField`'s own docblock),
|
|
37
|
+
// read through `planning-document.cts`'s seam
|
|
38
|
+
// (`readFrontmatterFieldsFromSource`) rather than calling `frontmatter.cts`'s
|
|
39
|
+
// `extractFrontmatter` directly. This module has no canonical `.planning/`-root
|
|
40
|
+
// artifact basename to gate `parsePlanningDoc` on (`*-PLAN.md` lives nested
|
|
41
|
+
// under `.planning/phase/*/plans/`, and two of the five real callers hold only
|
|
42
|
+
// in-memory content with no path at all), so it uses the entry point shaped
|
|
43
|
+
// for exactly that: content in hand, no filename, no other `PlanningDoc`
|
|
44
|
+
// capability (sections/tables/checklists) this module needs. The BULK form
|
|
45
|
+
// (`readFrontmatterFieldsFromSource`, not the single-key
|
|
46
|
+
// `readFrontmatterFieldFromSource`) is used deliberately: `parsePlanDocument`
|
|
47
|
+
// reads several keys off the SAME document, and the single-key entry point
|
|
48
|
+
// would independently re-detect the frontmatter span and re-parse the full
|
|
49
|
+
// YAML once per key. See both functions' docblocks in `planning-document.cts`
|
|
50
|
+
// for the full reasoning.
|
|
51
|
+
const planning_document_cjs_1 = require("./planning-document.cjs");
|
|
38
52
|
// ─── Frozen vocabularies ──────────────────────────────────────────────────────
|
|
39
53
|
/**
|
|
40
54
|
* How a task row was expressed in the document. `auto` is the ordinary
|
|
@@ -203,23 +217,80 @@ function extractObjective(content) {
|
|
|
203
217
|
function planIdFromFile(planFile) {
|
|
204
218
|
return planFile.replace('-PLAN.md', '').replace('PLAN.md', '');
|
|
205
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* The frontmatter keys `parsePlanDocument` reads: the 7 scheduling fields
|
|
222
|
+
* this absorption originally scoped (`wave`, `depends_on`, `autonomous`,
|
|
223
|
+
* `files_modified`/`files-modified`, `files_deleted`/`files-deleted`,
|
|
224
|
+
* `agent_hint`, `type` — 9 key spellings across those 7 fields), PLUS
|
|
225
|
+
* `objective` as an 8th field. `objective` is read through this SAME
|
|
226
|
+
* frontmatter-key-read path — not a deliberate exclusion, as an earlier
|
|
227
|
+
* revision of this comment claimed — because `parsePlanDocument`'s return
|
|
228
|
+
* statement below falls back to `frontmatterField(content, 'objective')`
|
|
229
|
+
* whenever the `<objective>` XML tag (`extractObjective`, unrelated and
|
|
230
|
+
* untouched by this absorption) is absent; it is included here simply
|
|
231
|
+
* because reading it is the exact same pattern as the other seven, and the
|
|
232
|
+
* migration below naturally covers it. `gap_closure` (#4924) is a 9th field,
|
|
233
|
+
* absorbed onto this same bulk-read seam by this fix rather than left on the
|
|
234
|
+
* removed `fm[key]` object-property read it was rebased in against.
|
|
235
|
+
*/
|
|
236
|
+
const FRONTMATTER_READ_KEYS = [
|
|
237
|
+
'wave',
|
|
238
|
+
'depends_on',
|
|
239
|
+
'autonomous',
|
|
240
|
+
'files_modified',
|
|
241
|
+
'files-modified',
|
|
242
|
+
'files_deleted',
|
|
243
|
+
'files-deleted',
|
|
244
|
+
'agent_hint',
|
|
245
|
+
'type',
|
|
246
|
+
'objective',
|
|
247
|
+
'gap_closure',
|
|
248
|
+
];
|
|
249
|
+
/**
|
|
250
|
+
* Read one top-level frontmatter key out of an already-bulk-read
|
|
251
|
+
* `Record<string, NodeRead>` (`readFrontmatterFieldsFromSource`'s return
|
|
252
|
+
* value, computed ONCE per `parsePlanDocument` call — see
|
|
253
|
+
* `FRONTMATTER_READ_KEYS`), unwrapped to the SAME shape a direct `fm[key]`
|
|
254
|
+
* object-property read would give: the field's value, or `undefined` when it
|
|
255
|
+
* is absent, the document has no frontmatter, or the frontmatter is
|
|
256
|
+
* unparseable — `readFrontmatterFieldsFromSource` reports all three of those
|
|
257
|
+
* as an `ok: false` result per key, and `extractFrontmatter`'s own object
|
|
258
|
+
* would likewise simply lack the key in every one of those cases.
|
|
259
|
+
*/
|
|
260
|
+
function frontmatterField(fields, key) {
|
|
261
|
+
const read = fields[key];
|
|
262
|
+
return read?.ok ? read.value : undefined;
|
|
263
|
+
}
|
|
206
264
|
/**
|
|
207
265
|
* Parse one plan document.
|
|
208
266
|
*
|
|
209
267
|
* @param content Raw `*-PLAN.md` text.
|
|
210
|
-
* @param planPath
|
|
211
|
-
* truncated-frontmatter diagnostic (#1882)
|
|
212
|
-
*
|
|
213
|
-
*
|
|
268
|
+
* @param planPath Historically the path passed to `extractFrontmatter`'s
|
|
269
|
+
* truncated-frontmatter diagnostic (#1882) — a stderr-only
|
|
270
|
+
* side channel, never part of this function's return value.
|
|
271
|
+
* #5026: the 9 frontmatter fields below (7 scheduling fields,
|
|
272
|
+
* plus `objective` and `gap_closure`) now read through
|
|
273
|
+
* `readFrontmatterFieldFromSource`, which has no path
|
|
274
|
+
* parameter, so this diagnostic's file-naming/dedup key is a
|
|
275
|
+
* documented, accepted side-effect-only regression (falls
|
|
276
|
+
* back to content-digest dedup, the same fallback
|
|
277
|
+
* `extractFrontmatter` already uses for the two real callers
|
|
278
|
+
* that never had a path to give it). Kept for call-site
|
|
279
|
+
* signature compatibility only.
|
|
214
280
|
*/
|
|
215
|
-
function parsePlanDocument(content,
|
|
216
|
-
const fm = extractFrontmatter(content, planPath);
|
|
281
|
+
function parsePlanDocument(content, _planPath = '') {
|
|
217
282
|
const xmlTasks = parseXmlTasks(content);
|
|
218
283
|
const tasks = xmlTasks.length > 0 ? xmlTasks : parseMarkdownTasks(content);
|
|
219
|
-
|
|
284
|
+
// Detect the frontmatter span and parse its YAML ONCE (#5026 follow-up: the
|
|
285
|
+
// prior per-key `frontmatterField(content, key)` calls each independently
|
|
286
|
+
// re-detected the span and re-parsed the full YAML from scratch — 10x
|
|
287
|
+
// redundant detection+parse per call). Every `frontmatterField` read below
|
|
288
|
+
// shares this SAME parsed result.
|
|
289
|
+
const fields = (0, planning_document_cjs_1.readFrontmatterFieldsFromSource)(content, FRONTMATTER_READ_KEYS);
|
|
290
|
+
const parsedWave = parseInt(frontmatterField(fields, 'wave'), 10);
|
|
220
291
|
const declaredWave = Number.isNaN(parsedWave) ? null : parsedWave;
|
|
221
292
|
let dependsOn = [];
|
|
222
|
-
const fmDeps =
|
|
293
|
+
const fmDeps = frontmatterField(fields, 'depends_on');
|
|
223
294
|
if (Array.isArray(fmDeps)) {
|
|
224
295
|
dependsOn = fmDeps.map(String);
|
|
225
296
|
}
|
|
@@ -227,37 +298,38 @@ function parsePlanDocument(content, planPath = '') {
|
|
|
227
298
|
dependsOn = [fmDeps];
|
|
228
299
|
}
|
|
229
300
|
let autonomous = true;
|
|
230
|
-
|
|
301
|
+
const fmAutonomous = frontmatterField(fields, 'autonomous');
|
|
302
|
+
if (fmAutonomous !== undefined) {
|
|
231
303
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue comparison
|
|
232
|
-
autonomous =
|
|
304
|
+
autonomous = fmAutonomous === 'true' || String(fmAutonomous) === 'true';
|
|
233
305
|
}
|
|
234
306
|
let filesModified = [];
|
|
235
|
-
const fmFiles =
|
|
307
|
+
const fmFiles = frontmatterField(fields, 'files_modified') || frontmatterField(fields, 'files-modified');
|
|
236
308
|
if (fmFiles) {
|
|
237
309
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
|
|
238
310
|
filesModified = Array.isArray(fmFiles) ? fmFiles.map(String) : [String(fmFiles)];
|
|
239
311
|
}
|
|
240
312
|
let filesDeleted = [];
|
|
241
|
-
const fmDeleted =
|
|
313
|
+
const fmDeleted = frontmatterField(fields, 'files_deleted') || frontmatterField(fields, 'files-deleted');
|
|
242
314
|
if (fmDeleted) {
|
|
243
315
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
|
|
244
316
|
filesDeleted = Array.isArray(fmDeleted) ? fmDeleted.map(String) : [String(fmDeleted)];
|
|
245
317
|
}
|
|
246
318
|
let agentHint = null;
|
|
247
|
-
const fmAgentHint =
|
|
319
|
+
const fmAgentHint = frontmatterField(fields, 'agent_hint');
|
|
248
320
|
if (fmAgentHint !== undefined) {
|
|
249
321
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
|
|
250
322
|
const hintStr = String(fmAgentHint).trim();
|
|
251
323
|
agentHint = hintStr !== '' ? hintStr : null;
|
|
252
324
|
}
|
|
253
325
|
let planType = null;
|
|
254
|
-
const fmType =
|
|
326
|
+
const fmType = frontmatterField(fields, 'type');
|
|
255
327
|
if (fmType !== undefined) {
|
|
256
328
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue scalar-to-string
|
|
257
329
|
planType = String(fmType);
|
|
258
330
|
}
|
|
259
331
|
return {
|
|
260
|
-
objective: extractObjective(content) ||
|
|
332
|
+
objective: extractObjective(content) || frontmatterField(fields, 'objective') || null,
|
|
261
333
|
type: planType,
|
|
262
334
|
declaredWave,
|
|
263
335
|
dependsOn,
|
|
@@ -265,6 +337,8 @@ function parsePlanDocument(content, planPath = '') {
|
|
|
265
337
|
agentHint,
|
|
266
338
|
filesModified,
|
|
267
339
|
filesDeleted,
|
|
340
|
+
// extractFrontmatter yields every scalar as a string, so YAML `true` is 'true'.
|
|
341
|
+
gapClosure: frontmatterField(fields, 'gap_closure') === 'true',
|
|
268
342
|
tasks,
|
|
269
343
|
taskCount: tasks.length,
|
|
270
344
|
};
|
|
@@ -137,6 +137,11 @@ const PHASE_STATUS_RANKS = Object.freeze({
|
|
|
137
137
|
'phase complete': 2,
|
|
138
138
|
// ROADMAP.md "## Progress" table Status column vocabulary
|
|
139
139
|
'not started': 0,
|
|
140
|
+
// #5060: `roadmap update-plan-progress`'s own written token (Phase Status
|
|
141
|
+
// Module's `toRoadmapStatusCell`) for a phase with plans but no summaries
|
|
142
|
+
// yet — rank 1, the same "work has started" rank as STATE's "ready to
|
|
143
|
+
// execute" / "in progress".
|
|
144
|
+
'planned': 1,
|
|
140
145
|
'complete': 2,
|
|
141
146
|
'deferred': 0,
|
|
142
147
|
});
|
|
@@ -34,16 +34,21 @@ exports.PLANNING_ARTIFACTS = void 0;
|
|
|
34
34
|
exports.parsePlanningDoc = parsePlanningDoc;
|
|
35
35
|
exports.findField = findField;
|
|
36
36
|
exports.readNode = readNode;
|
|
37
|
+
exports.readFrontmatterField = readFrontmatterField;
|
|
38
|
+
exports.readFrontmatterFieldFromSource = readFrontmatterFieldFromSource;
|
|
39
|
+
exports.readFrontmatterFieldsFromSource = readFrontmatterFieldsFromSource;
|
|
37
40
|
exports.setFieldValue = setFieldValue;
|
|
38
41
|
exports.hasUnreadableNodes = hasUnreadableNodes;
|
|
39
42
|
exports.serialize = serialize;
|
|
43
|
+
exports.replaceProse = replaceProse;
|
|
40
44
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
41
45
|
const markdown_table_cjs_1 = require("./markdown-table.cjs");
|
|
42
46
|
const artifacts_cjs_1 = require("./artifacts.cjs");
|
|
43
47
|
// `frontmatter.cts` uses `export =` (CJS-style single export object), so it
|
|
44
48
|
// is imported as a default import (esModuleInterop), not a named import.
|
|
45
49
|
const frontmatter_cjs_1 = __importDefault(require("./frontmatter.cjs"));
|
|
46
|
-
const
|
|
50
|
+
const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
|
|
51
|
+
const { extractFrontmatter, FRONTMATTER_UNPARSEABLE } = frontmatter_cjs_1.default;
|
|
47
52
|
/**
|
|
48
53
|
* Canonical `.planning/` root artifact basenames this seam recognises,
|
|
49
54
|
* derived from the SAME registry `isCanonicalPlanningFile` consults
|
|
@@ -79,46 +84,28 @@ function splitLinesInfo(source) {
|
|
|
79
84
|
return out;
|
|
80
85
|
}
|
|
81
86
|
/**
|
|
82
|
-
* Locate the frontmatter block, if any,
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* re-derives it (ADR-4910 Decision 1).
|
|
87
|
+
* Locate the frontmatter block, if any, from `locateFrontmatterFence` — the
|
|
88
|
+
* one owner of the fence grammar (byte-0 rule, BOM tolerance, whole-line
|
|
89
|
+
* closing fence, an adjacent empty block), which every frontmatter reader and
|
|
90
|
+
* writer also reads; this seam never re-derives it (ADR-4910 Decision 1).
|
|
86
91
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* does not re-scan for the fences themselves.
|
|
92
|
+
* The span is an absolute range into `source`, from the opening fence (after
|
|
93
|
+
* any BOM) through the closing fence line's text, plus that line's CR when it
|
|
94
|
+
* ends in CRLF. Found while implementing #5105: the previous adapter re-derived
|
|
95
|
+
* the closing fence's end from the YAML region's length and was one character
|
|
96
|
+
* long on an adjacent empty block (`---\n---\n`), which has no line of its own
|
|
97
|
+
* before the closer.
|
|
94
98
|
*/
|
|
95
99
|
function findFrontmatterSpan(source) {
|
|
96
|
-
const
|
|
97
|
-
if (!
|
|
100
|
+
const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(source);
|
|
101
|
+
if (!fence)
|
|
98
102
|
return null;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const bomDelta = source.length - found.content.length;
|
|
103
|
-
const content = found.content;
|
|
104
|
-
if (!found.terminated) {
|
|
105
|
-
return { span: { start: bomDelta, end: bomDelta + content.length }, terminated: false };
|
|
103
|
+
const start = fence.bom.length;
|
|
104
|
+
if (!fence.closed) {
|
|
105
|
+
return { span: { start, end: source.length }, terminated: false };
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// (`region`), not an absolute span, so recover the closing fence's end
|
|
110
|
-
// from `region`'s length. The one thing still read directly here is the
|
|
111
|
-
// opening fence's fixed-width line ending (`\n` vs `\r\n`), needed to
|
|
112
|
-
// translate `region`'s length into a `content` offset — not a re-scan for
|
|
113
|
-
// the fence itself.
|
|
114
|
-
const headerEnd = content.startsWith('---\r\n') ? 5 : 4;
|
|
115
|
-
const yamlEnd = headerEnd + found.region.length;
|
|
116
|
-
const closingLineStart = content[yamlEnd] === '\r' ? yamlEnd + 1 : yamlEnd;
|
|
117
|
-
const fenceLineStart = closingLineStart + 1;
|
|
118
|
-
let fenceEnd = fenceLineStart + 3;
|
|
119
|
-
if (content[fenceEnd] === '\r')
|
|
120
|
-
fenceEnd += 1;
|
|
121
|
-
return { span: { start: bomDelta, end: bomDelta + fenceEnd }, terminated: true };
|
|
107
|
+
const end = fence.closingFenceEnd + (source[fence.closingFenceEnd] === '\r' ? 1 : 0);
|
|
108
|
+
return { span: { start, end }, terminated: true };
|
|
122
109
|
}
|
|
123
110
|
/** Build the set of 0-based line indices that fall inside a fenced code
|
|
124
111
|
* block (opening/closing delimiter lines included), so `**Label:**`/table/
|
|
@@ -311,7 +298,10 @@ function parsePlanningDoc(source, artifact) {
|
|
|
311
298
|
frontmatterEnd = fm.span.end;
|
|
312
299
|
}
|
|
313
300
|
if (source.length === 0) {
|
|
314
|
-
return {
|
|
301
|
+
return {
|
|
302
|
+
ok: true,
|
|
303
|
+
value: { source, artifact, nodes: [], staged: new Map() },
|
|
304
|
+
};
|
|
315
305
|
}
|
|
316
306
|
const lines = splitLinesInfo(source);
|
|
317
307
|
// Sections: one per heading, in document order — every heading is its own
|
|
@@ -333,7 +323,10 @@ function parsePlanningDoc(source, artifact) {
|
|
|
333
323
|
}
|
|
334
324
|
nodes.push(...scanBodyNodes(source, lines, frontmatterEnd));
|
|
335
325
|
nodes.sort((a, b) => a.span.start - b.span.start);
|
|
336
|
-
return {
|
|
326
|
+
return {
|
|
327
|
+
ok: true,
|
|
328
|
+
value: { source, artifact, nodes, staged: new Map() },
|
|
329
|
+
};
|
|
337
330
|
}
|
|
338
331
|
/** Find the id of the (first, document-order) `boldField` node whose label
|
|
339
332
|
* exactly matches `label`, or `null` when none does. */
|
|
@@ -359,11 +352,206 @@ function readNode(doc, id) {
|
|
|
359
352
|
}
|
|
360
353
|
return { ok: true, value: doc.source.slice(node.span.start, node.span.end) };
|
|
361
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Parse a frontmatter block's own span TEXT (fences included) via
|
|
357
|
+
* `frontmatter.cts`'s `extractFrontmatter`, exactly ONCE, returning either the
|
|
358
|
+
* parsed object or a `{ ok: false }` marker for the `FRONTMATTER_UNPARSEABLE`
|
|
359
|
+
* case. Split out of the single-key lookup below (#5026 follow-up) so a
|
|
360
|
+
* caller reading MULTIPLE keys off the SAME region
|
|
361
|
+
* (`readFrontmatterFieldsFromSource`) detects the span and parses its YAML
|
|
362
|
+
* once, not once per key — this function is the one and only place that
|
|
363
|
+
* detect-then-parse step happens; every reader (single-key or bulk) composes
|
|
364
|
+
* it rather than re-deriving it.
|
|
365
|
+
*/
|
|
366
|
+
function parseFrontmatterRegion(regionText) {
|
|
367
|
+
// A CRLF block's span ends on its closing fence line's CR (`findFrontmatterSpan`). That CR is
|
|
368
|
+
// half a line ending, not part of the fence: a `---` line ended by a lone CR does not close a
|
|
369
|
+
// block, so the text is parsed without it.
|
|
370
|
+
const fm = extractFrontmatter(regionText.endsWith('\r') ? regionText.slice(0, -1) : regionText);
|
|
371
|
+
if (fm[FRONTMATTER_UNPARSEABLE] === true) {
|
|
372
|
+
return { ok: false };
|
|
373
|
+
}
|
|
374
|
+
return { ok: true, value: fm };
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Shared core of `readFrontmatterField` / `readFrontmatterFieldFromSource` /
|
|
378
|
+
* `readFrontmatterFieldsFromSource`: given an ALREADY-PARSED frontmatter
|
|
379
|
+
* result (`parseFrontmatterRegion`'s output) and the `Span` to report on
|
|
380
|
+
* failure, shape one `key`'s lookup as a `NodeRead`. Every reader composes
|
|
381
|
+
* this SAME shaping step — neither may duplicate it (the exact
|
|
382
|
+
* `DEFECT.GENERATIVE-FIX` class this epic exists to close).
|
|
383
|
+
*
|
|
384
|
+
* A region that failed to parse as YAML (`extractFrontmatter` reports this by
|
|
385
|
+
* returning `{}` carrying the `FRONTMATTER_UNPARSEABLE` Symbol, per that
|
|
386
|
+
* module's own documented contract) → `{ ok: false, reason:
|
|
387
|
+
* 'unparseable-frontmatter', span }`. A parseable region whose key is simply
|
|
388
|
+
* absent → `{ ok: false, reason: 'field-not-found', span }` —
|
|
389
|
+
* `extractFrontmatter` itself has no notion of "field not found" (a missing
|
|
390
|
+
* key just reads `undefined` off its returned object), so this reason string
|
|
391
|
+
* is this seam's own, not a passthrough of an upstream contract.
|
|
392
|
+
*/
|
|
393
|
+
function shapeFrontmatterField(parsed, span, key) {
|
|
394
|
+
if (!parsed.ok) {
|
|
395
|
+
return { ok: false, reason: 'unparseable-frontmatter', span };
|
|
396
|
+
}
|
|
397
|
+
const value = parsed.value[key];
|
|
398
|
+
if (value === undefined) {
|
|
399
|
+
return { ok: false, reason: 'field-not-found', span };
|
|
400
|
+
}
|
|
401
|
+
return { ok: true, value };
|
|
402
|
+
}
|
|
403
|
+
/** Single-key lookup: parse `regionText` once (`parseFrontmatterRegion`) and
|
|
404
|
+
* shape `key`'s result (`shapeFrontmatterField`). Used by `readFrontmatterField`
|
|
405
|
+
* and `readFrontmatterFieldFromSource`, which each already have the region's
|
|
406
|
+
* own text and span in hand; a caller reading several keys off one region
|
|
407
|
+
* should use `readFrontmatterFieldsFromSource` instead, to avoid re-parsing
|
|
408
|
+
* once per key. */
|
|
409
|
+
function lookupFrontmatterField(regionText, span, key) {
|
|
410
|
+
return shapeFrontmatterField(parseFrontmatterRegion(regionText), span, key);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Read one top-level frontmatter key off an already-parsed `PlanningDoc`,
|
|
414
|
+
* composing `frontmatter.cts`'s `extractFrontmatter` rather than
|
|
415
|
+
* reimplementing YAML parsing (ADR-4910 Decision 1's precedent — the same
|
|
416
|
+
* composition `frontmatterRegion` already uses).
|
|
417
|
+
*
|
|
418
|
+
* No `FrontmatterNode` in `doc.nodes` (there is at most one per document) →
|
|
419
|
+
* `{ ok: false, reason: 'no-frontmatter', span: {0,0} }`, mirroring
|
|
420
|
+
* `findField`'s own not-found span convention (`readNode`'s unknown-id case).
|
|
421
|
+
*
|
|
422
|
+
* A `FrontmatterNode` is only ever pushed onto `doc.nodes` when its fence was
|
|
423
|
+
* terminated (`parsePlanningDoc` fails the whole document, before any node
|
|
424
|
+
* exists, on an unterminated fence) — so the "opened but never closed" case
|
|
425
|
+
* `extractFrontmatter` also handles is never reachable from an already-parsed
|
|
426
|
+
* `PlanningDoc` through this function. See `lookupFrontmatterField` for the
|
|
427
|
+
* unparseable-frontmatter / field-not-found / present result shapes.
|
|
428
|
+
*/
|
|
429
|
+
function readFrontmatterField(doc, key) {
|
|
430
|
+
const node = doc.nodes.find((n) => n.kind === 'frontmatter');
|
|
431
|
+
if (!node) {
|
|
432
|
+
return { ok: false, reason: 'no-frontmatter', span: { start: 0, end: 0 } };
|
|
433
|
+
}
|
|
434
|
+
const raw = doc.source.slice(node.span.start, node.span.end);
|
|
435
|
+
return lookupFrontmatterField(raw, node.span, key);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Read one top-level frontmatter key directly off raw `source` text, with no
|
|
439
|
+
* `PlanningDoc`/artifact-kind gate involved (#5026). `parsePlanningDoc`'s
|
|
440
|
+
* artifact-kind gate exists to distinguish "this document records nothing"
|
|
441
|
+
* from "wrong kind entirely" for a caller that might hand it any
|
|
442
|
+
* `.planning/`-root file, including a non-markdown one (config.json,
|
|
443
|
+
* state.json, …) — a risk that does not exist for a caller (`plan-document.
|
|
444
|
+
* cts`) that is ONLY ever invoked on real `*-PLAN.md` content, never on
|
|
445
|
+
* anything else, and is never given a canonical root-artifact basename to
|
|
446
|
+
* gate on in the first place (`*-PLAN.md` lives nested under
|
|
447
|
+
* `.planning/phase/*\/plans/`, never at the `.planning/` root
|
|
448
|
+
* `PLANNING_ARTIFACTS` enumerates — confirmed by execution:
|
|
449
|
+
* `isCanonicalPlanningFile('01-PLAN.md')` is `false`). This entry point
|
|
450
|
+
* routes around that gate rather than through it, for exactly that caller
|
|
451
|
+
* shape: content in hand, no filename to check, no need for any other
|
|
452
|
+
* `PlanningDoc` capability (sections/tables/checklists) this seam offers.
|
|
453
|
+
*
|
|
454
|
+
* Locates the frontmatter span via `findFrontmatterSpan` — the SAME
|
|
455
|
+
* `locateFrontmatterFence`-reading helper `parsePlanningDoc` itself uses to
|
|
456
|
+
* build a `FrontmatterNode` — so this is not a second detection mechanism,
|
|
457
|
+
* only a bypass of the node-parsing pipeline neither this caller nor its
|
|
458
|
+
* content needs.
|
|
459
|
+
*
|
|
460
|
+
* No frontmatter fence at byte 0 at all → `{ ok: false, reason:
|
|
461
|
+
* 'no-frontmatter', span: {0,0} }`, the same shape `readFrontmatterField`
|
|
462
|
+
* returns for its own not-found case. An OPENED-but-never-closed fence is
|
|
463
|
+
* reachable here (unlike `readFrontmatterField`, which can only ever see an
|
|
464
|
+
* already-terminated frontmatter node): `extractFrontmatter` treats that
|
|
465
|
+
* region as if it had none (`{}`, no `FRONTMATTER_UNPARSEABLE` marker), so
|
|
466
|
+
* every key on it comes back `field-not-found` — matching exactly what a
|
|
467
|
+
* direct `extractFrontmatter(source)[key] === undefined` check already
|
|
468
|
+
* produces for that same input today. See `lookupFrontmatterField` for the
|
|
469
|
+
* unparseable-frontmatter / field-not-found / present result shapes.
|
|
470
|
+
*/
|
|
471
|
+
function readFrontmatterFieldFromSource(source, key) {
|
|
472
|
+
const found = findFrontmatterSpan(source);
|
|
473
|
+
if (!found) {
|
|
474
|
+
return { ok: false, reason: 'no-frontmatter', span: { start: 0, end: 0 } };
|
|
475
|
+
}
|
|
476
|
+
const raw = source.slice(found.span.start, found.span.end);
|
|
477
|
+
return lookupFrontmatterField(raw, found.span, key);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Read MULTIPLE top-level frontmatter keys off raw `source` text in ONE pass
|
|
481
|
+
* — the bulk sibling of `readFrontmatterFieldFromSource`, for a caller (added
|
|
482
|
+
* for `plan-document.cts`'s `parsePlanDocument`, #5026 follow-up) that needs
|
|
483
|
+
* several keys off the SAME document. Calling `readFrontmatterFieldFromSource`
|
|
484
|
+
* once per key each independently re-detects the frontmatter span AND
|
|
485
|
+
* re-parses the full frontmatter YAML from scratch (`findFrontmatterSpan` +
|
|
486
|
+
* `parseFrontmatterRegion`, both non-trivial scans) — this function detects
|
|
487
|
+
* the span and parses the YAML exactly ONCE, then shapes every requested key
|
|
488
|
+
* off that SAME parsed result.
|
|
489
|
+
*
|
|
490
|
+
* This is a second ENTRY POINT into the one shared detect-span +
|
|
491
|
+
* parse-YAML + shape-a-key pipeline (`findFrontmatterSpan` /
|
|
492
|
+
* `parseFrontmatterRegion` / `shapeFrontmatterField`), never a second parser:
|
|
493
|
+
* per-key result shapes are byte-identical to calling
|
|
494
|
+
* `readFrontmatterFieldFromSource` once per key (same `no-frontmatter` /
|
|
495
|
+
* `unparseable-frontmatter` / `field-not-found` / present shapes, same span).
|
|
496
|
+
*/
|
|
497
|
+
function readFrontmatterFieldsFromSource(source, keys) {
|
|
498
|
+
const found = findFrontmatterSpan(source);
|
|
499
|
+
const out = {};
|
|
500
|
+
if (!found) {
|
|
501
|
+
const span = { start: 0, end: 0 };
|
|
502
|
+
for (const key of keys)
|
|
503
|
+
out[key] = { ok: false, reason: 'no-frontmatter', span };
|
|
504
|
+
return out;
|
|
505
|
+
}
|
|
506
|
+
const raw = source.slice(found.span.start, found.span.end);
|
|
507
|
+
const parsed = parseFrontmatterRegion(raw);
|
|
508
|
+
for (const key of keys)
|
|
509
|
+
out[key] = shapeFrontmatterField(parsed, found.span, key);
|
|
510
|
+
return out;
|
|
511
|
+
}
|
|
362
512
|
/**
|
|
363
513
|
* Stage a new value for a `boldField` node, returning a NEW `PlanningDoc`
|
|
364
514
|
* (immutable — `doc` itself is never mutated). Refuses an id this doc did
|
|
365
|
-
* not mint, and refuses any node kind other than `boldField` — only
|
|
515
|
+
* not mint, and refuses any node kind other than `boldField` — only
|
|
366
516
|
* `valueSpan` is ever writable this phase (ADR-4910 §1).
|
|
517
|
+
*
|
|
518
|
+
* #5007 / ADR-4910 Phase 6: a prior amendment here added a `{ allowSeparator:
|
|
519
|
+
* true }` escape hatch that spliced the caller's value across the FULL
|
|
520
|
+
* rest-of-line span (`valueSpan.start`..`trailingSpan.end`) to let a value
|
|
521
|
+
* legitimately containing the grammar's ` — ` trailing-separator token
|
|
522
|
+
* (`TRAILING_SEPARATOR_RE`) be written without triggering the round-trip
|
|
523
|
+
* refusal below. That option was REMOVED (still #5007, same phase) after a
|
|
524
|
+
* failing-first reproduction proved it only avoided the refusal AT WRITE
|
|
525
|
+
* TIME: the written bytes are correct, but `parseBoldFieldLine` splits on
|
|
526
|
+
* ` — ` unconditionally and without any escaping/metadata to distinguish
|
|
527
|
+
* "atomic value containing the token" from "value plus hand-annotation" —
|
|
528
|
+
* the same input shape either way. So the NEXT fresh `parsePlanningDoc` of
|
|
529
|
+
* that exact text (not the in-memory `doc` the option's own tests checked)
|
|
530
|
+
* silently re-truncates the value and demotes the rest to `trailingSpan`,
|
|
531
|
+
* with `findField`/`readNode` reporting a confident, wrong `ok: true`
|
|
532
|
+
* result and no error — reproduced live: staging `"Phase — COMPLETE"` this
|
|
533
|
+
* way, serializing, and re-parsing the output through a fresh
|
|
534
|
+
* `parsePlanningDoc` read back `"Phase"` via `readNode`, silently losing
|
|
535
|
+
* ` — COMPLETE`. This is exactly the #4917 finding-2 corruption this
|
|
536
|
+
* module's round-trip check exists to prevent, just moved one parse cycle
|
|
537
|
+
* downstream of where the check could still catch it. There is no escaping
|
|
538
|
+
* convention anywhere in this grammar (`BOLD_FIELD_RE`/`TRAILING_SEPARATOR_
|
|
539
|
+
* RE` are unconditional, unversioned regexes with no metadata channel), and
|
|
540
|
+
* `TRAILING_SEPARATOR_RE`'s split is relied on by every other reader of this
|
|
541
|
+
* seam (`findField`/`readNode`, used today for ROADMAP.md's `Plans`/
|
|
542
|
+
* `Depends on` fields) — narrowing or version-gating it here would be a
|
|
543
|
+
* grammar change with its own blast radius, not a local bug fix. Widening
|
|
544
|
+
* `setFieldValue`'s PUBLIC, shared contract to include a write path that is
|
|
545
|
+
* only safe for a caller who never reads the field back through this same
|
|
546
|
+
* module is an attractive nuisance: nothing stops a future `findField`/
|
|
547
|
+
* `readNode` caller from reaching for it and hitting this exact corruption.
|
|
548
|
+
* The one real caller (`stateReplaceField`, src/state-document.cts) never
|
|
549
|
+
* reads STATE.md fields back through `parsePlanningDoc`/`findField` (it uses
|
|
550
|
+
* `stateExtractField`'s own non-splitting regex instead), so it does its own
|
|
551
|
+
* local full-rest-of-line splice directly against `content`, using this
|
|
552
|
+
* module only to LOCATE the field's spans — keeping the dangerous affordance
|
|
553
|
+
* out of this shared seam's public surface entirely, rather than fixing it
|
|
554
|
+
* with a narrower version of the same false-safety option.
|
|
367
555
|
*/
|
|
368
556
|
function setFieldValue(doc, id, value) {
|
|
369
557
|
const node = doc.nodes.find((n) => n.id === id);
|
|
@@ -411,7 +599,10 @@ function setFieldValue(doc, id, value) {
|
|
|
411
599
|
}
|
|
412
600
|
const staged = new Map(doc.staged);
|
|
413
601
|
staged.set(id, value);
|
|
414
|
-
return {
|
|
602
|
+
return {
|
|
603
|
+
ok: true,
|
|
604
|
+
value: { source: doc.source, artifact: doc.artifact, nodes: doc.nodes, staged },
|
|
605
|
+
};
|
|
415
606
|
}
|
|
416
607
|
/** True when any node in `doc` failed to parse. */
|
|
417
608
|
function hasUnreadableNodes(doc) {
|
|
@@ -455,5 +646,47 @@ function serialize(doc) {
|
|
|
455
646
|
out += doc.source.slice(cursor);
|
|
456
647
|
return { ok: true, value: out };
|
|
457
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* Replace every occurrence of the single-line literal `from` with `to` in the
|
|
651
|
+
* document's prose — the seam's answer to a verbatim cross-reference rewrite
|
|
652
|
+
* (ADR-5057 §6, Phase 13, #5217: the migration's "Phase 1:" -> "Phase 1-01:"
|
|
653
|
+
* substitution in PROJECT.md / STATE.md).
|
|
654
|
+
*
|
|
655
|
+
* Fenced code blocks are never rewritten (rows 9/14: fenced content is not a
|
|
656
|
+
* node), every other byte — each line's own terminator included — is copied
|
|
657
|
+
* from `doc.source`, and a substitution that changes nothing returns a doc
|
|
658
|
+
* whose `source` is byte-identical. The result is re-parsed so its node spans
|
|
659
|
+
* describe the new text. Refuses (Result failure, nothing written) when `from`
|
|
660
|
+
* is empty or spans a line break, or when `doc` already carries staged
|
|
661
|
+
* field edits (their spans address the pre-substitution text).
|
|
662
|
+
*/
|
|
663
|
+
function replaceProse(doc, from, to) {
|
|
664
|
+
if (typeof from !== 'string' || from.length === 0) {
|
|
665
|
+
return { ok: false, reason: 'replaceProse: `from` must be a non-empty string' };
|
|
666
|
+
}
|
|
667
|
+
if (typeof to !== 'string' || /[\r\n]/.test(from) || /[\r\n]/.test(to)) {
|
|
668
|
+
return { ok: false, reason: 'replaceProse: `from` and `to` must be single-line strings' };
|
|
669
|
+
}
|
|
670
|
+
if (doc.staged.size > 0) {
|
|
671
|
+
return { ok: false, reason: 'replaceProse: refused while field edits are staged' };
|
|
672
|
+
}
|
|
673
|
+
const lines = splitLinesInfo(doc.source);
|
|
674
|
+
const fenced = fencedLineIndices(lines);
|
|
675
|
+
let out = '';
|
|
676
|
+
let cursor = 0;
|
|
677
|
+
let changed = false;
|
|
678
|
+
for (let i = 0; i < lines.length; i++) {
|
|
679
|
+
const line = lines[i];
|
|
680
|
+
if (fenced.has(i) || !line.text.includes(from))
|
|
681
|
+
continue;
|
|
682
|
+
out += doc.source.slice(cursor, line.start) + line.text.split(from).join(to);
|
|
683
|
+
cursor = line.end;
|
|
684
|
+
changed = true;
|
|
685
|
+
}
|
|
686
|
+
if (!changed)
|
|
687
|
+
return { ok: true, value: doc };
|
|
688
|
+
out += doc.source.slice(cursor);
|
|
689
|
+
return parsePlanningDoc(out, doc.artifact);
|
|
690
|
+
}
|
|
458
691
|
// Consumers: require('../gsd-core/bin/lib/planning-document.cjs')
|
|
459
692
|
// Named CJS exports are the canonical surface (ADR-457 .cts → .cjs build-at-publish).
|