@opengsd/gsd-core 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +458 -0
- package/agents/gsd-code-fixer.md +5 -5
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +345 -0
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +63 -35
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +444 -134
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +1 -0
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +2 -1
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/surface.md +18 -8
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +532 -174
- package/gsd-core/bin/lib/adr-parser.cjs +1 -1
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +39 -22
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +528 -116
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +134 -5
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +198 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +981 -79
- package/gsd-core/bin/lib/config-loader.cjs +4 -0
- package/gsd-core/bin/lib/config.cjs +153 -38
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +343 -28
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +539 -60
- package/gsd-core/bin/lib/install-engine.cjs +199 -14
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +36 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +41 -10
- package/gsd-core/bin/lib/model-resolver.cjs +101 -10
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
- package/gsd-core/bin/lib/phase-id.cjs +412 -31
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase.cjs +941 -98
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
- package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
- package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
- package/gsd-core/bin/lib/roadmap.cjs +292 -69
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +159 -32
- package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
- package/gsd-core/bin/lib/state-transition.cjs +465 -62
- package/gsd-core/bin/lib/state.cjs +906 -151
- package/gsd-core/bin/lib/surface.cjs +83 -10
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1420 -516
- package/gsd-core/bin/lib/update-context.cjs +36 -26
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +316 -23
- package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +531 -36
- package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +12 -3
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +5 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +17 -9
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/worktree-path-safety.md +112 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/phase-prompt.md +4 -0
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +15 -10
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +4 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
- package/gsd-core/workflows/code-review-fix.md +5 -3
- package/gsd-core/workflows/code-review.md +211 -43
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +40 -254
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +15 -156
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +78 -159
- package/gsd-core/workflows/execute-plan.md +28 -15
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +3 -1
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +52 -3
- package/gsd-core/workflows/milestone-summary.md +2 -0
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +55 -13
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project.md +37 -205
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +19 -4
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +144 -185
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +30 -10
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +21 -4
- package/gsd-core/workflows/reapply-patches.md +79 -3
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +19 -6
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +48 -43
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work.md +68 -182
- package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +371 -27
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1105 -0
- package/hooks/dist/gsd-statusline.js +18 -10
- package/hooks/dist/gsd-validate-commit.sh +474 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +42 -16
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +371 -27
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1105 -0
- package/hooks/gsd-statusline.js +18 -10
- package/hooks/gsd-validate-commit.sh +474 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -9
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +11 -4
- package/scripts/check-contract-drift.cjs +4 -1
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +133 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +17 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +465 -15
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +530 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-test-file-count.allowlist.json +4 -1
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +22 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +1 -0
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +2 -1
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* File-overlap partitioner — shared greedy first-fit stage assignment (#3674).
|
|
4
|
+
*
|
|
5
|
+
* Extracted from `claude-orchestration.cts`'s `partitionStages` (#1143), which
|
|
6
|
+
* emits sequential Workflow `parallel()` stage barriers so that no two plans
|
|
7
|
+
* sharing a `files_modified` entry ever cohabit a stage. This module is the
|
|
8
|
+
* SAME algorithm, generalized: it depends on no `Plan`/`Wave` interface from
|
|
9
|
+
* `claude-orchestration.cts` (or any other caller-specific shape), so a future
|
|
10
|
+
* consumer (quick-batch, #3675 / ADR-1239 "Quick-batch binding") can partition
|
|
11
|
+
* its own planned-path items without pulling in orchestration internals.
|
|
12
|
+
*
|
|
13
|
+
* This is a pure, behavior-preserving extraction (#3674's acceptance bar is
|
|
14
|
+
* byte-identical output for `claude-orchestration.cts`'s existing callers —
|
|
15
|
+
* not an improvement). Explicitly OUT of scope, per the #3674 design lock:
|
|
16
|
+
* - dependency-DAG ordering — this module only ever sees a flat item list
|
|
17
|
+
* and file-overlap; a caller resolves dependency order before calling in
|
|
18
|
+
* (same contract `partitionStages` already had);
|
|
19
|
+
* - path normalization — file entries are compared by exact string equality
|
|
20
|
+
* only. `Foo.ts` vs `foo.ts`, or `src/a.ts` vs `src\a.ts`, are treated as
|
|
21
|
+
* DISTINCT files. This is deliberate, not a gap to "fix" during extraction;
|
|
22
|
+
* - filesystem access — this module never reads a path off disk.
|
|
23
|
+
*
|
|
24
|
+
* Zero external dependencies. Pure function. Never throws on well-typed input.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Partition `items` into a near-minimal number of sequential stages (via
|
|
28
|
+
* greedy first-fit — not guaranteed optimal for arbitrary overlap graphs, but
|
|
29
|
+
* correct: no two items sharing a file ever cohabit a stage) such that no two
|
|
30
|
+
* items in the same stage share a file. Each item goes into the earliest
|
|
31
|
+
* stage where it does not overlap any item already there, in input order.
|
|
32
|
+
*
|
|
33
|
+
* An item with an EMPTY `files` array declares no files; it overlaps nothing
|
|
34
|
+
* and coalesces into stage 0 (mirrors `partitionStages`' original behavior —
|
|
35
|
+
* this module cannot guard against undeclared concurrent writes; a caller
|
|
36
|
+
* must declare `files` accurately).
|
|
37
|
+
*
|
|
38
|
+
* File comparison is EXACT STRING EQUALITY — no path normalization, no
|
|
39
|
+
* case-folding, no separator canonicalization. Duplicate `id`s in the input
|
|
40
|
+
* are NOT deduplicated; each item is placed independently, in input order.
|
|
41
|
+
*
|
|
42
|
+
* Deterministic: identical input (including input order) always yields an
|
|
43
|
+
* identical partition.
|
|
44
|
+
*/
|
|
45
|
+
function partitionByFileOverlap(items) {
|
|
46
|
+
const stages = [];
|
|
47
|
+
for (const item of items) {
|
|
48
|
+
const fileSet = new Set(item.files);
|
|
49
|
+
let placed = false;
|
|
50
|
+
for (const stage of stages) {
|
|
51
|
+
let overlap = false;
|
|
52
|
+
for (const f of fileSet) {
|
|
53
|
+
if (stage.files.has(f)) {
|
|
54
|
+
overlap = true;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!overlap) {
|
|
59
|
+
stage.items.push(item);
|
|
60
|
+
for (const f of fileSet)
|
|
61
|
+
stage.files.add(f);
|
|
62
|
+
placed = true;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!placed) {
|
|
67
|
+
stages.push({ items: [item], files: new Set(fileSet) });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return stages.map((s) => s.items.map((i) => i.id));
|
|
71
|
+
}
|
|
72
|
+
module.exports = {
|
|
73
|
+
partitionByFileOverlap,
|
|
74
|
+
};
|
|
@@ -652,46 +652,137 @@ function countTopLevelKeyShapedLines(region) {
|
|
|
652
652
|
* key its deduplication. Optional because this function has 50-odd call sites and several
|
|
653
653
|
* hold only an in-memory string; those dedup on a content digest instead.
|
|
654
654
|
*/
|
|
655
|
-
|
|
655
|
+
/**
|
|
656
|
+
* The frontmatter REGION of a document: the text between the opening `---`
|
|
657
|
+
* fence at byte 0 and its closing fence.
|
|
658
|
+
*
|
|
659
|
+
* One fence parser, not two (#3850 review round 2, B1). `extractFrontmatter`
|
|
660
|
+
* and `frontmatterListEntries` need the identical answer to "where does the
|
|
661
|
+
* frontmatter start and stop" — same BOM tolerance (#2977), same byte-0-only
|
|
662
|
+
* fence rule, same CR handling before the closing fence — and differ only in
|
|
663
|
+
* what they do with the region text afterwards. A second copy of this logic is
|
|
664
|
+
* the `DEFECT.GENERATIVE-FIX` shape: it silently stops agreeing the first time
|
|
665
|
+
* either is taught something.
|
|
666
|
+
*
|
|
667
|
+
* `terminated: false` is the fence-opened-but-never-closed case. It is not an
|
|
668
|
+
* error here because the two callers disagree about it: `extractFrontmatter`
|
|
669
|
+
* runs the #1882 truncation probe over the region and may warn, while
|
|
670
|
+
* `frontmatterListEntries` has no array to return and gives up. So the shape
|
|
671
|
+
* is reported and the decision is left to them.
|
|
672
|
+
*
|
|
673
|
+
* `content` is returned alongside because it is the BOM-STRIPPED text, and a
|
|
674
|
+
* caller reporting on the document (the truncation diagnostic) must describe
|
|
675
|
+
* the same bytes the offsets were computed against.
|
|
676
|
+
*/
|
|
677
|
+
function frontmatterRegion(content) {
|
|
656
678
|
// #2977: tolerate a single leading UTF-8 BOM (U+FEFF), which Windows tooling
|
|
657
|
-
// (PowerShell `>`/`Out-File` on PS 5.1, several editors) writes by default.
|
|
658
|
-
// strip, the byte-0 `startsWith('---')` fence check below fails
|
|
659
|
-
// parse collapses
|
|
660
|
-
// proceeds as though the file had no frontmatter
|
|
661
|
-
//
|
|
662
|
-
// Scope: BOM only. Arbitrary non-BOM content before the fence
|
|
663
|
-
// line/comment) is a separate product-intent
|
|
664
|
-
//
|
|
665
|
-
if (content.charCodeAt(0) === 0xFEFF)
|
|
679
|
+
// (PowerShell `>`/`Out-File` on PS 5.1, several editors) writes by default.
|
|
680
|
+
// Without this strip, the byte-0 `startsWith('---')` fence check below fails
|
|
681
|
+
// on the BOM and the whole parse collapses — every frontmatter field silently
|
|
682
|
+
// disappears, and the engine proceeds as though the file had no frontmatter
|
|
683
|
+
// at all. The BOM is a single codepoint; stripping it here restores byte-0
|
|
684
|
+
// alignment. Scope: BOM only. Arbitrary non-BOM content before the fence
|
|
685
|
+
// (leading whitespace/blank line/comment) is a separate product-intent
|
|
686
|
+
// decision (tolerate vs diagnose) left to a future change.
|
|
687
|
+
if (content.charCodeAt(0) === 0xFEFF)
|
|
666
688
|
content = content.slice(1);
|
|
667
|
-
|
|
668
|
-
//
|
|
669
|
-
// body (YAML examples, horizontal rules) must never be treated as frontmatter.
|
|
689
|
+
// Match frontmatter only at byte 0 — a `---` block later in the document body
|
|
690
|
+
// (YAML examples, horizontal rules) must never be treated as frontmatter.
|
|
670
691
|
const headerEnd = content.startsWith('---\r\n') ? 5 : content.startsWith('---\n') ? 4 : -1;
|
|
671
692
|
if (headerEnd === -1)
|
|
672
|
-
return
|
|
693
|
+
return null;
|
|
673
694
|
const closingLineStart = content.indexOf('\n---', headerEnd);
|
|
674
695
|
if (closingLineStart === -1) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
696
|
+
return { region: content.slice(headerEnd), terminated: false, content };
|
|
697
|
+
}
|
|
698
|
+
const yamlEnd = content[closingLineStart - 1] === '\r' ? closingLineStart - 1 : closingLineStart;
|
|
699
|
+
return { region: content.slice(headerEnd, yamlEnd), terminated: true, content };
|
|
700
|
+
}
|
|
701
|
+
function extractFrontmatter(content, sourcePath) {
|
|
702
|
+
// Fence location (BOM strip, byte-0 rule, CR handling) lives in
|
|
703
|
+
// `frontmatterRegion` so this and `frontmatterListEntries` cannot drift
|
|
704
|
+
// apart on where the frontmatter is.
|
|
705
|
+
const found = frontmatterRegion(content);
|
|
706
|
+
if (!found)
|
|
707
|
+
return {};
|
|
708
|
+
if (!found.terminated) {
|
|
709
|
+
const keyCount = countKeysBeforeTruncation(found.region);
|
|
710
|
+
if (keyCount >= UNTERMINATED_KEY_THRESHOLD && isFrontmatterShaped(found.region)) {
|
|
678
711
|
warnUnusableInput({
|
|
679
712
|
reason: UNUSABLE_REASON.FRONTMATTER_UNTERMINATED,
|
|
680
713
|
source: sourcePath,
|
|
681
|
-
content,
|
|
714
|
+
content: found.content,
|
|
682
715
|
});
|
|
683
716
|
}
|
|
684
717
|
return {};
|
|
685
718
|
}
|
|
686
|
-
const yamlEnd = content[closingLineStart - 1] === '\r' ? closingLineStart - 1 : closingLineStart;
|
|
687
|
-
const region = content.slice(headerEnd, yamlEnd);
|
|
688
719
|
try {
|
|
689
|
-
return parseGuardedYamlRegion(region);
|
|
720
|
+
return parseGuardedYamlRegion(found.region);
|
|
690
721
|
}
|
|
691
722
|
catch {
|
|
692
723
|
return unparseableResult();
|
|
693
724
|
}
|
|
694
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* The entries of a top-level frontmatter ARRAY key, VERBATIM — as the values
|
|
728
|
+
* YAML actually describes rather than the display-flattened strings
|
|
729
|
+
* `extractFrontmatter` returns (#3850).
|
|
730
|
+
*
|
|
731
|
+
* `extractFrontmatter` renders each object entry for HUMANS —
|
|
732
|
+
* `normalizeParsedValue` maps `{test, resolution}` to `"test: …, resolution: …"`
|
|
733
|
+
* via `flattenObjectListItem`. That is the right contract for a reader printing
|
|
734
|
+
* a list, and the wrong one for a reader that needs to branch on a specific
|
|
735
|
+
* field: `status`, `resolution` and `reason` are recoverable from that string
|
|
736
|
+
* only by re-parsing prose, which cannot distinguish a real `resolution:` field
|
|
737
|
+
* from the same text inside a quoted `truth:`.
|
|
738
|
+
*
|
|
739
|
+
* This returns the same entries BEFORE that display step, off the same
|
|
740
|
+
* `extractFrontmatter` parse path — same BOM strip (#2977), same byte-0 fence
|
|
741
|
+
* rule (shared via `frontmatterRegion`), same anchor/alias and sentinel guards,
|
|
742
|
+
* same ambiguous-colon repair. It is deliberately NOT a second parser: a
|
|
743
|
+
* hand-rolled fence regex or entry slicer re-loses whatever the real one
|
|
744
|
+
* learned, which is the `DEFECT.GENERATIVE-FIX` shape.
|
|
745
|
+
*
|
|
746
|
+
* EVERY element is returned, at its own index, whatever its type (#3850 review
|
|
747
|
+
* round 3, Blocker). An earlier revision filtered to objects, which compacted
|
|
748
|
+
* the array: a caller numbering entries by array position then numbered the
|
|
749
|
+
* SURVIVORS, so a list mixing object and non-object entries lost rows outright
|
|
750
|
+
* and mis-numbered the rest — the same silently-vanishing-row defect this whole
|
|
751
|
+
* issue exists to close, triggered by entry shape instead of file status.
|
|
752
|
+
* Deciding what a non-object entry MEANS is a caller's judgement (the two
|
|
753
|
+
* readers in `uat.cts` answer it differently and both are right for their own
|
|
754
|
+
* vocabulary); dropping it is nobody's.
|
|
755
|
+
*
|
|
756
|
+
* Returns `null` when the document has no frontmatter, the frontmatter is
|
|
757
|
+
* unterminated or unparseable, the key is absent, or the key is not an array —
|
|
758
|
+
* "nothing to iterate" cases the caller should not have to tell apart.
|
|
759
|
+
*/
|
|
760
|
+
function frontmatterListEntries(content, key) {
|
|
761
|
+
const found = frontmatterRegion(content);
|
|
762
|
+
if (!found || !found.terminated)
|
|
763
|
+
return null;
|
|
764
|
+
let raw;
|
|
765
|
+
try {
|
|
766
|
+
refuseAnchorsAndAliases(found.region);
|
|
767
|
+
refuseIfSentinelPresent(found.region);
|
|
768
|
+
raw = restoreNullBytesDeep(loadWithAmbiguousColonRepair(escapeNullBytesForParse(found.region)));
|
|
769
|
+
}
|
|
770
|
+
catch {
|
|
771
|
+
// Same posture as `extractFrontmatter`: an unparseable region is "no
|
|
772
|
+
// frontmatter", never a throw into a caller that was only reading a field.
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
776
|
+
return null;
|
|
777
|
+
const value = raw[key];
|
|
778
|
+
// `Array.isArray` narrows an `unknown` to `any[]`, and returning that
|
|
779
|
+
// unchecked is how `any` escapes a guarded parser into every caller. The
|
|
780
|
+
// element type genuinely IS unknown here — that is the point of this
|
|
781
|
+
// function — so say so.
|
|
782
|
+
if (!Array.isArray(value))
|
|
783
|
+
return null;
|
|
784
|
+
return value;
|
|
785
|
+
}
|
|
695
786
|
/**
|
|
696
787
|
* Escape a string for emission inside a YAML double-quoted scalar (#1779). ADR-3473 §8.1
|
|
697
788
|
* (#3881): routed through the vendored js-yaml's `dump()` (forced double-quoted style) rather
|
|
@@ -816,6 +907,22 @@ function agentScalarNeedsDoubleQuoting(s) {
|
|
|
816
907
|
return true;
|
|
817
908
|
return false;
|
|
818
909
|
}
|
|
910
|
+
/**
|
|
911
|
+
* #4053 — Quote a numeric-looking scalar that is not all-digit (`22.10`,
|
|
912
|
+
* `1.0`, `1e3`, `0x1F`) so a spec YAML reader keeps it a string: bare `22.10`
|
|
913
|
+
* reloads as the float 22.1 and collides with `22.1`, a different phase.
|
|
914
|
+
*
|
|
915
|
+
* All-digit strings stay bare as a deliberate, scoped trade-off — not a
|
|
916
|
+
* safety guarantee. A leading-zero value (`02`, `017`) also mis-parses under
|
|
917
|
+
* a spec reader (to 2, 17). It is left unquoted because zero-padded ids
|
|
918
|
+
* (`plan: 01`, `phase: 02`) are the pervasive GSD convention, quoting them
|
|
919
|
+
* all is the blanket quoting #4053 asked to avoid, and the loss is padding
|
|
920
|
+
* rather than identity: `02` and `2` normalize to the same phase; `22.1` and
|
|
921
|
+
* `22.10` do not.
|
|
922
|
+
*/
|
|
923
|
+
function generalScalarNeedsNumericQuoting(s) {
|
|
924
|
+
return YAML_NUMERIC_RE.test(s) && !/^\d+$/.test(s);
|
|
925
|
+
}
|
|
819
926
|
function reconstructFrontmatter(obj) {
|
|
820
927
|
const lines = [];
|
|
821
928
|
// #3257: read the full-line-comment channel (set by parseGuardedYamlRegion when comments
|
|
@@ -899,13 +1006,13 @@ function reconstructFrontmatter(obj) {
|
|
|
899
1006
|
else {
|
|
900
1007
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
901
1008
|
const sv = String(subval);
|
|
902
|
-
lines.push(` ${subkey}: ${sv.includes(':') || sv.includes('#') || scalarNeedsDoubleQuoting(sv) ? `"${escapeDoubleQuotedScalar(sv)}"` : sv}`);
|
|
1009
|
+
lines.push(` ${subkey}: ${sv.includes(':') || sv.includes('#') || scalarNeedsDoubleQuoting(sv) || generalScalarNeedsNumericQuoting(sv) ? `"${escapeDoubleQuotedScalar(sv)}"` : sv}`);
|
|
903
1010
|
}
|
|
904
1011
|
}
|
|
905
1012
|
}
|
|
906
1013
|
else {
|
|
907
1014
|
const sv = String(value);
|
|
908
|
-
if (sv.includes(':') || sv.includes('#') || sv.startsWith('[') || sv.startsWith('{') || scalarNeedsDoubleQuoting(sv)) {
|
|
1015
|
+
if (sv.includes(':') || sv.includes('#') || sv.startsWith('[') || sv.startsWith('{') || scalarNeedsDoubleQuoting(sv) || generalScalarNeedsNumericQuoting(sv)) {
|
|
909
1016
|
lines.push(`${key}: "${escapeDoubleQuotedScalar(sv)}"`);
|
|
910
1017
|
}
|
|
911
1018
|
else {
|
|
@@ -1529,6 +1636,13 @@ module.exports = {
|
|
|
1529
1636
|
stripFrontmatter,
|
|
1530
1637
|
noOpObjectListSetError,
|
|
1531
1638
|
parseMustHavesBlock,
|
|
1639
|
+
// #3850: an array key's entries as parsed OBJECTS, for a caller that must
|
|
1640
|
+
// branch on an entry's `status:`/`resolution:` rather than print it. Off the
|
|
1641
|
+
// same parse path as `extractFrontmatter`, minus only the display flattening.
|
|
1642
|
+
frontmatterListEntries,
|
|
1643
|
+
// #3850: the display rendering itself, so a caller deriving a name from those
|
|
1644
|
+
// objects produces the byte-identical string `extractFrontmatter` would have.
|
|
1645
|
+
flattenObjectListItem,
|
|
1532
1646
|
FRONTMATTER_SCHEMAS,
|
|
1533
1647
|
cmdFrontmatterGet,
|
|
1534
1648
|
cmdFrontmatterSet,
|
|
@@ -36,6 +36,9 @@ const { scanPhasePlans } = planScanMod;
|
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
37
|
const phaseIdMod = require("./phase-id.cjs");
|
|
38
38
|
const { scopeToPhase } = phaseIdMod;
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
|
+
const planningScopeMod = require("./planning-scope.cjs");
|
|
41
|
+
const { SCOPE } = planningScopeMod;
|
|
39
42
|
/**
|
|
40
43
|
* Parse REQ-IDs from REQUIREMENTS.md content.
|
|
41
44
|
*
|
|
@@ -303,6 +306,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
303
306
|
summary: 'workflow.post_planning_gaps disabled — skipping post-planning gap analysis',
|
|
304
307
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
305
308
|
phase_dir_read_error: null,
|
|
309
|
+
phase_dir_scope: SCOPE.COMPLETE,
|
|
306
310
|
};
|
|
307
311
|
}
|
|
308
312
|
const absPhaseDir = node_path_1.default.isAbsolute(phaseDir) ? phaseDir : node_path_1.default.join(cwd, phaseDir);
|
|
@@ -325,19 +329,20 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
325
329
|
}
|
|
326
330
|
// Read the phase directory once; reuse the listing for both context detection
|
|
327
331
|
// and plan-file enumeration (avoids redundant readdirSync calls).
|
|
328
|
-
|
|
329
|
-
// #
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
332
|
+
//
|
|
333
|
+
// #4014 (epic #3473 B4): findContextMdIn's directory-string form now owns
|
|
334
|
+
// the listing + ENOENT-vs-other discrimination, retiring the local
|
|
335
|
+
// existsSync-guarded readdirSync try/catch — ENOENT (genuinely absent)
|
|
336
|
+
// and a successful-but-empty read both resolve to SCOPE.COMPLETE with an
|
|
337
|
+
// empty listing, exactly like the existsSync guard's short-circuit did.
|
|
338
|
+
const { files: phaseDirFiles, scope: phaseDirScope } = findContextMdIn(absPhaseDir);
|
|
339
|
+
// #3885 (ADR-3473 §8.5): `phaseDirReadError` stays additive for the
|
|
340
|
+
// shipped `phase_dir_read_error` JSON field — derived from `phaseDirScope`
|
|
341
|
+
// rather than from its own caught error, since findContextMdIn's
|
|
342
|
+
// directory-string form reports SCOPE, not the raw errno message.
|
|
343
|
+
const phaseDirReadError = phaseDirScope === SCOPE.UNREADABLE
|
|
344
|
+
? `Could not read phase directory ${formatDiagnosticToken(absPhaseDir)}`
|
|
345
|
+
: null;
|
|
341
346
|
// #3511-class: scope the raw listing to this phase dir before the
|
|
342
347
|
// phase-numbered -CONTEXT.md predicate. `phaseDirFiles` itself stays raw —
|
|
343
348
|
// it is also reused below only as a `.length > 0` guard ahead of
|
|
@@ -405,6 +410,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
405
410
|
summary: coverageSummary + '; extracted 0 of N — possible format mismatch',
|
|
406
411
|
counts: { total: rows.length, covered, uncovered },
|
|
407
412
|
phase_dir_read_error: phaseDirReadError,
|
|
413
|
+
phase_dir_scope: phaseDirScope,
|
|
408
414
|
};
|
|
409
415
|
}
|
|
410
416
|
return {
|
|
@@ -414,6 +420,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
414
420
|
summary: 'extracted 0 of N — possible format mismatch',
|
|
415
421
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
416
422
|
phase_dir_read_error: phaseDirReadError,
|
|
423
|
+
phase_dir_scope: phaseDirScope,
|
|
417
424
|
};
|
|
418
425
|
}
|
|
419
426
|
// #1365: if no items at all, surface a clean no-check message.
|
|
@@ -431,6 +438,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
431
438
|
summary: 'no requirements or decisions to check',
|
|
432
439
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
433
440
|
phase_dir_read_error: phaseDirReadError,
|
|
441
|
+
phase_dir_scope: phaseDirScope,
|
|
434
442
|
};
|
|
435
443
|
}
|
|
436
444
|
const rows = sortRows([
|
|
@@ -449,6 +457,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
449
457
|
summary,
|
|
450
458
|
counts: { total: rows.length, covered, uncovered },
|
|
451
459
|
phase_dir_read_error: phaseDirReadError,
|
|
460
|
+
phase_dir_scope: phaseDirScope,
|
|
452
461
|
};
|
|
453
462
|
}
|
|
454
463
|
function cmdGapAnalysis(cwd, args, raw) {
|
|
@@ -122,6 +122,7 @@ function readEffectiveGitConfig(cwd, deps) {
|
|
|
122
122
|
config = {
|
|
123
123
|
base_branch: _readGitKey(normalized, 'base_branch'),
|
|
124
124
|
protected_branches: _readGitNested(normalized, 'protected_branches'),
|
|
125
|
+
allow_default_branch_commits: _readGitNested(normalized, 'allow_default_branch_commits'),
|
|
125
126
|
};
|
|
126
127
|
}
|
|
127
128
|
}
|
|
@@ -161,7 +162,10 @@ function readEffectiveGitConfig(cwd, deps) {
|
|
|
161
162
|
// Not a list at all — contributes no names, but is still a misconfiguration.
|
|
162
163
|
rejectedProtectedBranches.push(renderRejected(rawProtectedBranches));
|
|
163
164
|
}
|
|
164
|
-
|
|
165
|
+
// #3819: nested-only read (mirrors protected_branches — new key, no legacy
|
|
166
|
+
// flat form, so a top-level spelling must not become an undocumented alias).
|
|
167
|
+
const allowDefaultBranchCommits = config.allow_default_branch_commits === true;
|
|
168
|
+
return { baseBranch, protectedBranches, rejectedProtectedBranches, allowDefaultBranchCommits };
|
|
165
169
|
}
|
|
166
170
|
/**
|
|
167
171
|
* Try `git symbolic-ref --short refs/remotes/origin/HEAD` (no network).
|
|
@@ -302,13 +306,17 @@ function resolveBaseBranch(cwd, deps) {
|
|
|
302
306
|
function resolveProtectedBranchStatus(cwd, currentBranch, deps) {
|
|
303
307
|
const effectiveConfig = readEffectiveGitConfig(cwd, deps);
|
|
304
308
|
const { branch: baseBranch, verified } = resolveBaseBranchDiagnosticsWithConfig(cwd, effectiveConfig.baseBranch, deps);
|
|
305
|
-
const protectedBranches = [...new Set([
|
|
309
|
+
const protectedBranches = [...new Set([
|
|
310
|
+
...(effectiveConfig.allowDefaultBranchCommits ? [] : [baseBranch]),
|
|
311
|
+
...effectiveConfig.protectedBranches,
|
|
312
|
+
])];
|
|
306
313
|
return {
|
|
307
314
|
baseBranch,
|
|
308
315
|
protectedBranches,
|
|
309
316
|
rejectedProtectedBranches: effectiveConfig.rejectedProtectedBranches,
|
|
310
317
|
isProtected: protectedBranches.includes(currentBranch),
|
|
311
318
|
verified,
|
|
319
|
+
allowDefaultBranchCommits: effectiveConfig.allowDefaultBranchCommits,
|
|
312
320
|
};
|
|
313
321
|
}
|
|
314
322
|
/**
|
|
@@ -307,8 +307,7 @@ function buildStateMd(phaseMap) {
|
|
|
307
307
|
const currentPhaseNum = currentEntry ? zeroPad(currentEntry.phaseNum) : zeroPad(totalPhases);
|
|
308
308
|
const currentSlug = currentEntry ? slugify(currentEntry.slice.title) : 'complete';
|
|
309
309
|
const status = currentEntry ? 'Ready to plan' : 'All phases complete';
|
|
310
|
-
const
|
|
311
|
-
const bar = `[${'█'.repeat(filled)}${'░'.repeat(10 - filled)}]`;
|
|
310
|
+
const bar = `[${(0, phase_lifecycle_cjs_1.renderProgressBar)(pct, 10)}]`;
|
|
312
311
|
const today = clock_cjs_1.realClock.localToday();
|
|
313
312
|
return [
|
|
314
313
|
'# Project State',
|
|
@@ -56,7 +56,13 @@ const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
|
56
56
|
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
|
57
57
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
58
58
|
const validateMod = require("../validate.cjs");
|
|
59
|
-
|
|
59
|
+
// #612: `isPhaseDirName` is the convention-SELECTED shape test wrapping
|
|
60
|
+
// `phaseDirNameRe`. Handed no convention it delegates to that very regex, so a
|
|
61
|
+
// legacy repo's W005 reading is byte-identical; handed 'bracket' it also
|
|
62
|
+
// recognizes `{CODE}.{MM}-{PP}-slug`, which `phaseDirNameRe` rejects outright —
|
|
63
|
+
// left un-threaded, W005 fires on EVERY phase directory of a repo that opted
|
|
64
|
+
// into the convention, i.e. the check inverts on exactly the repos PR-2 widens.
|
|
65
|
+
const { isPhaseDirName } = validateMod;
|
|
60
66
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
61
67
|
const phaseIdMod = require("../phase-id.cjs");
|
|
62
68
|
const { extractPhaseToken, normalizePhaseName, comparePhaseNum } = phaseIdMod;
|
|
@@ -64,7 +70,7 @@ const { extractPhaseToken, normalizePhaseName, comparePhaseNum } = phaseIdMod;
|
|
|
64
70
|
function checkW005(snapshot) {
|
|
65
71
|
const diagnostics = [];
|
|
66
72
|
for (const name of snapshot.phaseDirs.value) {
|
|
67
|
-
if (!name.
|
|
73
|
+
if (!isPhaseDirName(name, snapshot.phaseIdConvention)) {
|
|
68
74
|
diagnostics.push({
|
|
69
75
|
code: 'W005',
|
|
70
76
|
severity: SEVERITY.WARNING,
|
|
@@ -116,8 +116,16 @@ const { phaseVariants } = validateMod;
|
|
|
116
116
|
* does the roadmap claim, for W007) call this — one matcher, reused, per the
|
|
117
117
|
* file-level comment.
|
|
118
118
|
*/
|
|
119
|
-
function dirsForPhase(dirs, phaseId) {
|
|
120
|
-
|
|
119
|
+
function dirsForPhase(dirs, phaseId, convention) {
|
|
120
|
+
// #612: `convention` reaches BOTH operands of this pairing or it pairs
|
|
121
|
+
// nothing. The phases come from a bracket-aware heading scan and the dirs
|
|
122
|
+
// from a bracket-aware disk scan, so a convention-less selector here leaves
|
|
123
|
+
// W006's existence check and W007's `claimedDirs` empty on exactly the repos
|
|
124
|
+
// both scans just widened — every bracket phase reads as "no directory on
|
|
125
|
+
// disk" AND every bracket directory reads as unclaimed. `matchPhaseDirs`
|
|
126
|
+
// forwards it to its primary `phaseTokenMatches`; handed nothing it is
|
|
127
|
+
// byte-identical, so no legacy repo's pairing changes.
|
|
128
|
+
const canonical = matchPhaseDirs(dirs, normalizePhaseName(phaseId), convention).matches;
|
|
121
129
|
if (canonical.length > 0)
|
|
122
130
|
return canonical;
|
|
123
131
|
// Bug 2 (#3309 W006/W007 migration cluster, found while fixing the
|
|
@@ -139,7 +147,12 @@ function dirsForPhase(dirs, phaseId) {
|
|
|
139
147
|
// alone misses without re-deriving a second matcher.
|
|
140
148
|
const variants = phaseVariants(phaseId);
|
|
141
149
|
return dirs.filter((d) => {
|
|
142
|
-
|
|
150
|
+
// #612: the same convention the primary matcher above got. This fallback
|
|
151
|
+
// compares a DIRECTORY-derived token against phase-id variants, so left
|
|
152
|
+
// convention-less it reads `GSD.02-05-slug` with the legacy grammar and can
|
|
153
|
+
// never agree with a bracket phase id — the fallback would then be dead on
|
|
154
|
+
// precisely the convention it is reached for.
|
|
155
|
+
const token = extractPhaseToken(d, convention).toUpperCase();
|
|
143
156
|
for (const variant of variants) {
|
|
144
157
|
if (token === variant.toUpperCase())
|
|
145
158
|
return true;
|
|
@@ -173,15 +186,40 @@ function checkW006(snapshot) {
|
|
|
173
186
|
if (snapshot.roadmapDeclaredPhases.scope !== SCOPE.COMPLETE)
|
|
174
187
|
return [];
|
|
175
188
|
const dirs = snapshot.allPhaseDirNames.value;
|
|
189
|
+
const convention = snapshot.phaseIdConvention;
|
|
176
190
|
const archivedTokens = new Set(snapshot.archivedPhaseTokens.value);
|
|
191
|
+
// No separate scope guard: `roadmapSentinelPhaseTokens` and
|
|
192
|
+
// `roadmapDeclaredPhases` are produced by ONE builder call
|
|
193
|
+
// (`buildRoadmapDeclaredPhasesField`) from ONE `buildRoadmapPhaseVariants`
|
|
194
|
+
// result, so their scopes are yoked by construction — the COMPLETE check
|
|
195
|
+
// above covers both. Stated rather than left implicit, because reading the
|
|
196
|
+
// sentinel set unconditionally would be a latent bug the day the two fields
|
|
197
|
+
// acquire independent builders: an UNREADABLE sentinel set silently degrades
|
|
198
|
+
// to "nothing is a sentinel," which ADDS W006 warnings rather than dropping
|
|
199
|
+
// them.
|
|
200
|
+
const sentinelTokens = new Set(snapshot.roadmapSentinelPhaseTokens.value);
|
|
177
201
|
const checkboxes = snapshot.roadmapPhaseCheckboxes.value;
|
|
178
202
|
const diagnostics = [];
|
|
179
203
|
for (const { phaseId } of snapshot.roadmapDeclaredPhases.value) {
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
|
|
204
|
+
// A backlog/icebox phase legitimately has no directory. The two guards are
|
|
205
|
+
// DISJOINT, which is why both are needed and neither subsumes the other:
|
|
206
|
+
//
|
|
207
|
+
// #612 `roadmapSentinelPhaseTokens` — bracket sentinels. Under the
|
|
208
|
+
// bracket convention the sentinel-ness lives in the MILESTONE, not
|
|
209
|
+
// the token: the heading `### [GSD.999] 07:` yields phase id `07`,
|
|
210
|
+
// so a bare-token test cannot see it. The snapshot's
|
|
211
|
+
// `buildRoadmapPhaseVariants` call carries the bracket id alongside
|
|
212
|
+
// and reports the token here. Empty unless the bracket convention
|
|
213
|
+
// is active, so no legacy repo's reading changes.
|
|
214
|
+
// #3225 `isSentinelPhaseId` — the legacy/bare leading-int rule (999.x/0.x),
|
|
215
|
+
// which sees `### Phase 999:` and is blind to `[GSD.999] 07`.
|
|
216
|
+
//
|
|
217
|
+
// Upstream's call is kept one-arg verbatim: every `phaseId` here is a bare
|
|
218
|
+
// token, never a `{CODE}.{MM}`-qualified id, so a convention argument could
|
|
219
|
+
// not change its result.
|
|
220
|
+
if (sentinelTokens.has(phaseId) || isSentinelPhaseId(phaseId))
|
|
183
221
|
continue;
|
|
184
|
-
if (dirsForPhase(dirs, phaseId).length > 0)
|
|
222
|
+
if (dirsForPhase(dirs, phaseId, convention).length > 0)
|
|
185
223
|
continue;
|
|
186
224
|
// Bug 1 (#3309 W006/W007 migration cluster): a phase whose ONLY
|
|
187
225
|
// directory lives under a milestone archive
|
|
@@ -209,10 +247,10 @@ function checkW006(snapshot) {
|
|
|
209
247
|
// ─── W007 — an on-disk phase directory has no matching ROADMAP entry ──────
|
|
210
248
|
// (verify.cts:2086-2101)
|
|
211
249
|
/** Every directory in `dirs` that ANY declared roadmap phase resolves to. */
|
|
212
|
-
function computeClaimedDirs(dirs, declaredPhases) {
|
|
250
|
+
function computeClaimedDirs(dirs, declaredPhases, convention) {
|
|
213
251
|
const claimed = new Set();
|
|
214
252
|
for (const { phaseId } of declaredPhases) {
|
|
215
|
-
for (const dir of dirsForPhase(dirs, phaseId))
|
|
253
|
+
for (const dir of dirsForPhase(dirs, phaseId, convention))
|
|
216
254
|
claimed.add(dir);
|
|
217
255
|
}
|
|
218
256
|
return claimed;
|
|
@@ -222,14 +260,19 @@ function checkW007(snapshot) {
|
|
|
222
260
|
if (snapshot.roadmapDeclaredPhases.scope !== SCOPE.COMPLETE)
|
|
223
261
|
return [];
|
|
224
262
|
const dirs = snapshot.allPhaseDirNames.value;
|
|
225
|
-
const
|
|
263
|
+
const convention = snapshot.phaseIdConvention;
|
|
264
|
+
const claimedDirs = computeClaimedDirs(dirs, snapshot.roadmapDeclaredPhases.value, convention);
|
|
226
265
|
const diagnostics = [];
|
|
227
266
|
for (const dirName of dirs) {
|
|
228
267
|
// `extractPhaseToken` is the phase-id.cts owner `PHASE_TOKEN_FROM_DIR_RE`
|
|
229
268
|
// (`src/validate.cts:73-76`) is documented to match exactly
|
|
230
269
|
// (verify.cts's original `p` key from `collectDiskPhaseEntries`,
|
|
231
270
|
// `verify.cts:1373-1397`) — same token, relocated read, not reinvented.
|
|
232
|
-
|
|
271
|
+
// #612: `collectDiskPhaseEntries` keyed that map through the
|
|
272
|
+
// convention-aware extractor, so the token this message names must be
|
|
273
|
+
// derived the same way — otherwise a bracket repo's unclaimed
|
|
274
|
+
// `GSD.02-05-slug` is reported as phase `GSD.02` rather than `05`.
|
|
275
|
+
const token = extractPhaseToken(dirName, convention);
|
|
233
276
|
// #3225: a sentinel dir on disk (999-interim, 0-drafts) is defined as
|
|
234
277
|
// never-on-roadmap; it must not trigger W007. #3639: judged on the DIR
|
|
235
278
|
// NAME via the dir-aware recognizer — the extracted token is
|