@opengsd/gsd-core 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +45 -1
- package/README.md +2 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debug-session-manager.md +78 -4
- package/agents/gsd-debugger.md +87 -29
- package/agents/gsd-executor.md +49 -9
- package/agents/gsd-intel-updater.md +3 -3
- package/agents/gsd-phase-researcher.md +4 -2
- package/agents/gsd-plan-checker.md +20 -0
- package/agents/gsd-planner.md +44 -59
- package/agents/gsd-project-researcher.md +2 -2
- package/agents/gsd-ui-auditor.md +0 -40
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +1338 -135
- package/commands/gsd/ai-integration-phase.md +1 -1
- package/commands/gsd/mempalace-capture.md +9 -5
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/plan-phase.md +5 -3
- package/commands/gsd/plan-review-convergence.md +7 -2
- package/gsd-core/bin/gsd-tools.cjs +2690 -2472
- package/gsd-core/bin/lib/adapter-imperative.cjs +8 -1
- package/gsd-core/bin/lib/agent-command-router.cjs +20 -5
- package/gsd-core/bin/lib/api-coverage.cjs +360 -53
- package/gsd-core/bin/lib/audit.cjs +8 -8
- package/gsd-core/bin/lib/broken-windows.cjs +716 -0
- package/gsd-core/bin/lib/capability-command-router.cjs +733 -0
- package/gsd-core/bin/lib/capability-consent.cjs +40 -1
- package/gsd-core/bin/lib/capability-lifecycle.cjs +58 -0
- package/gsd-core/bin/lib/capability-loader.cjs +23 -1
- package/gsd-core/bin/lib/capability-registry.cjs +1450 -160
- package/gsd-core/bin/lib/capability-trust.cjs +468 -33
- package/gsd-core/bin/lib/capability-validator.cjs +882 -6
- package/gsd-core/bin/lib/capability-writer.cjs +6 -1
- package/gsd-core/bin/lib/check-command-router.cjs +140 -27
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +15 -0
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +209 -31
- package/gsd-core/bin/lib/claude-orchestration.cjs +203 -25
- package/gsd-core/bin/lib/command-aliases.cjs +14 -0
- package/gsd-core/bin/lib/commands.cjs +326 -21
- package/gsd-core/bin/lib/config-loader.cjs +214 -30
- package/gsd-core/bin/lib/config.cjs +158 -22
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/decisions.cjs +32 -8
- package/gsd-core/bin/lib/docs.cjs +6 -0
- package/gsd-core/bin/lib/estimate-cli.cjs +336 -0
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +14 -2
- package/gsd-core/bin/lib/frontmatter.cjs +125 -15
- package/gsd-core/bin/lib/gap-checker.cjs +17 -2
- package/gsd-core/bin/lib/host-integration.cjs +215 -8
- package/gsd-core/bin/lib/init.cjs +155 -66
- package/gsd-core/bin/lib/install-engine.cjs +299 -23
- package/gsd-core/bin/lib/install-profiles.cjs +239 -1
- package/gsd-core/bin/lib/installer-migrations/005-opencode-baseline-commands-dir.cjs +146 -0
- package/gsd-core/bin/lib/installer-migrations/006-pi-extension-cjs-to-js.cjs +91 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +44 -5
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +107 -0
- package/gsd-core/bin/lib/milestone.cjs +248 -14
- package/gsd-core/bin/lib/model-catalog.cjs +69 -4
- package/gsd-core/bin/lib/model-resolver.cjs +189 -7
- package/gsd-core/bin/lib/observability/logger.cjs +7 -2
- package/gsd-core/bin/lib/onboard-projection.cjs +11 -8
- package/gsd-core/bin/lib/phase-command-router.cjs +10 -1
- package/gsd-core/bin/lib/phase-estimation.cjs +398 -0
- package/gsd-core/bin/lib/phase-id.cjs +304 -9
- package/gsd-core/bin/lib/phase.cjs +258 -17
- package/gsd-core/bin/lib/plan-drift-guard.cjs +1 -1
- package/gsd-core/bin/lib/plan-scan.cjs +70 -2
- package/gsd-core/bin/lib/planning-workspace.cjs +9 -2
- package/gsd-core/bin/lib/profile-output.cjs +34 -8
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +927 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +348 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +594 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +114 -32
- package/gsd-core/bin/lib/roadmap-parser.cjs +61 -10
- package/gsd-core/bin/lib/roadmap.cjs +23 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +38 -5
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +23 -9
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +156 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +15 -2
- package/gsd-core/bin/lib/smart-entry.cjs +70 -5
- package/gsd-core/bin/lib/state-document.cjs +171 -24
- package/gsd-core/bin/lib/state-transition.cjs +50 -11
- package/gsd-core/bin/lib/state.cjs +206 -32
- package/gsd-core/bin/lib/surface.cjs +51 -9
- package/gsd-core/bin/lib/uat-predicate.cjs +6 -4
- package/gsd-core/bin/lib/uat.cjs +428 -11
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +2 -2
- package/gsd-core/bin/lib/unusable-input.cjs +216 -0
- package/gsd-core/bin/lib/validate.cjs +44 -8
- package/gsd-core/bin/lib/verification.cjs +163 -31
- package/gsd-core/bin/lib/verify.cjs +348 -42
- package/gsd-core/bin/lib/worktree-safety.cjs +360 -15
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +4 -15
- package/gsd-core/bin/shared/model-catalog.json +5 -0
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +5 -0
- package/gsd-core/references/api-coverage.md +37 -7
- package/gsd-core/references/checkpoints.md +1 -1
- package/gsd-core/references/common-bug-patterns.md +13 -0
- package/gsd-core/references/context-budget.md +40 -0
- package/gsd-core/references/debugger-bug-taxonomy.md +111 -0
- package/gsd-core/references/debugger-fix-acceptance.md +157 -0
- package/gsd-core/references/debugger-philosophy.md +1 -0
- package/gsd-core/references/debugger-prevention.md +98 -0
- package/gsd-core/references/debugger-rca-branching.md +98 -0
- package/gsd-core/references/debugger-repro-hardening.md +130 -0
- package/gsd-core/references/debugger-sbfl.md +110 -0
- package/gsd-core/references/debugger-semantic-recall.md +81 -0
- package/gsd-core/references/execute-phase-quota-recovery.md +55 -0
- package/gsd-core/references/execute-phase-requirement-revert.md +8 -0
- package/gsd-core/references/execute-phase-response-language.md +7 -0
- package/gsd-core/references/gate-prompts.md +6 -3
- package/gsd-core/references/model-profile-resolution.md +64 -13
- package/gsd-core/references/offer-next.md +88 -0
- package/gsd-core/references/planner-antipatterns.md +6 -0
- package/gsd-core/references/planner-mvp-mode.md +12 -13
- package/gsd-core/references/planner-preconditions.md +156 -0
- package/gsd-core/references/planner-reversibility.md +132 -0
- package/gsd-core/references/planning-config.md +2 -1
- package/gsd-core/references/reviewer-instances.md +28 -19
- package/gsd-core/references/runtime-aware-dispatch.md +42 -0
- package/gsd-core/references/skeleton-template.md +1 -1
- package/gsd-core/references/thinking-models-planning.md +3 -1
- package/gsd-core/references/ui-consideration-probe.md +2 -2
- package/gsd-core/references/worktree-branch-check.md +4 -4
- package/gsd-core/templates/DEBUG.md +5 -3
- package/gsd-core/templates/summary-minimal.md +4 -0
- package/gsd-core/templates/summary-standard.md +4 -0
- package/gsd-core/templates/summary.md +7 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +3 -1
- package/gsd-core/workflows/add-todo.md +32 -1
- package/gsd-core/workflows/ai-integration-phase.md +8 -6
- package/gsd-core/workflows/audit-fix.md +6 -2
- package/gsd-core/workflows/audit-milestone.md +8 -0
- package/gsd-core/workflows/autonomous.md +19 -15
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +7 -1
- package/gsd-core/workflows/code-review-fix.md +14 -6
- package/gsd-core/workflows/code-review.md +93 -24
- package/gsd-core/workflows/complete-milestone.md +3 -0
- package/gsd-core/workflows/debug.md +35 -7
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discovery-phase.md +7 -0
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -4
- package/gsd-core/workflows/discuss-phase/modes/auto.md +0 -6
- package/gsd-core/workflows/discuss-phase/templates/context.md +16 -2
- package/gsd-core/workflows/discuss-phase-assumptions.md +18 -9
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +7 -1
- package/gsd-core/workflows/docs-update.md +9 -0
- package/gsd-core/workflows/eval-review.md +4 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +4 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +160 -0
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +4 -4
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -2
- package/gsd-core/workflows/execute-phase.md +110 -149
- package/gsd-core/workflows/execute-plan.md +20 -8
- package/gsd-core/workflows/explore.md +4 -0
- package/gsd-core/workflows/extract-learnings.md +21 -0
- package/gsd-core/workflows/graduation.md +3 -0
- package/gsd-core/workflows/health.md +7 -1
- package/gsd-core/workflows/help/modes/full.md +9 -5
- package/gsd-core/workflows/import.md +11 -2
- package/gsd-core/workflows/inbox.md +7 -0
- package/gsd-core/workflows/ingest-docs.md +19 -10
- package/gsd-core/workflows/manager.md +3 -1
- package/gsd-core/workflows/map-codebase.md +17 -10
- package/gsd-core/workflows/mvp-phase.md +3 -0
- package/gsd-core/workflows/new-milestone.md +79 -23
- package/gsd-core/workflows/new-project.md +28 -19
- package/gsd-core/workflows/new-workspace.md +3 -1
- package/gsd-core/workflows/next.md +5 -2
- package/gsd-core/workflows/onboard.md +3 -0
- package/gsd-core/workflows/plan-phase.md +56 -51
- package/gsd-core/workflows/plan-review-convergence.md +61 -12
- package/gsd-core/workflows/plant-seed.md +3 -0
- package/gsd-core/workflows/profile-user.md +7 -1
- package/gsd-core/workflows/progress.md +31 -3
- package/gsd-core/workflows/quick.md +33 -10
- package/gsd-core/workflows/remove-workspace.md +3 -0
- package/gsd-core/workflows/review.md +172 -585
- package/gsd-core/workflows/scan.md +10 -2
- package/gsd-core/workflows/secure-phase.md +13 -2
- package/gsd-core/workflows/settings-integrations.md +3 -0
- package/gsd-core/workflows/settings.md +3 -0
- package/gsd-core/workflows/ship.md +88 -11
- package/gsd-core/workflows/sketch.md +3 -0
- package/gsd-core/workflows/smart-entry.md +4 -1
- package/gsd-core/workflows/spike.md +7 -1
- package/gsd-core/workflows/ui-phase.md +11 -2
- package/gsd-core/workflows/ui-review.md +11 -1
- package/gsd-core/workflows/undo.md +7 -0
- package/gsd-core/workflows/update.md +106 -5
- package/gsd-core/workflows/validate-phase.md +13 -2
- package/gsd-core/workflows/verify-phase.md +2 -2
- package/gsd-core/workflows/verify-work.md +15 -4
- package/hooks/dist/gsd-context-monitor.js +27 -9
- package/hooks/dist/gsd-cursor-session-start.js +6 -2
- package/hooks/dist/gsd-cursor-stop.js +6 -2
- package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
- package/hooks/dist/gsd-graphify-update.sh +9 -0
- package/hooks/dist/gsd-phase-boundary.sh +14 -2
- package/hooks/dist/gsd-prompt-guard.js +101 -2
- package/hooks/dist/gsd-read-guard.js +100 -2
- package/hooks/dist/gsd-read-injection-scanner.js +109 -2
- package/hooks/dist/gsd-statusline.js +97 -9
- package/hooks/dist/gsd-workflow-guard.js +110 -6
- package/hooks/dist/gsd-worktree-path-guard.js +132 -8
- package/hooks/dist/lib/cursor-workspace.js +74 -0
- package/hooks/gsd-context-monitor.js +27 -9
- package/hooks/gsd-cursor-session-start.js +6 -2
- package/hooks/gsd-cursor-stop.js +6 -2
- package/hooks/gsd-cursor-subagent-start.js +6 -2
- package/hooks/gsd-graphify-update.sh +9 -0
- package/hooks/gsd-phase-boundary.sh +14 -2
- package/hooks/gsd-prompt-guard.js +101 -2
- package/hooks/gsd-read-guard.js +100 -2
- package/hooks/gsd-read-injection-scanner.js +109 -2
- package/hooks/gsd-statusline.js +97 -9
- package/hooks/gsd-workflow-guard.js +110 -6
- package/hooks/gsd-worktree-path-guard.js +132 -8
- package/hooks/lib/cursor-workspace.js +74 -0
- package/package.json +10 -8
- package/pi/gsd.cjs +34 -3
- package/scripts/changeset/lint.cjs +1 -0
- package/scripts/changeset/parse.cjs +26 -0
- package/scripts/check-coverage-gate.cjs +51 -0
- package/scripts/check-glossary-refs.cjs +244 -0
- package/scripts/ci-rebase-check.cjs +48 -4
- package/scripts/ci-test-scope.cjs +67 -17
- package/scripts/gen-adr-index.cjs +528 -0
- package/scripts/gen-capability-matrix.cjs +26 -2
- package/scripts/gen-capability-registry.cjs +132 -34
- package/scripts/gen-emitted-baseline.cjs +145 -0
- package/scripts/gen-test-timings.cjs +201 -0
- package/scripts/lint-compiled-artifact-sync.cjs +146 -0
- package/scripts/lint-emitted-drift-ack.cjs +149 -0
- package/scripts/lint-fix-has-regression-test.cjs +131 -0
- package/scripts/lint-portable-timeout.cjs +140 -0
- package/scripts/lint-resolution-provenance.cjs +9 -0
- package/scripts/lint-test-file-count.allowlist.json +1 -0
- package/scripts/mutation-matrix.cjs +4 -0
- package/scripts/prompt-injection-scan.sh +6 -0
- package/scripts/registry-schema.cjs +57 -8
- package/scripts/release-notes/conventional-title.cjs +19 -1
- package/scripts/release-notes/format-github-release-notes.cjs +7 -3
- package/scripts/release-tarball-smoke.cjs +18 -11
- package/scripts/run-tests.cjs +420 -58
- package/scripts/workflow-size.cjs +16 -8
- package/skills/gsd-ai-integration-phase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +9 -5
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-plan-phase/SKILL.md +5 -3
- package/skills/gsd-plan-review-convergence/SKILL.md +7 -2
- package/vscode/package.json +1 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +0 -77
- package/scripts/update-size-baseline.cjs +0 -68
|
@@ -24,7 +24,7 @@ const coreUtilsMod = require("./core-utils.cjs");
|
|
|
24
24
|
const { toPosixPath, generateSlugInternal, extractOneLinerFromBody } = coreUtilsMod;
|
|
25
25
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
26
26
|
const phaseIdMod = require("./phase-id.cjs");
|
|
27
|
-
const { normalizePhaseName, comparePhaseNum, extractPhaseToken } = phaseIdMod;
|
|
27
|
+
const { normalizePhaseName, comparePhaseNum, extractPhaseToken, PHASE_NUMBER_TOKEN_SOURCE } = phaseIdMod;
|
|
28
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
29
|
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
30
30
|
const { getArchivedPhaseDirs, findPhaseInternal } = phaseLocatorMod;
|
|
@@ -33,9 +33,14 @@ const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
|
33
33
|
const { extractCurrentMilestone, stripShippedMilestones: _stripShippedMilestones, getMilestoneInfo, getMilestonePhaseFilter, getRoadmapPhaseInternal } = roadmapParserMod;
|
|
34
34
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
35
35
|
const modelResolverMod = require("./model-resolver.cjs");
|
|
36
|
-
const { resolveModelInternal, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, resolveGranularityInternal, assertValidGranularityOverride } = modelResolverMod;
|
|
36
|
+
const { resolveModelInternal, resolveModelForTier, resolveProviderEscalation, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, resolveGranularityInternal, assertValidGranularityOverride } = modelResolverMod;
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
38
|
+
const agentCommandRouterMod = require("./agent-command-router.cjs");
|
|
39
|
+
const { AGENT_FAILURE_CLASSES } = agentCommandRouterMod;
|
|
37
40
|
const model_catalog_cjs_1 = require("./model-catalog.cjs");
|
|
38
41
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
|
+
const hostIntegrationMod = require("./host-integration.cjs");
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
39
44
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
40
45
|
const { planningDir, planningPaths } = planningWorkspace;
|
|
41
46
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -47,6 +52,44 @@ const { MODEL_PROFILES, VALID_PHASE_TYPES } = modelProfiles;
|
|
|
47
52
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
48
53
|
const clock_cjs_1 = require("./clock.cjs");
|
|
49
54
|
// ─── Phase Status ─────────────────────────────────────────────────────────────
|
|
55
|
+
/**
|
|
56
|
+
* Phase-status precedence ladder — furthest-along wins (#2408).
|
|
57
|
+
*
|
|
58
|
+
* `cmdStats` builds `phasesByNumber` by scanning on-disk phase directories.
|
|
59
|
+
* When two directories normalize to the same phase key (e.g. `05-real/` and
|
|
60
|
+
* `05-real-stray/`), the status field must be folded by precedence rather
|
|
61
|
+
* than overwritten last-write-wins — otherwise `/gsd-stats` reports whatever
|
|
62
|
+
* directory `fs.readdirSync` happened to yield last, which is non-deterministic
|
|
63
|
+
* across platforms and can silently call a `Complete` phase `Not Started`.
|
|
64
|
+
*/
|
|
65
|
+
const PHASE_STATUS_PRECEDENCE = [
|
|
66
|
+
'Complete',
|
|
67
|
+
'Needs Review',
|
|
68
|
+
'Executed',
|
|
69
|
+
'In Progress',
|
|
70
|
+
'Planned',
|
|
71
|
+
'Not Started',
|
|
72
|
+
'Pending',
|
|
73
|
+
];
|
|
74
|
+
const PHASE_STATUS_RANK = new Map(PHASE_STATUS_PRECEDENCE.map((s, i) => [s, i]));
|
|
75
|
+
/**
|
|
76
|
+
* Fold two phase statuses by precedence — returns whichever is further along
|
|
77
|
+
* the {@link PHASE_STATUS_PRECEDENCE} ladder. Unrecognized statuses fall behind
|
|
78
|
+
* every recognized one (so a recognized status always wins over an unknown one;
|
|
79
|
+
* two unrecognized statuses favor `a` for determinism).
|
|
80
|
+
*/
|
|
81
|
+
function foldPhaseStatus(a, b) {
|
|
82
|
+
const ra = PHASE_STATUS_RANK.get(a);
|
|
83
|
+
const rb = PHASE_STATUS_RANK.get(b);
|
|
84
|
+
if (ra === undefined && rb === undefined)
|
|
85
|
+
return a;
|
|
86
|
+
if (ra === undefined)
|
|
87
|
+
return b;
|
|
88
|
+
if (rb === undefined)
|
|
89
|
+
return a;
|
|
90
|
+
// Lower rank = higher precedence (Complete=0 wins over Not Started=5).
|
|
91
|
+
return ra <= rb ? a : b;
|
|
92
|
+
}
|
|
50
93
|
/**
|
|
51
94
|
* Determine phase status by checking plan/summary counts AND verification state.
|
|
52
95
|
* Introduces "Executed" for phases with all summaries but no passing verification.
|
|
@@ -63,12 +106,13 @@ function determinePhaseStatus(plans, summaries, phaseDir, defaultPending) {
|
|
|
63
106
|
const files = node_fs_1.default.readdirSync(phaseDir);
|
|
64
107
|
const verificationFile = files.find(f => f === 'VERIFICATION.md' || f.endsWith('-VERIFICATION.md'));
|
|
65
108
|
if (verificationFile) {
|
|
66
|
-
const
|
|
109
|
+
const verificationFilePath = node_path_1.default.join(phaseDir, verificationFile);
|
|
110
|
+
const content = (0, shell_command_projection_cjs_1.platformReadSync)(verificationFilePath) || '';
|
|
67
111
|
// #1159 (Defect A): read ONLY the frontmatter `status` key to avoid false
|
|
68
112
|
// matches from historical body metadata such as `previous_status: gaps_found`.
|
|
69
113
|
// Full-text regexes like /status:\s*gaps_found/ match the substring inside
|
|
70
114
|
// `previous_status: gaps_found`, producing incorrect phase status labels.
|
|
71
|
-
const fm = extractFrontmatter(content);
|
|
115
|
+
const fm = extractFrontmatter(content, verificationFilePath);
|
|
72
116
|
// Normalise to lower-case to preserve the prior case-insensitive behaviour
|
|
73
117
|
// while reading only the frontmatter `status` key (not the full body text).
|
|
74
118
|
const fmStatus = typeof fm['status'] === 'string' ? fm['status'].trim().toLowerCase() : '';
|
|
@@ -128,6 +172,9 @@ function cmdListTodos(cwd, area, raw) {
|
|
|
128
172
|
const createdMatch = content.match(/^created:\s*(.+)$/m);
|
|
129
173
|
const titleMatch = content.match(/^title:\s*(.+)$/m);
|
|
130
174
|
const areaMatch = content.match(/^area:\s*(.+)$/m);
|
|
175
|
+
// #2337: surface severity when present. Omit the key entirely for todos
|
|
176
|
+
// with no severity line so existing consumers of this JSON are unaffected.
|
|
177
|
+
const severityMatch = content.match(/^severity:\s*(.+)$/m);
|
|
131
178
|
const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
|
|
132
179
|
// Apply area filter if specified
|
|
133
180
|
if (area && todoArea !== area)
|
|
@@ -139,6 +186,7 @@ function cmdListTodos(cwd, area, raw) {
|
|
|
139
186
|
title: titleMatch ? titleMatch[1].trim() : 'Untitled',
|
|
140
187
|
area: todoArea,
|
|
141
188
|
path: toPosixPath(node_path_1.default.relative(cwd, node_path_1.default.join(pendingDir, file))),
|
|
189
|
+
...(severityMatch ? { severity: severityMatch[1].trim() } : {}),
|
|
142
190
|
});
|
|
143
191
|
}
|
|
144
192
|
}
|
|
@@ -216,7 +264,7 @@ function cmdListSeeds(cwd, statusFilter, raw) {
|
|
|
216
264
|
const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
|
|
217
265
|
if (content === null)
|
|
218
266
|
continue;
|
|
219
|
-
const fm = extractFrontmatter(content);
|
|
267
|
+
const fm = extractFrontmatter(content, safeFilePath);
|
|
220
268
|
const status = (fmStr(fm.status) || 'dormant').toLowerCase().trim() || 'dormant';
|
|
221
269
|
// Match on the raw lowercased status (both sides already normalized);
|
|
222
270
|
// sanitizeForDisplay is for output, not comparison.
|
|
@@ -302,11 +350,12 @@ function cmdHistoryDigest(cwd, raw) {
|
|
|
302
350
|
for (const { name: dir, fullPath: dirPath } of allPhaseDirs) {
|
|
303
351
|
const summaries = node_fs_1.default.readdirSync(dirPath).filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
304
352
|
for (const summary of summaries) {
|
|
305
|
-
const
|
|
353
|
+
const summaryFilePath = node_path_1.default.join(dirPath, summary);
|
|
354
|
+
const content = (0, shell_command_projection_cjs_1.platformReadSync)(summaryFilePath);
|
|
306
355
|
if (content === null)
|
|
307
356
|
continue;
|
|
308
357
|
try {
|
|
309
|
-
const fm = extractFrontmatter(content);
|
|
358
|
+
const fm = extractFrontmatter(content, summaryFilePath);
|
|
310
359
|
const phaseNum = fm['phase'] || dir.split('-')[0];
|
|
311
360
|
if (!digest.phases[phaseNum]) {
|
|
312
361
|
digest.phases[phaseNum] = {
|
|
@@ -394,7 +443,8 @@ function cmdResolveGranularity(cwd, phaseType, raw, override) {
|
|
|
394
443
|
* { model, profile, effort, effort_rendered, effort_param, effort_propagation,
|
|
395
444
|
* fast_mode, fast_mode_supported, [unknown_agent] }
|
|
396
445
|
*
|
|
397
|
-
* Flags: --effort <level>, --fast-mode <true|false>, --attempt <n
|
|
446
|
+
* Flags: --effort <level>, --fast-mode <true|false>, --attempt <n>,
|
|
447
|
+
* --failure-class <class> (#2296), --host <runtime-id> (#2481)
|
|
398
448
|
*/
|
|
399
449
|
function cmdResolveExecution(cwd, agentType, raw, opts) {
|
|
400
450
|
if (!agentType) {
|
|
@@ -403,7 +453,29 @@ function cmdResolveExecution(cwd, agentType, raw, opts) {
|
|
|
403
453
|
opts = opts || {};
|
|
404
454
|
const config = loadConfig(cwd);
|
|
405
455
|
const profile = config['model_profile'] || 'balanced';
|
|
406
|
-
|
|
456
|
+
// #2068: resolve the model per-attempt so dynamic_routing escalates the MODEL
|
|
457
|
+
// (heavy tier) alongside effort. Gated on an explicit --attempt exactly like the
|
|
458
|
+
// effort resolution below, so the two fields stay symmetric: with no --attempt
|
|
459
|
+
// the model comes from the classic profile path (unchanged for everyone,
|
|
460
|
+
// including dynamic_routing-enabled users who don't pass --attempt), and only an
|
|
461
|
+
// explicit attempt routes through the tier ladder. resolveModelForTier itself
|
|
462
|
+
// still falls back to resolveModelInternal when dynamic_routing is off.
|
|
463
|
+
let model = (opts.attempt !== undefined && opts.attempt !== null)
|
|
464
|
+
? resolveModelForTier(cwd, agentType, opts.attempt)
|
|
465
|
+
: resolveModelInternal(cwd, agentType);
|
|
466
|
+
// #2296: when the caller reports WHY the previous attempt failed, consult the
|
|
467
|
+
// provider-escalation ladder. Only a quota/rate-limit class warrants it — a
|
|
468
|
+
// heavier tier on the same throttled provider is still throttled, so this
|
|
469
|
+
// ladder swaps providers instead. Gated on an explicit --failure-class so the
|
|
470
|
+
// JSON contract is byte-identical for every existing caller.
|
|
471
|
+
let escalation;
|
|
472
|
+
if (opts.failureClass !== undefined) {
|
|
473
|
+
const applicable = opts.failureClass === AGENT_FAILURE_CLASSES.QUOTA_EXCEEDED;
|
|
474
|
+
const resolved = resolveProviderEscalation(cwd, agentType, opts.attempt, applicable);
|
|
475
|
+
if (resolved.escalated)
|
|
476
|
+
model = resolved.to;
|
|
477
|
+
escalation = { class: opts.failureClass, ...resolved };
|
|
478
|
+
}
|
|
407
479
|
const effortOpts = {};
|
|
408
480
|
if (typeof opts.effortOverride === 'string')
|
|
409
481
|
effortOpts['override'] = opts.effortOverride;
|
|
@@ -428,10 +500,54 @@ function cmdResolveExecution(cwd, agentType, raw, opts) {
|
|
|
428
500
|
fast_mode: fastMode,
|
|
429
501
|
fast_mode_supported: fastModeSupported,
|
|
430
502
|
};
|
|
503
|
+
// ADR-1239 amendment (#2481) / ADR-443 path (a): invocation-time effort for a
|
|
504
|
+
// named host. The host's negotiated `effortSurface` decides WHETHER an argument
|
|
505
|
+
// is emitted; the catalog knows the syntax. Absent --host the contract is
|
|
506
|
+
// byte-identical to before, so every existing caller is unaffected.
|
|
507
|
+
if (typeof opts.host === 'string' && opts.host.length > 0) {
|
|
508
|
+
const surface = effortSurfaceForHost(cwd, opts.host);
|
|
509
|
+
const argvRendered = (0, model_catalog_cjs_1.renderEffortArgv)(opts.host, effort, surface);
|
|
510
|
+
result['host'] = opts.host;
|
|
511
|
+
result['effort_surface'] = surface;
|
|
512
|
+
result['effort_argv'] = argvRendered.argv;
|
|
513
|
+
result['effort_argv_string'] = argvRendered.argv.join(' ');
|
|
514
|
+
result['effort_argv_value'] = argvRendered.value;
|
|
515
|
+
}
|
|
431
516
|
if (!agentModels)
|
|
432
517
|
result['unknown_agent'] = true;
|
|
518
|
+
if (escalation)
|
|
519
|
+
result['escalation'] = escalation;
|
|
433
520
|
output(result, raw, effort);
|
|
434
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* ADR-1239 amendment (#2481) — resolve a host's negotiated `effortSurface`.
|
|
524
|
+
*
|
|
525
|
+
* Reads the host's runtime descriptor from the generated capability registry and
|
|
526
|
+
* runs it through the Host-Integration negotiation so the trust-boundary invariant
|
|
527
|
+
* applies here exactly as everywhere else: an unknown host, a missing axis, or the
|
|
528
|
+
* `undocumented` sentinel all degrade to the safe floor rather than being trusted.
|
|
529
|
+
* Never throws — a lookup failure yields `'none'`, which renders no argument.
|
|
530
|
+
*/
|
|
531
|
+
function effortSurfaceForHost(cwd, host) {
|
|
532
|
+
void cwd;
|
|
533
|
+
try {
|
|
534
|
+
// Mirrors the lazy-require pattern from runtime-slash.cts §runtimeSlash —
|
|
535
|
+
// capability-registry.cjs is generated and carries no type declarations.
|
|
536
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
537
|
+
const { runtimes } = require('./capability-registry.cjs');
|
|
538
|
+
const declared = runtimes[host]?.runtime?.hostIntegration;
|
|
539
|
+
if (!declared || typeof declared !== 'object')
|
|
540
|
+
return 'none';
|
|
541
|
+
// The descriptor is untrusted JSON; negotiation applies the trust-boundary
|
|
542
|
+
// invariant (effective ⊆ host-declared ∩ engine-known) and fails closed.
|
|
543
|
+
const negotiated = hostIntegrationMod.negotiateHostCapabilities(declared);
|
|
544
|
+
const surface = negotiated?.effective?.effortSurface;
|
|
545
|
+
return typeof surface === 'string' ? surface : 'none';
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
return 'none';
|
|
549
|
+
}
|
|
550
|
+
}
|
|
435
551
|
/**
|
|
436
552
|
* #488 — Replace or inject the `effort:` value in YAML frontmatter.
|
|
437
553
|
* Unlike injectEffortFrontmatter (install.js), this overwrites an existing value.
|
|
@@ -524,6 +640,59 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
524
640
|
}
|
|
525
641
|
output({ synced, skipped, changes, dry_run: dryRun, agents_dir: agentsDir }, raw, synced > 0 ? 'changed' : 'ok');
|
|
526
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* Detect the phase number for a commit from its `--files` path list.
|
|
645
|
+
*
|
|
646
|
+
* #2539: the extraction is anchored to the directory segment immediately under
|
|
647
|
+
* `.planning/phases/` or `.planning/milestones/<version>-phases/`, then run
|
|
648
|
+
* through the project-code-aware `extractPhaseToken` helper. The prior
|
|
649
|
+
* unanchored `match(/(\d+(?:\.\d+)*)-/)` returned the leftmost digit-run-then-
|
|
650
|
+
* hyphen anywhere in the joined path, so a project_code ending in a digit
|
|
651
|
+
* (e.g. PROJECT_V2) made `…/PROJECT_V2-07-name/…` match the `2-` inside `V2-`
|
|
652
|
+
* before the real `07-` phase token — resolving phase "2" instead of "7".
|
|
653
|
+
*
|
|
654
|
+
* Returns the phase number string (e.g. '07', '45.14'), or null when no phase
|
|
655
|
+
* directory segment is present in any of the file paths (e.g. a commit of
|
|
656
|
+
* `.planning/ROADMAP.md` has no phase segment, so no branch is resolved —
|
|
657
|
+
* matching the prior regex-no-match behaviour).
|
|
658
|
+
*/
|
|
659
|
+
function detectPhaseNumberFromFiles(files) {
|
|
660
|
+
if (!files || files.length === 0)
|
|
661
|
+
return null;
|
|
662
|
+
// A phase directory lives one segment below a `phases` parent segment:
|
|
663
|
+
// .planning/phases/<phase-dir>/…
|
|
664
|
+
// .planning/milestones/v1.0-phases/<phase-dir>/…
|
|
665
|
+
// The segment immediately after the `…phases` segment is the phase directory
|
|
666
|
+
// name. extractPhaseToken owns the project-code-aware token read.
|
|
667
|
+
for (const file of files) {
|
|
668
|
+
const norm = String(file).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
669
|
+
const segments = norm.split('/');
|
|
670
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
671
|
+
if (segments[i] === 'phases' || segments[i].endsWith('-phases')) {
|
|
672
|
+
const phaseDir = segments[i + 1];
|
|
673
|
+
if (!phaseDir)
|
|
674
|
+
continue;
|
|
675
|
+
const token = extractPhaseToken(phaseDir);
|
|
676
|
+
// extractPhaseToken falls back to returning dirName unchanged when no
|
|
677
|
+
// numeric token is found. normalizePhaseName is the canonical arbiter
|
|
678
|
+
// of "is this a real phase token": it strips the project-code prefix
|
|
679
|
+
// and returns a zero-padded numeric form for a genuine phase token, or
|
|
680
|
+
// the input unchanged otherwise. Accept the token only when it
|
|
681
|
+
// normalizes to a numeric phase form (the single-owner rule shared by
|
|
682
|
+
// every other phase-token reader — see #2528).
|
|
683
|
+
const normalized = normalizePhaseName(token);
|
|
684
|
+
// Built from the single-owner PHASE_NUMBER_TOKEN_SOURCE (the canonical
|
|
685
|
+
// phase-number grammar — #2128 anti-divergence guard) so this read-side
|
|
686
|
+
// acceptance check cannot drift from every other phase-token reader.
|
|
687
|
+
const phaseTokenShape = new RegExp(`^${PHASE_NUMBER_TOKEN_SOURCE}$`, 'i');
|
|
688
|
+
if (token !== phaseDir && phaseTokenShape.test(normalized)) {
|
|
689
|
+
return token;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
527
696
|
function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
528
697
|
if (!message && !amend) {
|
|
529
698
|
error('commit message required');
|
|
@@ -559,10 +728,21 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
559
728
|
if (branchingStrategy && branchingStrategy !== 'none') {
|
|
560
729
|
let branchName = null;
|
|
561
730
|
if (branchingStrategy === 'phase') {
|
|
562
|
-
// Determine which phase we're committing for from the file paths
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
731
|
+
// Determine which phase we're committing for from the file paths.
|
|
732
|
+
// #2539: the extraction is anchored to the directory SEGMENT immediately
|
|
733
|
+
// under `.planning/phases/` (or `.planning/milestones/<v>-phases/`) and
|
|
734
|
+
// runs through the project-code-aware extractPhaseToken helper, NOT a
|
|
735
|
+
// free unanchored regex. The prior `match(/(\d+(?:\.\d+)*)-/)` returned
|
|
736
|
+
// the leftmost digit-run-then-hyphen anywhere in the joined path, so a
|
|
737
|
+
// project_code ending in a digit (PROJECT_V2) made `.../PROJECT_V2-07-…`
|
|
738
|
+
// match the `2-` inside `V2-` before the real `07-` phase token —
|
|
739
|
+
// resolving phase "2" instead of phase "7" and silently checking out the
|
|
740
|
+
// wrong branch. extractPhaseToken already owns project-code-aware phase-
|
|
741
|
+
// token parsing (it is the single owner shared by the other 6 call sites
|
|
742
|
+
// — see #2528 for the parallel drift problem in phase-locator/phase),
|
|
743
|
+
// so this is the canonical path-segment-bound read, not a fourth copy.
|
|
744
|
+
const phaseNum = detectPhaseNumberFromFiles(files);
|
|
745
|
+
if (phaseNum) {
|
|
566
746
|
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
567
747
|
if (phaseInfo) {
|
|
568
748
|
branchName = config['phase_branch_template']
|
|
@@ -582,10 +762,23 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
582
762
|
if (branchName) {
|
|
583
763
|
const currentBranch = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
|
|
584
764
|
if (currentBranch.exitCode === 0 && currentBranch.stdout.trim() !== branchName) {
|
|
585
|
-
//
|
|
765
|
+
// #2539: the #1278 intent is to CREATE the phase/milestone branch
|
|
766
|
+
// before the FIRST commit on it — not to force-switch an already-
|
|
767
|
+
// checked-out working branch onto a DIFFERENT existing branch. The
|
|
768
|
+
// prior fallback to a bare `git checkout <branch>` silently switched
|
|
769
|
+
// the whole working tree onto an existing unrelated branch in the same
|
|
770
|
+
// call that then committed (the only trace was a reflog entry). So:
|
|
771
|
+
// create-if-absent only. If the resolved branch already exists and the
|
|
772
|
+
// tree is on some other branch, do NOT switch — but never silently: log
|
|
773
|
+
// the resolution so the operator sees that the phase branch was
|
|
774
|
+
// resolved and deliberately not switched to (#2539 AC2: an auto-
|
|
775
|
+
// checkout mid-commit must never happen silently).
|
|
586
776
|
const create = (0, shell_command_projection_cjs_1.execGit)(['checkout', '-b', branchName], { cwd });
|
|
587
777
|
if (create.exitCode !== 0) {
|
|
588
|
-
|
|
778
|
+
// `git checkout -b` fails (non-zero) when the branch already exists.
|
|
779
|
+
// The operator is on the branch they intend to be on; commit there.
|
|
780
|
+
process.stderr.write(`Warning: resolved ${branchingStrategy} branch "${branchName}" already exists; ` +
|
|
781
|
+
`committing on the current branch "${currentBranch.stdout.trim()}" instead of switching.\n`);
|
|
589
782
|
}
|
|
590
783
|
}
|
|
591
784
|
}
|
|
@@ -594,8 +787,21 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
594
787
|
const explicitFiles = files && files.length > 0;
|
|
595
788
|
const filesToStage = explicitFiles ? files : ['.planning/'];
|
|
596
789
|
const stagedPaths = [];
|
|
790
|
+
// #2608: a `git add` that fails must abort the commit, not be skipped.
|
|
791
|
+
// #2523 stopped a failed path entering the commit pathspec, but skipping it
|
|
792
|
+
// silently left two bad outcomes: a PARTIAL commit when only some requested
|
|
793
|
+
// paths failed, and a misleading `nothing_to_commit` when all of them did —
|
|
794
|
+
// in both cases the original staging error (permissions, unwritable index in
|
|
795
|
+
// a linked worktree, timeout) was discarded and the operator saw a downstream
|
|
796
|
+
// pathspec error pointing at an innocent file.
|
|
797
|
+
const stagingFailures = [];
|
|
798
|
+
// Paths already in the index BEFORE this call. On a staging failure the
|
|
799
|
+
// rollback below unstages only what THIS call added — unstaging a path the
|
|
800
|
+
// caller had staged themselves would destroy their work.
|
|
801
|
+
const preStaged = new Set((0, shell_command_projection_cjs_1.execGit)(['diff', '--cached', '--name-only'], { cwd })
|
|
802
|
+
.stdout.split('\n').map(s => s.trim()).filter(Boolean));
|
|
597
803
|
for (const file of filesToStage) {
|
|
598
|
-
const fullPath = node_path_1.default.
|
|
804
|
+
const fullPath = node_path_1.default.resolve(cwd, file);
|
|
599
805
|
if (!node_fs_1.default.existsSync(fullPath)) {
|
|
600
806
|
if (explicitFiles) {
|
|
601
807
|
// Caller passed an explicit --files list: missing files are skipped.
|
|
@@ -605,12 +811,73 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
605
811
|
}
|
|
606
812
|
// Default mode (staging all of .planning/): stage the deletion so
|
|
607
813
|
// removed planning files are not left dangling in the index.
|
|
608
|
-
|
|
814
|
+
// This mutates the index exactly like `git add` does, so it fails closed
|
|
815
|
+
// the same way — an unwritable index must not be swallowed here either.
|
|
816
|
+
// `--ignore-unmatch` already makes "no such path" a success, so a non-zero
|
|
817
|
+
// exit is a real I/O failure, not a missing file.
|
|
818
|
+
const rmResult = (0, shell_command_projection_cjs_1.execGit)(['rm', '--cached', '--ignore-unmatch', file], { cwd });
|
|
819
|
+
if (rmResult.exitCode !== 0) {
|
|
820
|
+
const rmErr = rmResult.error;
|
|
821
|
+
stagingFailures.push({
|
|
822
|
+
file,
|
|
823
|
+
error: rmResult.stderr || rmResult.stdout,
|
|
824
|
+
timed_out: rmResult.signal === 'SIGTERM' && rmErr?.code === 'ETIMEDOUT',
|
|
825
|
+
});
|
|
826
|
+
}
|
|
609
827
|
}
|
|
610
828
|
else {
|
|
611
|
-
(0, shell_command_projection_cjs_1.execGit)(['add', file], { cwd });
|
|
612
|
-
|
|
829
|
+
const addResult = (0, shell_command_projection_cjs_1.execGit)(['add', file], { cwd });
|
|
830
|
+
// Only record paths that actually staged — a failed `git add` (permissions,
|
|
831
|
+
// out-of-repo edge) must not enter the commit pathspec (#2523). Mirrors
|
|
832
|
+
// cmdCommitToSubrepo's exitCode-gated push.
|
|
833
|
+
if (addResult.exitCode === 0) {
|
|
834
|
+
stagedPaths.push(file);
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
// `SpawnResultOutput.error` is typed `Error | null`; widen to the errno
|
|
838
|
+
// shape by ANNOTATION rather than assertion — `Error` is assignable to
|
|
839
|
+
// `NodeJS.ErrnoException` (its extra fields are optional), so an `as`
|
|
840
|
+
// cast here trips no-unnecessary-type-assertion.
|
|
841
|
+
const addErr = addResult.error;
|
|
842
|
+
stagingFailures.push({
|
|
843
|
+
file,
|
|
844
|
+
error: addResult.stderr || addResult.stdout,
|
|
845
|
+
// The projection exposes a timeout distinctly (#2608 AC5); this is the
|
|
846
|
+
// same SIGTERM+ETIMEDOUT idiom worktree-safety.cts uses.
|
|
847
|
+
timed_out: addResult.signal === 'SIGTERM' && addErr?.code === 'ETIMEDOUT',
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
// #2608: fail closed before `git commit` runs. Checked ahead of the
|
|
853
|
+
// nothing_to_commit branch below so a run where EVERY path failed to stage
|
|
854
|
+
// reports the staging cause rather than "nothing to commit", and ahead of the
|
|
855
|
+
// commit itself so a multi-file scope never partially commits the subset that
|
|
856
|
+
// happened to stage.
|
|
857
|
+
if (stagingFailures.length > 0) {
|
|
858
|
+
// Fail closed AND clean. Without this the paths that DID stage stay in the
|
|
859
|
+
// index with no commit made, so the next bare `git commit` sweeps them up —
|
|
860
|
+
// the same silent partial commit this fix exists to prevent, deferred one
|
|
861
|
+
// step. Mirrors cmdPrSubrepo's rollback-then-error convention. Only paths
|
|
862
|
+
// this call staged are unstaged (preStaged is excluded), and the reset is
|
|
863
|
+
// best-effort: if the index is unwritable — the very failure being reported
|
|
864
|
+
// — the reset cannot succeed either, and the staging error is still what
|
|
865
|
+
// gets returned.
|
|
866
|
+
const toUnstage = stagedPaths.filter(p => !preStaged.has(p));
|
|
867
|
+
if (toUnstage.length > 0) {
|
|
868
|
+
(0, shell_command_projection_cjs_1.execGit)(['reset', '-q', '--', ...toUnstage], { cwd });
|
|
613
869
|
}
|
|
870
|
+
const first = stagingFailures[0];
|
|
871
|
+
const result = {
|
|
872
|
+
committed: false,
|
|
873
|
+
hash: null,
|
|
874
|
+
reason: first.timed_out ? 'staging_timeout' : 'staging_failed',
|
|
875
|
+
file: first.file,
|
|
876
|
+
error: first.error,
|
|
877
|
+
failures: stagingFailures,
|
|
878
|
+
};
|
|
879
|
+
output(result, raw, 'failed');
|
|
880
|
+
return;
|
|
614
881
|
}
|
|
615
882
|
// Commit — when the caller declared a scope (--files), append a pathspec so
|
|
616
883
|
// only the declared files land in the commit, not the entire index (#2112).
|
|
@@ -732,13 +999,44 @@ function cmdCommitToSubrepo(cwd, message, files, raw) {
|
|
|
732
999
|
for (const [repo, repoFiles] of Object.entries(grouped)) {
|
|
733
1000
|
const repoCwd = node_path_1.default.join(cwd, repo);
|
|
734
1001
|
// Stage files (strip sub-repo prefix for paths relative to that repo)
|
|
1002
|
+
// #2608: this is the sub-repo twin of cmdCommit's staging loop and carried
|
|
1003
|
+
// the identical defect — a failed `git add` was dropped silently and the
|
|
1004
|
+
// function went straight on to commit the subset that happened to stage,
|
|
1005
|
+
// discarding git's stderr. Fails closed per-repo, with the same rollback of
|
|
1006
|
+
// only what this call staged.
|
|
1007
|
+
const preStagedSub = new Set((0, shell_command_projection_cjs_1.execGit)(['diff', '--cached', '--name-only'], { cwd: repoCwd })
|
|
1008
|
+
.stdout.split('\n').map(s => s.trim()).filter(Boolean));
|
|
735
1009
|
const stagedRelPaths = [];
|
|
1010
|
+
const subStagingFailures = [];
|
|
736
1011
|
for (const file of repoFiles) {
|
|
737
1012
|
const relativePath = file.slice(repo.length + 1);
|
|
738
1013
|
const addResult = (0, shell_command_projection_cjs_1.execGit)(['add', relativePath], { cwd: repoCwd });
|
|
739
1014
|
if (addResult.exitCode === 0) {
|
|
740
1015
|
stagedRelPaths.push(relativePath);
|
|
741
1016
|
}
|
|
1017
|
+
else {
|
|
1018
|
+
const addErr = addResult.error;
|
|
1019
|
+
subStagingFailures.push({
|
|
1020
|
+
file,
|
|
1021
|
+
error: addResult.stderr || addResult.stdout,
|
|
1022
|
+
timed_out: addResult.signal === 'SIGTERM' && addErr?.code === 'ETIMEDOUT',
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (subStagingFailures.length > 0) {
|
|
1027
|
+
const toUnstageSub = stagedRelPaths.filter(p => !preStagedSub.has(p));
|
|
1028
|
+
if (toUnstageSub.length > 0) {
|
|
1029
|
+
(0, shell_command_projection_cjs_1.execGit)(['reset', '-q', '--', ...toUnstageSub], { cwd: repoCwd });
|
|
1030
|
+
}
|
|
1031
|
+
const firstSub = subStagingFailures[0];
|
|
1032
|
+
repos[repo] = {
|
|
1033
|
+
committed: false,
|
|
1034
|
+
hash: null,
|
|
1035
|
+
files: repoFiles,
|
|
1036
|
+
reason: firstSub.timed_out ? 'staging_timeout' : 'staging_failed',
|
|
1037
|
+
error: firstSub.error,
|
|
1038
|
+
};
|
|
1039
|
+
continue;
|
|
742
1040
|
}
|
|
743
1041
|
// Commit — pathspec limits the commit to the staged files only (#2112)
|
|
744
1042
|
const isMergeInProgressSub = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '-q', '--verify', 'MERGE_HEAD'], { cwd: repoCwd }).exitCode === 0;
|
|
@@ -924,7 +1222,7 @@ function cmdSummaryExtract(cwd, summaryPath, fields, raw) {
|
|
|
924
1222
|
return;
|
|
925
1223
|
}
|
|
926
1224
|
const content = node_fs_1.default.readFileSync(fullPath, 'utf-8');
|
|
927
|
-
const fm = extractFrontmatter(content);
|
|
1225
|
+
const fm = extractFrontmatter(content, fullPath);
|
|
928
1226
|
// Parse key-decisions into structured format
|
|
929
1227
|
const parseDecisions = (decisionsList) => {
|
|
930
1228
|
if (!decisionsList || !Array.isArray(decisionsList))
|
|
@@ -1385,7 +1683,12 @@ function cmdStats(cwd, format, raw) {
|
|
|
1385
1683
|
name: existing?.name || phaseName,
|
|
1386
1684
|
plans: (existing?.plans || 0) + plans,
|
|
1387
1685
|
summaries: (existing?.summaries || 0) + summaries,
|
|
1388
|
-
|
|
1686
|
+
// #2408: fold colliding statuses by precedence rather than overwriting
|
|
1687
|
+
// last-write-wins. fs.readdirSync order is non-deterministic across
|
|
1688
|
+
// platforms, so a naive overwrite can report a Complete phase as Not
|
|
1689
|
+
// Started (or vice versa) depending on read order. The fold picks the
|
|
1690
|
+
// furthest-along status, matching what an operator expects.
|
|
1691
|
+
status: existing ? foldPhaseStatus(existing.status, status) : status,
|
|
1389
1692
|
});
|
|
1390
1693
|
}
|
|
1391
1694
|
}
|
|
@@ -1507,6 +1810,8 @@ function cmdCheckCommit(cwd, raw) {
|
|
|
1507
1810
|
module.exports = {
|
|
1508
1811
|
groupFilesBySubrepo,
|
|
1509
1812
|
determinePhaseStatus,
|
|
1813
|
+
foldPhaseStatus,
|
|
1814
|
+
PHASE_STATUS_PRECEDENCE,
|
|
1510
1815
|
cmdGenerateSlug,
|
|
1511
1816
|
cmdCurrentTimestamp,
|
|
1512
1817
|
cmdListTodos,
|