@opengsd/gsd-core 1.10.0 → 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 +1 -1
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +5 -5
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +37 -0
- package/agents/gsd-planner.md +44 -46
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +12 -3
- package/bin/install.js +841 -971
- 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 +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.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 +469 -88
- package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
- package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
- package/gsd-core/bin/lib/api-coverage.cjs +3 -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 +876 -240
- package/gsd-core/bin/lib/broken-windows.cjs +1 -1
- 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 +575 -101
- 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 +495 -22
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +71 -37
- 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 +22 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +651 -86
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +75 -0
- package/gsd-core/bin/lib/config.cjs +10 -1
- package/gsd-core/bin/lib/core-utils.cjs +127 -29
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +155 -20
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +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-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init.cjs +321 -129
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +745 -258
- 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 +134 -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-migrations.cjs +138 -31
- package/gsd-core/bin/lib/io.cjs +10 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +754 -70
- 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 +444 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +125 -18
- package/gsd-core/bin/lib/phase.cjs +646 -143
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- 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 +56 -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/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +34 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +385 -94
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +115 -3
- 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/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
- package/gsd-core/bin/lib/smart-entry.cjs +142 -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 +371 -117
- package/gsd-core/bin/lib/state.cjs +1794 -357
- package/gsd-core/bin/lib/surface.cjs +23 -9
- 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 +9 -3
- package/gsd-core/bin/lib/uat.cjs +399 -56
- 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 +24 -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 +258 -8
- package/gsd-core/bin/lib/verify.cjs +368 -888
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- 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/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- 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/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/autonomous.md +1 -1
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +38 -12
- package/gsd-core/workflows/complete-milestone.md +141 -18
- package/gsd-core/workflows/debug.md +7 -5
- package/gsd-core/workflows/diagnose-issues.md +35 -9
- 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.md +2 -1
- 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 +31 -6
- 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 +2 -0
- package/gsd-core/workflows/execute-phase.md +38 -50
- 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/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +5 -5
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone.md +14 -9
- package/gsd-core/workflows/new-project.md +14 -14
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase.md +41 -17
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress.md +34 -6
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
- package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
- package/gsd-core/workflows/quick.md +35 -15
- package/gsd-core/workflows/review.md +26 -5
- 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/spec-phase.md +30 -12
- package/gsd-core/workflows/sync-skills.md +63 -8
- package/gsd-core/workflows/transition.md +46 -11
- package/gsd-core/workflows/ui-phase.md +5 -5
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update.md +1 -1
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +9 -7
- package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- 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 +77 -2
- 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 +38 -24
- package/hooks/dist/gsd-statusline.js +18 -0
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- 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 +9 -0
- package/hooks/gsd-agent-isolation-guard.js +103 -14
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +77 -2
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +38 -24
- package/hooks/gsd-statusline.js +18 -0
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- 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 +9 -0
- package/package.json +21 -9
- package/pi/gsd.cjs +19 -5
- 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/changeset/lint.cjs +60 -5
- 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-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-registry.cjs +3 -14
- 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 +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -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 +21 -10
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +25 -0
- 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 +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +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 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -290,6 +290,56 @@ function _resetRuntimeWarningCacheForTests() {
|
|
|
290
290
|
_warnedConfigKeys.clear();
|
|
291
291
|
_warnedUnknownConfigKeys.clear();
|
|
292
292
|
_warnedUnusableConfig.clear();
|
|
293
|
+
_warnedShadowedGlobalKeys.clear();
|
|
294
|
+
}
|
|
295
|
+
// ─── #3532 (10b): shadowed global-defaults diagnostic ────────────────────────
|
|
296
|
+
// The keys Branch D's `_globalBaseCfg` demonstrably honors from
|
|
297
|
+
// ~/.gsd/defaults.json when no project config exists. Under a project
|
|
298
|
+
// .planning/config.json (Branch A — every real project) the global file is
|
|
299
|
+
// never opened, so each of these set globally is silently inert for resolution.
|
|
300
|
+
// `effort` is in Branch D's honored set but is EXCLUDED from the shadow warning:
|
|
301
|
+
// the install-time effort sync (readGsdEffectiveEffortConfig) DOES merge the
|
|
302
|
+
// global file, so warning on it would be false for the channel users actually
|
|
303
|
+
// control via `effort sync`. Keep this list in lockstep with `_globalBaseCfg`
|
|
304
|
+
// below — the per-key canary in tests/config-loader.test.cjs fails first on
|
|
305
|
+
// drift in either direction.
|
|
306
|
+
const GLOBAL_DEFAULTS_RESOLUTION_KEYS = [
|
|
307
|
+
'model_profile', 'commit_docs', 'research', 'plan_checker', 'verifier',
|
|
308
|
+
'nyquist_validation', 'post_planning_gaps', 'parallelization', 'text_mode',
|
|
309
|
+
'resolve_model_ids', 'context_window', 'subagent_timeout', 'model_overrides',
|
|
310
|
+
'models', 'granularity', 'granularities', 'planning', 'dynamic_routing',
|
|
311
|
+
'effort', 'fast_mode', 'agent_skills', 'response_language', 'runtime',
|
|
312
|
+
'model_profile_overrides', 'model_policy',
|
|
313
|
+
];
|
|
314
|
+
// Module-level dedup keyed on the SORTED shadowed-key set: a later call with
|
|
315
|
+
// the same shadowed set stays quiet, while a config that grows a new shadowed
|
|
316
|
+
// key re-arms the warning. Stronger than _warnedUnknownConfigKeys (which keys
|
|
317
|
+
// on insertion order) — same discipline, order-independent key.
|
|
318
|
+
const _warnedShadowedGlobalKeys = new Set();
|
|
319
|
+
function _warnShadowedGlobalDefaults(globalDefaults, globalPath) {
|
|
320
|
+
const shadowed = GLOBAL_DEFAULTS_RESOLUTION_KEYS.filter(k => k !== 'effort' && Object.prototype.hasOwnProperty.call(globalDefaults, k));
|
|
321
|
+
// Branch D also honors the nested alias workflow.post_planning_gaps (the
|
|
322
|
+
// `?? globalDefaults['workflow']?.['post_planning_gaps']` fallback in
|
|
323
|
+
// _globalBaseCfg) — a global file using only the nested form is equally
|
|
324
|
+
// shadowed, so it reports under its dotted name.
|
|
325
|
+
if (!shadowed.includes('post_planning_gaps')) {
|
|
326
|
+
const wf = globalDefaults['workflow'];
|
|
327
|
+
if (wf && typeof wf === 'object' && !Array.isArray(wf) &&
|
|
328
|
+
Object.prototype.hasOwnProperty.call(wf, 'post_planning_gaps')) {
|
|
329
|
+
shadowed.push('workflow.post_planning_gaps');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (shadowed.length === 0)
|
|
333
|
+
return;
|
|
334
|
+
const dedupKey = shadowed.slice().sort().join(',');
|
|
335
|
+
if (_warnedShadowedGlobalKeys.has(dedupKey))
|
|
336
|
+
return;
|
|
337
|
+
_warnedShadowedGlobalKeys.add(dedupKey);
|
|
338
|
+
try {
|
|
339
|
+
process.stderr.write(`gsd-tools: warning: ${globalPath} sets ${shadowed.join(', ')} but a project config ` +
|
|
340
|
+
`takes precedence here — those global keys are ignored for model resolution. (#3532)\n`);
|
|
341
|
+
}
|
|
342
|
+
catch { /* stderr might be closed in some test harnesses */ }
|
|
293
343
|
}
|
|
294
344
|
// ─── FIX 2: Federated overlay helpers ────────────────────────────────────────
|
|
295
345
|
/**
|
|
@@ -751,6 +801,13 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
751
801
|
fast_mode: (parsed['fast_mode']) || null,
|
|
752
802
|
agent_skills: (parsed['agent_skills']) || {},
|
|
753
803
|
agent_skills_security: (parsed['agent_skills_security']) || null,
|
|
804
|
+
// #3587: phase_commit_docs.<phase-id> — a dynamic-key family shaped like
|
|
805
|
+
// agent_skills above (`{ "<phase-id>": boolean }`). Must be threaded here
|
|
806
|
+
// explicitly: `_baseConfig` is a hand-maintained allowlist, so a key that
|
|
807
|
+
// is only in config-schema.manifest.json's dynamicKeyPatterns (and not
|
|
808
|
+
// projected here) is silently dropped on read — the exact `features`-key
|
|
809
|
+
// failure mode this module's own A3 test guards against.
|
|
810
|
+
phase_commit_docs: (parsed['phase_commit_docs']) || {},
|
|
754
811
|
manager: (parsed['manager']) || {},
|
|
755
812
|
response_language: get('response_language') || null,
|
|
756
813
|
claude_md_path: get('claude_md_path') || null,
|
|
@@ -777,6 +834,22 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
777
834
|
// A1 vs A2: disambiguate by whether a real workstream was requested.
|
|
778
835
|
// Fix 4: empty-string ws ('') resolves the root path → source:'root'.
|
|
779
836
|
const source = wsRequested ? 'workstream' : 'root';
|
|
837
|
+
// #3532 (10b): a parsed project config means Branch D never runs, so every
|
|
838
|
+
// key ~/.gsd/defaults.json sets that Branch D would honor is silently inert
|
|
839
|
+
// here. Observation only — one deduped stderr warning; precedence is
|
|
840
|
+
// untouched. Faults in the global file stay silent in this branch (the
|
|
841
|
+
// project config governs; the nearer file is the actionable one).
|
|
842
|
+
try {
|
|
843
|
+
const shadowHome = process.env['GSD_HOME'] || node_os_1.default.homedir();
|
|
844
|
+
const shadowPath = node_path_1.default.join(shadowHome, '.gsd', 'defaults.json');
|
|
845
|
+
const shadowRead = _readConfigFile(shadowPath);
|
|
846
|
+
if (shadowRead.kind === 'ok') {
|
|
847
|
+
_warnShadowedGlobalDefaults(shadowRead.data, shadowPath);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
catch {
|
|
851
|
+
// Observation only — never let the diagnostic perturb resolution.
|
|
852
|
+
}
|
|
780
853
|
// This config parsed — but a DIFFERENT file on the resolution path may not
|
|
781
854
|
// have. A workstream config that loads cleanly while the root config it
|
|
782
855
|
// inherits from is corrupt is still a degraded resolution: the root's
|
|
@@ -908,6 +981,8 @@ module.exports = {
|
|
|
908
981
|
_getNestedConfigDefault,
|
|
909
982
|
_deepMergeConfig,
|
|
910
983
|
_warnedUnknownConfigKeys,
|
|
984
|
+
_warnedShadowedGlobalKeys,
|
|
985
|
+
GLOBAL_DEFAULTS_RESOLUTION_KEYS,
|
|
911
986
|
_warnUnknownProfileOverrides,
|
|
912
987
|
_resetRuntimeWarningCacheForTests,
|
|
913
988
|
_warnedConfigKeys,
|
|
@@ -68,6 +68,9 @@ const SCHEMA_DEFAULTS = {
|
|
|
68
68
|
'planner.stall_detect_interval_minutes': 5,
|
|
69
69
|
'planner.stall_threshold_minutes': 10,
|
|
70
70
|
'git.create_tag': true,
|
|
71
|
+
// #1689: per-plan agent_hint executor routing — default-on. A no-op for plans
|
|
72
|
+
// without an agent_hint field, so existing dispatch is byte-identical.
|
|
73
|
+
'workflow.agent_hint_routing': true,
|
|
71
74
|
// Derived from the defaults manifest rather than restated, so the manifest
|
|
72
75
|
// stays the single source of truth for the smart-zone budget (#2630).
|
|
73
76
|
'workflow.smart_zone_tokens': CONFIG_DEFAULTS.smart_zone_tokens,
|
|
@@ -652,7 +655,7 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
652
655
|
const val = value;
|
|
653
656
|
validateKnownConfigKeyPath(kp);
|
|
654
657
|
if (!isValidConfigKey(kp, cwd)) {
|
|
655
|
-
error(`Unknown config key: "${kp}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>, features.<feature_name>`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
658
|
+
error(`Unknown config key: "${kp}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>, features.<feature_name>, phase_commit_docs.<phase-id>`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
656
659
|
}
|
|
657
660
|
// Parse value (handle booleans, numbers, and JSON arrays/objects)
|
|
658
661
|
let parsedValue = val;
|
|
@@ -736,6 +739,12 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
736
739
|
error(`Invalid workflow.post_planning_gaps '${val}'. Must be a boolean (true or false).`);
|
|
737
740
|
}
|
|
738
741
|
}
|
|
742
|
+
// Per-plan executor routing via agent_hint frontmatter (#1689)
|
|
743
|
+
if (kp === 'workflow.agent_hint_routing') {
|
|
744
|
+
if (typeof parsedValue !== 'boolean') {
|
|
745
|
+
error(`Invalid workflow.agent_hint_routing '${val}'. Must be a boolean (true or false).`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
739
748
|
// #3086 — git.create_tag: boolean only
|
|
740
749
|
if (kp === 'git.create_tag') {
|
|
741
750
|
if (typeof parsedValue !== 'boolean') {
|
|
@@ -23,7 +23,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
23
23
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
24
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
25
|
const phaseIdModule = require("./phase-id.cjs");
|
|
26
|
-
const { comparePhaseNum } = phaseIdModule;
|
|
26
|
+
const { comparePhaseNum, scopeToPhase } = phaseIdModule;
|
|
27
27
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
28
28
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
29
29
|
const { findContextMdIn } = planningWorkspace;
|
|
@@ -72,17 +72,30 @@ function extractOneLinerFromBody(content) {
|
|
|
72
72
|
if (!content)
|
|
73
73
|
return null;
|
|
74
74
|
const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
75
|
-
const body = normalized.replace(/^---\n[\s\S]*?\n---\n*/, '');
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
const body = normalized.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n*/, '');
|
|
76
|
+
// #3170: anchor to a summary-shaped heading (Summary / Overview /
|
|
77
|
+
// Accomplishments) so an incidental first heading (a rule list, task
|
|
78
|
+
// breakdown, deviation note) does not contribute its first bold run as the
|
|
79
|
+
// deliverable one-liner. Iterate headings in document order and extract from
|
|
80
|
+
// the first summary-shaped one that has a bold run; fall back to null (not the
|
|
81
|
+
// wrong text) when no such heading exists.
|
|
82
|
+
const headingRe = /^#+\s*([^\n]*)\n+\*\*([^*\n]+)\*\*([^\n]*)/gm;
|
|
83
|
+
let match;
|
|
84
|
+
while ((match = headingRe.exec(body)) !== null) {
|
|
85
|
+
if (!/summary|overview|accomplish/i.test(match[1]))
|
|
86
|
+
continue;
|
|
87
|
+
const boldInner = match[2].trim();
|
|
88
|
+
const afterBold = match[3];
|
|
89
|
+
if (/:\s*$/.test(boldInner)) {
|
|
90
|
+
const prose = afterBold.trim();
|
|
91
|
+
if (prose.length > 0)
|
|
92
|
+
return prose;
|
|
93
|
+
}
|
|
94
|
+
else if (boldInner.length > 0) {
|
|
95
|
+
return boldInner;
|
|
96
|
+
}
|
|
84
97
|
}
|
|
85
|
-
return
|
|
98
|
+
return null;
|
|
86
99
|
}
|
|
87
100
|
// ─── Misc utilities ───────────────────────────────────────────────────────────
|
|
88
101
|
function pathExistsInternal(cwd, targetPath) {
|
|
@@ -151,27 +164,66 @@ function transliterateForSlug(text) {
|
|
|
151
164
|
}
|
|
152
165
|
return out;
|
|
153
166
|
}
|
|
154
|
-
// ─── Phase file helpers ──────────────────────────────────────────────────────
|
|
155
|
-
/** Filter a file list to just PLAN.md / *-PLAN.md entries. */
|
|
156
|
-
function filterPlanFiles(files) {
|
|
157
|
-
return files.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
|
|
158
|
-
}
|
|
159
|
-
/** Filter a file list to just SUMMARY.md / *-SUMMARY.md entries. */
|
|
160
|
-
function filterSummaryFiles(files) {
|
|
161
|
-
return files.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
162
|
-
}
|
|
163
167
|
/**
|
|
164
168
|
* Read a phase directory and return counts/flags for common file types.
|
|
169
|
+
*
|
|
170
|
+
* #3183 (ADR-3180 Decision 2): `plans`/`summaries` are derived from the
|
|
171
|
+
* canonical `scanPhasePlans` rather than a local re-derivation, so this
|
|
172
|
+
* primitive can no longer diverge from the single owner of live-plan
|
|
173
|
+
* counting. `scanPhasePlans`
|
|
174
|
+
* lives in plan-scan.cjs, which itself imports `countMatchedSummaries` from
|
|
175
|
+
* THIS module — a top-level import here would be circular, so the require
|
|
176
|
+
* is deferred (lazy, inside the function body) to break the cycle at load
|
|
177
|
+
* time. This mirrors the lazy-require seam already used elsewhere in this
|
|
178
|
+
* repo (see src/audit-command-router.cts) for the same "module A needs
|
|
179
|
+
* module B which needs module A" shape.
|
|
180
|
+
*
|
|
181
|
+
* `hasResearch`/`hasContext`/`hasVerification`/`hasReviews` stay on the raw
|
|
182
|
+
* `readdirSync` listing — they are not plan-scan concerns.
|
|
183
|
+
*
|
|
184
|
+
* #3511 BLOCKER-2: the raw listing is scoped through `scopeToPhase` (keyed on
|
|
185
|
+
* `path.basename(phaseDir)`) before any of the four artifact predicates run,
|
|
186
|
+
* so a stray cross-phase file (e.g. `04-VERIFICATION.md` sitting inside phase
|
|
187
|
+
* 03's directory) cannot flip `hasResearch`/`hasContext`/`hasVerification`/
|
|
188
|
+
* `hasReviews` true for a phase it does not belong to — the same membership
|
|
189
|
+
* rule every other aggregate phase-directory scan (`uat.cts`, `audit.cts`,
|
|
190
|
+
* `phase.cts`, `state.cts`) already routes through. `hasContext` is scoped by
|
|
191
|
+
* passing the already-scoped array into `findContextMdIn` at this call site
|
|
192
|
+
* only — `findContextMdIn` itself is unchanged and its other 4 call sites
|
|
193
|
+
* (roadmap.cts, gap-checker.cts, init.cts) are unaffected.
|
|
194
|
+
*
|
|
195
|
+
* Degrades on an unreadable directory instead of throwing: empty arrays,
|
|
196
|
+
* every flag false, scope UNREADABLE (mirroring scanPhasePlans's own
|
|
197
|
+
* degrade path).
|
|
165
198
|
*/
|
|
166
199
|
function getPhaseFileStats(phaseDir) {
|
|
167
|
-
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
201
|
+
const scanPhasePlans = require('./plan-scan.cjs');
|
|
202
|
+
const scan = scanPhasePlans(phaseDir);
|
|
203
|
+
let files;
|
|
204
|
+
try {
|
|
205
|
+
files = node_fs_1.default.readdirSync(phaseDir);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return {
|
|
209
|
+
plans: scan.planFiles,
|
|
210
|
+
summaries: scan.summaryFiles,
|
|
211
|
+
hasResearch: false,
|
|
212
|
+
hasContext: false,
|
|
213
|
+
hasVerification: false,
|
|
214
|
+
hasReviews: false,
|
|
215
|
+
scope: scan.scope,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const scopedFiles = scopeToPhase(files, node_path_1.default.basename(phaseDir));
|
|
168
219
|
return {
|
|
169
|
-
plans:
|
|
170
|
-
summaries:
|
|
171
|
-
hasResearch:
|
|
172
|
-
hasContext: findContextMdIn(
|
|
173
|
-
hasVerification:
|
|
174
|
-
hasReviews:
|
|
220
|
+
plans: scan.planFiles,
|
|
221
|
+
summaries: scan.summaryFiles,
|
|
222
|
+
hasResearch: scopedFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md'),
|
|
223
|
+
hasContext: findContextMdIn(scopedFiles) !== null,
|
|
224
|
+
hasVerification: scopedFiles.some(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md'),
|
|
225
|
+
hasReviews: scopedFiles.some(f => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md'),
|
|
226
|
+
scope: scan.scope,
|
|
175
227
|
};
|
|
176
228
|
}
|
|
177
229
|
/**
|
|
@@ -297,6 +349,35 @@ function summaryCandidates(plan) {
|
|
|
297
349
|
const extended = base.match(/^(\d+)-PLAN-(\d+)/i);
|
|
298
350
|
if (extended)
|
|
299
351
|
candidates.push(dir + extended[1] + '-' + extended[2] + '-SUMMARY.md');
|
|
352
|
+
// #3183: canonical-id form. Restores the coverage of the pre-migration
|
|
353
|
+
// bespoke I001 rule (verify.cts, pre-#3183, via validate.cjs's now-unused
|
|
354
|
+
// `canonicalPlanStem` — behaviourally identical to `extractCanonicalPlanId`,
|
|
355
|
+
// confirmed empirically), which matched a plan carrying a descriptive slug
|
|
356
|
+
// after its <phase>-<plan> id — e.g. `68-01-scaffolding-PLAN.md` — against
|
|
357
|
+
// a summary named only by the bare id — `68-01-SUMMARY.md`. None of the
|
|
358
|
+
// three candidates above produce that filename.
|
|
359
|
+
//
|
|
360
|
+
// Narrowed to the case `extractCanonicalPlanId` actually extracted an
|
|
361
|
+
// <id>-<id> pair (its result differs from the plan's own PLAN-stripped
|
|
362
|
+
// base). When no pair is found it falls back to returning that same base
|
|
363
|
+
// unchanged, which would otherwise push a redundant candidate identical to
|
|
364
|
+
// the `<stem>-SUMMARY.md` form above (e.g. `setup-PLAN.md` -> canonical
|
|
365
|
+
// 'setup' -> 'setup-SUMMARY.md', already candidate #2) rather than the
|
|
366
|
+
// original rule's actual behavior of matching only real id pairs.
|
|
367
|
+
//
|
|
368
|
+
// Collision, matching the original rule byte-for-behaviour: two plans that
|
|
369
|
+
// share the same <phase>-<plan> id but differ only in their descriptive
|
|
370
|
+
// slug (`68-01-alpha-PLAN.md` + `68-01-beta-PLAN.md`) both generate the
|
|
371
|
+
// SAME candidate `68-01-SUMMARY.md` and therefore BOTH read as summarized
|
|
372
|
+
// off one shared summary file. This is not a new regression: the
|
|
373
|
+
// pre-migration bespoke rule collapsed the same way (it populated one
|
|
374
|
+
// `summaryBases` Set keyed by canonical stem, so any plan whose canonical
|
|
375
|
+
// stem hit the set counted as matched, with no cardinality check against
|
|
376
|
+
// how many plans shared that stem).
|
|
377
|
+
const planStem = base.replace(/-PLAN$/i, '');
|
|
378
|
+
const canonicalId = extractCanonicalPlanId(base + '.md');
|
|
379
|
+
if (canonicalId !== planStem)
|
|
380
|
+
candidates.push(dir + canonicalId + '-SUMMARY.md');
|
|
300
381
|
return candidates;
|
|
301
382
|
}
|
|
302
383
|
/**
|
|
@@ -314,6 +395,24 @@ function findUnsummarizedPlans(planFiles, summaryFiles) {
|
|
|
314
395
|
const summarySet = new Set(summaryFiles);
|
|
315
396
|
return planFiles.filter((plan) => !summaryCandidates(plan).some((c) => summarySet.has(c)));
|
|
316
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* #3183: the mirror image of `findUnsummarizedPlans` — the summary files in
|
|
400
|
+
* `summaryFiles` that do NOT pair with ANY plan in `planFiles`, using the
|
|
401
|
+
* identical `summaryCandidates` matching rule as `countMatchedSummaries` /
|
|
402
|
+
* `findUnsummarizedPlans`. Callers that must name orphaned summaries (a
|
|
403
|
+
* stray non-plan summary, or a summary whose plan was renamed/removed) need
|
|
404
|
+
* this instead of a bespoke exact-suffix Set-diff, which cannot recognize
|
|
405
|
+
* the nested or extended naming forms `summaryCandidates` already handles —
|
|
406
|
+
* a divergence that produced false "orphan summary" warnings.
|
|
407
|
+
*/
|
|
408
|
+
function findOrphanSummaries(planFiles, summaryFiles) {
|
|
409
|
+
const claimed = new Set();
|
|
410
|
+
for (const plan of planFiles) {
|
|
411
|
+
for (const candidate of summaryCandidates(plan))
|
|
412
|
+
claimed.add(candidate);
|
|
413
|
+
}
|
|
414
|
+
return summaryFiles.filter((s) => !claimed.has(s));
|
|
415
|
+
}
|
|
317
416
|
module.exports = {
|
|
318
417
|
toPosixPath,
|
|
319
418
|
detectSubRepos,
|
|
@@ -321,12 +420,11 @@ module.exports = {
|
|
|
321
420
|
pathExistsInternal,
|
|
322
421
|
generateSlugInternal,
|
|
323
422
|
transliterateForSlug,
|
|
324
|
-
filterPlanFiles,
|
|
325
|
-
filterSummaryFiles,
|
|
326
423
|
getPhaseFileStats,
|
|
327
424
|
readSubdirectories,
|
|
328
425
|
timeAgo,
|
|
329
426
|
extractCanonicalPlanId,
|
|
330
427
|
countMatchedSummaries,
|
|
331
428
|
findUnsummarizedPlans,
|
|
429
|
+
findOrphanSummaries,
|
|
332
430
|
};
|
|
@@ -21,6 +21,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21
21
|
exports.extractDecisions = extractDecisions;
|
|
22
22
|
exports.parseDecisions = parseDecisions;
|
|
23
23
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
24
|
+
const token_scanner_cjs_1 = require("./token-scanner.cjs");
|
|
24
25
|
const DISCRETION_HEADINGS = new Set([
|
|
25
26
|
"claude's discretion",
|
|
26
27
|
'claudes discretion',
|
|
@@ -89,12 +90,14 @@ function parseDecisionLines(block) {
|
|
|
89
90
|
let category = '';
|
|
90
91
|
let inDiscretion = false;
|
|
91
92
|
let current = null;
|
|
93
|
+
let openIndent = null;
|
|
92
94
|
let parseMisses = 0;
|
|
93
95
|
const flush = () => {
|
|
94
96
|
if (current) {
|
|
95
97
|
current.text = current.text.trim();
|
|
96
98
|
out.push(current);
|
|
97
99
|
current = null;
|
|
100
|
+
openIndent = null;
|
|
98
101
|
}
|
|
99
102
|
};
|
|
100
103
|
for (const line of lines) {
|
|
@@ -115,6 +118,23 @@ function parseDecisionLines(block) {
|
|
|
115
118
|
inDiscretion = DISCRETION_HEADINGS.has(normalized);
|
|
116
119
|
continue;
|
|
117
120
|
}
|
|
121
|
+
// Nested bullet under an open decision (#3212 Phase 3, #3169): a bullet
|
|
122
|
+
// indented deeper than the currently-open decision's own bullet is that
|
|
123
|
+
// decision's elaboration (e.g. a cross-reference to a sibling decision),
|
|
124
|
+
// not a fresh declaration attempt — fold it into current.text exactly
|
|
125
|
+
// like a continuation line, before it ever reaches the declaration/
|
|
126
|
+
// parse-miss regexes below. A bullet at the SAME or a SHALLOWER indent
|
|
127
|
+
// is unaffected — tested exactly as before this fix. See design doc
|
|
128
|
+
// .gsd/phase/chore-3414-tokenizer-first-seam/40-design.md §1.3 for why
|
|
129
|
+
// nesting depth, not bullet content, is the signal that distinguishes
|
|
130
|
+
// this from a genuinely malformed top-level declaration.
|
|
131
|
+
if (current &&
|
|
132
|
+
openIndent !== null &&
|
|
133
|
+
trimmed.startsWith('-') &&
|
|
134
|
+
(0, token_scanner_cjs_1.indentWidth)(line) > openIndent) {
|
|
135
|
+
current.text += ' ' + trimmed;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
118
138
|
// Colon form: `- **D-NN[ [tags]]:** text`
|
|
119
139
|
const colonMatch = line.match(bulletColonRe);
|
|
120
140
|
if (colonMatch) {
|
|
@@ -125,6 +145,7 @@ function parseDecisionLines(block) {
|
|
|
125
145
|
: [];
|
|
126
146
|
const trackable = !inDiscretion && !tags.some((t) => NON_TRACKABLE_TAGS.has(t));
|
|
127
147
|
current = { id, text: colonMatch[3], category, tags, trackable };
|
|
148
|
+
openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
|
|
128
149
|
continue;
|
|
129
150
|
}
|
|
130
151
|
// Em-dash form: `- **D-NN[ [tags]] — title** body`
|
|
@@ -140,6 +161,7 @@ function parseDecisionLines(block) {
|
|
|
140
161
|
// title itself is embedded in the bold run but we report the body as text
|
|
141
162
|
// (consistent with how the gate cares only about coverage, not title/body split).
|
|
142
163
|
current = { id, text: emDashMatch[3] || '', category, tags, trackable };
|
|
164
|
+
openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
|
|
143
165
|
continue;
|
|
144
166
|
}
|
|
145
167
|
// Titled-colon form: `- **D-NN[ [tags]]: Title.** body` (#1639). Checked LAST — it is
|
|
@@ -155,6 +177,7 @@ function parseDecisionLines(block) {
|
|
|
155
177
|
: [];
|
|
156
178
|
const trackable = !inDiscretion && !tags.some((t) => NON_TRACKABLE_TAGS.has(t));
|
|
157
179
|
current = { id, text: titledColonMatch[3] || '', category, tags, trackable };
|
|
180
|
+
openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
|
|
158
181
|
continue;
|
|
159
182
|
}
|
|
160
183
|
// Parse-miss guard (FIX B + #1343): a line that looks like a `D-NN` decision
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Parses the real fallow `audit --format json` schema (schema_version 3
|
|
10
10
|
* envelope, nested dead_code/duplication sections). See fallow 2.70.0+.
|
|
11
|
+
*
|
|
12
|
+
* #3411 Phase 2 (#3618): binary resolution no longer lives here — it delegates
|
|
13
|
+
* to the platform seam's `resolveExecutableBinary` (shell-command-projection.cts).
|
|
14
|
+
* This file's prior private resolver deliberately included an extensionless
|
|
15
|
+
* `fallow` as a win32 candidate; the seam does not, and that is a fix, not a
|
|
16
|
+
* regression — an extensionless file sitting beside `fallow.cmd` is npm's POSIX
|
|
17
|
+
* `sh` shim, which `CreateProcess` cannot run (#3275).
|
|
11
18
|
*/
|
|
12
19
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
20
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -19,51 +26,20 @@ exports.normalizeFallowReport = normalizeFallowReport;
|
|
|
19
26
|
exports.normalizeFallowReportFile = normalizeFallowReportFile;
|
|
20
27
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
21
28
|
const node_path_1 = __importDefault(require("node:path"));
|
|
22
|
-
|
|
23
|
-
return process.platform === 'win32'
|
|
24
|
-
? ['fallow.exe', 'fallow.cmd', 'fallow.bat', 'fallow']
|
|
25
|
-
: ['fallow'];
|
|
26
|
-
}
|
|
27
|
-
function isExecutableFile(filePath) {
|
|
28
|
-
try {
|
|
29
|
-
const stat = node_fs_1.default.statSync(filePath);
|
|
30
|
-
if (!stat.isFile())
|
|
31
|
-
return false;
|
|
32
|
-
if (process.platform === 'win32')
|
|
33
|
-
return true;
|
|
34
|
-
node_fs_1.default.accessSync(filePath, node_fs_1.default.constants.X_OK);
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function findInPath(envPath) {
|
|
42
|
-
if (!envPath)
|
|
43
|
-
return null;
|
|
44
|
-
const names = candidateNames();
|
|
45
|
-
const segments = envPath.split(node_path_1.default.delimiter).filter(Boolean);
|
|
46
|
-
for (const segment of segments) {
|
|
47
|
-
for (const name of names) {
|
|
48
|
-
const candidate = node_path_1.default.join(segment, name);
|
|
49
|
-
if (isExecutableFile(candidate))
|
|
50
|
-
return candidate;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
function findInNodeModules(cwd) {
|
|
56
|
-
const names = candidateNames();
|
|
57
|
-
const binDir = node_path_1.default.join(cwd, 'node_modules', '.bin');
|
|
58
|
-
for (const name of names) {
|
|
59
|
-
const candidate = node_path_1.default.join(binDir, name);
|
|
60
|
-
if (isExecutableFile(candidate))
|
|
61
|
-
return candidate;
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
29
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
65
30
|
function resolveFallowBinary({ cwd, envPath = process.env['PATH'] ?? '' }) {
|
|
66
|
-
return
|
|
31
|
+
return (0, shell_command_projection_cjs_1.resolveExecutableBinary)('fallow', {
|
|
32
|
+
prependPaths: [node_path_1.default.join(cwd, 'node_modules', '.bin')],
|
|
33
|
+
// #3619 (epic #3411 Phase 3): pathOverride carries envPath as the search
|
|
34
|
+
// path while leaving `env` unset, so the seam falls back to its default
|
|
35
|
+
// `env` (process.env) for everything else — PATHEXT included. Ambient
|
|
36
|
+
// PATHEXT therefore still governs win32 resolution exactly as before, but
|
|
37
|
+
// this file never reads it itself: that's what keeps this module clean
|
|
38
|
+
// under local/no-private-binary-resolution, which forbids a PATHEXT read
|
|
39
|
+
// outside the seam.
|
|
40
|
+
pathOverride: envPath,
|
|
41
|
+
requireExecutable: true,
|
|
42
|
+
});
|
|
67
43
|
}
|
|
68
44
|
function requireFallowBinary({ cwd, envPath = process.env['PATH'] ?? '' }) {
|
|
69
45
|
const binary = resolveFallowBinary({ cwd, envPath });
|