@opengsd/gsd-core 1.20.0 → 1.21.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 +33 -2
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-plan-checker.md +1 -1
- package/agents/gsd-planner.md +3 -3
- package/agents/gsd-verifier.md +2 -2
- package/bin/install.js +265 -161
- package/commands/gsd/autonomous.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/new-project.md +2 -2
- package/gsd-core/bin/gsd-tools.cjs +554 -823
- package/gsd-core/bin/lib/capability-activation.cjs +8 -2
- package/gsd-core/bin/lib/capability-command-router.cjs +6 -2
- package/gsd-core/bin/lib/capability-ledger.cjs +1 -1
- package/gsd-core/bin/lib/capability-lock.cjs +19 -27
- package/gsd-core/bin/lib/capability-registry.cjs +74 -71
- package/gsd-core/bin/lib/capability-validator.cjs +1 -0
- package/gsd-core/bin/lib/capability-writer.cjs +7 -4
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +142 -62
- package/gsd-core/bin/lib/claude-orchestration.cjs +214 -122
- package/gsd-core/bin/lib/cli-surface.cjs +545 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +356 -103
- package/gsd-core/bin/lib/commands.cjs +99 -2
- package/gsd-core/bin/lib/config-file-read.cjs +85 -0
- package/gsd-core/bin/lib/config-loader.cjs +23 -15
- package/gsd-core/bin/lib/config.cjs +139 -101
- package/gsd-core/bin/lib/configuration.cjs +41 -2
- package/gsd-core/bin/lib/criterion-baseline.cjs +346 -0
- package/gsd-core/bin/lib/criterion-replay.cjs +380 -0
- package/gsd-core/bin/lib/estimate-cli.cjs +5 -1
- package/gsd-core/bin/lib/gap-checker.cjs +12 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +593 -27
- package/gsd-core/bin/lib/gate-phase-context.cjs +16 -4
- package/gsd-core/bin/lib/graphify.cjs +19 -2
- package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +5 -1
- package/gsd-core/bin/lib/init.cjs +20 -18
- package/gsd-core/bin/lib/install-effort-resolver.cjs +24 -4
- package/gsd-core/bin/lib/install-engine.cjs +20 -28
- package/gsd-core/bin/lib/install-fs-adapter.cjs +5 -1
- package/gsd-core/bin/lib/install-manifest-record.cjs +86 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -3
- package/gsd-core/bin/lib/install-profiles.cjs +4 -2
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +62 -31
- package/gsd-core/bin/lib/markdown-table.cjs +39 -1
- package/gsd-core/bin/lib/milestone.cjs +8 -6
- package/gsd-core/bin/lib/model-resolver.cjs +8 -4
- package/gsd-core/bin/lib/phase-entry-grammar.cjs +14 -0
- package/gsd-core/bin/lib/phase-id.cjs +26 -9
- package/gsd-core/bin/lib/phase-locator.cjs +8 -3
- package/gsd-core/bin/lib/phase.cjs +170 -45
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +46 -23
- package/gsd-core/bin/lib/planning-snapshot.cjs +10 -6
- package/gsd-core/bin/lib/planning-workspace.cjs +20 -10
- package/gsd-core/bin/lib/profile-output.cjs +9 -6
- package/gsd-core/bin/lib/project-context.cjs +439 -0
- package/gsd-core/bin/lib/project-root.cjs +9 -230
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +1 -1
- package/gsd-core/bin/lib/report-parser.cjs +16 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +357 -50
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +24 -5
- package/gsd-core/bin/lib/roadmap.cjs +2 -2
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +317 -134
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +81 -3
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +6 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +279 -8
- package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +1010 -10
- package/gsd-core/bin/lib/stale-bake-guard.cjs +4 -1
- package/gsd-core/bin/lib/state-transition.cjs +103 -46
- package/gsd-core/bin/lib/state.cjs +448 -63
- package/gsd-core/bin/lib/surface.cjs +4 -7
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +10 -3
- package/gsd-core/bin/lib/verify-command-router.cjs +6 -1
- package/gsd-core/bin/lib/verify.cjs +49 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +4 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +8 -111
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/autonomous-smart-discuss.md +99 -18
- package/gsd-core/references/checkpoints.md +1 -1
- package/gsd-core/references/planner-guidance.md +11 -0
- package/gsd-core/references/planner-human-verify-mode.md +1 -1
- package/gsd-core/references/planner-reversibility.md +2 -0
- package/gsd-core/references/planner-verify-command-grounding.md +22 -6
- package/gsd-core/workflows/autonomous.md +4 -4
- package/gsd-core/workflows/code-review-fix.md +5 -7
- package/gsd-core/workflows/execute-phase.md +21 -7
- package/gsd-core/workflows/plan-phase/steps/plan-pre-dispatch.md +101 -0
- package/gsd-core/workflows/plan-phase.md +18 -19
- package/hooks/dist/gsd-agent-isolation-guard.js +4 -3
- package/hooks/dist/gsd-config-reload.js +2 -2
- package/hooks/dist/gsd-context-monitor.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +9 -6
- package/hooks/dist/gsd-read-injection-scanner.js +2 -2
- package/hooks/dist/gsd-secret-read-guard.js +141 -6
- package/hooks/dist/gsd-statusline.js +3 -2
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/lib/config-read.js +49 -0
- package/hooks/dist/lib/git-cmd.js +3 -1
- package/hooks/dist/lib/git-head-tree.js +474 -0
- package/hooks/dist/lib/isolation-sentinel.js +168 -13
- package/hooks/dist/lib/secret-read-exemptions.js +161 -0
- package/hooks/gsd-agent-isolation-guard.js +4 -3
- package/hooks/gsd-config-reload.js +2 -2
- package/hooks/gsd-context-monitor.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +9 -6
- package/hooks/gsd-read-injection-scanner.js +2 -2
- package/hooks/gsd-secret-read-guard.js +141 -6
- package/hooks/gsd-statusline.js +3 -2
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/lib/config-read.js +49 -0
- package/hooks/lib/git-cmd.js +3 -1
- package/hooks/lib/git-head-tree.js +474 -0
- package/hooks/lib/isolation-sentinel.js +168 -13
- package/hooks/lib/secret-read-exemptions.js +161 -0
- package/package.json +7 -2
- package/pi/package-agents/gsd-executor.md +1 -1
- package/pi/package-agents/gsd-plan-checker.md +1 -1
- package/pi/package-agents/gsd-planner.md +3 -3
- package/pi/package-agents/gsd-verifier.md +2 -2
- package/pi/package-agents/manifest.json +4 -4
- package/pi/package-skills/gsd-autonomous/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
- package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
- package/scripts/changeset/cli.cjs +2 -2
- package/scripts/changeset/lint.cjs +28 -3
- package/scripts/changeset/serialize.cjs +12 -2
- package/scripts/ci-compact-v8-coverage.cjs +204 -0
- package/scripts/ci-fix-next-rerun.cjs +149 -0
- package/scripts/ci-next-health.cjs +52 -9
- package/scripts/ci-test-scope.cjs +77 -71
- package/scripts/command-contract-helpers.cjs +56 -7
- package/scripts/docs-guard-registry.cjs +3 -0
- package/scripts/gen-cli-surface-manifest.cjs +287 -0
- package/scripts/gen-platform-conformance-tier.cjs +258 -19
- package/scripts/lib/macos-conformance-tier.generated.cjs +59 -47
- package/scripts/lib/platform-conformance-tier.generated.cjs +23 -64
- package/scripts/lib/shellcheck-fetch.cjs +71 -4
- package/scripts/lint-removed-but-needed.cjs +562 -50
- package/scripts/merge-commit-pr.cjs +186 -0
- package/scripts/release-ancestry.cjs +86 -0
- package/scripts/release-notes/changelog-release-notes.cjs +345 -0
- package/scripts/release-notes/discord-release-summary.cjs +82 -1
- package/scripts/release-pipeline-pr.cjs +92 -0
- package/scripts/require-issue-link-policy.cjs +18 -9
- package/scripts/sync-next-version.cjs +11 -2
- package/scripts/timeout-evidence-from-ci.cjs +964 -0
- package/skills/gsd-autonomous/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-new-project/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -25,6 +25,7 @@ const capabilityRegistry = require('./capability-registry.cjs');
|
|
|
25
25
|
const { hostBehaviorsFor } = require('./runtime-name-policy.cjs');
|
|
26
26
|
const { resolveConfigHomeFromDescriptor } = require('./runtime-homes.cjs');
|
|
27
27
|
const { resolveActiveRuntime } = require('./runtime-slash.cjs');
|
|
28
|
+
const { readConfigFileBounded } = require('./config-file-read.cjs');
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Runtimes whose agent config is static frontmatter/TOML baked at install time,
|
|
@@ -226,7 +227,9 @@ function _readRuntimeConfig(cwd, { fsStatSync = fs.statSync } = {}) {
|
|
|
226
227
|
const candidate = path.join(probe, '.planning', 'config.json');
|
|
227
228
|
try {
|
|
228
229
|
fsStatSync(candidate);
|
|
229
|
-
|
|
230
|
+
// Bounded read (#5759): a FIFO/device/oversized config throws and is walked past like a
|
|
231
|
+
// missing one; null (raced away after the stat) makes JSON.parse('') throw into the same catch.
|
|
232
|
+
const raw = readConfigFileBounded(candidate) ?? '';
|
|
230
233
|
const parsed = JSON.parse(raw);
|
|
231
234
|
if (parsed && typeof parsed === 'object') return parsed;
|
|
232
235
|
return {};
|
|
@@ -31,6 +31,8 @@ exports.applyPreserveWhenUnchanged = applyPreserveWhenUnchanged;
|
|
|
31
31
|
exports.applyStatePreservation = applyStatePreservation;
|
|
32
32
|
exports.transitionCore = transitionCore;
|
|
33
33
|
exports.sliceCurrentPositionSection = sliceCurrentPositionSection;
|
|
34
|
+
exports.readPlanPosition = readPlanPosition;
|
|
35
|
+
exports.readRecordedPlanTotal = readRecordedPlanTotal;
|
|
34
36
|
exports.isStatePastPhase = isStatePastPhase;
|
|
35
37
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
38
|
const frontmatter = require("./frontmatter.cjs");
|
|
@@ -42,7 +44,7 @@ const roadmap_progress_cjs_1 = require("./roadmap-progress.cjs");
|
|
|
42
44
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
43
45
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
|
|
44
46
|
const phaseIdMod = require("./phase-id.cjs");
|
|
45
|
-
const { comparePhaseNum } = phaseIdMod;
|
|
47
|
+
const { comparePhaseNum, parsePhaseFromProse } = phaseIdMod;
|
|
46
48
|
// #4129: the completion-ratio kernel for the resync-arm ratchet's percent
|
|
47
49
|
// (planning-scope's SCOPE — state-document's own dependency, no cycle here:
|
|
48
50
|
// state-document never imports this module).
|
|
@@ -1261,6 +1263,94 @@ function planNumberFrom(digits) {
|
|
|
1261
1263
|
const n = Number(digits);
|
|
1262
1264
|
return Number.isSafeInteger(n) ? n : null;
|
|
1263
1265
|
}
|
|
1266
|
+
/**
|
|
1267
|
+
* The one plan-position parse: legacy pair first, then the hybrid, then
|
|
1268
|
+
* compound. `advancePlanCore` and `readPlanPosition` both consume it, so the
|
|
1269
|
+
* precedence is decided in exactly one place (#5786).
|
|
1270
|
+
*
|
|
1271
|
+
* These branches decide ONE thing: which numbers the position is read from.
|
|
1272
|
+
* They deliberately do not record which FIELD supplied them, because the
|
|
1273
|
+
* advance write path writes every spelling back from its own raw text (#3791
|
|
1274
|
+
* review round 6, B1/M1).
|
|
1275
|
+
*
|
|
1276
|
+
* Every branch reads its numbers out of an ANCHORED match's capture groups.
|
|
1277
|
+
* Nothing here calls parseInt on a raw field value, so a value the grammar
|
|
1278
|
+
* does not fully describe cannot half-parse into a plausible number.
|
|
1279
|
+
*/
|
|
1280
|
+
function parsePlanFields(content) {
|
|
1281
|
+
const legacyPlan = (0, state_document_cjs_1.stateExtractField)(content, 'Current Plan');
|
|
1282
|
+
const legacyTotal = (0, state_document_cjs_1.stateExtractField)(content, 'Total Plans in Phase');
|
|
1283
|
+
const planField = (0, state_document_cjs_1.stateExtractField)(content, 'Plan');
|
|
1284
|
+
const legacyNMatch = legacyPlan ? PLAN_SHAPE_N.exec(legacyPlan) : null;
|
|
1285
|
+
const legacyNofMMatch = legacyPlan ? PLAN_SHAPE_N_OF_M.exec(legacyPlan) : null;
|
|
1286
|
+
const totalNMatch = legacyTotal ? PLAN_SHAPE_N.exec(legacyTotal) : null;
|
|
1287
|
+
const planNofMMatch = planField ? PLAN_SHAPE_N_OF_M.exec(planField) : null;
|
|
1288
|
+
const planNMatch = planField ? PLAN_SHAPE_N.exec(planField) : null;
|
|
1289
|
+
let current = null;
|
|
1290
|
+
let total = null;
|
|
1291
|
+
if (legacyPlan && legacyTotal && (legacyNMatch || legacyNofMMatch) && totalNMatch) {
|
|
1292
|
+
// Legacy pair wins whenever both fields are present and both are readable,
|
|
1293
|
+
// even if the Current Plan value also carries an "of M" — the explicit
|
|
1294
|
+
// sibling field is the stated intent, so it supplies the total.
|
|
1295
|
+
current = planNumberFrom((legacyNMatch ?? legacyNofMMatch)[1]);
|
|
1296
|
+
total = planNumberFrom(totalNMatch[1]);
|
|
1297
|
+
}
|
|
1298
|
+
else if (legacyNofMMatch) {
|
|
1299
|
+
// Hybrid: legacy field name, compound value, no readable Total Plans
|
|
1300
|
+
// sibling. Written by hand (and by agents) often enough to be worth
|
|
1301
|
+
// reading — #3784.
|
|
1302
|
+
current = planNumberFrom(legacyNofMMatch[1]);
|
|
1303
|
+
total = planNumberFrom(legacyNofMMatch[2]);
|
|
1304
|
+
}
|
|
1305
|
+
else if (planNofMMatch) {
|
|
1306
|
+
current = planNumberFrom(planNofMMatch[1]);
|
|
1307
|
+
total = planNumberFrom(planNofMMatch[2]);
|
|
1308
|
+
}
|
|
1309
|
+
// No branch for a bare `Plan: N` paired with a `Total Plans in Phase: M`
|
|
1310
|
+
// sibling and no `Current Plan` at all (#3791 review round 6, M2). A revision
|
|
1311
|
+
// of this PR accepted it; base did not (its `else if (planField)` arm had no
|
|
1312
|
+
// `of M` match and errored via NaN), and it is out of #3784's scope, which is
|
|
1313
|
+
// the hybrid `Current Plan: N of M`. It cannot be given the schema-row +
|
|
1314
|
+
// forcing-test coupling the other shapes have, either: `Plan` is body-only,
|
|
1315
|
+
// `buildStateFrontmatter` never reads it into frontmatter, so there is no
|
|
1316
|
+
// `current_*` key to hang a row on. An accepted shape with no schema row and
|
|
1317
|
+
// no forcing test is exactly the drift this diff is otherwise built to
|
|
1318
|
+
// prevent, so the shape is refused and named in the error instead.
|
|
1319
|
+
return { legacyPlan, planField, legacyNMatch, legacyNofMMatch, planNofMMatch, planNMatch, current, total };
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Read the recorded plan position out of a STATE.md document through the one
|
|
1323
|
+
* plan-position grammar, or `null` when no readable position is recorded
|
|
1324
|
+
* (`Plan: 1 of ?`, `Total Plans in Phase: TBD`, no field at all). #5786.
|
|
1325
|
+
*/
|
|
1326
|
+
function readPlanPosition(content) {
|
|
1327
|
+
const { current, total } = parsePlanFields(content);
|
|
1328
|
+
return current === null || total === null ? null : { current, total };
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* The plan total a STATE.md document records, for the `init --validate`
|
|
1332
|
+
* plan-count check: a readable `Total Plans in Phase` first, else the
|
|
1333
|
+
* of-M total read straight from the plan fields: `Current Plan`'s of-M is
|
|
1334
|
+
* tried first, then `Plan`'s, and convertibility is checked per field (an
|
|
1335
|
+
* unconvertible `Current Plan` M falls through to `Plan`'s M), unlike
|
|
1336
|
+
* `parsePlanFields`, which commits to the first matching field. The fallback
|
|
1337
|
+
* does not go through the legacy pair, so an unconvertible `Total Plans in
|
|
1338
|
+
* Phase` beside `Current Plan: N of M` still yields M. `null` when neither
|
|
1339
|
+
* yields a convertible M. #5786.
|
|
1340
|
+
*/
|
|
1341
|
+
function readRecordedPlanTotal(content) {
|
|
1342
|
+
// An explicit `Total Plans in Phase` wins over an `of M` in either plan field.
|
|
1343
|
+
const raw = (0, state_document_cjs_1.stateExtractField)(content, 'Total Plans in Phase');
|
|
1344
|
+
const match = raw ? PLAN_SHAPE_N.exec(raw) : null;
|
|
1345
|
+
const t = match ? planNumberFrom(match[1]) : null;
|
|
1346
|
+
if (t !== null)
|
|
1347
|
+
return t;
|
|
1348
|
+
const { legacyNofMMatch, planNofMMatch } = parsePlanFields(content);
|
|
1349
|
+
const fromCurrentPlan = legacyNofMMatch ? planNumberFrom(legacyNofMMatch[2]) : null;
|
|
1350
|
+
if (fromCurrentPlan !== null)
|
|
1351
|
+
return fromCurrentPlan;
|
|
1352
|
+
return planNofMMatch ? planNumberFrom(planNofMMatch[2]) : null;
|
|
1353
|
+
}
|
|
1264
1354
|
/**
|
|
1265
1355
|
* Advance the leading integer of a written plan value, preserving everything
|
|
1266
1356
|
* the author wrote around it: the zero-padding width ("04" -> "05") and any
|
|
@@ -1483,47 +1573,7 @@ function advancePlanCore(content, deps) {
|
|
|
1483
1573
|
// `planSourceField`/`planRawValue` pair here and then wrote the OTHER
|
|
1484
1574
|
// spelling from this one's numbers, which is precisely how a field ended up
|
|
1485
1575
|
// holding a value nothing had derived for it.
|
|
1486
|
-
const legacyPlan
|
|
1487
|
-
const legacyTotal = (0, state_document_cjs_1.stateExtractField)(content, 'Total Plans in Phase');
|
|
1488
|
-
const planField = (0, state_document_cjs_1.stateExtractField)(content, 'Plan');
|
|
1489
|
-
// Every branch below reads its numbers out of an ANCHORED match's capture
|
|
1490
|
-
// groups. Nothing here calls parseInt on a raw field value, so a value the
|
|
1491
|
-
// grammar does not fully describe cannot half-parse into a plausible number.
|
|
1492
|
-
const legacyNMatch = legacyPlan ? PLAN_SHAPE_N.exec(legacyPlan) : null;
|
|
1493
|
-
const legacyNofMMatch = legacyPlan ? PLAN_SHAPE_N_OF_M.exec(legacyPlan) : null;
|
|
1494
|
-
const totalNMatch = legacyTotal ? PLAN_SHAPE_N.exec(legacyTotal) : null;
|
|
1495
|
-
const planNofMMatch = planField ? PLAN_SHAPE_N_OF_M.exec(planField) : null;
|
|
1496
|
-
const planNMatch = planField ? PLAN_SHAPE_N.exec(planField) : null;
|
|
1497
|
-
let parsedCurrent = null;
|
|
1498
|
-
let parsedTotal = null;
|
|
1499
|
-
if (legacyPlan && legacyTotal && (legacyNMatch || legacyNofMMatch) && totalNMatch) {
|
|
1500
|
-
// Legacy pair wins whenever both fields are present and both are readable,
|
|
1501
|
-
// even if the Current Plan value also carries an "of M" — the explicit
|
|
1502
|
-
// sibling field is the stated intent, so it supplies the total.
|
|
1503
|
-
parsedCurrent = planNumberFrom((legacyNMatch ?? legacyNofMMatch)[1]);
|
|
1504
|
-
parsedTotal = planNumberFrom(totalNMatch[1]);
|
|
1505
|
-
}
|
|
1506
|
-
else if (legacyNofMMatch) {
|
|
1507
|
-
// Hybrid: legacy field name, compound value, no readable Total Plans
|
|
1508
|
-
// sibling. Written by hand (and by agents) often enough to be worth
|
|
1509
|
-
// reading — #3784.
|
|
1510
|
-
parsedCurrent = planNumberFrom(legacyNofMMatch[1]);
|
|
1511
|
-
parsedTotal = planNumberFrom(legacyNofMMatch[2]);
|
|
1512
|
-
}
|
|
1513
|
-
else if (planNofMMatch) {
|
|
1514
|
-
parsedCurrent = planNumberFrom(planNofMMatch[1]);
|
|
1515
|
-
parsedTotal = planNumberFrom(planNofMMatch[2]);
|
|
1516
|
-
}
|
|
1517
|
-
// No branch for a bare `Plan: N` paired with a `Total Plans in Phase: M`
|
|
1518
|
-
// sibling and no `Current Plan` at all (#3791 review round 6, M2). A revision
|
|
1519
|
-
// of this PR accepted it; base did not (its `else if (planField)` arm had no
|
|
1520
|
-
// `of M` match and errored via NaN), and it is out of #3784's scope, which is
|
|
1521
|
-
// the hybrid `Current Plan: N of M`. It cannot be given the schema-row +
|
|
1522
|
-
// forcing-test coupling the other shapes have, either: `Plan` is body-only,
|
|
1523
|
-
// `buildStateFrontmatter` never reads it into frontmatter, so there is no
|
|
1524
|
-
// `current_*` key to hang a row on. An accepted shape with no schema row and
|
|
1525
|
-
// no forcing test is exactly the drift this diff is otherwise built to
|
|
1526
|
-
// prevent, so the shape is refused and named in the error instead.
|
|
1576
|
+
const { legacyPlan, planField, legacyNMatch, legacyNofMMatch, planNofMMatch, planNMatch, current: parsedCurrent, total: parsedTotal, } = parsePlanFields(content);
|
|
1527
1577
|
if (parsedCurrent === null || parsedTotal === null) {
|
|
1528
1578
|
return { content: reassemble(body), updated: [], data: { error: true } };
|
|
1529
1579
|
}
|
|
@@ -1724,8 +1774,10 @@ function nextPhaseWording(disk) {
|
|
|
1724
1774
|
* position; the caller combines this with "the phase is already complete in
|
|
1725
1775
|
* ROADMAP.md" so a first-time out-of-order completion (#2028) still
|
|
1726
1776
|
* transitions. An unparseable or absent current phase is "not past".
|
|
1777
|
+
* #5676: under `milestone-prefixed` the id is the whole `M-NN` (`3-02`, not `3`); that read goes
|
|
1778
|
+
* through `parsePhaseFromProse` (the #5626 grammar owner), so the two cannot drift.
|
|
1727
1779
|
*/
|
|
1728
|
-
function isStatePastPhase(content, phaseNum, sourcePath) {
|
|
1780
|
+
function isStatePastPhase(content, phaseNum, sourcePath, convention) {
|
|
1729
1781
|
const fm = extractFrontmatter(content, sourcePath);
|
|
1730
1782
|
const fromFm = fm['current_phase'];
|
|
1731
1783
|
let raw = typeof fromFm === 'string' || typeof fromFm === 'number' ? String(fromFm) : null;
|
|
@@ -1734,10 +1786,15 @@ function isStatePastPhase(content, phaseNum, sourcePath) {
|
|
|
1734
1786
|
raw = (0, state_document_cjs_1.stateExtractField)(body, 'Current Phase') || (0, state_document_cjs_1.stateExtractField)(body, 'Phase') || null;
|
|
1735
1787
|
}
|
|
1736
1788
|
// `Phase 4` / `phase 4 of 9` name phase 4 as surely as `4 of 9` does.
|
|
1737
|
-
|
|
1738
|
-
if (
|
|
1789
|
+
let id = null;
|
|
1790
|
+
if (raw !== null) {
|
|
1791
|
+
id = convention === 'milestone-prefixed'
|
|
1792
|
+
? parsePhaseFromProse(raw, 'milestone-prefixed').phase
|
|
1793
|
+
: (/^\s*(?:phase\s+)?(\d+(?:\.\d+)*)/i.exec(raw)?.[1] ?? null);
|
|
1794
|
+
}
|
|
1795
|
+
if (id === null)
|
|
1739
1796
|
return false;
|
|
1740
|
-
return comparePhaseNum(
|
|
1797
|
+
return comparePhaseNum(id, String(phaseNum)) > 0;
|
|
1741
1798
|
}
|
|
1742
1799
|
/**
|
|
1743
1800
|
* Apply a `completePhase` transition to STATE.md content.
|