@opengsd/gsd-core 1.8.0 → 1.9.1
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 +31 -1
- package/agents/gsd-code-fixer.md +107 -34
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debug-session-manager.md +36 -0
- package/agents/gsd-executor.md +20 -7
- 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 +15 -23
- package/agents/gsd-project-researcher.md +2 -2
- package/agents/gsd-ui-auditor.md +0 -40
- package/bin/install.js +236 -107
- package/commands/gsd/plan-review-convergence.md +5 -1
- package/gsd-core/bin/gsd-tools.cjs +882 -4
- package/gsd-core/bin/lib/api-coverage.cjs +22 -8
- package/gsd-core/bin/lib/audit.cjs +8 -8
- 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 +1353 -132
- 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/check-command-router.cjs +12 -2
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +15 -0
- package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +102 -12
- package/gsd-core/bin/lib/claude-orchestration.cjs +125 -22
- package/gsd-core/bin/lib/commands.cjs +246 -18
- package/gsd-core/bin/lib/config-loader.cjs +200 -28
- package/gsd-core/bin/lib/config.cjs +90 -5
- package/gsd-core/bin/lib/estimate-cli.cjs +336 -0
- package/gsd-core/bin/lib/frontmatter.cjs +125 -15
- package/gsd-core/bin/lib/host-integration.cjs +215 -8
- package/gsd-core/bin/lib/init.cjs +44 -19
- package/gsd-core/bin/lib/install-engine.cjs +1 -0
- package/gsd-core/bin/lib/milestone.cjs +36 -9
- package/gsd-core/bin/lib/model-catalog.cjs +51 -1
- package/gsd-core/bin/lib/observability/logger.cjs +7 -2
- 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 +278 -5
- package/gsd-core/bin/lib/phase.cjs +61 -6
- package/gsd-core/bin/lib/plan-drift-guard.cjs +1 -1
- package/gsd-core/bin/lib/plan-scan.cjs +1 -1
- 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/project-root.cjs +48 -0
- 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 +54 -6
- package/gsd-core/bin/lib/roadmap.cjs +10 -4
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +31 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +1 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +140 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +15 -2
- package/gsd-core/bin/lib/smart-entry.cjs +1 -1
- package/gsd-core/bin/lib/state-document.cjs +164 -20
- package/gsd-core/bin/lib/state-transition.cjs +28 -10
- package/gsd-core/bin/lib/state.cjs +141 -21
- package/gsd-core/bin/lib/uat-predicate.cjs +6 -4
- package/gsd-core/bin/lib/uat.cjs +9 -7
- 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 +32 -0
- package/gsd-core/bin/lib/verification.cjs +51 -14
- package/gsd-core/bin/lib/verify.cjs +146 -22
- 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 +1 -13
- 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/context-budget.md +40 -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/planning-config.md +2 -1
- package/gsd-core/references/reviewer-instances.md +28 -21
- package/gsd-core/references/runtime-aware-dispatch.md +42 -0
- package/gsd-core/references/ui-consideration-probe.md +2 -2
- package/gsd-core/references/worktree-branch-check.md +4 -4
- 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/ai-integration-phase.md +4 -4
- package/gsd-core/workflows/audit-fix.md +4 -0
- package/gsd-core/workflows/audit-milestone.md +8 -0
- package/gsd-core/workflows/autonomous.md +19 -15
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/code-review-fix.md +14 -6
- package/gsd-core/workflows/code-review.md +93 -21
- package/gsd-core/workflows/debug.md +10 -2
- package/gsd-core/workflows/diagnose-issues.md +4 -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-assumptions.md +15 -9
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/docs-update.md +8 -0
- package/gsd-core/workflows/eval-review.md +1 -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.md +85 -115
- package/gsd-core/workflows/execute-plan.md +5 -4
- package/gsd-core/workflows/explore.md +4 -0
- package/gsd-core/workflows/extract-learnings.md +21 -0
- package/gsd-core/workflows/help/modes/full.md +3 -3
- package/gsd-core/workflows/import.md +4 -1
- package/gsd-core/workflows/ingest-docs.md +4 -0
- package/gsd-core/workflows/map-codebase.md +13 -6
- package/gsd-core/workflows/new-milestone.md +10 -2
- package/gsd-core/workflows/new-project.md +11 -4
- package/gsd-core/workflows/next.md +5 -2
- package/gsd-core/workflows/plan-phase.md +42 -46
- package/gsd-core/workflows/plan-review-convergence.md +18 -14
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick.md +14 -3
- package/gsd-core/workflows/review.md +146 -575
- package/gsd-core/workflows/scan.md +9 -1
- package/gsd-core/workflows/secure-phase.md +10 -2
- package/gsd-core/workflows/ship.md +41 -11
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/ui-phase.md +8 -1
- package/gsd-core/workflows/ui-review.md +8 -1
- package/gsd-core/workflows/update.md +104 -5
- package/gsd-core/workflows/validate-phase.md +10 -2
- package/gsd-core/workflows/verify-work.md +8 -1
- 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 +9 -6
- 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-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 +9 -6
- 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 +7 -7
- package/pi/gsd.cjs +26 -1
- package/scripts/check-coverage-gate.cjs +51 -0
- package/scripts/check-glossary-refs.cjs +24 -0
- package/scripts/ci-test-scope.cjs +67 -17
- package/scripts/gen-adr-index.cjs +6 -4
- 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-registry.cjs +39 -15
- 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-resolution-provenance.cjs +9 -0
- package/scripts/mutation-matrix.cjs +4 -0
- package/scripts/prompt-injection-scan.sh +6 -0
- package/scripts/registry-schema.cjs +372 -94
- package/scripts/release-notes/conventional-title.cjs +19 -1
- package/scripts/release-notes/format-github-release-notes.cjs +7 -3
- package/scripts/validate-registry.cjs +10 -6
- package/scripts/workflow-size.cjs +16 -8
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -1
- package/vscode/package.json +1 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +0 -77
- package/scripts/update-size-baseline.cjs +0 -68
|
@@ -63,14 +63,35 @@ const { MODEL_PROFILES } = modelProfilesMod;
|
|
|
63
63
|
// Unused but imported for structural parity
|
|
64
64
|
void stripShippedMilestones;
|
|
65
65
|
void schema_detect_cjs_1.detectSchemaFiles;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Pure core of `verify-summary` (#2572).
|
|
68
|
+
*
|
|
69
|
+
* Same artifact↔git checks the CLI verb has always run, lifted out of the
|
|
70
|
+
* `output()` wrapper so other verbs can consume the structured
|
|
71
|
+
* `{ passed, checks, errors }` contract directly instead of shelling out and
|
|
72
|
+
* re-parsing JSON. `cmdVerifySummary` is now a thin adapter over this.
|
|
73
|
+
*
|
|
74
|
+
* Never throws and never writes to stdout: a missing SUMMARY, a non-repo, or an
|
|
75
|
+
* unresolvable commit all come back as structured `false`/`missing` values.
|
|
76
|
+
*
|
|
77
|
+
* Caveat for callers surfacing `commits_exist`: the hash pattern is a loose
|
|
78
|
+
* `\b[0-9a-f]{7,40}\b`, so any hex-shaped token in the prose counts as a
|
|
79
|
+
* candidate. That is cheap as an advisory signal and unacceptable as a gate.
|
|
80
|
+
*
|
|
81
|
+
* @param checkFileCount How many extracted candidates to probe. Defaults to 2 —
|
|
82
|
+
* the value the CLI verb has always used. Pass `Infinity` to probe every
|
|
83
|
+
* candidate (see `cmdPhaseComplete`, which reports on all of them).
|
|
84
|
+
* @param opts.checkCommits When `false`, the `git cat-file` probes are skipped
|
|
85
|
+
* entirely and `commits_exist` comes back `false` meaning *not checked*.
|
|
86
|
+
* Callers that do not surface `commits_exist` should pass `false` so this
|
|
87
|
+
* stays a pure-filesystem check with no subprocess cost.
|
|
88
|
+
*/
|
|
89
|
+
function verifySummaryCore(cwd, summaryPath, checkFileCount, opts) {
|
|
70
90
|
const fullPath = node_path_1.default.join(cwd, summaryPath);
|
|
71
91
|
const checkCount = checkFileCount || 2;
|
|
92
|
+
const checkCommits = opts?.checkCommits !== false;
|
|
72
93
|
if (!node_fs_1.default.existsSync(fullPath)) {
|
|
73
|
-
|
|
94
|
+
return {
|
|
74
95
|
passed: false,
|
|
75
96
|
checks: {
|
|
76
97
|
summary_exists: false,
|
|
@@ -80,34 +101,104 @@ function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
|
|
|
80
101
|
},
|
|
81
102
|
errors: ['SUMMARY.md not found'],
|
|
82
103
|
};
|
|
83
|
-
output(result, raw, 'failed');
|
|
84
|
-
return;
|
|
85
104
|
}
|
|
86
105
|
const content = node_fs_1.default.readFileSync(fullPath, 'utf-8');
|
|
87
106
|
const errors = [];
|
|
107
|
+
const projectRoot = node_path_1.default.resolve(cwd);
|
|
108
|
+
/**
|
|
109
|
+
* Is `candidate` plausibly a repo-relative file this check should probe?
|
|
110
|
+
*
|
|
111
|
+
* Deliberately narrowing. This is an ADVISORY, so the two error directions are
|
|
112
|
+
* not symmetric: a false positive tells a user their healthy project is
|
|
113
|
+
* missing a file that was never claimed, while a false negative just means one
|
|
114
|
+
* reference goes unprobed. Every rejection below is a noise class confirmed on
|
|
115
|
+
* #2685; when in doubt, skip rather than warn.
|
|
116
|
+
*/
|
|
117
|
+
const isProbableProjectFile = (candidate) => {
|
|
118
|
+
// Only repo-relative paths — a bare filename is too ambiguous to locate.
|
|
119
|
+
if (!candidate.includes('/'))
|
|
120
|
+
return false;
|
|
121
|
+
// URLs, protocol-relative links, and any other scheme.
|
|
122
|
+
if (candidate.startsWith('http') || candidate.startsWith('//'))
|
|
123
|
+
return false;
|
|
124
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(candidate))
|
|
125
|
+
return false;
|
|
126
|
+
// Globs name a set, not a file: `src/**/*.cts` is never "missing".
|
|
127
|
+
if (/[*?]/.test(candidate))
|
|
128
|
+
return false;
|
|
129
|
+
// Bare hostnames (`docs.example.com/guide.html`). A repo-relative path's
|
|
130
|
+
// first segment is a directory name, which in practice contains a dot only
|
|
131
|
+
// when it is a dotfile directory (`.github/`, `.changeset/`, `.planning/`)
|
|
132
|
+
// — i.e. the dot is at index 0. A dot anywhere later marks a hostname.
|
|
133
|
+
const firstSegment = candidate.split('/')[0] || '';
|
|
134
|
+
if (firstSegment.indexOf('.') > 0)
|
|
135
|
+
return false;
|
|
136
|
+
// Containment guard: a `../`-bearing reference must not turn this advisory
|
|
137
|
+
// into a filesystem existence probe outside the project.
|
|
138
|
+
const resolved = node_path_1.default.resolve(projectRoot, candidate);
|
|
139
|
+
if (resolved !== projectRoot && !resolved.startsWith(projectRoot + node_path_1.default.sep))
|
|
140
|
+
return false;
|
|
141
|
+
return true;
|
|
142
|
+
};
|
|
143
|
+
// Pattern 2 excludes `[` and `]` from its path class (#2685 Blocker 1). All
|
|
144
|
+
// three SUMMARY templates prescribe a YAML flow sequence for `key-files`:
|
|
145
|
+
//
|
|
146
|
+
// key-files:
|
|
147
|
+
// created: [src/auth/login.ts, src/auth/session.ts]
|
|
148
|
+
//
|
|
149
|
+
// and the label matches `(?:Created|Modified|…):` case-insensitively. Without
|
|
150
|
+
// the bracket exclusion the class captures the literal `[` as part of the
|
|
151
|
+
// first path, yielding `[src/auth/login.ts` — a candidate that can never exist
|
|
152
|
+
// on disk. That fired on healthy projects built from GSD's own shipped
|
|
153
|
+
// template. The exclusion also stops a markdown list in the body from
|
|
154
|
+
// reintroducing the same artifact.
|
|
155
|
+
//
|
|
156
|
+
// Stripping frontmatter first was the other remedy offered on #2685. It is a
|
|
157
|
+
// verified no-op on top of this exclusion — measured identical extraction
|
|
158
|
+
// across all three shipped templates — because the exclusion already makes a
|
|
159
|
+
// flow-sequence line contribute nothing. Consequence worth naming: the
|
|
160
|
+
// `key-files` block, the most authoritative statement of what a phase created,
|
|
161
|
+
// is still not read. Recovering it needs a real frontmatter parse, which is
|
|
162
|
+
// deliberately left as a follow-up rather than smuggled in here.
|
|
88
163
|
const mentionedFiles = new Set();
|
|
164
|
+
// #2844: Pattern 1 matches any backticked path-like token. A SUMMARY body is
|
|
165
|
+
// predominantly about what the phase DID, so a backticked path in prose ("Built
|
|
166
|
+
// `src/kept.ts`", a `- \`src/x.ts\`` list item) is a legitimate claim (#2685
|
|
167
|
+
// pins this). The false-positive class #2844 fixes is a path mentioned as a
|
|
168
|
+
// FUTURE/CONDITIONAL deliverable — "next phase will add `shared/types.ts`",
|
|
169
|
+
// "planned", "would", "to be created" — which is NOT a claim about this phase.
|
|
170
|
+
// Exclude those lines rather than requiring an explicit claim verb (which would
|
|
171
|
+
// drop the legitimate "Built …" / list-item forms #2685 protects).
|
|
172
|
+
const isFutureMention = (line) => /\b(?:will(?:\s+(?:add|create|build|land))?(?:[^.])?|(?:next|later|future)\s+phase|planned?|would\s+(?:be|add|create|build)|to\s+be\s+(?:added|created|built)|eventually|not\s+yet)\b/i.test(line);
|
|
89
173
|
const patterns = [
|
|
90
174
|
/`([^`]+\.[a-zA-Z]+)`/g,
|
|
91
|
-
/(?:Created|Modified|Added|Updated|Edited):\s*`?([^\s`]+\.[a-zA-Z]+)`?/gi,
|
|
175
|
+
/(?:Created|Modified|Added|Updated|Edited):\s*`?([^\s`[\]]+\.[a-zA-Z]+)`?/gi,
|
|
92
176
|
];
|
|
93
177
|
for (const pattern of patterns) {
|
|
94
178
|
let m;
|
|
95
179
|
while ((m = pattern.exec(content)) !== null) {
|
|
96
180
|
const filePath = m[1];
|
|
97
|
-
if (filePath
|
|
98
|
-
|
|
99
|
-
|
|
181
|
+
if (!filePath || !isProbableProjectFile(filePath))
|
|
182
|
+
continue;
|
|
183
|
+
// #2844: skip a backticked path on a future/conditional line — it names a
|
|
184
|
+
// deliverable this phase did NOT produce, so probing it is a false positive.
|
|
185
|
+
const lineStart = content.lastIndexOf('\n', m.index) + 1;
|
|
186
|
+
const lineEnd = content.indexOf('\n', m.index);
|
|
187
|
+
const line = content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd);
|
|
188
|
+
if (isFutureMention(line))
|
|
189
|
+
continue;
|
|
190
|
+
mentionedFiles.add(filePath);
|
|
100
191
|
}
|
|
101
192
|
}
|
|
102
193
|
const filesToCheck = Array.from(mentionedFiles).slice(0, checkCount);
|
|
103
194
|
const missing = [];
|
|
104
195
|
for (const file of filesToCheck) {
|
|
105
|
-
if (!node_fs_1.default.existsSync(node_path_1.default.
|
|
196
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.resolve(projectRoot, file))) {
|
|
106
197
|
missing.push(file);
|
|
107
198
|
}
|
|
108
199
|
}
|
|
109
200
|
const commitHashPattern = /\b[0-9a-f]{7,40}\b/g;
|
|
110
|
-
const hashes = content.match(commitHashPattern) || [];
|
|
201
|
+
const hashes = checkCommits ? content.match(commitHashPattern) || [] : [];
|
|
111
202
|
let commitsExist = false;
|
|
112
203
|
if (hashes.length > 0) {
|
|
113
204
|
for (const hash of hashes.slice(0, 3)) {
|
|
@@ -144,8 +235,15 @@ function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
|
|
|
144
235
|
self_check: selfCheck,
|
|
145
236
|
};
|
|
146
237
|
const passed = missing.length === 0 && selfCheck !== 'failed';
|
|
147
|
-
|
|
148
|
-
|
|
238
|
+
return { passed, checks, errors };
|
|
239
|
+
}
|
|
240
|
+
/** CLI adapter over verifySummaryCore — arg guard + output shaping only. */
|
|
241
|
+
function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
|
|
242
|
+
if (!summaryPath) {
|
|
243
|
+
error('summary-path required');
|
|
244
|
+
}
|
|
245
|
+
const result = verifySummaryCore(cwd, summaryPath, checkFileCount);
|
|
246
|
+
output(result, raw, result.passed ? 'passed' : 'failed');
|
|
149
247
|
}
|
|
150
248
|
/**
|
|
151
249
|
* Issue #429 — negative-grep comment-text echo gate.
|
|
@@ -649,13 +747,24 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
|
649
747
|
if (!filePath) {
|
|
650
748
|
error('file path required');
|
|
651
749
|
}
|
|
750
|
+
if (filePath.includes('\0')) {
|
|
751
|
+
error('file path contains null bytes');
|
|
752
|
+
}
|
|
652
753
|
const fullPath = node_path_1.default.isAbsolute(filePath) ? filePath : node_path_1.default.join(cwd, filePath);
|
|
653
754
|
const content = (0, shell_command_projection_cjs_1.platformReadSync)(fullPath);
|
|
654
755
|
if (!content) {
|
|
655
756
|
output({ error: 'File not found', path: filePath }, raw);
|
|
656
757
|
return;
|
|
657
758
|
}
|
|
658
|
-
|
|
759
|
+
// #2701: fail loud on NUL/binary corruption before structure checks. A
|
|
760
|
+
// structurally intact-but-NUL-corrupted plan otherwise passes as valid and is
|
|
761
|
+
// silently skipped by recursive/binary-skipping searchers downstream.
|
|
762
|
+
const encErr = (0, validate_cjs_2.textEncodingError)(content, filePath);
|
|
763
|
+
if (encErr) {
|
|
764
|
+
output({ valid: false, errors: [encErr] }, raw);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
const fm = extractFrontmatter(content, fullPath);
|
|
659
768
|
const errors = [];
|
|
660
769
|
const warnings = [];
|
|
661
770
|
const required = ['phase', 'plan', 'type', 'wave', 'depends_on', 'files_modified', 'autonomous', 'must_haves'];
|
|
@@ -909,7 +1018,7 @@ function collectPromisedFilesAtOrAfterWave(phaseDir, minWave) {
|
|
|
909
1018
|
const planContent = (0, shell_command_projection_cjs_1.platformReadSync)(planFullPath);
|
|
910
1019
|
if (!planContent)
|
|
911
1020
|
continue;
|
|
912
|
-
const fm = extractFrontmatter(planContent);
|
|
1021
|
+
const fm = extractFrontmatter(planContent, planFullPath);
|
|
913
1022
|
const waveRaw = fm['wave'];
|
|
914
1023
|
const wave = typeof waveRaw === 'string' ? parseInt(waveRaw, 10) : (typeof waveRaw === 'number' ? waveRaw : NaN);
|
|
915
1024
|
if (isNaN(wave) || wave < minWave)
|
|
@@ -944,7 +1053,7 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
944
1053
|
}
|
|
945
1054
|
// Derive the current plan's wave number and phase directory for wave-aware
|
|
946
1055
|
// missing-file handling (fix #1202).
|
|
947
|
-
const currentFm = extractFrontmatter(content);
|
|
1056
|
+
const currentFm = extractFrontmatter(content, fullPath);
|
|
948
1057
|
const currentWaveRaw = currentFm['wave'];
|
|
949
1058
|
const currentWave = typeof currentWaveRaw === 'string'
|
|
950
1059
|
? parseInt(currentWaveRaw, 10)
|
|
@@ -1041,8 +1150,16 @@ function listMilestoneArchiveDirs(planBase) {
|
|
|
1041
1150
|
.map((e) => node_path_1.default.join(milestonesDir, e.name))
|
|
1042
1151
|
.sort((a, b) => node_path_1.default.basename(a).localeCompare(node_path_1.default.basename(b), undefined, { numeric: true }));
|
|
1043
1152
|
}
|
|
1044
|
-
catch {
|
|
1045
|
-
|
|
1153
|
+
catch (err) {
|
|
1154
|
+
// #1883: distinguish genuine absence from a permission/I-O failure. ENOENT
|
|
1155
|
+
// (no milestones/ dir yet) keeps the long-standing [] contract that
|
|
1156
|
+
// collectPhaseRoots / forEachArchivedPhaseToken depend on for "no archives";
|
|
1157
|
+
// every other error (EACCES, EIO, …) must propagate — otherwise an unreadable
|
|
1158
|
+
// milestones/ dir is silently reported as "no archives" and active-milestone
|
|
1159
|
+
// resolution / archived-phase filtering misbehaves.
|
|
1160
|
+
if (err.code === 'ENOENT')
|
|
1161
|
+
return [];
|
|
1162
|
+
throw err;
|
|
1046
1163
|
}
|
|
1047
1164
|
}
|
|
1048
1165
|
function forEachArchivedPhaseToken(planBase, onPhase) {
|
|
@@ -1215,8 +1332,9 @@ function cmdValidateConsistency(cwd, raw) {
|
|
|
1215
1332
|
}
|
|
1216
1333
|
}
|
|
1217
1334
|
for (const plan of plans) {
|
|
1218
|
-
const
|
|
1219
|
-
const
|
|
1335
|
+
const planFilePath = node_path_1.default.join(phasePath, plan);
|
|
1336
|
+
const content = node_fs_1.default.readFileSync(planFilePath, 'utf-8');
|
|
1337
|
+
const fmData = extractFrontmatter(content, planFilePath);
|
|
1220
1338
|
if (!fmData['wave']) {
|
|
1221
1339
|
warnings.push(`${phaseLabel}/${plan}: missing 'wave' in frontmatter`);
|
|
1222
1340
|
}
|
|
@@ -2147,6 +2265,7 @@ module.exports = {
|
|
|
2147
2265
|
scanNegativeGrepCommentEcho,
|
|
2148
2266
|
scanFileWideNegativeGateConflict,
|
|
2149
2267
|
cmdVerifySummary,
|
|
2268
|
+
verifySummaryCore,
|
|
2150
2269
|
cmdVerifyPlanStructure,
|
|
2151
2270
|
cmdVerifyPhaseCompleteness,
|
|
2152
2271
|
cmdVerifyReferences,
|
|
@@ -2158,4 +2277,9 @@ module.exports = {
|
|
|
2158
2277
|
cmdValidateAgents,
|
|
2159
2278
|
cmdVerifySchemaDrift,
|
|
2160
2279
|
cmdVerifyCodebaseDrift,
|
|
2280
|
+
// Test seam (#1883): listMilestoneArchiveDirs is private and exercised through
|
|
2281
|
+
// the validate command, which runs in a subprocess — an fs monkeypatch in the
|
|
2282
|
+
// test process cannot reach it. Exposed under a leading underscore so the
|
|
2283
|
+
// permission-error path can be unit-tested directly (no chmod 0o000).
|
|
2284
|
+
_listMilestoneArchiveDirs: listMilestoneArchiveDirs,
|
|
2161
2285
|
};
|