@opengsd/gsd-core 1.14.0 → 1.15.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/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-code-fixer.compact.md +7 -6
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-debug-session-manager.compact.md +17 -2
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +13 -8
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +8 -7
- package/agents/gsd-planner.md +12 -8
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.md +155 -17
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +10 -9
- package/bin/install.js +642 -95
- package/commands/gsd/autonomous.md +2 -2
- package/commands/gsd/capture.md +1 -1
- package/commands/gsd/mempalace-capture.md +7 -3
- package/commands/gsd/plan-review-convergence.md +6 -6
- package/commands/gsd/progress.md +1 -1
- package/commands/gsd/quick-batch.md +1 -1
- package/commands/gsd/review.md +2 -3
- package/gsd-core/bin/gsd-tools.cjs +335 -22
- package/gsd-core/bin/lib/adr-parser.cjs +3 -1
- package/gsd-core/bin/lib/audit.cjs +81 -13
- package/gsd-core/bin/lib/capability-registry.cjs +82 -187
- package/gsd-core/bin/lib/capability-validator.cjs +0 -1
- package/gsd-core/bin/lib/check-command-router.cjs +101 -14
- package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
- package/gsd-core/bin/lib/commands.cjs +175 -42
- package/gsd-core/bin/lib/config-loader.cjs +65 -4
- package/gsd-core/bin/lib/config.cjs +33 -7
- package/gsd-core/bin/lib/decisions.cjs +30 -14
- package/gsd-core/bin/lib/frontmatter.cjs +13 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +207 -41
- package/gsd-core/bin/lib/install-engine.cjs +13 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
- package/gsd-core/bin/lib/milestone.cjs +18 -5
- package/gsd-core/bin/lib/model-resolver.cjs +159 -50
- package/gsd-core/bin/lib/phase-command-router.cjs +9 -1
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +109 -7
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase.cjs +227 -26
- package/gsd-core/bin/lib/plan-document.cjs +49 -1
- package/gsd-core/bin/lib/planning-document.cjs +459 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +18 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +8 -3
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +163 -3
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
- package/gsd-core/bin/lib/roadmap.cjs +251 -31
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +283 -31
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -33
- package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
- package/gsd-core/bin/lib/state-transition.cjs +39 -2
- package/gsd-core/bin/lib/state.cjs +42 -0
- package/gsd-core/bin/lib/surface.cjs +17 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
- package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
- package/gsd-core/bin/lib/uat.cjs +8 -0
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/verification.cjs +268 -15
- package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
- package/gsd-core/bin/lib/verify.cjs +132 -25
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
- package/gsd-core/bin/shared/config-defaults.manifest.json +3 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +11 -2
- package/gsd-core/references/tdd.md +27 -4
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/worktree-path-safety.md +321 -0
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +2 -2
- package/gsd-core/workflows/add-todo.md +3 -3
- package/gsd-core/workflows/ai-integration-phase.md +11 -3
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +16 -6
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +108 -22
- package/gsd-core/workflows/code-review.md +63 -46
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +1 -1
- package/gsd-core/workflows/complete-milestone.md +2 -2
- package/gsd-core/workflows/debug.md +3 -3
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update.md +3 -3
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +10 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +3 -3
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +36 -26
- package/gsd-core/workflows/execute-plan.md +5 -4
- package/gsd-core/workflows/explore.md +3 -3
- package/gsd-core/workflows/extract-learnings.md +2 -1
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -2
- package/gsd-core/workflows/help/modes/full.compact.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/import.md +2 -2
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +2 -2
- package/gsd-core/workflows/map-codebase.md +2 -2
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +2 -2
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +7 -7
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +24 -7
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +113 -13
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +1 -1
- package/gsd-core/workflows/quick.md +21 -9
- package/gsd-core/workflows/reapply-patches.md +9 -3
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +31 -16
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -2
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +4 -4
- package/gsd-core/workflows/ship.md +3 -2
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +44 -8
- package/gsd-core/workflows/ui-review.md +18 -4
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +7 -7
- package/gsd-core/workflows/validate-phase.md +3 -2
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +81 -16
- package/hooks/dist/gsd-agent-isolation-guard.js +24 -0
- package/hooks/dist/gsd-secret-read-guard.js +27 -1
- package/hooks/dist/gsd-statusline.js +70 -13
- package/hooks/dist/gsd-validate-commit.sh +63 -4
- package/hooks/gsd-agent-isolation-guard.js +24 -0
- package/hooks/gsd-secret-read-guard.js +27 -1
- package/hooks/gsd-statusline.js +70 -13
- package/hooks/gsd-validate-commit.sh +63 -4
- package/package.json +3 -2
- package/scripts/build-hooks.js +15 -6
- package/scripts/check-contract-drift.cjs +127 -11
- package/scripts/command-contract-helpers.cjs +3 -0
- package/scripts/docs-guard-registry.cjs +28 -0
- package/scripts/gen-loop-host-contract.cjs +69 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +14 -0
- package/scripts/lib/ndjson-reporter.cjs +3 -2
- package/scripts/lib/platform-conformance-tier.generated.cjs +11 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +28 -1
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-id-drift.cjs +290 -5
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +28 -9
- package/scripts/lint-workflow-shellcheck-baseline.json +15 -0
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/skills/gsd-autonomous/SKILL.md +2 -2
- package/skills/gsd-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +7 -3
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
- package/skills/gsd-progress/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +1 -1
- package/skills/gsd-review/SKILL.md +2 -3
- package/vscode/package.json +1 -1
|
@@ -28,6 +28,9 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
28
28
|
const node_os_1 = __importDefault(require("node:os"));
|
|
29
29
|
const node_path_1 = __importDefault(require("node:path"));
|
|
30
30
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
31
|
+
// #4717: runtime-identity fill — env rung + per-install marker rung.
|
|
32
|
+
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
33
|
+
const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
|
|
31
34
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
35
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
33
36
|
const { planningDir, planningRoot } = planningWorkspace;
|
|
@@ -150,8 +153,21 @@ const CONFIG_DEFAULTS = {
|
|
|
150
153
|
research_before_questions: _getNestedConfigDefault('workflow', 'research_before_questions'), // #3894
|
|
151
154
|
smart_zone_tokens: _getNestedConfigDefault('workflow', 'smart_zone_tokens'),
|
|
152
155
|
inline_plan_threshold: _getNestedConfigDefault('workflow', 'inline_plan_threshold'), // #3801
|
|
156
|
+
planner_stall_detection_enabled: _getNestedConfigDefault('planner', 'stall_detection_enabled'),
|
|
153
157
|
max_prompt_tokens: _getNestedConfigDefault('review', 'max_prompt_tokens'),
|
|
154
158
|
};
|
|
159
|
+
/**
|
|
160
|
+
* Resolve the planner watchdog policy from hand-edited configuration.
|
|
161
|
+
* Only a real JSON boolean may override the default; strings/numbers/null
|
|
162
|
+
* fail safe to the manifest-owned default-on behavior (#4570).
|
|
163
|
+
*/
|
|
164
|
+
function resolvePlannerStallDetectionEnabled(value) {
|
|
165
|
+
if (typeof value === 'boolean')
|
|
166
|
+
return value;
|
|
167
|
+
// A missing or skewed manifest must never convert an absent override into
|
|
168
|
+
// permission to disable the watchdog. The documented contract is default-on.
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
155
171
|
/**
|
|
156
172
|
* Deep-merge two plain config objects. `overlay` wins on key conflict.
|
|
157
173
|
* Explicit `null` in overlay overrides base (null means "unset this key").
|
|
@@ -601,7 +617,7 @@ function _warnUnusableConfig(fault) {
|
|
|
601
617
|
* cannot dirty the working tree; the ~30 callers that omit it keep persisting, so
|
|
602
618
|
* a legacy config is still migrated exactly once by ordinary use.
|
|
603
619
|
*/
|
|
604
|
-
function
|
|
620
|
+
function loadConfigResolvedInternal(cwd, options = {}) {
|
|
605
621
|
// Opt-OUT, not opt-in: omitting the option must preserve the historical
|
|
606
622
|
// write-back for every existing caller.
|
|
607
623
|
const persist = options['persist'] !== false;
|
|
@@ -614,7 +630,7 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
614
630
|
: (options['workstreamContext'] && Object.prototype.hasOwnProperty.call(options['workstreamContext'], 'ws'))
|
|
615
631
|
? options['workstreamContext']['ws']
|
|
616
632
|
: (process.env['GSD_WORKSTREAM'] || null);
|
|
617
|
-
const ws = typeof activeWorkstream === 'string' ? activeWorkstream
|
|
633
|
+
const ws = typeof activeWorkstream === 'string' ? activeWorkstream.trim() || null : null;
|
|
618
634
|
// wsRequested: true when caller explicitly requested a non-empty workstream.
|
|
619
635
|
// Used for source labeling (Fix 4) and early absent-dir intercept (Fix 2).
|
|
620
636
|
const wsRequested = ws != null && ws !== '';
|
|
@@ -858,6 +874,7 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
858
874
|
phase_naming: get('phase_naming') ?? defaults.phase_naming,
|
|
859
875
|
project_code: get('project_code') ?? defaults.project_code,
|
|
860
876
|
subagent_timeout: get('subagent_timeout', { section: 'workflow', field: 'subagent_timeout' }) ?? defaults.subagent_timeout,
|
|
877
|
+
planner_stall_detection_enabled: resolvePlannerStallDetectionEnabled(getNested('planner', 'stall_detection_enabled')),
|
|
861
878
|
model_overrides: (parsed['model_overrides']) || null,
|
|
862
879
|
agent_tools: (parsed['agent_tools']) || null,
|
|
863
880
|
models: (parsed['models']) || null,
|
|
@@ -959,7 +976,7 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
959
976
|
// `workstream: null` still wins the `hasOwnProperty` check at the top of this
|
|
960
977
|
// function, so spreading cannot let `workstreamContext` reintroduce a workstream.
|
|
961
978
|
if (wsRequested && rootParsed) {
|
|
962
|
-
const fb =
|
|
979
|
+
const fb = loadConfigResolvedInternal(cwd, { ...options, workstream: null });
|
|
963
980
|
return fallback({ config: fb.config, source: 'root', degraded: true });
|
|
964
981
|
}
|
|
965
982
|
// Branch B, C, D, E
|
|
@@ -967,7 +984,7 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
967
984
|
if (rootParsed) {
|
|
968
985
|
// Branch B: workstream requested but ws config.json absent; root config present.
|
|
969
986
|
// (Only reached when wsRequested is false — e.g. ws='' with .planning/workstreams//config.json)
|
|
970
|
-
const fb =
|
|
987
|
+
const fb = loadConfigResolvedInternal(cwd, { ...options, workstream: null });
|
|
971
988
|
return fallback({ config: fb.config, source: 'root', degraded: true });
|
|
972
989
|
}
|
|
973
990
|
// Branch C: .planning/ exists but no config.json and no root config — federated/builtin defaults
|
|
@@ -1016,6 +1033,7 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
1016
1033
|
resolve_model_ids: (globalDefaults['resolve_model_ids']) ?? defaults.resolve_model_ids,
|
|
1017
1034
|
context_window: (globalDefaults['context_window']) ?? defaults.context_window,
|
|
1018
1035
|
subagent_timeout: (globalDefaults['subagent_timeout']) ?? defaults.subagent_timeout,
|
|
1036
|
+
planner_stall_detection_enabled: resolvePlannerStallDetectionEnabled(globalDefaults['planner']?.['stall_detection_enabled']),
|
|
1019
1037
|
model_overrides: (globalDefaults['model_overrides']) || null,
|
|
1020
1038
|
models: (globalDefaults['models']) || null,
|
|
1021
1039
|
granularity: (globalDefaults['granularity']) !== undefined ? globalDefaults['granularity'] : null,
|
|
@@ -1054,6 +1072,48 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
1054
1072
|
}
|
|
1055
1073
|
}
|
|
1056
1074
|
}
|
|
1075
|
+
/**
|
|
1076
|
+
* #4717 — fill an empty `runtime` from the environment, then the per-install
|
|
1077
|
+
* marker. writeNonClaudeDefaults stamps `runtime` into the SHARED
|
|
1078
|
+
* ~/.gsd/defaults.json with whichever non-Claude runtime installed first, so
|
|
1079
|
+
* direct readers of config.runtime saw another runtime's identity (or
|
|
1080
|
+
* nothing) on a multi-runtime machine. Copy-on-write: the builtin-defaults
|
|
1081
|
+
* branch returns a shared object, so never assign into it. An explicit
|
|
1082
|
+
* config.runtime is never overridden.
|
|
1083
|
+
*/
|
|
1084
|
+
function fillRuntimeIdentity(resolved) {
|
|
1085
|
+
const cfg = resolved?.config;
|
|
1086
|
+
if (!cfg)
|
|
1087
|
+
return resolved;
|
|
1088
|
+
const runtime = (0, runtime_name_policy_cjs_1.resolveRuntimeNameFromCandidates)(process.env['GSD_RUNTIME'], (0, runtime_slash_cjs_1.readInstallRuntimeMarker)());
|
|
1089
|
+
// Only a runtime the name policy can canonicalize is an identity. Unknown
|
|
1090
|
+
// tokens pass THROUGH resolveRuntimeNameFromCandidates (future-runtime
|
|
1091
|
+
// tolerance) and must not be materialized into config.runtime, where ~30
|
|
1092
|
+
// consumers would read them — fail safe to no identity (#4717 review).
|
|
1093
|
+
const canonicalRuntime = runtime ? (0, runtime_name_policy_cjs_1.canonicalizeRuntimeName)(runtime) : null;
|
|
1094
|
+
if (!canonicalRuntime)
|
|
1095
|
+
return resolved;
|
|
1096
|
+
// Rung 1 — empty runtime: materialize THIS install's identity (env, then
|
|
1097
|
+
// the per-install marker). An explicit runtime is never overridden.
|
|
1098
|
+
if (!cfg['runtime']) {
|
|
1099
|
+
return { ...resolved, config: { ...cfg, runtime: canonicalRuntime } };
|
|
1100
|
+
}
|
|
1101
|
+
// Rung 2 (#4717 stamped-defaults leg): the global-defaults branch forwards
|
|
1102
|
+
// the SHARED ~/.gsd/defaults.json's runtime verbatim — whichever non-Claude
|
|
1103
|
+
// runtime installed FIRST stamped that machine-wide file, and every other
|
|
1104
|
+
// runtime's resolution inherited its identity (the issue's second failure
|
|
1105
|
+
// shape). When THIS install carries its own identity (GSD_RUNTIME or the
|
|
1106
|
+
// marker), the stamp — not the operator — is speaking: correct it. Project
|
|
1107
|
+
// and workstream configs are explicit operator intent and are never touched;
|
|
1108
|
+
// with no install identity of its own the stamped value stays (status quo).
|
|
1109
|
+
if (resolved.source === 'global-defaults') {
|
|
1110
|
+
return { ...resolved, config: { ...cfg, runtime: canonicalRuntime } };
|
|
1111
|
+
}
|
|
1112
|
+
return resolved;
|
|
1113
|
+
}
|
|
1114
|
+
function loadConfigResolved(cwd, options = {}) {
|
|
1115
|
+
return fillRuntimeIdentity(loadConfigResolvedInternal(cwd, options));
|
|
1116
|
+
}
|
|
1057
1117
|
/**
|
|
1058
1118
|
* loadConfig — backwards-compatible config loading, now a thin wrapper over loadConfigResolved.
|
|
1059
1119
|
* Returns the config object only; for provenance metadata use loadConfigResolved.
|
|
@@ -1072,6 +1132,7 @@ module.exports = {
|
|
|
1072
1132
|
_getNestedConfigDefault,
|
|
1073
1133
|
_getConfigValue,
|
|
1074
1134
|
_getConfigNested,
|
|
1135
|
+
resolvePlannerStallDetectionEnabled,
|
|
1075
1136
|
_deepMergeConfig,
|
|
1076
1137
|
_warnedUnknownConfigKeys,
|
|
1077
1138
|
_warnedShadowedGlobalKeys,
|
|
@@ -20,11 +20,11 @@ const cliExitMod = require("./cli-exit.cjs");
|
|
|
20
20
|
const { ExitError } = cliExitMod;
|
|
21
21
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
22
|
const configLoader = require("./config-loader.cjs");
|
|
23
|
-
const { CONFIG_DEFAULTS } = configLoader;
|
|
23
|
+
const { CONFIG_DEFAULTS, resolvePlannerStallDetectionEnabled } = configLoader;
|
|
24
24
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
25
25
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
26
26
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
27
|
-
const { planningDir, planningRoot, withPlanningLock } = planningWorkspace;
|
|
27
|
+
const { planningDir, planningRoot, resolveEnvWorkstream, withPlanningLock } = planningWorkspace;
|
|
28
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
29
|
const modelProfiles = require("./model-profiles.cjs");
|
|
30
30
|
const { VALID_PROFILES, getAgentToModelMapForProfile, formatAgentToModelMapAsTable } = modelProfiles;
|
|
@@ -66,6 +66,14 @@ const SHIP_PR_BODY_TEMPLATE_TOKENS = new Set([
|
|
|
66
66
|
'padded_phase',
|
|
67
67
|
]);
|
|
68
68
|
const SHIP_PR_BODY_SOURCE_RE = /^(ROADMAP|PLAN|SUMMARY|VERIFICATION|STATE|REQUIREMENTS|CONTEXT)\.md\s+##\s+[^\r\n#][^\r\n]*$/;
|
|
69
|
+
// ADR-612 PR-5: configuration accepts every convention the runtime can read.
|
|
70
|
+
// Keep this distinct from roadmap-upgrade's supported target set: sequential
|
|
71
|
+
// is valid project configuration but is not a migration destination.
|
|
72
|
+
const VALID_PHASE_ID_CONVENTIONS = Object.freeze([
|
|
73
|
+
'sequential',
|
|
74
|
+
'milestone-prefixed',
|
|
75
|
+
'bracket',
|
|
76
|
+
]);
|
|
69
77
|
/**
|
|
70
78
|
* Schema-level defaults for well-known config keys.
|
|
71
79
|
* When a key is absent from config.json and no --default flag was supplied,
|
|
@@ -75,6 +83,7 @@ const SCHEMA_DEFAULTS = {
|
|
|
75
83
|
'context_window': 200000,
|
|
76
84
|
'executor.stall_detect_interval_minutes': 5,
|
|
77
85
|
'executor.stall_threshold_minutes': 10,
|
|
86
|
+
'planner.stall_detection_enabled': CONFIG_DEFAULTS.planner_stall_detection_enabled,
|
|
78
87
|
'planner.stall_detect_interval_minutes': 5,
|
|
79
88
|
'planner.stall_threshold_minutes': 10,
|
|
80
89
|
'git.create_tag': true,
|
|
@@ -152,12 +161,15 @@ function resolveSchemaDefault(cwd, kp) {
|
|
|
152
161
|
* Centralizing emission here means masking can't be missed at a call site.
|
|
153
162
|
*/
|
|
154
163
|
function emitResolvedDefault(kp, value, raw) {
|
|
164
|
+
const resolvedValue = kp === 'planner.stall_detection_enabled'
|
|
165
|
+
? resolvePlannerStallDetectionEnabled(value)
|
|
166
|
+
: value;
|
|
155
167
|
if ((0, secrets_cjs_1.isSecretKey)(kp)) {
|
|
156
|
-
const masked = (0, secrets_cjs_1.maskSecret)(
|
|
168
|
+
const masked = (0, secrets_cjs_1.maskSecret)(resolvedValue);
|
|
157
169
|
output(masked, raw, masked);
|
|
158
170
|
return;
|
|
159
171
|
}
|
|
160
|
-
output(
|
|
172
|
+
output(resolvedValue, raw, String(resolvedValue));
|
|
161
173
|
}
|
|
162
174
|
// ─── Validation helpers ───────────────────────────────────────────────────────
|
|
163
175
|
function validateKnownConfigKeyPath(keyPath) {
|
|
@@ -542,7 +554,7 @@ function _setNestedValue(config, keyPath, parsedValue) {
|
|
|
542
554
|
}
|
|
543
555
|
/**
|
|
544
556
|
* Deletes a value from the config object, allowing nested values via dot
|
|
545
|
-
* notation (e.g., "review.models.
|
|
557
|
+
* notation (e.g., "review.models.codex"). Mirrors `_setNestedValue`'s
|
|
546
558
|
* prototype-pollution guard on every path segment (including intermediates).
|
|
547
559
|
*
|
|
548
560
|
* Unlike `_setNestedValue`, this NEVER creates missing intermediate objects —
|
|
@@ -806,6 +818,9 @@ function cmdConfigSet(cwd, keyPath, value, raw, options = {}) {
|
|
|
806
818
|
const VALID_CONTEXT_VALUES = ['dev', 'research', 'review'];
|
|
807
819
|
if (kp === 'context')
|
|
808
820
|
assertEnumValue(parsedValue, val, VALID_CONTEXT_VALUES, 'context value');
|
|
821
|
+
if (kp === 'phase_id_convention') {
|
|
822
|
+
assertEnumValue(parsedValue, val, VALID_PHASE_ID_CONVENTIONS, 'phase_id_convention');
|
|
823
|
+
}
|
|
809
824
|
// Codebase drift detector (#2003)
|
|
810
825
|
const VALID_DRIFT_ACTIONS = ['warn', 'auto-remap'];
|
|
811
826
|
if (kp === 'workflow.drift_action')
|
|
@@ -853,6 +868,13 @@ function cmdConfigSet(cwd, keyPath, value, raw, options = {}) {
|
|
|
853
868
|
error(`Invalid workflow.agent_hint_routing '${val}'. Must be a boolean (true or false).`);
|
|
854
869
|
}
|
|
855
870
|
}
|
|
871
|
+
// Planner watchdog opt-out (#4570) — only a real boolean may change the
|
|
872
|
+
// default-on policy. In particular, string "false" must not disable it.
|
|
873
|
+
if (kp === 'planner.stall_detection_enabled') {
|
|
874
|
+
if (typeof parsedValue !== 'boolean') {
|
|
875
|
+
error(`Invalid planner.stall_detection_enabled '${val}'. Must be a boolean (true or false).`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
856
878
|
// #3086 — git.create_tag: boolean only
|
|
857
879
|
if (kp === 'git.create_tag') {
|
|
858
880
|
if (typeof parsedValue !== 'boolean') {
|
|
@@ -1161,6 +1183,9 @@ function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
|
|
1161
1183
|
}
|
|
1162
1184
|
error(`Key not found: ${kp}`, ERROR_REASON.CONFIG_KEY_NOT_FOUND);
|
|
1163
1185
|
}
|
|
1186
|
+
if (kp === 'planner.stall_detection_enabled') {
|
|
1187
|
+
current = resolvePlannerStallDetectionEnabled(current);
|
|
1188
|
+
}
|
|
1164
1189
|
// Never echo plaintext for sensitive keys via config-get. Plaintext lives
|
|
1165
1190
|
// in config.json on disk; the CLI surface always shows the masked form.
|
|
1166
1191
|
if ((0, secrets_cjs_1.isSecretKey)(kp)) {
|
|
@@ -1190,7 +1215,7 @@ function resolveFromRootConfig(cwd, kp) {
|
|
|
1190
1215
|
// diverges from planningRoot without a workstream and loadConfigResolved does NOT
|
|
1191
1216
|
// inherit root — matching the runtime's own `if (ws)` gate keeps the two surfaces
|
|
1192
1217
|
// from diverging on the project-scoped (non-workstream) case.
|
|
1193
|
-
if (!
|
|
1218
|
+
if (!resolveEnvWorkstream())
|
|
1194
1219
|
return { found: false, value: undefined };
|
|
1195
1220
|
const root = planningRoot(cwd);
|
|
1196
1221
|
const rootConfigPath = node_path_1.default.join(root, 'config.json');
|
|
@@ -1297,7 +1322,7 @@ function cmdConfigPath(cwd, _raw, workstreamContext = null) {
|
|
|
1297
1322
|
* (caller uses `await` which is safe on a sync return value).
|
|
1298
1323
|
*/
|
|
1299
1324
|
function cmdMigrateConfig(cwd, raw) {
|
|
1300
|
-
const ws =
|
|
1325
|
+
const ws = resolveEnvWorkstream();
|
|
1301
1326
|
// #3749: resolve the migration target through the project-aware resolver so
|
|
1302
1327
|
// GSD_PROJECT scopes the write; migrateOnDisk itself cannot (see its
|
|
1303
1328
|
// configPathOverride note).
|
|
@@ -1345,6 +1370,7 @@ function cmdMigrateConfig(cwd, raw) {
|
|
|
1345
1370
|
}
|
|
1346
1371
|
module.exports = {
|
|
1347
1372
|
VALID_CONFIG_KEYS,
|
|
1373
|
+
VALID_PHASE_ID_CONVENTIONS,
|
|
1348
1374
|
cmdConfigEnsureSection,
|
|
1349
1375
|
cmdConfigSet,
|
|
1350
1376
|
cmdConfigGet,
|
|
@@ -85,14 +85,14 @@ const DECISION_ID_SOURCE = 'D[0-9]*-[A-Za-z0-9][A-Za-z0-9_-]*';
|
|
|
85
85
|
const ID_ATTEMPT_SOURCE = 'D(?:[0-9][A-Za-z0-9]*)?-';
|
|
86
86
|
/**
|
|
87
87
|
* Colon form: `- **D[phase]-NN[ [tags]]:** text`
|
|
88
|
-
* (#1343:
|
|
88
|
+
* (#1343: the pre-colon run subsumes any pre-colon prose, stops at `:**`; #4788: the run is code-span-aware — a backticked span is consumed whole, so a `:` or `*` INSIDE it is data, never grammar)
|
|
89
89
|
* Group 1 captures the FULL id including any phase prefix (#4130).
|
|
90
90
|
* The ID is consumed atomically `(?=(…))\1` — see the hardening note above
|
|
91
91
|
* the constants (#4130 follow-up); with the tail unable to give back, the
|
|
92
92
|
* remaining `[^:*]*:` scan has a single viable split and the whole match is
|
|
93
93
|
* linear in line length.
|
|
94
94
|
*/
|
|
95
|
-
const bulletColonRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?[
|
|
95
|
+
const bulletColonRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?(?:\\u0060[^\\u0060]*\\u0060|[^:*\\u0060])*:\\*\\*\\s*(.*)$`);
|
|
96
96
|
/**
|
|
97
97
|
* Em-dash form: `- **D[phase]-NN[ [tags]] — title** body`
|
|
98
98
|
* The em-dash (U+2014) or its lookalike separates the ID+tags group from a title
|
|
@@ -112,22 +112,32 @@ const bulletColonRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1
|
|
|
112
112
|
* candidate and `**` succeeds or fails identically; no capture involves the
|
|
113
113
|
* dash position. The ID is atomic like the other forms (driver #1).
|
|
114
114
|
*/
|
|
115
|
-
const bulletEmDashRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?[
|
|
115
|
+
const bulletEmDashRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?(?:\\u0060[^\\u0060]*\\u0060|[^*—–\\u0060])*[—–](?:\\u0060[^\\u0060]*\\u0060|[^*\\u0060])*\\*\\*\\s*(.*)$`);
|
|
116
116
|
/**
|
|
117
117
|
* Titled-colon form: `- **D[phase]-NN[ [tags]]: Title.** body`
|
|
118
118
|
* A title sits between the colon and the closing `**` (so the `:**` anchor of
|
|
119
119
|
* bulletColonRe fails, and there is no em-dash for bulletEmDashRe). This is a strict
|
|
120
120
|
* superset of the colon-immediate form, so it MUST be checked AFTER bulletColonRe and
|
|
121
|
-
* bulletEmDashRe — it only catches bullets those two miss. The title
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
121
|
+
* bulletEmDashRe — it only catches bullets those two miss. The title runs are code-span-aware
|
|
122
|
+
* (#4788) but still exclude `*`, so a genuinely-malformed bullet with no bare colon at all
|
|
123
|
+
* before `**` still fails the anchor and falls through to the parse-miss guard — the #1639
|
|
124
|
+
* fail-loud discipline: the separator must be a BARE colon before `**`. A code span's
|
|
125
|
+
* `:`/`*` is data, never grammar; an UNTERMINATED backtick now fails loud (previously it was
|
|
126
|
+
* an ordinary character) — the deliberate cost of span opacity.
|
|
127
|
+
*
|
|
128
|
+
* #4793/#4958: the pre-separator run tolerates a bare colon as prose ONLY when it is
|
|
129
|
+
* immediately followed by whitespace (`:(?=\s)`), e.g. a natural sentence colon in
|
|
130
|
+
* `D-02: Two managers … same pair: the second is rejected.`. A colon with no following
|
|
131
|
+
* whitespace (a compact/ratio-style colon like `3:1`) is never consumed by this branch, so
|
|
132
|
+
* it still hard-caps the pre-separator run and the #1639/#4130/#1343 negative-space fixtures
|
|
133
|
+
* (e.g. `D-07 ratio 3:1:**`) still fail loud. The post-separator branch is unchanged and
|
|
134
|
+
* still excludes colons, so normal greedy backtracking selects the LAST valid split (a bare
|
|
135
|
+
* colon followed by whitespace, or the anchor colon itself) before the closing `**`.
|
|
126
136
|
*
|
|
127
137
|
* The ID is consumed atomically `(?=(…))\1` like the other forms — the
|
|
128
138
|
* hardening note above the constants explains why (#4130 follow-up).
|
|
129
139
|
*/
|
|
130
|
-
const bulletTitledColonRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?[
|
|
140
|
+
const bulletTitledColonRe = new RegExp(`^\\s*-\\s+\\*\\*(?=(${DECISION_ID_SOURCE}))\\1(?:\\s*\\[([^\\]]+)\\])?(?:\\u0060[^\\u0060]*\\u0060|:(?=\\s)|[^:*\\u0060])*:(?:\\u0060[^\\u0060]*\\u0060|[^:*\\u0060])*\\*\\*\\s*(.*)$`);
|
|
131
141
|
/**
|
|
132
142
|
* #4130: the parse-miss guard's probe — a line whose bold lead-in ATTEMPTS the
|
|
133
143
|
* ID grammar (see `ID_ATTEMPT_SOURCE`) but failed all three bullet patterns
|
|
@@ -403,6 +413,7 @@ function parseDecisionLines(block) {
|
|
|
403
413
|
let current = null;
|
|
404
414
|
let openIndent = null;
|
|
405
415
|
let parseMisses = 0;
|
|
416
|
+
const unreadableIds = [];
|
|
406
417
|
const flush = () => {
|
|
407
418
|
if (current) {
|
|
408
419
|
current.text = current.text.trim();
|
|
@@ -501,6 +512,11 @@ function parseDecisionLines(block) {
|
|
|
501
512
|
if (parseMissGuardRe.test(line)) {
|
|
502
513
|
flush();
|
|
503
514
|
parseMisses += 1;
|
|
515
|
+
// #4794: carry the attempted id so a caller capturing stdout (a JSON gate)
|
|
516
|
+
// can name the decision to fix — today it exists only in this stderr warn.
|
|
517
|
+
const attempted = line.match(/\*\*\s*(D(?:[0-9][A-Za-z0-9]*)?-[A-Za-z0-9_-]*)/);
|
|
518
|
+
if (attempted && !unreadableIds.includes(attempted[1]))
|
|
519
|
+
unreadableIds.push(attempted[1]);
|
|
504
520
|
console.warn(`parseDecisions: ignored unparseable decision bullet: ${trimmed}`);
|
|
505
521
|
continue;
|
|
506
522
|
}
|
|
@@ -516,7 +532,7 @@ function parseDecisionLines(block) {
|
|
|
516
532
|
}
|
|
517
533
|
}
|
|
518
534
|
flush();
|
|
519
|
-
return { decisions: out, parseMisses };
|
|
535
|
+
return { decisions: out, parseMisses, unreadableIds };
|
|
520
536
|
}
|
|
521
537
|
// ─── Primary entry point: extractDecisions ────────────────────────────────────
|
|
522
538
|
/**
|
|
@@ -541,13 +557,13 @@ function extractDecisions(content) {
|
|
|
541
557
|
const taggedBlocks = (0, markdown_sectionizer_cjs_1.extractTaggedBlocks)(stripped, 'decisions');
|
|
542
558
|
if (taggedBlocks.length > 0) {
|
|
543
559
|
const combined = taggedBlocks.join('\n\n');
|
|
544
|
-
const { decisions, parseMisses } = parseDecisionLines(combined);
|
|
560
|
+
const { decisions, parseMisses, unreadableIds } = parseDecisionLines(combined);
|
|
545
561
|
if (decisions.length > 0 && parseMisses === 0) {
|
|
546
562
|
return { decisions, outcome: 'parsed' };
|
|
547
563
|
}
|
|
548
564
|
// FIX B: parse-misses present — could-not-parse even if some decisions extracted.
|
|
549
565
|
if (parseMisses > 0) {
|
|
550
|
-
return { decisions, outcome: 'could-not-parse' };
|
|
566
|
+
return { decisions, outcome: 'could-not-parse', unreadableIds };
|
|
551
567
|
}
|
|
552
568
|
// FIX A: Block present but 0 extracted and no parse-misses.
|
|
553
569
|
// Only report could-not-parse when there is genuine evidence of real decisions
|
|
@@ -568,13 +584,13 @@ function extractDecisions(content) {
|
|
|
568
584
|
// stripFences:true → inner fences inside the section body are stripped.
|
|
569
585
|
const section = (0, markdown_sectionizer_cjs_1.collectSection)(content, (h) => /decisions?\b/i.test(h.text), { levelBounded: true, stripFences: true });
|
|
570
586
|
if (section !== null) {
|
|
571
|
-
const { decisions, parseMisses } = parseDecisionLines(section.body);
|
|
587
|
+
const { decisions, parseMisses, unreadableIds } = parseDecisionLines(section.body);
|
|
572
588
|
if (decisions.length > 0 && parseMisses === 0) {
|
|
573
589
|
return { decisions, outcome: 'parsed' };
|
|
574
590
|
}
|
|
575
591
|
// FIX B: parse-misses present — could-not-parse even if some decisions extracted.
|
|
576
592
|
if (parseMisses > 0) {
|
|
577
|
-
return { decisions, outcome: 'could-not-parse' };
|
|
593
|
+
return { decisions, outcome: 'could-not-parse', unreadableIds };
|
|
578
594
|
}
|
|
579
595
|
// FIX A: Heading found but 0 extracted and no parse-misses.
|
|
580
596
|
// Report could-not-parse when the section body holds a decision-entry-shaped
|
|
@@ -1394,6 +1394,14 @@ function cmdFrontmatterGet(cwd, filePath, field, raw) {
|
|
|
1394
1394
|
// Pass the resolved path so a truncated file is named in the diagnostic and deduplicated
|
|
1395
1395
|
// per file rather than per content digest (#1882, ADR-1411 wiring clause).
|
|
1396
1396
|
const fm = extractFrontmatter(content, fullPath);
|
|
1397
|
+
// #4806: an unparseable frontmatter block is a distinct outcome — the file
|
|
1398
|
+
// HAS a frontmatter block but its YAML failed to parse. Reporting
|
|
1399
|
+
// "Field not found" tells the caller the key is absent, which is
|
|
1400
|
+
// indistinguishable from a file that genuinely lacks it.
|
|
1401
|
+
if (fm[FRONTMATTER_UNPARSEABLE] === true) {
|
|
1402
|
+
output({ error: 'Frontmatter is not parseable YAML — fix the syntax error in the frontmatter block', path: filePath }, raw, undefined);
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1397
1405
|
if (field) {
|
|
1398
1406
|
const value = fm[field];
|
|
1399
1407
|
if (value === undefined) {
|
|
@@ -1649,4 +1657,9 @@ module.exports = {
|
|
|
1649
1657
|
cmdFrontmatterMerge,
|
|
1650
1658
|
cmdFrontmatterValidate,
|
|
1651
1659
|
propagateCommentChannel,
|
|
1660
|
+
// #4917 / ADR-4910 Decision 1: additive-only export so `planning-document.cts`
|
|
1661
|
+
// can COMPOSE this seam's fence-detection grammar (byte-0 rule, BOM strip,
|
|
1662
|
+
// CR handling) instead of reimplementing it. No behavior change — same
|
|
1663
|
+
// function `extractFrontmatter`/`frontmatterListEntries` already call.
|
|
1664
|
+
frontmatterRegion,
|
|
1652
1665
|
};
|
|
@@ -445,14 +445,17 @@ function graphifyStatus(cwd) {
|
|
|
445
445
|
return disabledResponse();
|
|
446
446
|
const { graphPath, configured } = resolveGraphLocation(cwd, planningDir);
|
|
447
447
|
if (!node_fs_1.default.existsSync(graphPath)) {
|
|
448
|
-
return { exists: false, message: configured
|
|
448
|
+
return { exists: false, graph_path: graphPath, message: configured
|
|
449
449
|
? `Configured graph not found at ${graphPath}. Set graphify.graph_path or run /gsd:graphify build.`
|
|
450
450
|
: 'No graph built yet. Run graphify build to create one.' };
|
|
451
451
|
}
|
|
452
452
|
const stat = node_fs_1.default.statSync(graphPath);
|
|
453
453
|
const graph = safeReadJson(graphPath);
|
|
454
454
|
if (!graph) {
|
|
455
|
-
|
|
455
|
+
// Still surface graph_path (#4836 Minor 1): callers gate on `exists`, not
|
|
456
|
+
// on this outcome, so without it they'd fall through to the CLI-first
|
|
457
|
+
// branch with no --graph value to substitute for the <graph> placeholder.
|
|
458
|
+
return { error: 'Failed to parse graph.json', graph_path: graphPath };
|
|
456
459
|
}
|
|
457
460
|
const STALE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
458
461
|
const age = Date.now() - stat.mtimeMs;
|
|
@@ -482,6 +485,11 @@ function graphifyStatus(cwd) {
|
|
|
482
485
|
(lastBuildAutoUpdate.status === 'failed' || lastBuildAutoUpdate.status === 'running');
|
|
483
486
|
return {
|
|
484
487
|
exists: true,
|
|
488
|
+
// The absolute location the whole graphify surface reads, already resolved
|
|
489
|
+
// through `graphify.graph_path` (#1825). Callers that shell out to the
|
|
490
|
+
// graphify CLI pass this as `--graph` so the umbrella override is honoured
|
|
491
|
+
// there too, instead of re-deriving `.planning/graphs/graph.json` (#4836).
|
|
492
|
+
graph_path: graphPath,
|
|
485
493
|
last_build: stat.mtime.toISOString(),
|
|
486
494
|
node_count: (graph.nodes || []).length,
|
|
487
495
|
edge_count: (graph.edges || graph.links || []).length,
|
|
@@ -24,6 +24,7 @@ exports.resolveReportedRuntime = resolveReportedRuntime;
|
|
|
24
24
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
25
25
|
const node_path_1 = __importDefault(require("node:path"));
|
|
26
26
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
27
|
+
const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
|
|
27
28
|
const update_context_cjs_1 = require("./update-context.cjs");
|
|
28
29
|
Object.defineProperty(exports, "CODEX_CONFIG_MARKER", { enumerable: true, get: function () { return update_context_cjs_1.CODEX_CONFIG_MARKER; } });
|
|
29
30
|
// Codex sandbox env vars set by the shell tool / Seatbelt child-process spawn.
|
|
@@ -127,6 +128,14 @@ function resolveReportedRuntimeUnsafe(projectDir, deps) {
|
|
|
127
128
|
const explicit = (0, runtime_slash_cjs_1.resolveExplicitRuntime)(projectDir, deps?.env ?? process.env);
|
|
128
129
|
if (explicit)
|
|
129
130
|
return explicit;
|
|
131
|
+
// #4717: the per-install marker names the runtime that owns THIS tree — a
|
|
132
|
+
// stronger signal than host sniffing, which misreports every session on a
|
|
133
|
+
// multi-runtime machine (e.g. a globally exported CODEX_HOME makes a Claude
|
|
134
|
+
// Code session read as codex). Marker-less trees (dev/source, pre-#2297
|
|
135
|
+
// installs) fall through to host detection unchanged.
|
|
136
|
+
const marker = (0, runtime_name_policy_cjs_1.resolveRuntimeNameFromCandidates)((0, runtime_slash_cjs_1.readInstallRuntimeMarker)());
|
|
137
|
+
if (marker)
|
|
138
|
+
return marker;
|
|
130
139
|
const detected = detectHostRuntime(deps);
|
|
131
140
|
if (detected.runtime)
|
|
132
141
|
return detected.runtime;
|