@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
|
@@ -18,7 +18,14 @@
|
|
|
18
18
|
* 5. "main" (last-resort default)
|
|
19
19
|
*
|
|
20
20
|
* Every git subprocess is bounded with a timeout (≤ 30 s); on timeout/error
|
|
21
|
-
* the resolver degrades gracefully to the next tier — it never throws.
|
|
21
|
+
* the resolver degrades gracefully to the next tier — it never throws. Tier 5
|
|
22
|
+
* is reachable two ways that `resolveBaseBranch()` alone cannot tell apart: a
|
|
23
|
+
* repository that genuinely has no candidate branch (every git query on tiers
|
|
24
|
+
* 2-4 completed and cleanly answered "nothing"), or a total resolution
|
|
25
|
+
* failure (some query timed out / could not run). `resolveBaseBranchDiagnostics()`
|
|
26
|
+
* distinguishes the two via `verified`; `cmdGitBaseBranch` surfaces the
|
|
27
|
+
* unverified case as a stderr diagnostic without changing its stdout contract
|
|
28
|
+
* (#3057 B4).
|
|
22
29
|
*
|
|
23
30
|
* Pure/testable: all I/O is injectable via the `deps` argument so unit
|
|
24
31
|
* tests can run without touching the real filesystem or spawning real git.
|
|
@@ -31,8 +38,11 @@ exports.readConfigBaseBranch = readConfigBaseBranch;
|
|
|
31
38
|
exports.trySymbolicRef = trySymbolicRef;
|
|
32
39
|
exports.tryRemoteShow = tryRemoteShow;
|
|
33
40
|
exports.tryLocalBranch = tryLocalBranch;
|
|
41
|
+
exports.resolveBaseBranchDiagnostics = resolveBaseBranchDiagnostics;
|
|
34
42
|
exports.resolveBaseBranch = resolveBaseBranch;
|
|
35
43
|
exports.gitWorktreeInfoInternal = gitWorktreeInfoInternal;
|
|
44
|
+
exports.phaseStartCommit = phaseStartCommit;
|
|
45
|
+
exports.changedFilesSince = changedFilesSince;
|
|
36
46
|
exports.cmdGitBaseBranch = cmdGitBaseBranch;
|
|
37
47
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
38
48
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -115,7 +125,8 @@ function tryRemoteShow(cwd, execGit) {
|
|
|
115
125
|
const branch = m[1];
|
|
116
126
|
// git emits "(unknown)" when the remote is offline but the local cache
|
|
117
127
|
// resolved it; treat that as non-authoritative and fall through.
|
|
118
|
-
|
|
128
|
+
// No `!branch ||` guard: m[1] comes from the `(\S+)` capture group above, so it is never empty.
|
|
129
|
+
if (branch === '(unknown)')
|
|
119
130
|
return null;
|
|
120
131
|
return branch;
|
|
121
132
|
}
|
|
@@ -153,41 +164,70 @@ function tryLocalBranch(cwd, execGit) {
|
|
|
153
164
|
}
|
|
154
165
|
}
|
|
155
166
|
/**
|
|
156
|
-
* Resolve the default/base branch for the repository at `cwd
|
|
167
|
+
* Resolve the default/base branch for the repository at `cwd`, along with
|
|
168
|
+
* whether the tier-5 last-resort default (if reached) was verified.
|
|
157
169
|
*
|
|
158
170
|
* Consults the full precedence ladder and always returns a non-empty string.
|
|
159
171
|
* Never throws.
|
|
160
172
|
*/
|
|
161
|
-
function
|
|
162
|
-
const
|
|
173
|
+
function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
174
|
+
const rawExecGit = deps?.execGit ?? shell_command_projection_cjs_1.execGit;
|
|
175
|
+
// A genuine execGit failure (timeout, or the call could not even spawn —
|
|
176
|
+
// e.g. git missing, surfaced as exitCode 127 with `error` set) is distinct
|
|
177
|
+
// from git completing and cleanly reporting a negative answer (non-zero
|
|
178
|
+
// exit with no useful output, or exit 0 with empty stdout). Only the former
|
|
179
|
+
// means a tier's answer was never actually obtained. Wrapping execGit here
|
|
180
|
+
// observes every tier's calls uniformly without changing trySymbolicRef /
|
|
181
|
+
// tryRemoteShow / tryLocalBranch's own return contracts.
|
|
182
|
+
let anyGitFailure = false;
|
|
183
|
+
const execGit = (args, opts) => {
|
|
184
|
+
const r = rawExecGit(args, opts);
|
|
185
|
+
if (r.timedOut || r.error)
|
|
186
|
+
anyGitFailure = true;
|
|
187
|
+
return r;
|
|
188
|
+
};
|
|
163
189
|
// Derive .planning dir relative to cwd (mirrors planningDir() in planning-workspace.cjs)
|
|
164
190
|
const planningDir = node_path_1.default.join(cwd, '.planning');
|
|
165
191
|
// 1. Config override
|
|
166
192
|
const configured = readConfigBaseBranch(planningDir, deps);
|
|
167
193
|
if (configured)
|
|
168
|
-
return configured;
|
|
194
|
+
return { branch: configured, verified: true };
|
|
169
195
|
// 2. symbolic-ref (fast, no network)
|
|
170
196
|
const symref = trySymbolicRef(cwd, execGit);
|
|
171
197
|
if (symref)
|
|
172
|
-
return symref;
|
|
198
|
+
return { branch: symref, verified: true };
|
|
173
199
|
// 3. git remote show origin (authoritative when origin/HEAD unset)
|
|
174
200
|
const remoteShow = tryRemoteShow(cwd, execGit);
|
|
175
201
|
if (remoteShow)
|
|
176
|
-
return remoteShow;
|
|
202
|
+
return { branch: remoteShow, verified: true };
|
|
177
203
|
// 4. Local branch existence
|
|
178
204
|
const local = tryLocalBranch(cwd, execGit);
|
|
179
205
|
if (local)
|
|
180
|
-
return local;
|
|
181
|
-
// 5. Last-resort default
|
|
182
|
-
|
|
206
|
+
return { branch: local, verified: true };
|
|
207
|
+
// 5. Last-resort default. `verified:false` when at least one tier-2/3/4
|
|
208
|
+
// execGit call timed out or failed to run — the default was never actually
|
|
209
|
+
// checked against this repository, it is just what's left after git could
|
|
210
|
+
// not answer (#3057 B4).
|
|
211
|
+
return { branch: 'main', verified: !anyGitFailure };
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Resolve the default/base branch for the repository at `cwd`.
|
|
215
|
+
*
|
|
216
|
+
* Consults the full precedence ladder and always returns a non-empty string.
|
|
217
|
+
* Never throws. See {@link resolveBaseBranchDiagnostics} for a caller that
|
|
218
|
+
* needs to distinguish a verified answer from an unverified fallback.
|
|
219
|
+
*/
|
|
220
|
+
function resolveBaseBranch(cwd, deps) {
|
|
221
|
+
return resolveBaseBranchDiagnostics(cwd, deps).branch;
|
|
183
222
|
}
|
|
184
223
|
/**
|
|
185
224
|
* Detect whether `cwd` sits inside a git worktree, and if so, return the
|
|
186
225
|
* absolute path of the worktree root.
|
|
187
226
|
*/
|
|
188
|
-
function gitWorktreeInfoInternal(cwd) {
|
|
227
|
+
function gitWorktreeInfoInternal(cwd, deps) {
|
|
228
|
+
const execGit = deps?.execGit ?? shell_command_projection_cjs_1.execGit;
|
|
189
229
|
try {
|
|
190
|
-
const insideResult =
|
|
230
|
+
const insideResult = execGit(['rev-parse', '--is-inside-work-tree'], { cwd, timeout: 5000 });
|
|
191
231
|
if (insideResult.exitCode !== 0) {
|
|
192
232
|
return { inside: false, worktreeRoot: null };
|
|
193
233
|
}
|
|
@@ -195,7 +235,7 @@ function gitWorktreeInfoInternal(cwd) {
|
|
|
195
235
|
if (insideStdout !== 'true') {
|
|
196
236
|
return { inside: false, worktreeRoot: null };
|
|
197
237
|
}
|
|
198
|
-
const rootResult =
|
|
238
|
+
const rootResult = execGit(['rev-parse', '--show-toplevel'], { cwd, timeout: 5000 });
|
|
199
239
|
if (rootResult.exitCode !== 0) {
|
|
200
240
|
return { inside: true, worktreeRoot: null };
|
|
201
241
|
}
|
|
@@ -206,6 +246,106 @@ function gitWorktreeInfoInternal(cwd) {
|
|
|
206
246
|
return { inside: false, worktreeRoot: null };
|
|
207
247
|
}
|
|
208
248
|
}
|
|
249
|
+
// ─── Adapter 3: phase-start anchor + touched-file listing (issue #1953) ───────
|
|
250
|
+
/**
|
|
251
|
+
* Resolve the commit that ADDED `<phaseDir>/*-PLAN.md` — the anchor commit
|
|
252
|
+
* marking when the phase began (see `.gsd/phase/feat-1953-complexity-triggered-
|
|
253
|
+
* refactor/42-router-contract.md`, "Touched-file anchor"). `phaseDir` is a
|
|
254
|
+
* project-relative path (backslashes are normalized unconditionally before
|
|
255
|
+
* building the pathspec, never via `path.sep` — matches the repo's
|
|
256
|
+
* cross-platform path-normalization convention).
|
|
257
|
+
*
|
|
258
|
+
* Bounded (`timeout: 15_000`), degrades to `null` on any failure or when no
|
|
259
|
+
* such commit exists (a phase never planned through git, a shallow clone).
|
|
260
|
+
* Never throws.
|
|
261
|
+
*/
|
|
262
|
+
function phaseStartCommit(cwd, phaseDir, execGit) {
|
|
263
|
+
const git = execGit ?? shell_command_projection_cjs_1.execGit;
|
|
264
|
+
try {
|
|
265
|
+
const normalizedPhaseDir = phaseDir.replace(/\\/g, '/');
|
|
266
|
+
const pathspec = `${normalizedPhaseDir}/*-PLAN.md`;
|
|
267
|
+
const r = git(['log', '--format=%H', '--diff-filter=A', '-1', '--', pathspec], { cwd, timeout: 15_000 });
|
|
268
|
+
if (r.exitCode !== 0 || !r.stdout)
|
|
269
|
+
return null;
|
|
270
|
+
const sha = r.stdout.trim();
|
|
271
|
+
return sha || null;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Reject characters/sequences that have no legitimate use in a git revision
|
|
279
|
+
* expression reaching this module (a ref name, SHA, or `<ref>~N` / `<ref>^N`
|
|
280
|
+
* / `<ref>@{...}` navigation) but that a hostile `--since` value could use to
|
|
281
|
+
* confuse either the shell-out or a downstream reader: whitespace, ASCII
|
|
282
|
+
* control characters, and the option-shaped `?`, `*`, `[`, `\` characters
|
|
283
|
+
* that `git check-ref-format` also disallows in ref *names*. A leading `-`
|
|
284
|
+
* is rejected outright — that is the actual option-injection vector `--end-
|
|
285
|
+
* of-options` (below) already neutralizes, so this is belt-and-suspenders
|
|
286
|
+
* for older git. `..` is rejected because `sinceRef` is a single revision
|
|
287
|
+
* that this function itself turns into a range (`sinceRef..HEAD`); a
|
|
288
|
+
* `sinceRef` that already contains `..` can only produce a malformed or
|
|
289
|
+
* misleading range. A trailing `.lock` is rejected per `check-ref-format`.
|
|
290
|
+
*
|
|
291
|
+
* Deliberately NOT rejected: `~`, `^`, `:`, `@`, `{`, `}` — `check-ref-
|
|
292
|
+
* format` disallows these in a bare ref *name*, but this value is a git
|
|
293
|
+
* *revision expression*, and rejecting them would break entirely ordinary
|
|
294
|
+
* user input such as `HEAD~1`, `HEAD^`, or `main@{yesterday}`. None of
|
|
295
|
+
* these characters can reintroduce option parsing once `--end-of-options`
|
|
296
|
+
* is in effect, so allowing them costs nothing security-wise.
|
|
297
|
+
*/
|
|
298
|
+
function isSafeRevisionRef(ref) {
|
|
299
|
+
if (ref === '')
|
|
300
|
+
return false;
|
|
301
|
+
if (ref.startsWith('-'))
|
|
302
|
+
return false;
|
|
303
|
+
if (/[\x00-\x1f\x7f ?*[\\]/.test(ref))
|
|
304
|
+
return false;
|
|
305
|
+
if (ref.includes('..'))
|
|
306
|
+
return false;
|
|
307
|
+
if (ref.endsWith('.lock'))
|
|
308
|
+
return false;
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* List files changed between `sinceRef` and `HEAD`, NUL-delimited and
|
|
313
|
+
* quotepath-safe. Load-bearing details (see the router contract):
|
|
314
|
+
* - `-z` and `-c core.quotepath=false` avoid git's lossy quote-and-escape
|
|
315
|
+
* round-trip for non-ASCII paths;
|
|
316
|
+
* - splitting on `NUL` (never `\n`) tolerates a filename containing a real
|
|
317
|
+
* newline (git permits it);
|
|
318
|
+
* - `sinceRef` is validated by `isSafeRevisionRef` AND the revision-range
|
|
319
|
+
* argument is preceded by `--end-of-options`. A trailing `--` alone does
|
|
320
|
+
* NOT stop git from option-parsing an argument that appears BEFORE it —
|
|
321
|
+
* it only stops PATHSPEC interpretation of arguments AFTER it — so
|
|
322
|
+
* `--since '--output=/tmp/pwn'` would otherwise become the argument
|
|
323
|
+
* `--output=/tmp/pwn..HEAD`, which git accepts as an option and uses to
|
|
324
|
+
* redirect diff output to an attacker-chosen path. `--end-of-options`
|
|
325
|
+
* (git >= 2.24) is the correct fix: everything after it is parsed as a
|
|
326
|
+
* revision or path, never as an option, regardless of leading `-`.
|
|
327
|
+
*
|
|
328
|
+
* Bounded (`timeout: 15_000`), degrades to `null` when `sinceRef` fails
|
|
329
|
+
* validation or the underlying git call fails (non-zero exit, timeout, or
|
|
330
|
+
* spawn error) — never throws. An empty result set (no files changed
|
|
331
|
+
* between the two revisions) is a valid, non-null answer: `[]`.
|
|
332
|
+
*/
|
|
333
|
+
function changedFilesSince(cwd, sinceRef, execGit) {
|
|
334
|
+
if (!isSafeRevisionRef(sinceRef))
|
|
335
|
+
return null;
|
|
336
|
+
const git = execGit ?? shell_command_projection_cjs_1.execGit;
|
|
337
|
+
try {
|
|
338
|
+
const r = git(['-c', 'core.quotepath=false', 'diff', '--name-only', '-z', '--end-of-options', `${sinceRef}..HEAD`, '--'], { cwd, timeout: 15_000 });
|
|
339
|
+
if (r.exitCode !== 0)
|
|
340
|
+
return null;
|
|
341
|
+
if (!r.stdout)
|
|
342
|
+
return [];
|
|
343
|
+
return r.stdout.split('\0').filter((f) => f.length > 0);
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
209
349
|
// ─── CLI entry point ──────────────────────────────────────────────────────────
|
|
210
350
|
/**
|
|
211
351
|
* CLI command: `gsd-tools git base-branch`
|
|
@@ -213,7 +353,12 @@ function gitWorktreeInfoInternal(cwd) {
|
|
|
213
353
|
* Called by workflows via `gsd_run query git.base-branch`.
|
|
214
354
|
*/
|
|
215
355
|
function cmdGitBaseBranch(cwd, _args, deps) {
|
|
216
|
-
const branch =
|
|
356
|
+
const { branch, verified } = resolveBaseBranchDiagnostics(cwd, deps);
|
|
357
|
+
if (!verified) {
|
|
358
|
+
const writeDiagnostic = deps?.writeDiagnostic ?? ((s) => process.stderr.write(s));
|
|
359
|
+
writeDiagnostic(`⚠ git-base-branch: defaulted to 'main' WITHOUT verifying against this repository — ` +
|
|
360
|
+
`a git query timed out or could not run. See #3057.\n`);
|
|
361
|
+
}
|
|
217
362
|
const write = deps?.write ?? ((s) => process.stdout.write(s));
|
|
218
363
|
write(branch + '\n');
|
|
219
364
|
return branch;
|
|
@@ -16,6 +16,15 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
|
|
|
16
16
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
17
17
|
const capabilityStateMod = require("./capability-state.cjs");
|
|
18
18
|
const { isCapabilityActive } = capabilityStateMod;
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
|
20
|
+
const ioMod = require("./io.cjs");
|
|
21
|
+
const { serializeForOutput } = ioMod;
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- prompt-budget.cjs is an export= CommonJS module
|
|
23
|
+
const promptBudget = require("./prompt-budget.cjs");
|
|
24
|
+
// The repo's single token scale (phase-estimation.cts documents the rule: a
|
|
25
|
+
// ratio between two measurement methods measures the methods, not the miss).
|
|
26
|
+
// This module previously carried a private copy of the same chars/4 formula.
|
|
27
|
+
const { estimateTokens } = promptBudget;
|
|
19
28
|
/**
|
|
20
29
|
* Return the standard disabled response object.
|
|
21
30
|
*/
|
|
@@ -52,8 +61,12 @@ function execGraphify(cwd, args, options = {}) {
|
|
|
52
61
|
reason: GRAPHIFY_REASON.ENOENT,
|
|
53
62
|
};
|
|
54
63
|
}
|
|
55
|
-
// Timeout —
|
|
56
|
-
|
|
64
|
+
// Timeout — result.timedOut is derived by the shared isSpawnTimeout predicate
|
|
65
|
+
// (shell-command-projection.cts), keyed on error.code === 'ETIMEDOUT' rather
|
|
66
|
+
// than signal === 'SIGTERM': Windows does not reliably report SIGTERM on a
|
|
67
|
+
// timeout kill, and an externally-delivered SIGTERM (error is null) is not
|
|
68
|
+
// a timeout at all.
|
|
69
|
+
if (result.timedOut) {
|
|
57
70
|
return {
|
|
58
71
|
exitCode: 124,
|
|
59
72
|
stdout: result.stdout,
|
|
@@ -104,15 +117,28 @@ function checkGraphifyVersion() {
|
|
|
104
117
|
}
|
|
105
118
|
}
|
|
106
119
|
// Strategy 2: fall back to python3 importlib.metadata
|
|
120
|
+
let pyPackageConfirmed = false;
|
|
107
121
|
if (!versionStr) {
|
|
108
122
|
const pyResult = (0, shell_command_projection_cjs_1.execTool)('python3', [
|
|
109
123
|
'-c',
|
|
110
124
|
'from importlib.metadata import version; print(version("graphifyy"))',
|
|
111
125
|
], { timeout: 5000 });
|
|
112
126
|
if (!pyResult.error && pyResult.exitCode === 0 && pyResult.stdout) {
|
|
113
|
-
versionStr = pyResult.stdout;
|
|
127
|
+
versionStr = pyResult.stdout.trim();
|
|
128
|
+
pyPackageConfirmed = true; // importlib.metadata confirmed the package
|
|
114
129
|
}
|
|
115
130
|
}
|
|
131
|
+
else {
|
|
132
|
+
// #3020: verify the `graphify` binary on PATH is actually the graphifyy
|
|
133
|
+
// package — a foreign binary that happens to print a version-like string
|
|
134
|
+
// must not silently report compatible. If importlib.metadata cannot confirm
|
|
135
|
+
// the package, emit an identity warning even if the version looks right.
|
|
136
|
+
const pyVerify = (0, shell_command_projection_cjs_1.execTool)('python3', [
|
|
137
|
+
'-c',
|
|
138
|
+
'from importlib.metadata import version; print(version("graphifyy"))',
|
|
139
|
+
], { timeout: 5000 });
|
|
140
|
+
pyPackageConfirmed = !pyVerify.error && pyVerify.exitCode === 0 && !!pyVerify.stdout;
|
|
141
|
+
}
|
|
116
142
|
if (!versionStr) {
|
|
117
143
|
return { version: null, compatible: null, warning: 'Could not determine graphify version' };
|
|
118
144
|
}
|
|
@@ -120,9 +146,19 @@ function checkGraphifyVersion() {
|
|
|
120
146
|
if (parts.length < 2 || parts.some(isNaN)) {
|
|
121
147
|
return { version: versionStr, compatible: null, warning: 'Could not parse version: ' + versionStr };
|
|
122
148
|
}
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
149
|
+
const versionInRange = parts[0] === 0 && parts[1] >= 4;
|
|
150
|
+
// #3020: if the `graphify` binary answered --version but the Python package
|
|
151
|
+
// graphifyy could not be confirmed, the tool identity is unverified — emit
|
|
152
|
+
// a warning naming the mismatch regardless of version-range compatibility.
|
|
153
|
+
if (!pyPackageConfirmed) {
|
|
154
|
+
return {
|
|
155
|
+
version: versionStr,
|
|
156
|
+
compatible: false,
|
|
157
|
+
warning: 'graphify version ' + versionStr + ' detected but the graphifyy Python package could not be confirmed — the `graphify` binary on PATH may be a different tool. Verify with: pip show graphifyy',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const warning = versionInRange ? null : 'graphify version ' + versionStr + ' is outside tested range >=0.4.0,<1.0';
|
|
161
|
+
return { version: versionStr, compatible: versionInRange, warning };
|
|
126
162
|
}
|
|
127
163
|
/**
|
|
128
164
|
* Safely read and parse a JSON file. Returns null on missing file or parse error.
|
|
@@ -195,41 +231,118 @@ function seedAndExpand(graph, term, maxHops = 2) {
|
|
|
195
231
|
const resultNodes = [...visitedNodes].map(id => nodeMap[id]).filter((n) => Boolean(n));
|
|
196
232
|
return { nodes: resultNodes, edges: collectedEdges, seeds: new Set(seeds.map(n => n.id)) };
|
|
197
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* The single definition of `graphifyQuery`'s wire shape.
|
|
236
|
+
*
|
|
237
|
+
* Both the emitter (`graphifyQuery`'s return) and the budget estimator go
|
|
238
|
+
* through here, so `budget_estimate` measures the object the caller actually
|
|
239
|
+
* receives rather than a private approximation of it (#2738).
|
|
240
|
+
*/
|
|
241
|
+
function buildQueryResponse(term, core) {
|
|
242
|
+
return {
|
|
243
|
+
term,
|
|
244
|
+
nodes: core.nodes,
|
|
245
|
+
edges: core.edges,
|
|
246
|
+
total_nodes: core.nodes.length,
|
|
247
|
+
total_edges: core.edges.length,
|
|
248
|
+
trimmed: core.trimmed,
|
|
249
|
+
// Budget outcome (#2738) — only present when a budget was requested
|
|
250
|
+
...(core.budget ? { budget_met: core.budget.met, budget_estimate: core.budget.estimate } : {}),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
198
253
|
/**
|
|
199
254
|
* Apply token budget by dropping edges by confidence tier (D-04, D-05, D-06).
|
|
200
|
-
* Token estimation: Math.ceil(JSON.stringify(obj).length / 4).
|
|
201
255
|
* Drop order: AMBIGUOUS -> INFERRED -> EXTRACTED.
|
|
256
|
+
*
|
|
257
|
+
* The estimate measures the response **as emitted** — `serializeForOutput()`
|
|
258
|
+
* over the same object `graphifyQuery` returns, pretty-printed and including
|
|
259
|
+
* the wrapper keys. Measuring a compact `{nodes, edges}` instead understates
|
|
260
|
+
* the payload the caller receives, which makes `budget_met` a confident claim
|
|
261
|
+
* about a payload nobody is handed (#2738).
|
|
262
|
+
*
|
|
263
|
+
* `term` participates in the emitted bytes, so it is threaded through; the
|
|
264
|
+
* default keeps direct unit calls on the same wire shape, minus those bytes.
|
|
202
265
|
*/
|
|
203
|
-
function applyBudget(result, budgetTokens) {
|
|
204
|
-
|
|
266
|
+
function applyBudget(result, budgetTokens, term = '') {
|
|
267
|
+
// == null (not truthiness): --budget 0 is a valid parsed budget the router
|
|
268
|
+
// forwards, and treating it as "no budget" silently returns the unbounded
|
|
269
|
+
// result — the same silent-non-application defect class as #974/#2738.
|
|
270
|
+
//
|
|
271
|
+
// Number.isFinite additionally keeps NaN out of the comparisons below, where
|
|
272
|
+
// every `estimate <= NaN` is false: the loop would strip all three tiers and
|
|
273
|
+
// return a seeds-only payload indistinguishable from a legitimate aggressive
|
|
274
|
+
// trim. The CLI cannot reach that state — graphify-command-router rejects a
|
|
275
|
+
// non-numeric --budget before this is called — but graphifyQuery and
|
|
276
|
+
// applyBudget are module-level entry points a future caller could reach
|
|
277
|
+
// without that validation. Infinity routes here too, and deliberately: an
|
|
278
|
+
// unbounded budget is not a budget.
|
|
279
|
+
if (budgetTokens == null || !Number.isFinite(budgetTokens))
|
|
205
280
|
return result;
|
|
206
281
|
const CONFIDENCE_ORDER = ['AMBIGUOUS', 'INFERRED', 'EXTRACTED'];
|
|
207
282
|
let edges = [...result.edges];
|
|
208
283
|
let omitted = 0;
|
|
209
|
-
|
|
284
|
+
// Nodes that survive a given edge set: edge-reachable, plus seeds (always kept)
|
|
285
|
+
const survivingNodes = (edgeSet) => {
|
|
286
|
+
const reachableNodes = new Set();
|
|
287
|
+
for (const edge of edgeSet) {
|
|
288
|
+
reachableNodes.add(edge.source);
|
|
289
|
+
reachableNodes.add(edge.target);
|
|
290
|
+
}
|
|
291
|
+
return result.nodes.filter(n => reachableNodes.has(n.id) || (result.seeds && result.seeds.has(n.id)));
|
|
292
|
+
};
|
|
293
|
+
const trimmedLabel = (dropped, unreachable) => dropped > 0 ? `[${dropped} edges omitted, ${unreachable} nodes unreachable]` : null;
|
|
294
|
+
/**
|
|
295
|
+
* Tokens of the response as `output()` will emit it.
|
|
296
|
+
*
|
|
297
|
+
* Self-referential by construction: `budget_estimate` is itself one of the
|
|
298
|
+
* emitted fields, so its own digit width counts toward the total. Resolved by
|
|
299
|
+
* iterating to a fixed point — the sequence is non-decreasing (a wider number,
|
|
300
|
+
* and `false` over `true`, can only add characters), so it settles in a couple
|
|
301
|
+
* of passes. The cap is a guard rather than an expectation, and it exits on the
|
|
302
|
+
* larger value: over-reporting is the safe direction for a budget signal;
|
|
303
|
+
* under-reporting is the defect this fixes.
|
|
304
|
+
*/
|
|
305
|
+
const wireEstimate = (candidateNodes, candidateEdges, trimmed) => {
|
|
306
|
+
let est = 0;
|
|
307
|
+
for (let i = 0; i < 8; i++) {
|
|
308
|
+
const next = estimateTokens(serializeForOutput(buildQueryResponse(term, {
|
|
309
|
+
nodes: candidateNodes,
|
|
310
|
+
edges: candidateEdges,
|
|
311
|
+
trimmed,
|
|
312
|
+
budget: { met: est <= budgetTokens, estimate: est },
|
|
313
|
+
})));
|
|
314
|
+
if (next === est)
|
|
315
|
+
break;
|
|
316
|
+
est = next;
|
|
317
|
+
}
|
|
318
|
+
return est;
|
|
319
|
+
};
|
|
320
|
+
// Estimate against the post-pruning node set after each tier removal, so a
|
|
321
|
+
// removal that already fits (once orphaned nodes are excluded) stops the loop
|
|
322
|
+
// instead of dropping the next, higher-confidence tier too (#2738).
|
|
323
|
+
let nodes = survivingNodes(edges);
|
|
324
|
+
let estimate = wireEstimate(nodes, edges, trimmedLabel(omitted, result.nodes.length - nodes.length));
|
|
210
325
|
for (const tier of CONFIDENCE_ORDER) {
|
|
211
|
-
if (
|
|
326
|
+
if (estimate <= budgetTokens)
|
|
212
327
|
break;
|
|
213
328
|
const before = edges.length;
|
|
214
329
|
// Check both confidence and confidence_score field names (Open Question 1)
|
|
215
330
|
edges = edges.filter(e => (e.confidence || e.confidence_score) !== tier);
|
|
216
331
|
omitted += before - edges.length;
|
|
332
|
+
nodes = survivingNodes(edges);
|
|
333
|
+
estimate = wireEstimate(nodes, edges, trimmedLabel(omitted, result.nodes.length - nodes.length));
|
|
217
334
|
}
|
|
218
|
-
// Find unreachable nodes after edge removal
|
|
219
|
-
const reachableNodes = new Set();
|
|
220
|
-
for (const edge of edges) {
|
|
221
|
-
reachableNodes.add(edge.source);
|
|
222
|
-
reachableNodes.add(edge.target);
|
|
223
|
-
}
|
|
224
|
-
// Always keep seed nodes
|
|
225
|
-
const nodes = result.nodes.filter(n => reachableNodes.has(n.id) || (result.seeds && result.seeds.has(n.id)));
|
|
226
335
|
const unreachable = result.nodes.length - nodes.length;
|
|
227
336
|
return {
|
|
228
337
|
nodes,
|
|
229
338
|
edges,
|
|
230
|
-
trimmed: omitted
|
|
339
|
+
trimmed: trimmedLabel(omitted, unreachable),
|
|
231
340
|
total_nodes: nodes.length,
|
|
232
341
|
total_edges: edges.length,
|
|
342
|
+
// Seeds are retained unconditionally, so the seed set is a floor the
|
|
343
|
+
// reduction cannot go below — report the outcome instead of hiding a miss (#2738)
|
|
344
|
+
budget_met: estimate <= budgetTokens,
|
|
345
|
+
budget_estimate: estimate,
|
|
233
346
|
};
|
|
234
347
|
}
|
|
235
348
|
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
@@ -303,17 +416,19 @@ function graphifyQuery(cwd, term, options = {}) {
|
|
|
303
416
|
return { error: 'Failed to parse graph.json' };
|
|
304
417
|
}
|
|
305
418
|
let result = seedAndExpand(graph, term);
|
|
306
|
-
if (options.budget) {
|
|
307
|
-
result = applyBudget(result, options.budget);
|
|
419
|
+
if (options.budget != null) {
|
|
420
|
+
result = applyBudget(result, options.budget, term);
|
|
308
421
|
}
|
|
309
|
-
|
|
310
|
-
|
|
422
|
+
// Same builder the estimator measured, so budget_estimate describes exactly
|
|
423
|
+
// these bytes (#2738).
|
|
424
|
+
return buildQueryResponse(term, {
|
|
311
425
|
nodes: result.nodes,
|
|
312
426
|
edges: result.edges,
|
|
313
|
-
total_nodes: result.nodes.length,
|
|
314
|
-
total_edges: result.edges.length,
|
|
315
427
|
trimmed: 'trimmed' in result ? (result.trimmed || null) : null,
|
|
316
|
-
|
|
428
|
+
budget: 'budget_met' in result
|
|
429
|
+
? { met: result.budget_met, estimate: result.budget_estimate }
|
|
430
|
+
: undefined,
|
|
431
|
+
});
|
|
317
432
|
}
|
|
318
433
|
/**
|
|
319
434
|
* Return status information about the knowledge graph (STAT-01, STAT-02).
|
|
@@ -27,9 +27,16 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
27
27
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
28
28
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
29
29
|
const clock_cjs_1 = require("./clock.cjs");
|
|
30
|
+
const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core-utils.cjs is an export= CommonJS module
|
|
32
|
+
const coreUtilsMod = require("./core-utils.cjs");
|
|
30
33
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
34
|
const ioMod = require("./io.cjs");
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
|
|
36
|
+
const frontmatterMod = require("./frontmatter.cjs");
|
|
32
37
|
const { output } = ioMod;
|
|
38
|
+
const { transliterateForSlug } = coreUtilsMod;
|
|
39
|
+
const { stripFrontmatter } = frontmatterMod;
|
|
33
40
|
// ─── Utilities ──────────────────────────────────────────────────────────────
|
|
34
41
|
function readOptional(filePath) {
|
|
35
42
|
try {
|
|
@@ -43,7 +50,11 @@ function zeroPad(n, width = 2) {
|
|
|
43
50
|
return String(n).padStart(width, '0');
|
|
44
51
|
}
|
|
45
52
|
function slugify(title) {
|
|
46
|
-
|
|
53
|
+
// #2848: transliterate Cyrillic to ASCII before the filter so a non-Latin
|
|
54
|
+
// title does not collapse to an empty slug. The shared primitive keeps this
|
|
55
|
+
// in sync with generateSlugInternal. slugify's DISTINCT contract is preserved:
|
|
56
|
+
// single leading/trailing hyphen strip (/^-|-$/), and NO 60-char truncation.
|
|
57
|
+
return transliterateForSlug(title).replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
47
58
|
}
|
|
48
59
|
// ─── GSD-2 Parser ───────────────────────────────────────────────────────────
|
|
49
60
|
/**
|
|
@@ -116,6 +127,12 @@ function parseTaskMustHaves(content) {
|
|
|
116
127
|
/**
|
|
117
128
|
* Read all task plan files from a GSD-2 tasks/ directory.
|
|
118
129
|
*/
|
|
130
|
+
// #3183 (ADR-3180 Decision 4(a) — bucket B, out of scope for the
|
|
131
|
+
// scanPhasePlans migration): this reads a FOREIGN GSD-2 legacy project's own
|
|
132
|
+
// `tasks/` directory convention (`T##-PLAN.md`) during a one-time import —
|
|
133
|
+
// it is not this project's `.planning/phases/<phase>/` layout at all, has no
|
|
134
|
+
// nested-plans/superseded-status concept, and scanPhasePlans's grammar
|
|
135
|
+
// (which is scoped to GSD's OWN phase directories) does not apply here.
|
|
119
136
|
function readTasksDir(tasksDir) {
|
|
120
137
|
if (!node_fs_1.default.existsSync(tasksDir))
|
|
121
138
|
return [];
|
|
@@ -218,9 +235,22 @@ function buildPlanMd(task, phasePrefix, planPrefix, phaseSlug, milestoneTitle) {
|
|
|
218
235
|
*/
|
|
219
236
|
function buildSummaryMd(task, phasePrefix, planPrefix) {
|
|
220
237
|
const raw = task.summary || '';
|
|
221
|
-
// Strip GSD-2 frontmatter block
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
// Strip the GSD-2 frontmatter block via the canonical primitive (#2703). The
|
|
239
|
+
// previous local regex required a bare `\n` after the closing `---`, so a
|
|
240
|
+
// CRLF-authored summary never matched, fell through to the untouched-raw
|
|
241
|
+
// branch, and had its frontmatter emitted a second time inside the body of
|
|
242
|
+
// the document this function then wrapped in a fresh v1 block.
|
|
243
|
+
//
|
|
244
|
+
// `extractFrontmatter` — which the issue names — returns only the parsed
|
|
245
|
+
// object and never the body, so it cannot serve this call site;
|
|
246
|
+
// `stripFrontmatter` is the same module's canonical body primitive.
|
|
247
|
+
//
|
|
248
|
+
// `once` is load-bearing. A GSD-2 summary is an arbitrary user-authored
|
|
249
|
+
// document, not a GSD artefact with a known frontmatter-doubling failure
|
|
250
|
+
// mode, so a body opening with a thematic-break-delimited section
|
|
251
|
+
// (`---` / heading / `---`) is far likelier than a corrupt second header —
|
|
252
|
+
// and the default greedy loop would delete it without a trace.
|
|
253
|
+
const body = stripFrontmatter(raw, { once: true }).trim();
|
|
224
254
|
return [
|
|
225
255
|
'---',
|
|
226
256
|
`phase: "${phasePrefix}"`,
|
|
@@ -271,7 +301,9 @@ function buildStateMd(phaseMap) {
|
|
|
271
301
|
const currentEntry = phaseMap.find(p => !p.slice.done);
|
|
272
302
|
const totalPhases = phaseMap.length;
|
|
273
303
|
const donePhases = phaseMap.filter(p => p.slice.done).length;
|
|
274
|
-
|
|
304
|
+
// ADR-3180 D7: one owner for completion percent. clampPercent's 100 ceiling is
|
|
305
|
+
// unreachable here (donePhases is a subset of totalPhases) — the value is unchanged.
|
|
306
|
+
const pct = (0, phase_lifecycle_cjs_1.clampPercent)(donePhases, totalPhases);
|
|
275
307
|
const currentPhaseNum = currentEntry ? zeroPad(currentEntry.phaseNum) : zeroPad(totalPhases);
|
|
276
308
|
const currentSlug = currentEntry ? slugify(currentEntry.slice.title) : 'complete';
|
|
277
309
|
const status = currentEntry ? 'Ready to plan' : 'All phases complete';
|