@opengsd/gsd-core 1.9.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +27 -3
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +12 -10
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +46 -0
- package/agents/gsd-planner.md +49 -54
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +26 -73
- package/bin/install.js +1272 -1238
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +2 -2
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/plan-phase.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1009 -115
- package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
- package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
- package/gsd-core/bin/lib/api-coverage.cjs +123 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +926 -202
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +608 -148
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +507 -24
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +114 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +94 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +665 -99
- package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +76 -0
- package/gsd-core/bin/lib/config.cjs +22 -2
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +217 -40
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +239 -32
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1325 -169
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +805 -264
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +160 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
- package/gsd-core/bin/lib/io.cjs +38 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +821 -109
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +507 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +258 -58
- package/gsd-core/bin/lib/phase.cjs +891 -156
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +405 -84
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
- package/gsd-core/bin/lib/smart-entry.cjs +154 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +424 -105
- package/gsd-core/bin/lib/state.cjs +1927 -401
- package/gsd-core/bin/lib/surface.cjs +35 -10
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
- package/gsd-core/bin/lib/uat.cjs +706 -64
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +33 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -20
- package/gsd-core/bin/lib/verify.cjs +368 -880
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
- package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
- package/gsd-core/bin/lib/workstream.cjs +8 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +8 -7
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +33 -70
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +74 -166
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +160 -95
- package/gsd-core/workflows/debug.md +16 -17
- package/gsd-core/workflows/diagnose-issues.md +56 -8
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +103 -187
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +7 -6
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +35 -47
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +27 -240
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +89 -209
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +45 -159
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +55 -405
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +41 -13
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +41 -20
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +106 -13
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +53 -31
- package/gsd-core/workflows/ui-phase.md +13 -12
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +19 -8
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +17 -65
- package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
- package/hooks/dist/gsd-check-update-worker.js +64 -12
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +45 -24
- package/hooks/dist/gsd-statusline.js +90 -6
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +277 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +517 -0
- package/hooks/gsd-check-update-worker.js +64 -12
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +607 -26
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +45 -24
- package/hooks/gsd-statusline.js +90 -6
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +277 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +31 -10
- package/pi/gsd.cjs +71 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +68 -6
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +439 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +150 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-compiled-artifact-sync.cjs +6 -1
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +40 -3
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +52 -6
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +2 -2
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +2 -2
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +1 -2
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -577
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/gen-emitted-baseline.cjs +0 -145
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1050
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* gen-section-manifest.cjs — generates gsd-core/workflows/section-manifest.json
|
|
6
|
+
* from the `<!-- gsd:section -->` markers in gsd-core/workflows/*.md (ADR-1671
|
|
7
|
+
* epic #1671; Phase 5 / issue #2932 introduced the artifact,
|
|
8
|
+
* `.gsd/phase/chore-2932-init-section-manifest/40-design.md`; Phase 6.1 /
|
|
9
|
+
* issue #2992 generalized it from single-workflow to PER-WORKFLOW,
|
|
10
|
+
* `.gsd/phase/chore-2992-widen-when-vocabulary/40-design.md`).
|
|
11
|
+
*
|
|
12
|
+
* Reuses `parseWorkflowSections` from the compiled `workflow-fragments.cjs`
|
|
13
|
+
* (src/workflow-fragments.cts, Phase 3 / #2930) UNCHANGED — this module never
|
|
14
|
+
* re-implements marker parsing (design "Rejected #6"; a second parser is the
|
|
15
|
+
* `DEFECT.GENERATIVE-FIX` divergence class).
|
|
16
|
+
*
|
|
17
|
+
* The committed artifact is placed INSIDE the `gsd-core/` tree (not `docs/`,
|
|
18
|
+
* unlike `docs/CONTEXT-INDEX.json`/`docs/INVENTORY-MANIFEST.json`) because it
|
|
19
|
+
* must SHIP: `bin/install.js`'s `copyWithPathReplacement` only copies
|
|
20
|
+
* `gsd-core/`, and the init CLI's run-time selection reads this artifact
|
|
21
|
+
* from the INSTALLED tree, not the dev repo. `copyWithPathReplacement`
|
|
22
|
+
* only runs `composeWorkflow`/converters on `*.md` — a `.json` leaf falls
|
|
23
|
+
* through to a plain `fs.copyFileSync`, so the artifact ships byte-identical.
|
|
24
|
+
*
|
|
25
|
+
* Shape (#2992 Phase 6.1): `{ "workflows": { "<workflow-name>": [{id, when,
|
|
26
|
+
* read}, ...], ... } }`, where `<workflow-name>` is a source `.md` file's
|
|
27
|
+
* basename without extension. A workflow with zero explicit sections
|
|
28
|
+
* contributes NO key at all (absence, not `[]` — an init caller for that
|
|
29
|
+
* workflow must degrade to `null`, never be attributed some OTHER workflow's
|
|
30
|
+
* sections). Keys are serialized in sorted (filename) order; each workflow's
|
|
31
|
+
* own sections stay in document order. The pre-6.1 shape was a single flat
|
|
32
|
+
* `{ sections: [...] }` array with no workflow key at all — `isValidManifestShape`
|
|
33
|
+
* REJECTS that shape outright so a stale committed artifact can never be
|
|
34
|
+
* mis-attributed to whichever workflow asks first (design row C4).
|
|
35
|
+
*
|
|
36
|
+
* Per design's "Rejected" list, a workflow's section list carries NEITHER
|
|
37
|
+
* section content (would duplicate every section's bytes, fighting Phase 4's
|
|
38
|
+
* emitted-byte caps) NOR line numbers (re-drifts on any line shift, and
|
|
39
|
+
* per-runtime converters rewrite text so ranges would differ per runtime).
|
|
40
|
+
* It carries only `{id, when, read}` triples — `read` is a POSIX-normalized
|
|
41
|
+
* path, relative to the repo root, of the step file the section body was
|
|
42
|
+
* moved to.
|
|
43
|
+
*
|
|
44
|
+
* Usage:
|
|
45
|
+
* node scripts/gen-section-manifest.cjs # print to stdout
|
|
46
|
+
* node scripts/gen-section-manifest.cjs --write # write the manifest
|
|
47
|
+
* node scripts/gen-section-manifest.cjs --check # exit 1 if stale/invalid
|
|
48
|
+
* node scripts/gen-section-manifest.cjs --check --json # same, + typed report
|
|
49
|
+
* node scripts/gen-section-manifest.cjs --write --workflows-dir <p> --manifest-path <p>
|
|
50
|
+
* # override the two hardcoded
|
|
51
|
+
* # repo-root paths (tests point
|
|
52
|
+
* # the real CLI at a temp fixture
|
|
53
|
+
* # tree with no fs monkeypatching)
|
|
54
|
+
*
|
|
55
|
+
* Only `.md` files directly inside `--workflows-dir` are scanned (not files
|
|
56
|
+
* already inside a `<workflow>/steps/` subdirectory — those are MOVED-TO
|
|
57
|
+
* output, never source-with-markers). A workflow with zero explicit sections
|
|
58
|
+
* (most workflows today) contributes no key to `workflows` and is never
|
|
59
|
+
* orphan-checked — orphan-checking is scoped only to a workflow's OWN
|
|
60
|
+
* `steps/` directory, and only for workflows that declare at least one
|
|
61
|
+
* `gsd:section` marker.
|
|
62
|
+
*
|
|
63
|
+
* `--check` fails closed (exit 1, never a stack trace) on:
|
|
64
|
+
* - the compiled `workflow-fragments.cjs` dependency being unbuilt/unloadable
|
|
65
|
+
* (FAIL_LIB_NOT_BUILT — `npm run build:lib` has not run)
|
|
66
|
+
* - the live build itself failing: a marker's derived step file does not
|
|
67
|
+
* exist on disk (FAIL_MISSING_STEP_FILE), a step file in a managed
|
|
68
|
+
* workflow's `steps/` dir is referenced by no marker AND no reachable
|
|
69
|
+
* prose reference (FAIL_ORPHAN_STEP_FILE — see `findOrphanStepFiles`),
|
|
70
|
+
* or the source itself fails to parse (FAIL_SOURCE_PARSE_ERROR, wraps
|
|
71
|
+
* `parseWorkflowSections`' typed `WorkflowFragmentsError`)
|
|
72
|
+
* - the committed manifest: absent (FAIL_MANIFEST_MISSING), empty/unparseable
|
|
73
|
+
* JSON (FAIL_MANIFEST_UNPARSEABLE), valid JSON but the wrong shape
|
|
74
|
+
* (FAIL_MANIFEST_MALFORMED_SHAPE — `0`, `"s"`, `[]`, `null`, `true` all
|
|
75
|
+
* land here), or parseable-and-shaped but not equal to the live build
|
|
76
|
+
* (FAIL_STALE)
|
|
77
|
+
*
|
|
78
|
+
* `--write --json` reports the same typed envelope on failure: the compiled
|
|
79
|
+
* dependency being unloadable (FAIL_LIB_NOT_BUILT, same as above) or the
|
|
80
|
+
* atomic write itself failing (FAIL_WRITE_ERROR — see `writeManifestAtomically`).
|
|
81
|
+
*
|
|
82
|
+
* Orphan detection (`findOrphanStepFiles`) is plain substring reachability
|
|
83
|
+
* over prose, NOT a second marker parser: starting from the parent workflow's
|
|
84
|
+
* raw text, it does a fixed-point search for the literal token
|
|
85
|
+
* `steps/<basename>` across the parent text and every already-reached step
|
|
86
|
+
* file's own text (so a step file that is itself an on-demand delegation
|
|
87
|
+
* TARGET of another step file — e.g. `regression-gate.md`'s own "Read and
|
|
88
|
+
* execute `.../steps/regression-gate-run.md`" line — is correctly resolved
|
|
89
|
+
* as non-orphan without the generator needing to know that convention).
|
|
90
|
+
*
|
|
91
|
+
* `--write` writes atomically: content lands at a same-directory temp path
|
|
92
|
+
* first, then `fs.renameSync` swaps it into place. If either step throws, the
|
|
93
|
+
* temp path is removed (best-effort) and the target manifest is left exactly
|
|
94
|
+
* as it was — never truncated or partially written.
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
const fs = require('node:fs');
|
|
98
|
+
const path = require('node:path');
|
|
99
|
+
|
|
100
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
101
|
+
|
|
102
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
103
|
+
const WORKFLOWS_DIR = path.join(ROOT, 'gsd-core', 'workflows');
|
|
104
|
+
const MANIFEST_PATH = path.join(WORKFLOWS_DIR, 'section-manifest.json');
|
|
105
|
+
|
|
106
|
+
// ─── Typed reason enum (CONTRIBUTING.md "Prohibited: Raw Text Matching") ───────
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Stable reason codes for `checkReport`'s `reason` field. Tests assert via
|
|
110
|
+
* `assert.equal(report.reason, REASON.X)` rather than regex-matching the
|
|
111
|
+
* human-readable prose the non-JSON `--check` mode still writes to
|
|
112
|
+
* stdout/stderr.
|
|
113
|
+
*
|
|
114
|
+
* Adding a new reason requires updating this map AND the test that locks
|
|
115
|
+
* `Object.keys(REASON).sort()` as a coordinated change.
|
|
116
|
+
*/
|
|
117
|
+
const REASON = Object.freeze({
|
|
118
|
+
OK_UP_TO_DATE: 'ok_up_to_date',
|
|
119
|
+
FAIL_STALE: 'fail_stale',
|
|
120
|
+
FAIL_MANIFEST_MISSING: 'fail_manifest_missing',
|
|
121
|
+
FAIL_MANIFEST_UNPARSEABLE: 'fail_manifest_unparseable',
|
|
122
|
+
FAIL_MANIFEST_MALFORMED_SHAPE: 'fail_manifest_malformed_shape',
|
|
123
|
+
FAIL_MISSING_STEP_FILE: 'fail_missing_step_file',
|
|
124
|
+
FAIL_ORPHAN_STEP_FILE: 'fail_orphan_step_file',
|
|
125
|
+
FAIL_SOURCE_PARSE_ERROR: 'fail_source_parse_error',
|
|
126
|
+
FAIL_LIB_NOT_BUILT: 'fail_lib_not_built',
|
|
127
|
+
FAIL_WRITE_ERROR: 'fail_write_error',
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ─── Loaders ──────────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
const WORKFLOW_FRAGMENTS_LIB_PATH = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'workflow-fragments.cjs');
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Load the compiled workflow-fragments library. The artifact is a gitignored
|
|
136
|
+
* tsc build output of src/workflow-fragments.cts and only exists after
|
|
137
|
+
* `npm run build:lib`. Throws a clean `ManifestBuildError` (REASON.FAIL_LIB_NOT_BUILT;
|
|
138
|
+
* never a bare MODULE_NOT_FOUND stack) naming the remedy when it is missing —
|
|
139
|
+
* `ManifestBuildError` extends `ExitError`, so `runMain` still prints only the
|
|
140
|
+
* friendly message, and `checkReport`/the `--write --json` path can still read
|
|
141
|
+
* `.reason`/`.subject` off it to emit the typed envelope.
|
|
142
|
+
*
|
|
143
|
+
* @returns {{ parseWorkflowSections: Function }}
|
|
144
|
+
*/
|
|
145
|
+
function loadWorkflowFragmentsLib() {
|
|
146
|
+
try {
|
|
147
|
+
delete require.cache[require.resolve(WORKFLOW_FRAGMENTS_LIB_PATH)];
|
|
148
|
+
return require(WORKFLOW_FRAGMENTS_LIB_PATH);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
const subject = relPosix(ROOT, WORKFLOW_FRAGMENTS_LIB_PATH);
|
|
151
|
+
throw new ManifestBuildError(
|
|
152
|
+
REASON.FAIL_LIB_NOT_BUILT,
|
|
153
|
+
subject,
|
|
154
|
+
`Cannot load ${subject}: ${err && err.message}\n` +
|
|
155
|
+
'Run:\n npm run build:lib\n',
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ─── POSIX path helpers ─────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
/** Unconditional backslash->forward-slash normalization (CONTEXT.md
|
|
163
|
+
* path-separator-normalization rule: never gate on `path.sep`). */
|
|
164
|
+
function toPosix(p) {
|
|
165
|
+
return p.replace(/\\/g, '/');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Repo-root-relative POSIX path for a file under `repoRoot`.
|
|
170
|
+
*
|
|
171
|
+
* @param {string} repoRoot
|
|
172
|
+
* @param {string} absPath
|
|
173
|
+
*/
|
|
174
|
+
function relPosix(repoRoot, absPath) {
|
|
175
|
+
return toPosix(path.relative(repoRoot, absPath));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ─── Orphan detection (plain substring reachability, not a marker parser) ────
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Fixed-point reachability scan over a managed workflow's `steps/` directory:
|
|
182
|
+
* a step file is "reached" once the literal token `steps/<its-basename>`
|
|
183
|
+
* appears in the parent workflow's raw text OR in the text of any
|
|
184
|
+
* already-reached step file (so nested delegation — a step file that itself
|
|
185
|
+
* names another step file — resolves without the generator knowing that
|
|
186
|
+
* convention explicitly). Returns the SORTED list of `.md` basenames in
|
|
187
|
+
* `stepsDir` that are never reached (i.e. orphans). Returns `[]` if
|
|
188
|
+
* `stepsDir` does not exist.
|
|
189
|
+
*
|
|
190
|
+
* @param {string} parentText - the workflow.md's raw source text
|
|
191
|
+
* @param {string} stepsDir - absolute path to `<workflow>/steps/`
|
|
192
|
+
* @returns {string[]}
|
|
193
|
+
*/
|
|
194
|
+
function findOrphanStepFiles(parentText, stepsDir) {
|
|
195
|
+
if (!fs.existsSync(stepsDir)) return [];
|
|
196
|
+
const files = fs
|
|
197
|
+
.readdirSync(stepsDir, { withFileTypes: true })
|
|
198
|
+
.filter((d) => d.isFile() && d.name.endsWith('.md'))
|
|
199
|
+
.map((d) => d.name)
|
|
200
|
+
.sort();
|
|
201
|
+
|
|
202
|
+
const textOf = new Map(files.map((f) => [f, fs.readFileSync(path.join(stepsDir, f), 'utf8')]));
|
|
203
|
+
const reached = new Set();
|
|
204
|
+
const haystacks = [parentText];
|
|
205
|
+
|
|
206
|
+
let changed = true;
|
|
207
|
+
while (changed) {
|
|
208
|
+
changed = false;
|
|
209
|
+
for (const f of files) {
|
|
210
|
+
if (reached.has(f)) continue;
|
|
211
|
+
const token = `steps/${f}`;
|
|
212
|
+
if (haystacks.some((h) => h.includes(token))) {
|
|
213
|
+
reached.add(f);
|
|
214
|
+
haystacks.push(textOf.get(f));
|
|
215
|
+
changed = true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return files.filter((f) => !reached.has(f));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ─── Live manifest build ─────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
/** Thrown by `buildFreshManifest`/`loadWorkflowFragmentsLib`/`writeManifestAtomically`
|
|
226
|
+
* for every fail-closed condition below; carries a stable `reason` + the
|
|
227
|
+
* offending `subject` path so `checkReport`/the `--write --json` path never
|
|
228
|
+
* need to string-match a message. Extends `ExitError` (not plain `Error`) so
|
|
229
|
+
* `runMain` still prints only the friendly `message` — never a bare stack
|
|
230
|
+
* trace — for the `default`/`--write` (non-`--json`) code paths that let it
|
|
231
|
+
* propagate uncaught, exactly like every other `ExitError`. */
|
|
232
|
+
class ManifestBuildError extends ExitError {
|
|
233
|
+
constructor(reason, subject, message) {
|
|
234
|
+
super(1, message);
|
|
235
|
+
this.name = 'ManifestBuildError';
|
|
236
|
+
this.reason = reason;
|
|
237
|
+
this.subject = subject;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Scan `workflowsDir` for `.md` files carrying `gsd:section` markers and
|
|
243
|
+
* build the live (freshly-derived) manifest: `{ workflows: { <name>:
|
|
244
|
+
* [{id, when, read}], ... } }` (#2992 Phase 6.1). Workflow keys are
|
|
245
|
+
* serialized in sorted (filename) order; a workflow's own sections stay in
|
|
246
|
+
* document order. A workflow with zero explicit sections contributes NO key
|
|
247
|
+
* at all — absence, not `[]` (design row C4/C11: absence must degrade to
|
|
248
|
+
* `null` at the init seam, never be confused with "computed, no sections").
|
|
249
|
+
* Throws `ManifestBuildError` on any fail-closed condition (missing step
|
|
250
|
+
* file, orphan step file, unparseable source).
|
|
251
|
+
*
|
|
252
|
+
* @param {string} workflowsDir - defaults to the real repo-root gsd-core/workflows/
|
|
253
|
+
* @param {string} repoRoot - root `read` paths are computed relative to
|
|
254
|
+
* @returns {{ workflows: Record<string, Array<{id: string, when: string, read: string}>> }}
|
|
255
|
+
*/
|
|
256
|
+
function buildFreshManifest(workflowsDir = WORKFLOWS_DIR, repoRoot = ROOT) {
|
|
257
|
+
const { parseWorkflowSections } = loadWorkflowFragmentsLib();
|
|
258
|
+
|
|
259
|
+
const workflowFiles = fs
|
|
260
|
+
.readdirSync(workflowsDir, { withFileTypes: true })
|
|
261
|
+
.filter((d) => d.isFile() && d.name.endsWith('.md'))
|
|
262
|
+
.map((d) => d.name)
|
|
263
|
+
.sort();
|
|
264
|
+
|
|
265
|
+
const workflows = {};
|
|
266
|
+
|
|
267
|
+
for (const fileName of workflowFiles) {
|
|
268
|
+
const filePath = path.join(workflowsDir, fileName);
|
|
269
|
+
const relSourcePath = relPosix(repoRoot, filePath);
|
|
270
|
+
let content;
|
|
271
|
+
try {
|
|
272
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
273
|
+
} catch (err) {
|
|
274
|
+
throw new ManifestBuildError(REASON.FAIL_SOURCE_PARSE_ERROR, relSourcePath, `Cannot read ${relSourcePath}: ${err && err.message}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let parsed;
|
|
278
|
+
try {
|
|
279
|
+
parsed = parseWorkflowSections(content, relSourcePath);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
throw new ManifestBuildError(REASON.FAIL_SOURCE_PARSE_ERROR, relSourcePath, `${relSourcePath}: ${err && err.message}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const explicitSections = parsed.filter((s) => s.explicit);
|
|
285
|
+
if (explicitSections.length === 0) continue;
|
|
286
|
+
|
|
287
|
+
const workflowName = fileName.replace(/\.md$/, '');
|
|
288
|
+
const stepsDir = path.join(workflowsDir, workflowName, 'steps');
|
|
289
|
+
const sections = [];
|
|
290
|
+
|
|
291
|
+
for (const section of explicitSections) {
|
|
292
|
+
const stepFileAbs = path.join(stepsDir, `${section.id}.md`);
|
|
293
|
+
if (!fs.existsSync(stepFileAbs)) {
|
|
294
|
+
const relStepPath = relPosix(repoRoot, stepFileAbs);
|
|
295
|
+
throw new ManifestBuildError(
|
|
296
|
+
REASON.FAIL_MISSING_STEP_FILE,
|
|
297
|
+
relStepPath,
|
|
298
|
+
`${relSourcePath}: section "${section.id}" (when="${section.when}") names step file ${relStepPath}, which does not exist`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
sections.push({
|
|
302
|
+
id: section.id,
|
|
303
|
+
when: section.when,
|
|
304
|
+
read: relPosix(repoRoot, stepFileAbs),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const orphans = findOrphanStepFiles(content, stepsDir);
|
|
309
|
+
if (orphans.length > 0) {
|
|
310
|
+
const relOrphanPath = relPosix(repoRoot, path.join(stepsDir, orphans[0]));
|
|
311
|
+
throw new ManifestBuildError(
|
|
312
|
+
REASON.FAIL_ORPHAN_STEP_FILE,
|
|
313
|
+
relOrphanPath,
|
|
314
|
+
`${relOrphanPath} is not referenced by any gsd:section marker or reachable "steps/" reference in ${relSourcePath}`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
workflows[workflowName] = sections;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return { workflows };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ─── Serialization ────────────────────────────────────────────────────────────
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* @param {{ workflows: Record<string, Array<{id: string, when: string, read: string}>> }} manifest
|
|
328
|
+
* @returns {string}
|
|
329
|
+
*/
|
|
330
|
+
function serializeManifest(manifest) {
|
|
331
|
+
return JSON.stringify(manifest, null, 2) + '\n';
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Workflow-count / section-count pair for a manifest's `workflows` map, used
|
|
336
|
+
* by the `--write` stdout summary and the `--check` OK message.
|
|
337
|
+
*
|
|
338
|
+
* @param {{ workflows: Record<string, Array<{id: string, when: string, read: string}>> }} manifest
|
|
339
|
+
* @returns {{ workflowCount: number, sectionCount: number }}
|
|
340
|
+
*/
|
|
341
|
+
function countManifest(manifest) {
|
|
342
|
+
const workflowNames = Object.keys(manifest.workflows);
|
|
343
|
+
const sectionCount = workflowNames.reduce((sum, name) => sum + manifest.workflows[name].length, 0);
|
|
344
|
+
return { workflowCount: workflowNames.length, sectionCount };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* True when `parsed` has the expected committed-manifest shape (#2992 Phase
|
|
349
|
+
* 6.1): a plain object (not an array, not null) carrying a `workflows` plain
|
|
350
|
+
* object (not an array, not null) whose every own value is an array of
|
|
351
|
+
* `{id, when, read}` string triples. Rejects `0`, `"s"`, `[]`, `null`, `true`
|
|
352
|
+
* — AND rejects the pre-6.1 flat `{sections:[...]}` shape (no `workflows`
|
|
353
|
+
* key ⇒ `parsed.workflows` is `undefined`, which fails the object check),
|
|
354
|
+
* so a stale committed artifact can never be silently mis-attributed to
|
|
355
|
+
* whichever workflow asks first (design row C4).
|
|
356
|
+
*
|
|
357
|
+
* @param {unknown} parsed
|
|
358
|
+
* @returns {boolean}
|
|
359
|
+
*/
|
|
360
|
+
function isValidManifestShape(parsed) {
|
|
361
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return false;
|
|
362
|
+
const workflows = parsed.workflows;
|
|
363
|
+
if (workflows === null || typeof workflows !== 'object' || Array.isArray(workflows)) return false;
|
|
364
|
+
return Object.values(workflows).every(
|
|
365
|
+
(sections) =>
|
|
366
|
+
Array.isArray(sections) &&
|
|
367
|
+
sections.every(
|
|
368
|
+
(s) => s !== null && typeof s === 'object' && typeof s.id === 'string' && typeof s.when === 'string' && typeof s.read === 'string',
|
|
369
|
+
),
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ─── Atomic write ─────────────────────────────────────────────────────────────
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Write `content` to `targetPath` atomically: write to a same-directory temp
|
|
377
|
+
* path, then `fs.renameSync` it into place (same filesystem, so the rename is
|
|
378
|
+
* atomic). On ANY failure (the write or the rename), the temp path is removed
|
|
379
|
+
* best-effort and a `ManifestBuildError` (REASON.FAIL_WRITE_ERROR, subject
|
|
380
|
+
* `targetPath`) is thrown — `targetPath` is left exactly as it was before the
|
|
381
|
+
* call, never truncated or partially written. `ManifestBuildError` extends
|
|
382
|
+
* `ExitError`, so a caller that lets it propagate uncaught (e.g. `--write`
|
|
383
|
+
* without `--json`) still gets only the friendly message, never a stack trace.
|
|
384
|
+
*
|
|
385
|
+
* @param {string} targetPath
|
|
386
|
+
* @param {string} content
|
|
387
|
+
*/
|
|
388
|
+
function writeManifestAtomically(targetPath, content) {
|
|
389
|
+
const tmpPath = path.join(path.dirname(targetPath), `.${path.basename(targetPath)}.tmp-${process.pid}-${Date.now()}`);
|
|
390
|
+
try {
|
|
391
|
+
fs.writeFileSync(tmpPath, content, 'utf8');
|
|
392
|
+
fs.renameSync(tmpPath, targetPath);
|
|
393
|
+
} catch (err) {
|
|
394
|
+
try {
|
|
395
|
+
fs.unlinkSync(tmpPath);
|
|
396
|
+
} catch (_cleanupErr) {
|
|
397
|
+
// best-effort: tmpPath may never have been created (writeFileSync itself threw)
|
|
398
|
+
}
|
|
399
|
+
throw new ManifestBuildError(REASON.FAIL_WRITE_ERROR, targetPath, `Cannot write ${targetPath}: ${err && err.message}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ─── Typed check report ───────────────────────────────────────────────────────
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Empty-report shape shared by every early-exit branch below.
|
|
407
|
+
*
|
|
408
|
+
* @returns {{ subject: string | null }}
|
|
409
|
+
*/
|
|
410
|
+
function emptyReportFields() {
|
|
411
|
+
return { subject: null };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Compute the full `--check` result as a typed, non-throwing report.
|
|
416
|
+
*
|
|
417
|
+
* @param {string} [workflowsDir]
|
|
418
|
+
* @param {string} [manifestPath]
|
|
419
|
+
* @param {string} [repoRoot]
|
|
420
|
+
* @returns {{ ok: boolean, reason: string, subject: string | null, message: string }}
|
|
421
|
+
*/
|
|
422
|
+
function checkReport(workflowsDir = WORKFLOWS_DIR, manifestPath = MANIFEST_PATH, repoRoot = ROOT) {
|
|
423
|
+
let live;
|
|
424
|
+
try {
|
|
425
|
+
live = buildFreshManifest(workflowsDir, repoRoot);
|
|
426
|
+
} catch (err) {
|
|
427
|
+
if (err instanceof ManifestBuildError) {
|
|
428
|
+
return { ok: false, reason: err.reason, subject: err.subject, message: `${err.message}\n` };
|
|
429
|
+
}
|
|
430
|
+
throw err;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!fs.existsSync(manifestPath)) {
|
|
434
|
+
return {
|
|
435
|
+
ok: false,
|
|
436
|
+
reason: REASON.FAIL_MANIFEST_MISSING,
|
|
437
|
+
...emptyReportFields(),
|
|
438
|
+
subject: manifestPath,
|
|
439
|
+
message: `${manifestPath} does not exist. Run:\n node scripts/gen-section-manifest.cjs --write\n`,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let committedText;
|
|
444
|
+
try {
|
|
445
|
+
committedText = fs.readFileSync(manifestPath, 'utf8');
|
|
446
|
+
} catch (err) {
|
|
447
|
+
return {
|
|
448
|
+
ok: false,
|
|
449
|
+
reason: REASON.FAIL_MANIFEST_UNPARSEABLE,
|
|
450
|
+
subject: manifestPath,
|
|
451
|
+
message: `Cannot read ${manifestPath}: ${err && err.message}\n`,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
let committed;
|
|
456
|
+
try {
|
|
457
|
+
committed = JSON.parse(committedText);
|
|
458
|
+
} catch (err) {
|
|
459
|
+
return {
|
|
460
|
+
ok: false,
|
|
461
|
+
reason: REASON.FAIL_MANIFEST_UNPARSEABLE,
|
|
462
|
+
subject: manifestPath,
|
|
463
|
+
message: `${manifestPath} is not valid JSON: ${err && err.message}\n` +
|
|
464
|
+
'Run:\n node scripts/gen-section-manifest.cjs --write\n',
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (!isValidManifestShape(committed)) {
|
|
469
|
+
return {
|
|
470
|
+
ok: false,
|
|
471
|
+
reason: REASON.FAIL_MANIFEST_MALFORMED_SHAPE,
|
|
472
|
+
subject: manifestPath,
|
|
473
|
+
message: `${manifestPath} is valid JSON but does not have the expected {workflows:{<name>:[{id,when,read}]}} shape.\n` +
|
|
474
|
+
'Run:\n node scripts/gen-section-manifest.cjs --write\n',
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (JSON.stringify(committed) !== JSON.stringify(live)) {
|
|
479
|
+
return {
|
|
480
|
+
ok: false,
|
|
481
|
+
reason: REASON.FAIL_STALE,
|
|
482
|
+
subject: manifestPath,
|
|
483
|
+
message: `${manifestPath} is stale. Run:\n node scripts/gen-section-manifest.cjs --write\n`,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const { workflowCount, sectionCount } = countManifest(live);
|
|
488
|
+
return {
|
|
489
|
+
ok: true,
|
|
490
|
+
reason: REASON.OK_UP_TO_DATE,
|
|
491
|
+
subject: null,
|
|
492
|
+
message: `${manifestPath} is up to date (${workflowCount} workflow${workflowCount === 1 ? '' : 's'}, ${sectionCount} section${sectionCount === 1 ? '' : 's'}).\n`,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// ─── Argument parsing ─────────────────────────────────────────────────────────
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* @param {string|undefined} value
|
|
500
|
+
* @returns {boolean}
|
|
501
|
+
*/
|
|
502
|
+
function isMissingPathValue(value) {
|
|
503
|
+
return value === undefined || value === '' || value.startsWith('-');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* @param {string[]} argv
|
|
508
|
+
* @returns {{ mode: 'check'|'write'|'default'|'unknown', json: boolean, workflowsDir: string, manifestPath: string, repoRoot: string, unknownArg?: string, usageMessage?: string }}
|
|
509
|
+
*/
|
|
510
|
+
function parseArgs(argv) {
|
|
511
|
+
const opts = { mode: 'default', json: false, workflowsDir: WORKFLOWS_DIR, manifestPath: MANIFEST_PATH, repoRoot: ROOT };
|
|
512
|
+
let sawCheck = false;
|
|
513
|
+
let sawWrite = false;
|
|
514
|
+
|
|
515
|
+
for (let i = 0; i < argv.length; i++) {
|
|
516
|
+
const arg = argv[i];
|
|
517
|
+
if (arg === '--check') {
|
|
518
|
+
sawCheck = true;
|
|
519
|
+
opts.mode = 'check';
|
|
520
|
+
} else if (arg === '--write') {
|
|
521
|
+
sawWrite = true;
|
|
522
|
+
opts.mode = 'write';
|
|
523
|
+
} else if (arg === '--json') {
|
|
524
|
+
opts.json = true;
|
|
525
|
+
} else if (arg === '--workflows-dir' || arg === '--manifest-path' || arg === '--repo-root') {
|
|
526
|
+
const value = argv[i + 1];
|
|
527
|
+
if (isMissingPathValue(value)) {
|
|
528
|
+
return {
|
|
529
|
+
...opts,
|
|
530
|
+
mode: 'unknown',
|
|
531
|
+
unknownArg: arg,
|
|
532
|
+
usageMessage: `${arg} requires a non-empty path argument (got ${value === undefined ? 'nothing' : JSON.stringify(value)})`,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
i++;
|
|
536
|
+
if (arg === '--workflows-dir') opts.workflowsDir = path.resolve(value);
|
|
537
|
+
else if (arg === '--manifest-path') opts.manifestPath = path.resolve(value);
|
|
538
|
+
else opts.repoRoot = path.resolve(value);
|
|
539
|
+
} else {
|
|
540
|
+
opts.mode = 'unknown';
|
|
541
|
+
opts.unknownArg = arg;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (sawCheck && sawWrite) {
|
|
546
|
+
return { ...opts, mode: 'unknown', usageMessage: '--check and --write are mutually exclusive' };
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return opts;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
553
|
+
|
|
554
|
+
function main() {
|
|
555
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
556
|
+
|
|
557
|
+
if (opts.mode === 'unknown') {
|
|
558
|
+
process.stderr.write('Usage: gen-section-manifest.cjs [--write|--check] [--json] [--workflows-dir <path>] [--manifest-path <path>] [--repo-root <path>]\n');
|
|
559
|
+
if (opts.usageMessage) process.stderr.write(`${opts.usageMessage}\n`);
|
|
560
|
+
throw new ExitError(1);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (opts.mode === 'default') {
|
|
564
|
+
process.stdout.write(serializeManifest(buildFreshManifest(opts.workflowsDir, opts.repoRoot)));
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (opts.mode === 'check') {
|
|
569
|
+
const report = checkReport(opts.workflowsDir, opts.manifestPath, opts.repoRoot);
|
|
570
|
+
|
|
571
|
+
if (opts.json) {
|
|
572
|
+
process.stdout.write(JSON.stringify({ ok: report.ok, reason: report.reason, subject: report.subject }) + '\n');
|
|
573
|
+
} else if (report.ok) {
|
|
574
|
+
process.stdout.write(report.message);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (!report.ok) {
|
|
578
|
+
throw new ExitError(1, opts.json ? undefined : report.message);
|
|
579
|
+
}
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// opts.mode === 'write'. Same typed-envelope pattern as --check --json above:
|
|
584
|
+
// a ManifestBuildError (FAIL_LIB_NOT_BUILT from buildFreshManifest,
|
|
585
|
+
// FAIL_WRITE_ERROR from writeManifestAtomically) is caught here so --json
|
|
586
|
+
// still emits {ok, reason, subject} instead of letting the error propagate
|
|
587
|
+
// to runMain unobserved by the JSON caller.
|
|
588
|
+
let manifest;
|
|
589
|
+
let writeErr;
|
|
590
|
+
try {
|
|
591
|
+
manifest = buildFreshManifest(opts.workflowsDir, opts.repoRoot);
|
|
592
|
+
writeManifestAtomically(opts.manifestPath, serializeManifest(manifest));
|
|
593
|
+
} catch (err) {
|
|
594
|
+
if (!(err instanceof ManifestBuildError)) throw err;
|
|
595
|
+
writeErr = err;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (opts.json) {
|
|
599
|
+
process.stdout.write(JSON.stringify(
|
|
600
|
+
writeErr
|
|
601
|
+
? { ok: false, reason: writeErr.reason, subject: writeErr.subject }
|
|
602
|
+
: { ok: true, reason: REASON.OK_UP_TO_DATE, subject: null },
|
|
603
|
+
) + '\n');
|
|
604
|
+
} else if (!writeErr) {
|
|
605
|
+
const { workflowCount, sectionCount } = countManifest(manifest);
|
|
606
|
+
process.stdout.write(`Wrote ${opts.manifestPath}\n ${workflowCount} workflow${workflowCount === 1 ? '' : 's'}, ${sectionCount} section${sectionCount === 1 ? '' : 's'}\n`);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (writeErr) {
|
|
610
|
+
throw new ExitError(1, opts.json ? undefined : writeErr.message);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// ─── Exports (for tests) ──────────────────────────────────────────────────────
|
|
615
|
+
|
|
616
|
+
module.exports = {
|
|
617
|
+
loadWorkflowFragmentsLib,
|
|
618
|
+
findOrphanStepFiles,
|
|
619
|
+
buildFreshManifest,
|
|
620
|
+
serializeManifest,
|
|
621
|
+
countManifest,
|
|
622
|
+
isValidManifestShape,
|
|
623
|
+
writeManifestAtomically,
|
|
624
|
+
checkReport,
|
|
625
|
+
parseArgs,
|
|
626
|
+
ManifestBuildError,
|
|
627
|
+
REASON,
|
|
628
|
+
WORKFLOW_FRAGMENTS_LIB_PATH,
|
|
629
|
+
WORKFLOWS_DIR,
|
|
630
|
+
MANIFEST_PATH,
|
|
631
|
+
ROOT,
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
// ─── CLI entry point ──────────────────────────────────────────────────────────
|
|
635
|
+
|
|
636
|
+
if (require.main === module) {
|
|
637
|
+
runMain(main);
|
|
638
|
+
}
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* `deriveIdentity(pkg)` is the pure core: it turns a parsed package.json into
|
|
8
8
|
* the coordinate record every consumer needs. The generated runtime module
|
|
9
9
|
* `gsd-core/bin/lib/package-identity.cjs` bakes those values at build
|
|
10
|
-
* time, because the installed tree carries
|
|
11
|
-
* `{"type":"commonjs"}`
|
|
10
|
+
* time, because the installed tree carries no package.json with a `.name` —
|
|
11
|
+
* the only ones GSD stages are synthetic `{"type":"commonjs"}` markers, which
|
|
12
|
+
* since #2544 live inside GSD's own directories (`hooks/`, and the native
|
|
13
|
+
* plugin dir) rather than at the config root — so a runtime
|
|
12
14
|
* `require('package.json').name` resolves to `undefined` (the #378 bug this
|
|
13
15
|
* seam retires). Baking from package.json reconciles #378 (renames survive)
|
|
14
16
|
* with #2992 (the value is never an LLM runtime choice).
|