@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
|
@@ -17,12 +17,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.setExplicitProjectRoot = setExplicitProjectRoot;
|
|
21
|
+
exports.resolveProjectRoot = resolveProjectRoot;
|
|
20
22
|
exports.findProjectRoot = findProjectRoot;
|
|
21
23
|
exports.consentProjectRoot = consentProjectRoot;
|
|
22
24
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
23
25
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
26
|
const node_os_1 = __importDefault(require("node:os"));
|
|
25
27
|
const FIND_PROJECT_ROOT_MAX_DEPTH = 10;
|
|
28
|
+
// #4894: an operator-supplied `--project-dir` IS the project root — the
|
|
29
|
+
// dispatcher validates it and skips the ancestor walk-up for `cwd`, but code
|
|
30
|
+
// that derives a root from a PHASE DIRECTORY (verification) never sees `cwd`.
|
|
31
|
+
// The dispatcher records the validated root here, once per process, and those
|
|
32
|
+
// callers resolve through `resolveProjectRoot`. `null` (the default, and what
|
|
33
|
+
// the dispatcher sets when the flag is absent) leaves `findProjectRoot`'s
|
|
34
|
+
// walk-up as the only source of truth, so no-flag behavior is unchanged.
|
|
35
|
+
let explicitProjectRoot = null;
|
|
36
|
+
function setExplicitProjectRoot(root) {
|
|
37
|
+
explicitProjectRoot = root;
|
|
38
|
+
}
|
|
39
|
+
function resolveProjectRoot(startDir) {
|
|
40
|
+
return explicitProjectRoot ?? findProjectRoot(startDir);
|
|
41
|
+
}
|
|
26
42
|
function findProjectRoot(startDir) {
|
|
27
43
|
let resolvedStart;
|
|
28
44
|
try {
|
|
@@ -43,12 +59,35 @@ function findProjectRoot(startDir) {
|
|
|
43
59
|
catch {
|
|
44
60
|
// fall through
|
|
45
61
|
}
|
|
62
|
+
// A `.planning` that is itself a symlink is a deliberate hop into a separate
|
|
63
|
+
// tree (the documented "keep planning content out of the tracked repo"
|
|
64
|
+
// convention: `.planning -> ~/.gsd-external-planning/<project>/`, its own git
|
|
65
|
+
// repo). `fs.existsSync(d + '/.git')` DEREFERENCES `d`, so at `d ===
|
|
66
|
+
// <the .planning symlink path>` it finds the EXTERNAL store's `.git`, and
|
|
67
|
+
// both isInsideGitRepo/nearestGitRoot misread it as a crossed nested-child-
|
|
68
|
+
// repo boundary (#2843's guard). `fs.lstatSync` does not dereference its
|
|
69
|
+
// final path component, so it answers "is `d` ITSELF a symlink" (#4815).
|
|
70
|
+
//
|
|
71
|
+
// Deliberately scoped to a symlink named `.planning`, NOT any symlinked
|
|
72
|
+
// directory: a nested child repo reached through some other symlinked
|
|
73
|
+
// ancestor is still a real repo boundary, and skipping it would reopen
|
|
74
|
+
// #2843 through a different trigger.
|
|
75
|
+
function isSymlinkedPlanningDir(d) {
|
|
76
|
+
if (node_path_1.default.basename(d) !== '.planning')
|
|
77
|
+
return false;
|
|
78
|
+
try {
|
|
79
|
+
return node_fs_1.default.lstatSync(d).isSymbolicLink();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
46
85
|
// Walk upward, mirroring isInsideGitRepo from the CJS reference.
|
|
47
86
|
function isInsideGitRepo(candidateParent) {
|
|
48
87
|
let d = resolvedStart;
|
|
49
88
|
while (d !== fsRoot) {
|
|
50
89
|
try {
|
|
51
|
-
if (node_fs_1.default.existsSync(d + node_path_1.default.sep + '.git'))
|
|
90
|
+
if (!isSymlinkedPlanningDir(d) && node_fs_1.default.existsSync(d + node_path_1.default.sep + '.git'))
|
|
52
91
|
return true;
|
|
53
92
|
}
|
|
54
93
|
catch {
|
|
@@ -77,7 +116,7 @@ function findProjectRoot(startDir) {
|
|
|
77
116
|
if (d === upTo)
|
|
78
117
|
break;
|
|
79
118
|
try {
|
|
80
|
-
if (node_fs_1.default.existsSync(d + node_path_1.default.sep + '.git'))
|
|
119
|
+
if (!isSymlinkedPlanningDir(d) && node_fs_1.default.existsSync(d + node_path_1.default.sep + '.git'))
|
|
81
120
|
return d;
|
|
82
121
|
}
|
|
83
122
|
catch {
|
|
@@ -64,7 +64,7 @@ exports.checkReviewerDocsParity = checkReviewerDocsParity;
|
|
|
64
64
|
* `args` is an argv TEMPLATE, not a prefix. The injected pieces — model, effort, output file,
|
|
65
65
|
* argv-borne prompt — do not all go in the same place, and no positional rule expresses that:
|
|
66
66
|
* `codex` injects the model in the MIDDLE (after the `exec --ephemeral` subcommand) and the output
|
|
67
|
-
* file later still, while `
|
|
67
|
+
* file later still, while `kimi-code` injects the model first and five lanes end with a bare `-` that
|
|
68
68
|
* must stay last. Splicing by position silently produced
|
|
69
69
|
* `codex --model M -o F exec --ephemeral …`, which is not a valid codex invocation.
|
|
70
70
|
*
|
|
@@ -94,36 +94,16 @@ const SPAWN_STDIN_STDOUT = {
|
|
|
94
94
|
outputChannel: 'stdout',
|
|
95
95
|
};
|
|
96
96
|
/**
|
|
97
|
-
* The
|
|
97
|
+
* The eleven declared lanes, in `write_reviews` order.
|
|
98
|
+
*
|
|
99
|
+
* The `gemini` lane was retired by #4709: Google sunset Gemini CLI on 2026-06-18 (the same
|
|
100
|
+
* sunset that removed the gemini RUNTIME in #1928/1.8.0), so the lane spawned a binary that no
|
|
101
|
+
* longer serves the free/Pro/Ultra tiers that are GSD's audience.
|
|
98
102
|
*
|
|
99
103
|
* `kimi-code` joined in Phase 5b (#2799, closes #2718) — ADR-2782's phase table lands it here
|
|
100
104
|
* rather than in 5a precisely so it arrives together with the iteration that can invoke it.
|
|
101
105
|
*/
|
|
102
106
|
exports.REVIEWER_LANES = Object.freeze([
|
|
103
|
-
{
|
|
104
|
-
slug: 'gemini',
|
|
105
|
-
flags: ['--gemini'],
|
|
106
|
-
transport: 'spawn',
|
|
107
|
-
probe: { kind: 'command-exists', binary: 'gemini' },
|
|
108
|
-
invoke: {
|
|
109
|
-
binary: 'gemini',
|
|
110
|
-
args: ['{{model}}', '-p', '-'],
|
|
111
|
-
...SPAWN_STDIN_STDOUT,
|
|
112
|
-
modelArg: '-m',
|
|
113
|
-
effortChannel: 'none',
|
|
114
|
-
},
|
|
115
|
-
timeoutFloorMs: 900_000,
|
|
116
|
-
timeoutConfigKey: 'review.timeouts.gemini',
|
|
117
|
-
emptyOutput: 'stub-with-stderr',
|
|
118
|
-
reviewsSection: 'Gemini',
|
|
119
|
-
evidenceClass: 'source-grounded',
|
|
120
|
-
requiresBinaries: [],
|
|
121
|
-
promptBudgetKey: 'review.max_prompt_tokens_per_reviewer.gemini',
|
|
122
|
-
modelConfigKey: 'review.models.gemini',
|
|
123
|
-
effortConfigKey: null,
|
|
124
|
-
defaultEffort: null,
|
|
125
|
-
handler: null,
|
|
126
|
-
},
|
|
127
107
|
{
|
|
128
108
|
// 1_200_000 rather than the 900_000 floor: headless Claude measured ~525 s
|
|
129
109
|
// on a large plan set (review.md:304).
|
|
@@ -139,8 +119,8 @@ exports.REVIEWER_LANES = Object.freeze([
|
|
|
139
119
|
effortChannel: 'argv',
|
|
140
120
|
// #2483: without these the claude leg is the only reviewer that additionally inherits the
|
|
141
121
|
// invoking user's global CLAUDE.md, the project CLAUDE.md, and Claude Code auto-memory —
|
|
142
|
-
// a context asymmetry against the independent-review premise (
|
|
143
|
-
// assembled prompt
|
|
122
|
+
// a context asymmetry against the independent-review premise (codex sees only the
|
|
123
|
+
// assembled prompt and runs --ephemeral). Both flags, not just the first: CLAUDE.md
|
|
144
124
|
// loading and auto-memory are independently-toggled mechanisms, and an environment
|
|
145
125
|
// exporting CLAUDE_CODE_DISABLE_AUTO_MEMORY=0 forces auto-memory back ON — the explicit
|
|
146
126
|
// pair is robust against that. Applies when /gsd:review runs from a non-Claude-Code host;
|
|
@@ -796,7 +776,7 @@ const NON_LANE_SIGNATURE_FLAGS = new Set(['--all']);
|
|
|
796
776
|
/**
|
|
797
777
|
* Is `flag` documented in `text` under one of the two structural shapes docs actually use?
|
|
798
778
|
*
|
|
799
|
-
* Backticked is the `COMMANDS.md` table-cell shape; bracketed (`[--
|
|
779
|
+
* Backticked is the `COMMANDS.md` table-cell shape; bracketed (`[--codex]`) is the
|
|
800
780
|
* `FEATURES.md` signature shape. Requiring one of those two delimiters — rather than a bare
|
|
801
781
|
* substring — is what keeps prose and fenced examples from satisfying the gate, and it bounds the
|
|
802
782
|
* token for free: a backticked `--claude` demands its closing backtick, so a backticked
|
|
@@ -889,7 +869,7 @@ const BRACKETED_FLAG_RE = /\[(--[a-z0-9][a-z0-9-]*)\]/g;
|
|
|
889
869
|
* cell 3. A file-scoped check is therefore satisfied by the forwarding row alone, so deleting a
|
|
890
870
|
* lane's actual table row — the exact #2781 regression — passes. Keying on cell 1 separates the
|
|
891
871
|
* two: `| `--agy` / `--antigravity` | … |` is one lane row declaring two flags, while
|
|
892
|
-
* `| Reviewer flags | No | … `--
|
|
872
|
+
* `| Reviewer flags | No | … `--codex`, `--claude` … |` is not a lane row at all.
|
|
893
873
|
*/
|
|
894
874
|
function flagsInFirstTableCell(lines, declared) {
|
|
895
875
|
const found = new Set();
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* `reviewer` bodies in the capability registry, not a hand-maintained tail.
|
|
21
21
|
* A capability of EITHER `role: "runtime"` (the six dual-purpose hosts —
|
|
22
22
|
* antigravity/claude/codex/cursor/opencode/qwen) or the lane-only
|
|
23
|
-
* `role: "reviewer"` (
|
|
23
|
+
* `role: "reviewer"` (coderabbit/ollama/lm_studio/llama_cpp) may carry
|
|
24
24
|
* a `reviewer` body, and it is the body's `reviewer.slug` — NOT the capability
|
|
25
25
|
* id — that becomes the roster entry: the two differ for `lm-studio` (id) /
|
|
26
26
|
* `lm_studio` (slug) and `llama-cpp` (id) / `llama_cpp` (slug), ADR-2782's
|
|
@@ -229,7 +229,7 @@ function resolveReviewerSelection(input) {
|
|
|
229
229
|
// ADR-2782 D4: absent-safe governs DISCOVERY, never explicit selection.
|
|
230
230
|
// Not finding a lane nobody asked for is normal; failing to run a lane
|
|
231
231
|
// somebody asked for is an error. Every miss used to be an `info`, so a
|
|
232
|
-
// PARTIAL miss (`--
|
|
232
|
+
// PARTIAL miss (`--codex --qwen` with qwen absent) ran the review with a
|
|
233
233
|
// thinner reviewer set while present_results reported success — "a cross-AI
|
|
234
234
|
// review that silently drops a lane is blind in one eye" (review.md).
|
|
235
235
|
// A total miss already errored, but only as a side effect of the selected
|
|
@@ -19,6 +19,9 @@ const { routeCjsCommandFamily } = cjsCommandRouterAdapter;
|
|
|
19
19
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
20
|
const roadmapUpgrade = require("./roadmap-upgrade.cjs");
|
|
21
21
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
|
+
const phaseIdCardMod = require("./phase-id-card.cjs");
|
|
23
|
+
const { phaseIdCard } = phaseIdCardMod;
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
25
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
23
26
|
const { planningDir } = planningWorkspace;
|
|
24
27
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -234,7 +237,7 @@ function routeRoadmapCommand({ roadmap, args, cwd, raw, error }) {
|
|
|
234
237
|
'upgrade': () => {
|
|
235
238
|
const dryRun = !args.includes('--apply');
|
|
236
239
|
// Parse `--convention <value>` and `--convention=<value>`. When the flag is
|
|
237
|
-
// absent entirely, default to the
|
|
240
|
+
// absent entirely, default to the legacy supported convention; when present
|
|
238
241
|
// with a missing/unsupported value, fall through to the rejection below
|
|
239
242
|
// (fail-closed — never silently run a migration the user did not request).
|
|
240
243
|
let convention = 'milestone-prefixed';
|
|
@@ -245,13 +248,18 @@ function routeRoadmapCommand({ roadmap, args, cwd, raw, error }) {
|
|
|
245
248
|
? token.slice(token.indexOf('=') + 1)
|
|
246
249
|
: (args[conventionFlagIdx + 1] ?? '');
|
|
247
250
|
}
|
|
248
|
-
if (convention !== 'milestone-prefixed') {
|
|
251
|
+
if (convention !== 'milestone-prefixed' && convention !== 'bracket') {
|
|
249
252
|
// No-throw hub contract (ADR-0012): a hub-dispatched handler must not call
|
|
250
253
|
// process.exit. Throw instead — the hub converts this to HandlerFailure and
|
|
251
254
|
// the adapter routes it through the injected error() boundary.
|
|
252
|
-
throw new Error('Only --convention milestone-prefixed is supported');
|
|
255
|
+
throw new Error('Only --convention milestone-prefixed or bracket is supported');
|
|
256
|
+
}
|
|
257
|
+
if (convention === 'bracket') {
|
|
258
|
+
// Keep stdout machine-readable for the dry-run JSON plan. The card is
|
|
259
|
+
// human guidance emitted at command start for both dry-run and apply.
|
|
260
|
+
process.stderr.write(`${phaseIdCard({ title: 'Bracket phase-ID convention' })}\n`);
|
|
253
261
|
}
|
|
254
|
-
const plan = roadmapUpgrade.computeMigrationPlan(cwd);
|
|
262
|
+
const plan = roadmapUpgrade.computeMigrationPlan(cwd, { convention });
|
|
255
263
|
roadmapUpgrade.applyMigration(cwd, plan, { dryRun });
|
|
256
264
|
},
|
|
257
265
|
},
|
|
@@ -571,6 +571,55 @@ function sliceMilestoneWindow(content, version) {
|
|
|
571
571
|
return null;
|
|
572
572
|
return content.slice(selected.index, computeMilestoneSectionEnd(content, selected[0], selected.index));
|
|
573
573
|
}
|
|
574
|
+
/**
|
|
575
|
+
* Enumerate the milestone section ranges recognized by the ROADMAP readers.
|
|
576
|
+
* Markdown boundaries reuse the reader's heading tokenizer, milestone-signal
|
|
577
|
+
* vocabulary, name/version extractor, and section-end owner. Collapsed
|
|
578
|
+
* archives reuse the same `<details><summary>` shape handled by
|
|
579
|
+
* `extractCurrentMilestoneScoped`.
|
|
580
|
+
*/
|
|
581
|
+
function milestoneSections(content) {
|
|
582
|
+
const sections = [];
|
|
583
|
+
const headings = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content);
|
|
584
|
+
for (const heading of headings) {
|
|
585
|
+
if (heading.level < 1 || heading.level > 3)
|
|
586
|
+
continue;
|
|
587
|
+
if (/^Phase\s+\S/i.test(heading.text))
|
|
588
|
+
continue;
|
|
589
|
+
if (!MILESTONE_HEADING_SIGNAL_PATTERN.test(heading.text))
|
|
590
|
+
continue;
|
|
591
|
+
const extracted = extractMilestoneHeadingName(heading.text);
|
|
592
|
+
const version = extracted?.version ?? null;
|
|
593
|
+
const versionMajor = version?.match(/^v(\d+)/i);
|
|
594
|
+
const milestoneInt = versionMajor ? parseInt(versionMajor[1], 10) : null;
|
|
595
|
+
const lineEnd = content.indexOf('\n', heading.offset);
|
|
596
|
+
const fullLine = content.slice(heading.offset, lineEnd === -1 ? content.length : lineEnd);
|
|
597
|
+
sections.push({
|
|
598
|
+
start: heading.offset,
|
|
599
|
+
end: computeMilestoneSectionEnd(content, fullLine, heading.offset, undefined, headings),
|
|
600
|
+
version,
|
|
601
|
+
milestoneInt: milestoneInt !== null && Number.isSafeInteger(milestoneInt) ? milestoneInt : null,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
const detailsPattern = /<details\b[^>]*>[\s\S]*?<\/details>/gi;
|
|
605
|
+
let detailsMatch;
|
|
606
|
+
while ((detailsMatch = detailsPattern.exec(content)) !== null) {
|
|
607
|
+
const summary = detailsMatch[0].match(/<summary[^>]*>([^<]*)<\/summary>/i)?.[1]?.trim() ?? '';
|
|
608
|
+
if (!MILESTONE_HEADING_SIGNAL_PATTERN.test(summary))
|
|
609
|
+
continue;
|
|
610
|
+
const extracted = extractMilestoneHeadingName(summary);
|
|
611
|
+
const version = extracted?.version ?? null;
|
|
612
|
+
const versionMajor = version?.match(/^v(\d+)/i);
|
|
613
|
+
const milestoneInt = versionMajor ? parseInt(versionMajor[1], 10) : null;
|
|
614
|
+
sections.push({
|
|
615
|
+
start: detailsMatch.index,
|
|
616
|
+
end: detailsMatch.index + detailsMatch[0].length,
|
|
617
|
+
version,
|
|
618
|
+
milestoneInt: milestoneInt !== null && Number.isSafeInteger(milestoneInt) ? milestoneInt : null,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return sections.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
622
|
+
}
|
|
574
623
|
/**
|
|
575
624
|
* #3184: counts RAW phase references — a `#{2,4} Phase <id>:` heading
|
|
576
625
|
* (fence-aware via `tokenizeHeadings`) or a `#2199` bullet entry — BEFORE any
|
|
@@ -600,17 +649,37 @@ function sliceMilestoneWindow(content, version) {
|
|
|
600
649
|
// decoy `### [GSD.04] Notes:` outside the window manufactured a false V005
|
|
601
650
|
// while suppressing the correct V004. The digit anchor forecloses both.
|
|
602
651
|
const BRACKET_PHASE_ENTRY_HEADING_RE = new RegExp(`^${PHASE_HEADING_PREFIX_SRC}${PHASE_NUMBER_TOKEN_SOURCE}${OPTIONAL_PHASE_TAG_SOURCE}\\s*:`, 'i');
|
|
652
|
+
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal
|
|
653
|
+
// mirror of OPTIONAL_PHASE_TAG_SOURCE). Hoisted out of hasPhaseEntries (#4144
|
|
654
|
+
// round 5 Blocker 1) so a consumer OUTSIDE this file's own phase-entry count —
|
|
655
|
+
// the bracket roadmap migrator's "phase-like but unparsed" refusal
|
|
656
|
+
// (src/roadmap-upgrade.cts) — can ask "is this heading text a phase heading
|
|
657
|
+
// by the readers' own rules" without re-deriving the pattern. A heading with
|
|
658
|
+
// no phase-number token and no trailing colon (`## Phase Details`, `##
|
|
659
|
+
// Phase Lifecycle`, `### Phase Notes`) is NOT a phase heading by this
|
|
660
|
+
// grammar — it is an ordinary section heading that happens to start with the
|
|
661
|
+
// word "Phase".
|
|
662
|
+
const PHASE_HEADING_TEXT_RE = /^(?:\[[^\]]{1,200}\]\s*)?Phase\s+([\w][\w.-]*)(?:\s*\([^)\n]{0,200}\))?\s*:/i;
|
|
663
|
+
/**
|
|
664
|
+
* #4144 round 5 Blocker 1: whether `headingText` — a heading's text with its
|
|
665
|
+
* leading `#{1,6}` markers and surrounding whitespace already stripped
|
|
666
|
+
* (`tokenizeHeadings`'s own `h.text`, or the equivalent for a line a caller
|
|
667
|
+
* already knows is a `#{2,4}` heading) — is a phase heading by the readers'
|
|
668
|
+
* own grammar. The single owner of this test; `hasPhaseEntries` below is
|
|
669
|
+
* itself just the first caller.
|
|
670
|
+
*/
|
|
671
|
+
function isPhaseHeadingText(headingText) {
|
|
672
|
+
return PHASE_HEADING_TEXT_RE.test(headingText);
|
|
673
|
+
}
|
|
603
674
|
function hasPhaseEntries(markdown, phaseIdConvention) {
|
|
604
|
-
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
605
675
|
// #3641: the widened grammar engages ONLY when the resolved convention is
|
|
606
676
|
// 'bracket' — a project that has not opted in runs the legacy pattern
|
|
607
677
|
// alone, byte-identically.
|
|
608
|
-
const phaseHeadingPattern = /^(?:\[[^\]]{1,200}\]\s*)?Phase\s+([\w][\w.-]*)(?:\s*\([^)\n]{0,200}\))?\s*:/i;
|
|
609
678
|
const bracketMode = phaseIdConvention === 'bracket';
|
|
610
679
|
for (const h of (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(markdown)) {
|
|
611
680
|
if (h.level < 2 || h.level > 4)
|
|
612
681
|
continue;
|
|
613
|
-
if (
|
|
682
|
+
if (isPhaseHeadingText(h.text))
|
|
614
683
|
return true;
|
|
615
684
|
if (bracketMode && BRACKET_PHASE_ENTRY_HEADING_RE.test(h.text))
|
|
616
685
|
return true;
|
|
@@ -671,6 +740,23 @@ function collectTablePhaseRows(window) {
|
|
|
671
740
|
}
|
|
672
741
|
return rows;
|
|
673
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* True when `content` contains a GFM table whose header row is phase-listing
|
|
745
|
+
* SHAPED (`| Phase | ... |`), regardless of whether it also declares a `Name`
|
|
746
|
+
* column. `collectTablePhaseRows` correctly refuses to mint a phase from a
|
|
747
|
+
* `| Phase | Status |`-shaped table (no name column, so no declaration) —
|
|
748
|
+
* but that table's mere presence still signals this roadmap has moved to
|
|
749
|
+
* table-based tracking, not checklist-based tracking. Callers that decide
|
|
750
|
+
* whether to treat bare checklist entries as phase declarations (#4899's
|
|
751
|
+
* roadmap.cts synthesis fallback) must see this signal even when
|
|
752
|
+
* `collectTablePhaseRows` itself returns nothing for the same content —
|
|
753
|
+
* otherwise a thin, name-less Progress table cannot suppress a checklist
|
|
754
|
+
* synthesis it was never meant to coexist with.
|
|
755
|
+
*/
|
|
756
|
+
function hasPhaseListingTableHeader(content) {
|
|
757
|
+
const unfenced = (0, markdown_sectionizer_cjs_1.stripFencedCode)(content).text;
|
|
758
|
+
return unfenced.split(/\r?\n/).some((line) => PHASE_LISTING_HEADER_RE.test(line));
|
|
759
|
+
}
|
|
674
760
|
/**
|
|
675
761
|
* #3262: the sole owner of "which phase ids does THIS milestone window
|
|
676
762
|
* declare". Extracted verbatim from `getMilestonePhaseFilter`'s former inline
|
|
@@ -965,6 +1051,7 @@ function extractCurrentMilestoneScoped(content, cwd, ws, phaseIdConvention) {
|
|
|
965
1051
|
const preambleCutoff = firstMilestoneMatch ? firstMilestoneMatch.index : detailsOpenIdx;
|
|
966
1052
|
const preamble = (0, markdown_sectionizer_cjs_1.stripTaggedBlocks)(content.slice(0, preambleCutoff), 'details')
|
|
967
1053
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
1054
|
+
// phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
|
|
968
1055
|
.replace(/^#{2,4}\s*Phase\s+[\w][\w.-]*(?:\s*\([^)\n]{0,200}\))?\s*:[^\n]*(?:\n(?!#{1,6}\s)[^\n]*)*\n?/gim, '')
|
|
969
1056
|
.replace(/^#{1,4}\s*Phase Details\b[^\n]*\n?/gim, '');
|
|
970
1057
|
const value = preamble + content.slice(detailsOpenIdx, detailsEnd);
|
|
@@ -1200,6 +1287,7 @@ function extractCurrentMilestoneScoped(content, cwd, ws, phaseIdConvention) {
|
|
|
1200
1287
|
// every phase (phase_count: 0, exit 0). Only strip preamble phase details when
|
|
1201
1288
|
// the selected milestone section actually contains its own — otherwise the
|
|
1202
1289
|
// preamble phases ARE this milestone's phases and must be preserved.
|
|
1290
|
+
// phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
|
|
1203
1291
|
const currentSectionHasPhaseDetails = /^#{2,4}\s*Phase\s+\S/im.test(currentSection);
|
|
1204
1292
|
const preambleBase = (0, markdown_sectionizer_cjs_1.stripTaggedBlocks)(beforeMilestones, 'details');
|
|
1205
1293
|
// #3235: the conditional wraps the REPLACE, not the pattern. This used to select between the
|
|
@@ -1208,6 +1296,7 @@ function extractCurrentMilestoneScoped(content, cwd, ws, phaseIdConvention) {
|
|
|
1208
1296
|
// replacement argument, so changing `''` would silently give the no-op branch a real effect.
|
|
1209
1297
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
1210
1298
|
const preambleWithoutPhaseDetails = currentSectionHasPhaseDetails
|
|
1299
|
+
// phase-id-owner: pre-existing hand-rolled Phase-heading pattern — grandfathered pending Phase 6 migration (ADR-4910 §8, epic #4906)
|
|
1211
1300
|
? preambleBase.replace(/^#{2,4}\s*Phase\s+[\w][\w.-]*(?:\s*\([^)\n]{0,200}\))?\s*:[^\n]*(?:\n(?!#{1,6}\s)[^\n]*)*\n?/gim, '')
|
|
1212
1301
|
: preambleBase;
|
|
1213
1302
|
// Unconditional in BOTH branches -- the #730 `Phase Details` heading strip is independent of
|
|
@@ -2056,6 +2145,69 @@ function currentMilestoneRawRanges(content, cwd) {
|
|
|
2056
2145
|
}
|
|
2057
2146
|
return { primary: { start: sectionStart, end: sectionEnd }, details };
|
|
2058
2147
|
}
|
|
2148
|
+
/**
|
|
2149
|
+
* #4731 — extract a bold-labeled phase field (Goal, Requirements, ...) reading
|
|
2150
|
+
* past hard-wrapped continuation lines. The roadmapper soft-wraps long fields
|
|
2151
|
+
* at ~85 chars, so a single-line capture silently truncated every wrapped
|
|
2152
|
+
* Goal/Requirements. Stops at: another `**Field**` label line, a blank line,
|
|
2153
|
+
* or any heading. Wrapped lines are trimmed and joined with single spaces; a
|
|
2154
|
+
* single-line field is returned unchanged (trimmed). Returns null when the
|
|
2155
|
+
* label is absent.
|
|
2156
|
+
*/
|
|
2157
|
+
function extractPhaseFieldMultiline(section, label) {
|
|
2158
|
+
// #2769 label shapes: `**X:**`, `**X**:`, and the spaced `**X** :`.
|
|
2159
|
+
// #4837: anchored to line start (`^[ \t]*`, `m` flag) so an inline
|
|
2160
|
+
// `**Label**` mention embedded in an EARLIER field's own body text cannot
|
|
2161
|
+
// shadow the real field's own declaration line. `[ \t]*` (not `\s*`) keeps
|
|
2162
|
+
// the anchor scoped to same-line leading whitespace under the `m` flag.
|
|
2163
|
+
const labelRe = new RegExp('^[ \\t]*\\*\\*' + label + '(?::\\*\\*|\\*\\*\\s*:?)\\s*([^\\n]+)', 'im');
|
|
2164
|
+
const match = section.match(labelRe);
|
|
2165
|
+
if (!match)
|
|
2166
|
+
return null;
|
|
2167
|
+
const startIdx = match.index ?? 0;
|
|
2168
|
+
const after = section.slice(startIdx + match[0].length);
|
|
2169
|
+
const firstLine = match[1].trim();
|
|
2170
|
+
// #4837 (isolated-review finding): the continuation loop's fence-stop check
|
|
2171
|
+
// only ever sees lines AFTER the label's own line — a fence opener on the
|
|
2172
|
+
// SAME line as the label (`**Requirements:** ```js`) was invisible to it,
|
|
2173
|
+
// leaking one line of fence content before the closing fence line
|
|
2174
|
+
// coincidentally matched the same stop check. Checked here too, so a fence
|
|
2175
|
+
// opening on the label's own line is caught just as conservatively.
|
|
2176
|
+
if (/^(?:`{3,}|~{3,})/.test(firstLine))
|
|
2177
|
+
return null;
|
|
2178
|
+
const contLines = [];
|
|
2179
|
+
const lines = after.split('\n');
|
|
2180
|
+
for (let li = 0; li < lines.length; li++) {
|
|
2181
|
+
const raw = lines[li];
|
|
2182
|
+
// The split's first element is the remainder of the captured first line's
|
|
2183
|
+
// own newline — an empty leading element is the line break, not a blank
|
|
2184
|
+
// continuation line.
|
|
2185
|
+
if (li === 0 && !raw.trim())
|
|
2186
|
+
continue;
|
|
2187
|
+
if (!raw.trim())
|
|
2188
|
+
break;
|
|
2189
|
+
// #4837: a list item is a structural boundary — the real corruption
|
|
2190
|
+
// vector (a `- Deferred to Phase N: OTHER-ID` bullet folding a foreign
|
|
2191
|
+
// REQ-ID into this field's citation-scan input).
|
|
2192
|
+
if (/^\s*[-*+]\s/.test(raw))
|
|
2193
|
+
break;
|
|
2194
|
+
// #4837: case-insensitive label-start character class — a
|
|
2195
|
+
// lowercase-first-letter label (`**requirements:**`) is as much a field
|
|
2196
|
+
// boundary as a capitalized one.
|
|
2197
|
+
if (/^\s*\*\*[A-Za-z][A-Za-z ]*:?(\*\*)?:?\s/.test(raw))
|
|
2198
|
+
break;
|
|
2199
|
+
if (/^\s*#{1,4}\s/.test(raw))
|
|
2200
|
+
break;
|
|
2201
|
+
if (/^\s*\|/.test(raw))
|
|
2202
|
+
break;
|
|
2203
|
+
// #4837: a fenced code block opener stops the scan — folding fence
|
|
2204
|
+
// content is never safe, and stopping early is conservative.
|
|
2205
|
+
if (/^\s*(?:`{3,}|~{3,})/.test(raw))
|
|
2206
|
+
break;
|
|
2207
|
+
contLines.push(raw.trim());
|
|
2208
|
+
}
|
|
2209
|
+
return [firstLine, ...contLines].join(' ').trim() || null;
|
|
2210
|
+
}
|
|
2059
2211
|
module.exports = {
|
|
2060
2212
|
stripShippedMilestones,
|
|
2061
2213
|
extractCurrentMilestone,
|
|
@@ -2077,6 +2229,7 @@ module.exports = {
|
|
|
2077
2229
|
// own doc comment. milestone.cts's destructive-consumer guard consumes
|
|
2078
2230
|
// this instead of composing locate+select+section-end itself.
|
|
2079
2231
|
sliceMilestoneWindow,
|
|
2232
|
+
milestoneSections,
|
|
2080
2233
|
hasVersionedMilestones,
|
|
2081
2234
|
hasMilestoneSectioning,
|
|
2082
2235
|
// #3642: the >=1 sibling buildStateFrontmatter's unbounded branch consumes.
|
|
@@ -2090,9 +2243,16 @@ module.exports = {
|
|
|
2090
2243
|
// guards and the edit-phase workflow's pre/post capture are built on.
|
|
2091
2244
|
scanMilestonePhaseIds,
|
|
2092
2245
|
collectTablePhaseRows,
|
|
2246
|
+
hasPhaseListingTableHeader,
|
|
2093
2247
|
findMilestoneScopeHeadingLines,
|
|
2094
2248
|
// #3641: the scope axis's phase-ENTRY predicate, exported so roadmap
|
|
2095
2249
|
// validate's V004 document-level check routes through the same single
|
|
2096
2250
|
// owner (and its convention gate) instead of a private inline copy.
|
|
2251
|
+
extractPhaseFieldMultiline,
|
|
2097
2252
|
hasPhaseEntries,
|
|
2253
|
+
// #4144 round 5 Blocker 1: the readers' own phase-heading grammar, so the
|
|
2254
|
+
// bracket roadmap migrator's "phase-like but unparsed" refusal
|
|
2255
|
+
// (src/roadmap-upgrade.cts) can gate on it instead of a private, looser
|
|
2256
|
+
// "starts with the word Phase" regex.
|
|
2257
|
+
isPhaseHeadingText,
|
|
2098
2258
|
};
|