@opengsd/gsd-core 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +12 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +22 -14
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +5 -3
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +23 -2
- package/agents/gsd-plan-checker.md +50 -53
- package/agents/gsd-planner.md +50 -50
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +13 -4
- package/bin/install.js +1448 -1103
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +9 -5
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1035 -138
- package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
- package/gsd-core/bin/lib/api-coverage.cjs +33 -14
- package/gsd-core/bin/lib/artifacts.cjs +5 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +1026 -268
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +845 -130
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +700 -40
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +216 -42
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +1172 -108
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
- package/gsd-core/bin/lib/config-loader.cjs +187 -23
- package/gsd-core/bin/lib/config.cjs +102 -3
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +208 -33
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +899 -229
- package/gsd-core/bin/lib/gap-checker.cjs +95 -10
- package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +439 -168
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +811 -259
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
- package/gsd-core/bin/lib/install-profiles.cjs +212 -61
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +170 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +183 -22
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +842 -73
- package/gsd-core/bin/lib/model-catalog.cjs +232 -16
- package/gsd-core/bin/lib/model-resolver.cjs +193 -68
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
- package/gsd-core/bin/lib/phase-id.cjs +514 -40
- package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
- package/gsd-core/bin/lib/phase-locator.cjs +262 -34
- package/gsd-core/bin/lib/phase.cjs +1038 -214
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +98 -3
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
- package/gsd-core/bin/lib/probe-core.cjs +5 -2
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
- package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +442 -96
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
- package/gsd-core/bin/lib/smart-entry.cjs +133 -23
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +52 -19
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +338 -8
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +846 -176
- package/gsd-core/bin/lib/state.cjs +2589 -369
- package/gsd-core/bin/lib/surface.cjs +33 -11
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
- package/gsd-core/bin/lib/uat.cjs +1761 -167
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
- package/gsd-core/bin/lib/unusable-input.cjs +37 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +75 -0
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +272 -9
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +453 -918
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/references/agent-contracts.md +44 -26
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +39 -21
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +4 -6
- package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +17 -11
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +5 -4
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +61 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +44 -13
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +5 -5
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -17
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +5 -4
- package/gsd-core/templates/state.md +11 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +13 -20
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +11 -27
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +64 -5
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
- package/gsd-core/workflows/code-review-fix.md +38 -11
- package/gsd-core/workflows/code-review.md +159 -52
- package/gsd-core/workflows/complete-milestone.md +151 -23
- package/gsd-core/workflows/debug.md +12 -8
- package/gsd-core/workflows/diagnose-issues.md +47 -15
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +27 -2
- package/gsd-core/workflows/eval-review.md +7 -14
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +72 -100
- package/gsd-core/workflows/execute-plan.md +52 -15
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +76 -10
- package/gsd-core/workflows/import.md +18 -15
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +49 -16
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +8 -5
- package/gsd-core/workflows/new-milestone.md +22 -29
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +26 -40
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +14 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +162 -59
- package/gsd-core/workflows/plan-review-convergence.md +96 -11
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +61 -18
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
- package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
- package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +55 -44
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +219 -20
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +69 -7
- package/gsd-core/workflows/ship.md +116 -50
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +53 -13
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +64 -9
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +49 -14
- package/gsd-core/workflows/ui-phase.md +15 -21
- package/gsd-core/workflows/ui-review.md +8 -12
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +7 -11
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +66 -25
- package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
- package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +37 -27
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +55 -32
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +231 -24
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +162 -46
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +158 -30
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +2 -3
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +83 -3
- package/hooks/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +37 -27
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +55 -32
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +231 -24
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +162 -46
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +28 -11
- package/pi/gsd.cjs +19 -5
- package/scripts/base64-scan.sh +74 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +5 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +64 -14
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +11 -21
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +138 -25
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +308 -0
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +492 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +471 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +488 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +950 -0
- package/scripts/lint-test-file-count.allowlist.json +137 -8
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +830 -0
- package/scripts/lint-vendored-deps.cjs +297 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +100 -14
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +24 -7
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +9 -5
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -107
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -14,8 +14,18 @@ exports.DYNAMIC_KEY_PATTERNS = exports.RUNTIME_STATE_KEYS = exports.VALID_CONFIG
|
|
|
14
14
|
exports.normalizeLegacyKeys = normalizeLegacyKeys;
|
|
15
15
|
exports.mergeDefaults = mergeDefaults;
|
|
16
16
|
exports.migrateOnDisk = migrateOnDisk;
|
|
17
|
+
exports.isConfigSection = isConfigSection;
|
|
17
18
|
const node_fs_1 = require("node:fs");
|
|
18
19
|
const node_path_1 = require("node:path");
|
|
20
|
+
// ⚠️ DO NOT add a sibling `.cjs` import to this module. `configuration.cjs` must load
|
|
21
|
+
// from an install layout containing ONLY itself plus `bin/shared/*.manifest.json` —
|
|
22
|
+
// that is the #3571 contract, pinned by "co-located bin/shared manifests let
|
|
23
|
+
// configuration.cjs load without sdk/shared" in tests/install.test.cjs. A `require`
|
|
24
|
+
// for a sibling that the installer does not co-locate fails at load time with
|
|
25
|
+
// MODULE_NOT_FOUND. This is why #3760's out-of-band diagnostic is emitted by this
|
|
26
|
+
// module's CALLERS (`cmdMigrateConfig` in config.cts, `loadConfigResolved` in
|
|
27
|
+
// config-loader.cts) rather than here: `normalizeLegacyKeys` reports refusals
|
|
28
|
+
// in-band via `skipped[]`, which keeps it both pure AND dependency-free.
|
|
19
29
|
// In .cts (CommonJS output) files, `require` is available as a global.
|
|
20
30
|
const _require = require;
|
|
21
31
|
// ─── Manifest requires ───────────────────────────────────────────────────────
|
|
@@ -109,42 +119,107 @@ function deepMergeConfig(base, overlay) {
|
|
|
109
119
|
}
|
|
110
120
|
return result;
|
|
111
121
|
}
|
|
122
|
+
/** Type name for a report — `'array'` for arrays, otherwise `typeof`. */
|
|
123
|
+
function describeSectionType(value) {
|
|
124
|
+
return Array.isArray(value) ? 'array' : typeof value;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Is `value` usable as a config SECTION — something a nested key can be written into?
|
|
128
|
+
*
|
|
129
|
+
* Three cases, and the distinction between the second and third is the whole of #3760:
|
|
130
|
+
*
|
|
131
|
+
* - `null` / `undefined` — ABSENT. Not a section yet, but nothing is lost by creating one.
|
|
132
|
+
* Callers substitute `{}`; this predicate reports `false` and callers check for absence
|
|
133
|
+
* separately, so the two are never conflated.
|
|
134
|
+
* - a non-null, non-array `object` — a SECTION. Spreading it is safe and correct.
|
|
135
|
+
* - anything else (string, number, boolean, array) — a PRESENT NON-OBJECT, i.e. user data
|
|
136
|
+
* that a spread would destroy. `{...'main'}` is `{0:'m',1:'a',2:'i',3:'n'}`; `{...7}` is
|
|
137
|
+
* `{}`. An array is included here because `typeof [] === 'object'` and `{...['a']}` is
|
|
138
|
+
* `{0:'a'}` — the identical expansion a bare `typeof` guard would wave through.
|
|
139
|
+
*
|
|
140
|
+
* This is the nested-section analog of the top-level shape check ADR-227 already requires
|
|
141
|
+
* in `_readConfigFile` (`config-loader.cts`): valid JSON is not the same as a config object.
|
|
142
|
+
* It lives here, exported, rather than being copied into `config-loader.cts`, because two
|
|
143
|
+
* hand-rolled copies of one predicate is `DEFECT.GENERATIVE-FIX` by construction — the same
|
|
144
|
+
* reasoning that made `unusable-input.cts` a shared seam.
|
|
145
|
+
*/
|
|
146
|
+
function isConfigSection(value) {
|
|
147
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
148
|
+
}
|
|
112
149
|
// ─── Exported functions ───────────────────────────────────────────────────────
|
|
150
|
+
/**
|
|
151
|
+
* Hoist a legacy top-level key into its canonical nested section.
|
|
152
|
+
*
|
|
153
|
+
* Refuses — and reports the refusal — when the destination section is present but is not an
|
|
154
|
+
* object. Refusing means leaving BOTH the section and the legacy key exactly as they were and
|
|
155
|
+
* pushing no `Normalization`, so this block never marks the config dirty and nothing it
|
|
156
|
+
* touched can be written back. That is the #3760 contract: never expand, never drop, never
|
|
157
|
+
* persist a shape the user did not write.
|
|
158
|
+
*/
|
|
159
|
+
function hoistLegacyKey(result, normalizations, skipped, legacyKey, section, field) {
|
|
160
|
+
const raw = result[section];
|
|
161
|
+
// `null`/`undefined` mean "no section yet" and have always been treated as absent here.
|
|
162
|
+
// They carry no user data, so creating the section loses nothing.
|
|
163
|
+
const absent = raw === null || raw === undefined;
|
|
164
|
+
if (!absent && !isConfigSection(raw)) {
|
|
165
|
+
skipped.push({
|
|
166
|
+
from: legacyKey,
|
|
167
|
+
to: `${section}.${field}`,
|
|
168
|
+
section,
|
|
169
|
+
reason: 'non_object_section',
|
|
170
|
+
value: result[legacyKey],
|
|
171
|
+
sectionType: describeSectionType(raw),
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// `raw` is already narrowed by the isConfigSection guard above.
|
|
176
|
+
const existing = absent ? {} : raw;
|
|
177
|
+
const value = result[legacyKey];
|
|
178
|
+
// Canonical nested wins when it is already set; otherwise the legacy value is hoisted.
|
|
179
|
+
result[section] = existing[field] === undefined
|
|
180
|
+
? { ...existing, [field]: value }
|
|
181
|
+
: { ...existing };
|
|
182
|
+
delete result[legacyKey];
|
|
183
|
+
normalizations.push({ from: legacyKey, to: `${section}.${field}`, value });
|
|
184
|
+
}
|
|
113
185
|
function normalizeLegacyKeys(parsed) {
|
|
114
186
|
const result = { ...parsed };
|
|
115
187
|
const normalizations = [];
|
|
188
|
+
const skipped = [];
|
|
116
189
|
// 1. branching_strategy → git.branching_strategy
|
|
117
190
|
if (Object.prototype.hasOwnProperty.call(result, 'branching_strategy')) {
|
|
118
|
-
|
|
119
|
-
const git = (result['git'] ?? {});
|
|
120
|
-
if (git['branching_strategy'] === undefined) {
|
|
121
|
-
result['git'] = { ...git, branching_strategy: value };
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
// canonical nested wins — just delete the stale top-level
|
|
125
|
-
result['git'] = { ...git };
|
|
126
|
-
}
|
|
127
|
-
delete result['branching_strategy'];
|
|
128
|
-
normalizations.push({ from: 'branching_strategy', to: 'git.branching_strategy', value });
|
|
191
|
+
hoistLegacyKey(result, normalizations, skipped, 'branching_strategy', 'git', 'branching_strategy');
|
|
129
192
|
}
|
|
130
193
|
// 2. top-level sub_repos → planning.sub_repos
|
|
131
194
|
if (Object.prototype.hasOwnProperty.call(result, 'sub_repos')) {
|
|
132
|
-
|
|
133
|
-
const planning = (result['planning'] ?? {});
|
|
134
|
-
if (planning['sub_repos'] === undefined) {
|
|
135
|
-
result['planning'] = { ...planning, sub_repos: value };
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
// canonical nested wins — just drop the stale top-level
|
|
139
|
-
result['planning'] = { ...planning };
|
|
140
|
-
}
|
|
141
|
-
delete result['sub_repos'];
|
|
142
|
-
normalizations.push({ from: 'sub_repos', to: 'planning.sub_repos', value });
|
|
195
|
+
hoistLegacyKey(result, normalizations, skipped, 'sub_repos', 'planning', 'sub_repos');
|
|
143
196
|
}
|
|
144
197
|
// 3. multiRepo: true → marker (filesystem detection deferred to migrateOnDisk / caller)
|
|
145
198
|
if (result['multiRepo'] === true) {
|
|
146
|
-
|
|
147
|
-
|
|
199
|
+
// #3760: refuse here too, for the same reason as blocks 1 and 2 — and it must be
|
|
200
|
+
// decided HERE, not in the caller. The caller is the one that runs filesystem
|
|
201
|
+
// detection, but whether `planning` can receive the result is knowable from the
|
|
202
|
+
// parsed config alone. Deciding it later meant `multiRepo` had already been
|
|
203
|
+
// deleted and a Normalization already pushed: the config was written, the
|
|
204
|
+
// sub_repos injection silently no-opped against the non-object section, and the
|
|
205
|
+
// user's `multiRepo: true` was consumed with nothing to show for it and no
|
|
206
|
+
// diagnostic. Refusing here keeps the marker, keeps the config clean of a
|
|
207
|
+
// migration that did not happen, and gives all three callers the same report.
|
|
208
|
+
const planning = result['planning'];
|
|
209
|
+
if (planning !== null && planning !== undefined && !isConfigSection(planning)) {
|
|
210
|
+
skipped.push({
|
|
211
|
+
from: 'multiRepo',
|
|
212
|
+
to: 'planning.sub_repos',
|
|
213
|
+
section: 'planning',
|
|
214
|
+
reason: 'non_object_section',
|
|
215
|
+
value: true,
|
|
216
|
+
sectionType: describeSectionType(planning),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
delete result['multiRepo'];
|
|
221
|
+
normalizations.push({ from: 'multiRepo', to: 'planning.sub_repos', value: true, requiresFilesystem: true });
|
|
222
|
+
}
|
|
148
223
|
}
|
|
149
224
|
// 4. top-level depth → granularity
|
|
150
225
|
if (Object.prototype.hasOwnProperty.call(result, 'depth') && !Object.prototype.hasOwnProperty.call(result, 'granularity')) {
|
|
@@ -154,26 +229,36 @@ function normalizeLegacyKeys(parsed) {
|
|
|
154
229
|
delete result['depth'];
|
|
155
230
|
normalizations.push({ from: 'depth', to: 'granularity', value: mapped });
|
|
156
231
|
}
|
|
157
|
-
|
|
232
|
+
// 5. top-level base_branch → git.base_branch
|
|
233
|
+
if (Object.prototype.hasOwnProperty.call(result, 'base_branch')) {
|
|
234
|
+
hoistLegacyKey(result, normalizations, skipped, 'base_branch', 'git', 'base_branch');
|
|
235
|
+
}
|
|
236
|
+
return { parsed: result, normalizations, skipped };
|
|
158
237
|
}
|
|
159
238
|
function mergeDefaults(parsed) {
|
|
160
239
|
// Start with a deep clone of defaults, then overlay parsed
|
|
161
240
|
const defaults = structuredClone(CONFIG_DEFAULTS);
|
|
162
241
|
return deepMergeConfig(defaults, parsed);
|
|
163
242
|
}
|
|
164
|
-
function migrateOnDisk(cwd, workstream) {
|
|
165
|
-
|
|
243
|
+
function migrateOnDisk(cwd, workstream, configPathOverride) {
|
|
244
|
+
// #3749: the caller (cmdMigrateConfig in config.cts) supplies the config
|
|
245
|
+
// path resolved through planning-workspace's PROJECT-aware planningDir.
|
|
246
|
+
// This module cannot import that sibling (#3571 install-layout contract),
|
|
247
|
+
// and its own local planningDir above is deliberately workstream-only —
|
|
248
|
+
// resolving here through the local copy made migrate-config under
|
|
249
|
+
// GSD_PROJECT rewrite the ROOT config instead of the scoped one.
|
|
250
|
+
const configPath = configPathOverride ?? (0, node_path_1.join)(planningDir(cwd, workstream), 'config.json');
|
|
166
251
|
let raw;
|
|
167
252
|
try {
|
|
168
253
|
raw = (0, node_fs_1.readFileSync)(configPath, 'utf-8');
|
|
169
254
|
}
|
|
170
255
|
catch {
|
|
171
256
|
// File missing — nothing to migrate
|
|
172
|
-
return { migrated: false, normalizations: [], wrote: null };
|
|
257
|
+
return { migrated: false, normalizations: [], wrote: null, skipped: [] };
|
|
173
258
|
}
|
|
174
259
|
const trimmed = raw.trim();
|
|
175
260
|
if (trimmed === '') {
|
|
176
|
-
return { migrated: false, normalizations: [], wrote: null };
|
|
261
|
+
return { migrated: false, normalizations: [], wrote: null, skipped: [] };
|
|
177
262
|
}
|
|
178
263
|
let parsed;
|
|
179
264
|
try {
|
|
@@ -181,23 +266,30 @@ function migrateOnDisk(cwd, workstream) {
|
|
|
181
266
|
}
|
|
182
267
|
catch {
|
|
183
268
|
// Malformed — can't migrate
|
|
184
|
-
return { migrated: false, normalizations: [], wrote: null };
|
|
185
|
-
}
|
|
186
|
-
const { parsed: normalized, normalizations } = normalizeLegacyKeys(parsed);
|
|
187
|
-
if (normalizations.length === 0) {
|
|
188
|
-
return { migrated: false, normalizations: [], wrote: null };
|
|
269
|
+
return { migrated: false, normalizations: [], wrote: null, skipped: [] };
|
|
189
270
|
}
|
|
271
|
+
const { parsed: normalized, normalizations, skipped } = normalizeLegacyKeys(parsed);
|
|
190
272
|
// Resolve multiRepo filesystem detection
|
|
191
273
|
const result = { ...normalized };
|
|
192
274
|
for (const norm of normalizations) {
|
|
193
275
|
if (norm.requiresFilesystem) {
|
|
194
276
|
const detected = detectSubRepos(cwd);
|
|
195
277
|
if (detected.length > 0) {
|
|
196
|
-
|
|
197
|
-
|
|
278
|
+
// #3760: `requiresFilesystem` is pushed by block 3 only when `planning` was
|
|
279
|
+
// absent or an object, and nothing between there and here changes it — so
|
|
280
|
+
// `isConfigSection` picks between merge and create, and never has to discard.
|
|
281
|
+
const planning = result['planning'];
|
|
282
|
+
const existing = isConfigSection(planning) ? planning : {};
|
|
283
|
+
result['planning'] = { ...existing, sub_repos: detected, commit_docs: false };
|
|
198
284
|
}
|
|
199
285
|
}
|
|
200
286
|
}
|
|
287
|
+
if (normalizations.length === 0) {
|
|
288
|
+
// Nothing changed — and that now includes the case where every legacy key was
|
|
289
|
+
// REFUSED. Returning early here is what keeps the corrupted-section input from
|
|
290
|
+
// reaching writeFileSync at all.
|
|
291
|
+
return { migrated: false, normalizations: [], wrote: null, skipped };
|
|
292
|
+
}
|
|
201
293
|
try {
|
|
202
294
|
(0, node_fs_1.writeFileSync)(configPath, JSON.stringify(result, null, 2));
|
|
203
295
|
}
|
|
@@ -205,5 +297,5 @@ function migrateOnDisk(cwd, workstream) {
|
|
|
205
297
|
const msg = err instanceof Error ? err.message : String(err);
|
|
206
298
|
throw new Error(`Failed to write migrated config at ${configPath}: ${msg}`);
|
|
207
299
|
}
|
|
208
|
-
return { migrated: true, normalizations, wrote: configPath };
|
|
300
|
+
return { migrated: true, normalizations, wrote: configPath, skipped };
|
|
209
301
|
}
|
|
@@ -15,6 +15,22 @@
|
|
|
15
15
|
* - node:fs / node:path (stdlib)
|
|
16
16
|
* - ./phase-id.cjs (comparePhaseNum, used by readSubdirectories)
|
|
17
17
|
* - ./planning-workspace.cjs (findContextMdIn, used by getPhaseFileStats)
|
|
18
|
+
*
|
|
19
|
+
* #3883 (ADR-3473 §8.3): two of this module's cyclic partners require
|
|
20
|
+
* generateSlugInternal, the canonical slug formula:
|
|
21
|
+
* - phase-id.cjs requires this module directly.
|
|
22
|
+
* - planning-workspace.cjs is a cyclic partner via a longer path:
|
|
23
|
+
* core-utils.cjs -> planning-workspace.cjs -> active-workstream-store.cjs
|
|
24
|
+
* -> workstream-name-policy.cjs -> core-utils.cjs.
|
|
25
|
+
* Both are genuine circular requires. They are safe ONLY because every side
|
|
26
|
+
* accesses the other's exports lazily, through the live module-namespace
|
|
27
|
+
* object (`phaseIdModule.foo(...)` / `planningWorkspace.foo(...)`) inside a
|
|
28
|
+
* function body, never via a top-level destructure — a top-level
|
|
29
|
+
* `const { foo } = require(...)` copies the binding at import time and would
|
|
30
|
+
* silently capture `undefined` whichever module loses the load-order race.
|
|
31
|
+
* This is an absolute rule with no exception in this file: every cyclic
|
|
32
|
+
* partner's export is accessed through its module-namespace object, never
|
|
33
|
+
* destructured at the top level.
|
|
18
34
|
*/
|
|
19
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -23,12 +39,58 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
23
39
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
40
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
41
|
const phaseIdModule = require("./phase-id.cjs");
|
|
26
|
-
const { comparePhaseNum } = phaseIdModule;
|
|
27
42
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
28
43
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
29
|
-
const { findContextMdIn } = planningWorkspace;
|
|
30
44
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
45
|
const shellCommandProjection = require("./shell-command-projection.cjs");
|
|
46
|
+
// ─── Line-ending normalization ─────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Normalize every line ending in `content` to a bare `\n`, ONCE — the shared
|
|
49
|
+
* seam every document-READ boundary in this codebase should route through
|
|
50
|
+
* (#3707-CR follow-up MAJOR).
|
|
51
|
+
*
|
|
52
|
+
* CommonMark treats a lone CR (no paired LF) as a line ending — such a
|
|
53
|
+
* document RENDERS as separate lines to a human reader — but a parser that
|
|
54
|
+
* splits/tokenizes/scans on `\n` alone treats a lone-CR-separated document as
|
|
55
|
+
* ONE unbroken line, hiding every row boundary in it. `src/uat.cts` originally
|
|
56
|
+
* carried this exact fix as a PRIVATE, unexported function applied inside two
|
|
57
|
+
* of its own parse functions (`parseUatItemsWithStats`, `parseCurrentTest`) —
|
|
58
|
+
* which is why two OTHER read sites in the same module (`cmdAuditUat`'s
|
|
59
|
+
* VERIFICATION and deferred-items.md ingresses) were missed: normalizing
|
|
60
|
+
* per-parser means every new parser must remember to call it. Promoted here,
|
|
61
|
+
* to the shared leaf module every document consumer can reach without a new
|
|
62
|
+
* dependency edge, so normalization can be applied at the READ boundary
|
|
63
|
+
* instead — every current and future parser fed from a boundary that calls
|
|
64
|
+
* this gets normalized text by construction.
|
|
65
|
+
*
|
|
66
|
+
* `/\r\n?/g` is deliberately ONE alternation, not two separate replaces: a
|
|
67
|
+
* two-pass `replace(/\r\n/g,'\n').replace(/\r/g,'\n')` is equivalent here
|
|
68
|
+
* because the first pass already consumes every CRLF pair before the second
|
|
69
|
+
* pass ever runs, but a single regex avoids relying on pass ORDER and matches
|
|
70
|
+
* greedily left-to-right in one scan, so a CRLF is always consumed as ONE
|
|
71
|
+
* unit (never left as a stray trailing `\r` after the `\n` half is matched
|
|
72
|
+
* first) and a lone CR — including one immediately followed by nothing, i.e.
|
|
73
|
+
* at EOF, or by another lone CR — is still replaced.
|
|
74
|
+
*
|
|
75
|
+
* This is deliberately NOT a length-preserving transform (CRLF, two UTF-16
|
|
76
|
+
* units, becomes LF, one), so any offsets a caller computes must be compared
|
|
77
|
+
* only against THIS normalized string, never against the original raw text.
|
|
78
|
+
*
|
|
79
|
+
* U+2028 LINE SEPARATOR / U+2029 PARAGRAPH SEPARATOR (#3078-CR) are
|
|
80
|
+
* DELIBERATELY NOT folded here, unlike `\r`/`\r\n`. Folding is unnecessary:
|
|
81
|
+
* `String.prototype.split('\n')` never treats U+2028/U+2029 as a delimiter,
|
|
82
|
+
* so an exotic separator can never manufacture a fake line start for a
|
|
83
|
+
* consumer that scans lines produced by `split('\n')`, rather than anchoring
|
|
84
|
+
* a multiline (`/m`) regex directly over unsplit text. Only the latter
|
|
85
|
+
* pattern is vulnerable to the ECMA-262 LineTerminator set including these
|
|
86
|
+
* two code points. This module performs no line-anchored matching of its
|
|
87
|
+
* own; a caller that scans lines should split first and match per-line
|
|
88
|
+
* rather than anchor `/m` over unsplit text — this comment makes no claim
|
|
89
|
+
* about whether any particular caller currently does so.
|
|
90
|
+
*/
|
|
91
|
+
function normalizeLineEndings(content) {
|
|
92
|
+
return content.replace(/\r\n?/g, '\n');
|
|
93
|
+
}
|
|
32
94
|
// ─── Path helpers ────────────────────────────────────────────────────────────
|
|
33
95
|
/**
|
|
34
96
|
* Normalize a relative path to always use forward slashes (cross-platform).
|
|
@@ -72,17 +134,30 @@ function extractOneLinerFromBody(content) {
|
|
|
72
134
|
if (!content)
|
|
73
135
|
return null;
|
|
74
136
|
const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
75
|
-
const body = normalized.replace(/^---\n[\s\S]*?\n---\n*/, '');
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
137
|
+
const body = normalized.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n*/, '');
|
|
138
|
+
// #3170: anchor to a summary-shaped heading (Summary / Overview /
|
|
139
|
+
// Accomplishments) so an incidental first heading (a rule list, task
|
|
140
|
+
// breakdown, deviation note) does not contribute its first bold run as the
|
|
141
|
+
// deliverable one-liner. Iterate headings in document order and extract from
|
|
142
|
+
// the first summary-shaped one that has a bold run; fall back to null (not the
|
|
143
|
+
// wrong text) when no such heading exists.
|
|
144
|
+
const headingRe = /^#+\s*([^\n]*)\n+\*\*([^*\n]+)\*\*([^\n]*)/gm;
|
|
145
|
+
let match;
|
|
146
|
+
while ((match = headingRe.exec(body)) !== null) {
|
|
147
|
+
if (!/summary|overview|accomplish/i.test(match[1]))
|
|
148
|
+
continue;
|
|
149
|
+
const boldInner = match[2].trim();
|
|
150
|
+
const afterBold = match[3];
|
|
151
|
+
if (/:\s*$/.test(boldInner)) {
|
|
152
|
+
const prose = afterBold.trim();
|
|
153
|
+
if (prose.length > 0)
|
|
154
|
+
return prose;
|
|
155
|
+
}
|
|
156
|
+
else if (boldInner.length > 0) {
|
|
157
|
+
return boldInner;
|
|
158
|
+
}
|
|
84
159
|
}
|
|
85
|
-
return
|
|
160
|
+
return null;
|
|
86
161
|
}
|
|
87
162
|
// ─── Misc utilities ───────────────────────────────────────────────────────────
|
|
88
163
|
function pathExistsInternal(cwd, targetPath) {
|
|
@@ -95,7 +170,19 @@ function pathExistsInternal(cwd, targetPath) {
|
|
|
95
170
|
return false;
|
|
96
171
|
}
|
|
97
172
|
}
|
|
98
|
-
|
|
173
|
+
/**
|
|
174
|
+
* #3883 (ADR-3473 §8.3 remediation): `maxLen` lets a caller state its own
|
|
175
|
+
* truncation contract instead of being forced into this function's
|
|
176
|
+
* historical 60-char cap. Some call sites truncated at 60 before the #3883
|
|
177
|
+
* consolidation (commands.cts:cmdGenerateSlug) and some never truncated at
|
|
178
|
+
* all (phase-id.cts toDir/getPhaseDirFromPhaseId, the init.cts/phase-locator
|
|
179
|
+
* phase_slug sites, workstream-name-policy.cts toWorkstreamSlug) — collapsing
|
|
180
|
+
* every caller onto a single hard-coded 60 introduced two identity
|
|
181
|
+
* collisions (distinct >60-char names/phase-slugs truncating to the same
|
|
182
|
+
* value) that did not exist pre-migration. `maxLen: 60` remains the default
|
|
183
|
+
* so untouched callers keep prior behavior; pass `null` for no truncation.
|
|
184
|
+
*/
|
|
185
|
+
function generateSlugInternal(text, maxLen = 60) {
|
|
99
186
|
if (!text)
|
|
100
187
|
return null;
|
|
101
188
|
// #2849: strip leading/trailing hyphens AFTER truncation, not only before.
|
|
@@ -103,7 +190,9 @@ function generateSlugInternal(text) {
|
|
|
103
190
|
// the strip step exists to prevent. Truncation cannot add a leading hyphen, so
|
|
104
191
|
// running the full ^-+|-+$ pass last is equivalent for leading hyphens and
|
|
105
192
|
// fixes the trailing-hyphen-after-truncation case.
|
|
106
|
-
|
|
193
|
+
const collapsed = transliterateForSlug(text).replace(/[^a-z0-9]+/g, '-');
|
|
194
|
+
const truncated = maxLen === null ? collapsed : collapsed.substring(0, maxLen);
|
|
195
|
+
return truncated.replace(/^-+|-+$/g, '');
|
|
107
196
|
}
|
|
108
197
|
// ─── Transliteration (#2848) ─────────────────────────────────────────────────
|
|
109
198
|
//
|
|
@@ -151,27 +240,66 @@ function transliterateForSlug(text) {
|
|
|
151
240
|
}
|
|
152
241
|
return out;
|
|
153
242
|
}
|
|
154
|
-
// ─── Phase file helpers ──────────────────────────────────────────────────────
|
|
155
|
-
/** Filter a file list to just PLAN.md / *-PLAN.md entries. */
|
|
156
|
-
function filterPlanFiles(files) {
|
|
157
|
-
return files.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
|
|
158
|
-
}
|
|
159
|
-
/** Filter a file list to just SUMMARY.md / *-SUMMARY.md entries. */
|
|
160
|
-
function filterSummaryFiles(files) {
|
|
161
|
-
return files.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
162
|
-
}
|
|
163
243
|
/**
|
|
164
244
|
* Read a phase directory and return counts/flags for common file types.
|
|
245
|
+
*
|
|
246
|
+
* #3183 (ADR-3180 Decision 2): `plans`/`summaries` are derived from the
|
|
247
|
+
* canonical `scanPhasePlans` rather than a local re-derivation, so this
|
|
248
|
+
* primitive can no longer diverge from the single owner of live-plan
|
|
249
|
+
* counting. `scanPhasePlans`
|
|
250
|
+
* lives in plan-scan.cjs, which itself imports `countMatchedSummaries` from
|
|
251
|
+
* THIS module — a top-level import here would be circular, so the require
|
|
252
|
+
* is deferred (lazy, inside the function body) to break the cycle at load
|
|
253
|
+
* time. This mirrors the lazy-require seam already used elsewhere in this
|
|
254
|
+
* repo (see src/audit-command-router.cts) for the same "module A needs
|
|
255
|
+
* module B which needs module A" shape.
|
|
256
|
+
*
|
|
257
|
+
* `hasResearch`/`hasContext`/`hasVerification`/`hasReviews` stay on the raw
|
|
258
|
+
* `readdirSync` listing — they are not plan-scan concerns.
|
|
259
|
+
*
|
|
260
|
+
* #3511 BLOCKER-2: the raw listing is scoped through `scopeToPhase` (keyed on
|
|
261
|
+
* `path.basename(phaseDir)`) before any of the four artifact predicates run,
|
|
262
|
+
* so a stray cross-phase file (e.g. `04-VERIFICATION.md` sitting inside phase
|
|
263
|
+
* 03's directory) cannot flip `hasResearch`/`hasContext`/`hasVerification`/
|
|
264
|
+
* `hasReviews` true for a phase it does not belong to — the same membership
|
|
265
|
+
* rule every other aggregate phase-directory scan (`uat.cts`, `audit.cts`,
|
|
266
|
+
* `phase.cts`, `state.cts`) already routes through. `hasContext` is scoped by
|
|
267
|
+
* passing the already-scoped array into `findContextMdIn` at this call site
|
|
268
|
+
* only — `findContextMdIn` itself is unchanged and its other 4 call sites
|
|
269
|
+
* (roadmap.cts, gap-checker.cts, init.cts) are unaffected.
|
|
270
|
+
*
|
|
271
|
+
* Degrades on an unreadable directory instead of throwing: empty arrays,
|
|
272
|
+
* every flag false, scope UNREADABLE (mirroring scanPhasePlans's own
|
|
273
|
+
* degrade path).
|
|
165
274
|
*/
|
|
166
275
|
function getPhaseFileStats(phaseDir) {
|
|
167
|
-
|
|
276
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
277
|
+
const scanPhasePlans = require('./plan-scan.cjs');
|
|
278
|
+
const scan = scanPhasePlans(phaseDir);
|
|
279
|
+
let files;
|
|
280
|
+
try {
|
|
281
|
+
files = node_fs_1.default.readdirSync(phaseDir);
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return {
|
|
285
|
+
plans: scan.planFiles,
|
|
286
|
+
summaries: scan.summaryFiles,
|
|
287
|
+
hasResearch: false,
|
|
288
|
+
hasContext: false,
|
|
289
|
+
hasVerification: false,
|
|
290
|
+
hasReviews: false,
|
|
291
|
+
scope: scan.scope,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const scopedFiles = phaseIdModule.scopeToPhase(files, node_path_1.default.basename(phaseDir));
|
|
168
295
|
return {
|
|
169
|
-
plans:
|
|
170
|
-
summaries:
|
|
171
|
-
hasResearch:
|
|
172
|
-
hasContext: findContextMdIn(
|
|
173
|
-
hasVerification:
|
|
174
|
-
hasReviews:
|
|
296
|
+
plans: scan.planFiles,
|
|
297
|
+
summaries: scan.summaryFiles,
|
|
298
|
+
hasResearch: scopedFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md'),
|
|
299
|
+
hasContext: planningWorkspace.findContextMdIn(scopedFiles) !== null,
|
|
300
|
+
hasVerification: scopedFiles.some(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md'),
|
|
301
|
+
hasReviews: scopedFiles.some(f => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md'),
|
|
302
|
+
scope: scan.scope,
|
|
175
303
|
};
|
|
176
304
|
}
|
|
177
305
|
/**
|
|
@@ -183,7 +311,7 @@ function readSubdirectories(dirPath, sort = false) {
|
|
|
183
311
|
try {
|
|
184
312
|
const entries = node_fs_1.default.readdirSync(dirPath, { withFileTypes: true });
|
|
185
313
|
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
186
|
-
return sort ? dirs.sort((a, b) => comparePhaseNum(a, b)) : dirs;
|
|
314
|
+
return sort ? dirs.sort((a, b) => phaseIdModule.comparePhaseNum(a, b)) : dirs;
|
|
187
315
|
}
|
|
188
316
|
catch {
|
|
189
317
|
return [];
|
|
@@ -297,6 +425,35 @@ function summaryCandidates(plan) {
|
|
|
297
425
|
const extended = base.match(/^(\d+)-PLAN-(\d+)/i);
|
|
298
426
|
if (extended)
|
|
299
427
|
candidates.push(dir + extended[1] + '-' + extended[2] + '-SUMMARY.md');
|
|
428
|
+
// #3183: canonical-id form. Restores the coverage of the pre-migration
|
|
429
|
+
// bespoke I001 rule (verify.cts, pre-#3183, via validate.cjs's now-unused
|
|
430
|
+
// `canonicalPlanStem` — behaviourally identical to `extractCanonicalPlanId`,
|
|
431
|
+
// confirmed empirically), which matched a plan carrying a descriptive slug
|
|
432
|
+
// after its <phase>-<plan> id — e.g. `68-01-scaffolding-PLAN.md` — against
|
|
433
|
+
// a summary named only by the bare id — `68-01-SUMMARY.md`. None of the
|
|
434
|
+
// three candidates above produce that filename.
|
|
435
|
+
//
|
|
436
|
+
// Narrowed to the case `extractCanonicalPlanId` actually extracted an
|
|
437
|
+
// <id>-<id> pair (its result differs from the plan's own PLAN-stripped
|
|
438
|
+
// base). When no pair is found it falls back to returning that same base
|
|
439
|
+
// unchanged, which would otherwise push a redundant candidate identical to
|
|
440
|
+
// the `<stem>-SUMMARY.md` form above (e.g. `setup-PLAN.md` -> canonical
|
|
441
|
+
// 'setup' -> 'setup-SUMMARY.md', already candidate #2) rather than the
|
|
442
|
+
// original rule's actual behavior of matching only real id pairs.
|
|
443
|
+
//
|
|
444
|
+
// Collision, matching the original rule byte-for-behaviour: two plans that
|
|
445
|
+
// share the same <phase>-<plan> id but differ only in their descriptive
|
|
446
|
+
// slug (`68-01-alpha-PLAN.md` + `68-01-beta-PLAN.md`) both generate the
|
|
447
|
+
// SAME candidate `68-01-SUMMARY.md` and therefore BOTH read as summarized
|
|
448
|
+
// off one shared summary file. This is not a new regression: the
|
|
449
|
+
// pre-migration bespoke rule collapsed the same way (it populated one
|
|
450
|
+
// `summaryBases` Set keyed by canonical stem, so any plan whose canonical
|
|
451
|
+
// stem hit the set counted as matched, with no cardinality check against
|
|
452
|
+
// how many plans shared that stem).
|
|
453
|
+
const planStem = base.replace(/-PLAN$/i, '');
|
|
454
|
+
const canonicalId = extractCanonicalPlanId(base + '.md');
|
|
455
|
+
if (canonicalId !== planStem)
|
|
456
|
+
candidates.push(dir + canonicalId + '-SUMMARY.md');
|
|
300
457
|
return candidates;
|
|
301
458
|
}
|
|
302
459
|
/**
|
|
@@ -314,19 +471,37 @@ function findUnsummarizedPlans(planFiles, summaryFiles) {
|
|
|
314
471
|
const summarySet = new Set(summaryFiles);
|
|
315
472
|
return planFiles.filter((plan) => !summaryCandidates(plan).some((c) => summarySet.has(c)));
|
|
316
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* #3183: the mirror image of `findUnsummarizedPlans` — the summary files in
|
|
476
|
+
* `summaryFiles` that do NOT pair with ANY plan in `planFiles`, using the
|
|
477
|
+
* identical `summaryCandidates` matching rule as `countMatchedSummaries` /
|
|
478
|
+
* `findUnsummarizedPlans`. Callers that must name orphaned summaries (a
|
|
479
|
+
* stray non-plan summary, or a summary whose plan was renamed/removed) need
|
|
480
|
+
* this instead of a bespoke exact-suffix Set-diff, which cannot recognize
|
|
481
|
+
* the nested or extended naming forms `summaryCandidates` already handles —
|
|
482
|
+
* a divergence that produced false "orphan summary" warnings.
|
|
483
|
+
*/
|
|
484
|
+
function findOrphanSummaries(planFiles, summaryFiles) {
|
|
485
|
+
const claimed = new Set();
|
|
486
|
+
for (const plan of planFiles) {
|
|
487
|
+
for (const candidate of summaryCandidates(plan))
|
|
488
|
+
claimed.add(candidate);
|
|
489
|
+
}
|
|
490
|
+
return summaryFiles.filter((s) => !claimed.has(s));
|
|
491
|
+
}
|
|
317
492
|
module.exports = {
|
|
318
493
|
toPosixPath,
|
|
494
|
+
normalizeLineEndings,
|
|
319
495
|
detectSubRepos,
|
|
320
496
|
extractOneLinerFromBody,
|
|
321
497
|
pathExistsInternal,
|
|
322
498
|
generateSlugInternal,
|
|
323
499
|
transliterateForSlug,
|
|
324
|
-
filterPlanFiles,
|
|
325
|
-
filterSummaryFiles,
|
|
326
500
|
getPhaseFileStats,
|
|
327
501
|
readSubdirectories,
|
|
328
502
|
timeAgo,
|
|
329
503
|
extractCanonicalPlanId,
|
|
330
504
|
countMatchedSummaries,
|
|
331
505
|
findUnsummarizedPlans,
|
|
506
|
+
findOrphanSummaries,
|
|
332
507
|
};
|
|
@@ -21,6 +21,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21
21
|
exports.extractDecisions = extractDecisions;
|
|
22
22
|
exports.parseDecisions = parseDecisions;
|
|
23
23
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
24
|
+
const token_scanner_cjs_1 = require("./token-scanner.cjs");
|
|
24
25
|
const DISCRETION_HEADINGS = new Set([
|
|
25
26
|
"claude's discretion",
|
|
26
27
|
'claudes discretion',
|
|
@@ -89,12 +90,14 @@ function parseDecisionLines(block) {
|
|
|
89
90
|
let category = '';
|
|
90
91
|
let inDiscretion = false;
|
|
91
92
|
let current = null;
|
|
93
|
+
let openIndent = null;
|
|
92
94
|
let parseMisses = 0;
|
|
93
95
|
const flush = () => {
|
|
94
96
|
if (current) {
|
|
95
97
|
current.text = current.text.trim();
|
|
96
98
|
out.push(current);
|
|
97
99
|
current = null;
|
|
100
|
+
openIndent = null;
|
|
98
101
|
}
|
|
99
102
|
};
|
|
100
103
|
for (const line of lines) {
|
|
@@ -115,6 +118,23 @@ function parseDecisionLines(block) {
|
|
|
115
118
|
inDiscretion = DISCRETION_HEADINGS.has(normalized);
|
|
116
119
|
continue;
|
|
117
120
|
}
|
|
121
|
+
// Nested bullet under an open decision (#3212 Phase 3, #3169): a bullet
|
|
122
|
+
// indented deeper than the currently-open decision's own bullet is that
|
|
123
|
+
// decision's elaboration (e.g. a cross-reference to a sibling decision),
|
|
124
|
+
// not a fresh declaration attempt — fold it into current.text exactly
|
|
125
|
+
// like a continuation line, before it ever reaches the declaration/
|
|
126
|
+
// parse-miss regexes below. A bullet at the SAME or a SHALLOWER indent
|
|
127
|
+
// is unaffected — tested exactly as before this fix. See design doc
|
|
128
|
+
// .gsd/phase/chore-3414-tokenizer-first-seam/40-design.md §1.3 for why
|
|
129
|
+
// nesting depth, not bullet content, is the signal that distinguishes
|
|
130
|
+
// this from a genuinely malformed top-level declaration.
|
|
131
|
+
if (current &&
|
|
132
|
+
openIndent !== null &&
|
|
133
|
+
trimmed.startsWith('-') &&
|
|
134
|
+
(0, token_scanner_cjs_1.indentWidth)(line) > openIndent) {
|
|
135
|
+
current.text += ' ' + trimmed;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
118
138
|
// Colon form: `- **D-NN[ [tags]]:** text`
|
|
119
139
|
const colonMatch = line.match(bulletColonRe);
|
|
120
140
|
if (colonMatch) {
|
|
@@ -125,6 +145,7 @@ function parseDecisionLines(block) {
|
|
|
125
145
|
: [];
|
|
126
146
|
const trackable = !inDiscretion && !tags.some((t) => NON_TRACKABLE_TAGS.has(t));
|
|
127
147
|
current = { id, text: colonMatch[3], category, tags, trackable };
|
|
148
|
+
openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
|
|
128
149
|
continue;
|
|
129
150
|
}
|
|
130
151
|
// Em-dash form: `- **D-NN[ [tags]] — title** body`
|
|
@@ -140,6 +161,7 @@ function parseDecisionLines(block) {
|
|
|
140
161
|
// title itself is embedded in the bold run but we report the body as text
|
|
141
162
|
// (consistent with how the gate cares only about coverage, not title/body split).
|
|
142
163
|
current = { id, text: emDashMatch[3] || '', category, tags, trackable };
|
|
164
|
+
openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
|
|
143
165
|
continue;
|
|
144
166
|
}
|
|
145
167
|
// Titled-colon form: `- **D-NN[ [tags]]: Title.** body` (#1639). Checked LAST — it is
|
|
@@ -155,6 +177,7 @@ function parseDecisionLines(block) {
|
|
|
155
177
|
: [];
|
|
156
178
|
const trackable = !inDiscretion && !tags.some((t) => NON_TRACKABLE_TAGS.has(t));
|
|
157
179
|
current = { id, text: titledColonMatch[3] || '', category, tags, trackable };
|
|
180
|
+
openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
|
|
158
181
|
continue;
|
|
159
182
|
}
|
|
160
183
|
// Parse-miss guard (FIX B + #1343): a line that looks like a `D-NN` decision
|
|
@@ -27,6 +27,9 @@ exports.validateResolution = validateResolution;
|
|
|
27
27
|
exports.proposeEdges = proposeEdges;
|
|
28
28
|
exports.analyzeCoverage = analyzeCoverage;
|
|
29
29
|
const probe_core_cjs_1 = require("./probe-core.cjs");
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
|
+
const cliExitModule = require("./cli-exit.cjs");
|
|
32
|
+
const { runMain } = cliExitModule;
|
|
30
33
|
/**
|
|
31
34
|
* Word-boundary cues mapping requirement prose -> data/behavior shape.
|
|
32
35
|
* Heuristic and intentionally lossy; an authored `shapes` array overrides it.
|
|
@@ -192,5 +195,10 @@ function analyzeCoverage(requirements, resolutions = []) {
|
|
|
192
195
|
* `require.main === module` so it runs only when the compiled `.cjs` is executed directly.
|
|
193
196
|
*/
|
|
194
197
|
if (require.main === module) {
|
|
195
|
-
|
|
198
|
+
// runProbeCli's default `exit` now throws ExitError (src/probe-core.cts) rather
|
|
199
|
+
// than calling process.exit directly, so this entry point must run under
|
|
200
|
+
// runMain to translate that throw into process.exitCode.
|
|
201
|
+
runMain(() => {
|
|
202
|
+
(0, probe_core_cjs_1.runProbeCli)((requirements, resolutions) => analyzeCoverage(requirements, resolutions), { usage: 'edge-probe.cjs <requirements.json> [resolutions.json]' });
|
|
203
|
+
});
|
|
196
204
|
}
|