@pareto4/pp-core 1.17.1 → 1.18.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.
Files changed (86) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +5 -3
  3. package/agents/pp-code-fixer.md +0 -11
  4. package/agents/pp-code-reviewer.md +1 -78
  5. package/agents/pp-executor.md +9 -23
  6. package/agents/pp-planner.md +11 -11
  7. package/agents/pp-vault-reviewer.md +43 -189
  8. package/agents/pp-verifier.md +0 -6
  9. package/commands/pp-ns-manage.md +1 -1
  10. package/commands/pp-ns-review.md +1 -1
  11. package/commands/pp-vault-promote.md +7 -8
  12. package/commands/pp-vault-review.md +6 -7
  13. package/gemini-extension.json +1 -1
  14. package/package.json +3 -6
  15. package/pp-core/bin/lib/capability-registry.cjs +13 -355
  16. package/pp-core/bin/lib/capability-state.cjs +15 -79
  17. package/pp-core/bin/lib/capability-writer.cjs +2 -16
  18. package/pp-core/bin/lib/check-command-router.cjs +277 -2273
  19. package/pp-core/bin/lib/clusters.cjs +2 -2
  20. package/pp-core/bin/lib/compose-bindings.cjs +2 -2
  21. package/pp-core/bin/lib/config.cjs +3 -6
  22. package/pp-core/bin/lib/git-base-branch.cjs +10 -13
  23. package/pp-core/bin/lib/git-provider.cjs +5 -6
  24. package/pp-core/bin/lib/init-contract.cjs +7 -35
  25. package/pp-core/bin/lib/init-interactive.cjs +6 -20
  26. package/pp-core/bin/lib/io.cjs +2 -8
  27. package/pp-core/bin/lib/loop-resolver.cjs +8 -32
  28. package/pp-core/bin/lib/org-profile.cjs +16 -92
  29. package/pp-core/bin/lib/vault-command-router.cjs +174 -1206
  30. package/pp-core/bin/lib/vault-coordinates.cjs +3 -3
  31. package/pp-core/bin/lib/vault-index.cjs +27 -70
  32. package/pp-core/bin/lib/vault-seal.cjs +37 -122
  33. package/pp-core/bin/lib/vault-standards-promote.cjs +577 -0
  34. package/pp-core/bin/lib/vault-standards-review.cjs +293 -0
  35. package/pp-core/bin/lib/vault-standards.cjs +15 -16
  36. package/pp-core/bin/lib/vault-trace.cjs +40 -3016
  37. package/pp-core/bin/lib/vault-update.cjs +1 -26
  38. package/pp-core/bin/lib/vault.cjs +120 -314
  39. package/pp-core/bin/pp-tools.cjs +5 -32
  40. package/pp-core/references/autonomous-best-effort.md +1 -1
  41. package/pp-core/references/planner-vault-standards.md +16 -25
  42. package/pp-core/templates/README.md +1 -1
  43. package/pp-core/templates/VAULT-REVIEW.md +24 -170
  44. package/pp-core/workflows/add-tests.md +3 -3
  45. package/pp-core/workflows/audit-fix.md +3 -3
  46. package/pp-core/workflows/code-review-fix.md +3 -3
  47. package/pp-core/workflows/code-review.md +1 -57
  48. package/pp-core/workflows/debug.md +2 -2
  49. package/pp-core/workflows/discovery-sprint/prototype.md +2 -2
  50. package/pp-core/workflows/discovery-sprint/vault-context.md +4 -10
  51. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +11 -13
  52. package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +5 -25
  53. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +6 -6
  54. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +6 -109
  55. package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +1 -17
  56. package/pp-core/workflows/execute-phase.md +2 -5
  57. package/pp-core/workflows/execute-plan.md +6 -8
  58. package/pp-core/workflows/help/modes/full.md +1 -1
  59. package/pp-core/workflows/quick.md +8 -8
  60. package/pp-core/workflows/validate-phase.md +3 -3
  61. package/pp-core/workflows/vault-review.md +64 -186
  62. package/pp-core/workflows/vault-write-back.md +73 -110
  63. package/pp-core/workflows/verify-work.md +1 -1
  64. package/scripts/gen-capability-registry.cjs +12 -93
  65. package/scripts/lint-test-file-count.allowlist.json +1 -19
  66. package/scripts/lint-test-file-count.cjs +5 -6
  67. package/scripts/prompt-injection-scan.sh +0 -3
  68. package/pp-core/bin/lib/practices-review-check.cjs +0 -944
  69. package/pp-core/bin/lib/vault-audit.cjs +0 -2594
  70. package/pp-core/bin/lib/vault-mode.cjs +0 -235
  71. package/pp-core/bin/lib/vault-practices.cjs +0 -2057
  72. package/pp-core/bin/lib/vault-review.cjs +0 -2102
  73. package/pp-core/bin/lib/vault-write-back.cjs +0 -905
  74. package/pp-core/references/planner-vault-practices.md +0 -95
  75. package/pp-core/references/practice-arithmetic-contract.md +0 -121
  76. package/pp-core/references/verifier-practice-verdicts.md +0 -263
  77. package/pp-core/templates/practices/README.md +0 -248
  78. package/pp-core/templates/practices/error-handling.md +0 -18
  79. package/pp-core/templates/practices/minimal-comments.md +0 -14
  80. package/pp-core/templates/practices/naming-convention.md +0 -18
  81. package/pp-core/templates/practices/practice-template.md +0 -23
  82. package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +0 -165
  83. package/scripts/gen-practice-arithmetic-contract.cjs +0 -412
  84. package/scripts/practice-arithmetic-contract.allow.json +0 -35
  85. package/scripts/record-vault-mode-golden.cjs +0 -89
  86. package/scripts/vault-calibrate.cjs +0 -996
@@ -0,0 +1,293 @@
1
+ 'use strict';
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.REVIEW_FILES_CAP = exports.REVIEW_VERDICTS_HEADING = exports.REVIEW_ARTIFACT_MARKER = exports.REVIEW_ARTIFACT_SUFFIX = exports.REVIEW_VERDICTS = void 0;
7
+ exports.deriveStandardsReview = deriveStandardsReview;
8
+ exports.formatReviewCounts = formatReviewCounts;
9
+ exports.validateReviewArtifact = validateReviewArtifact;
10
+ /**
11
+ * src/vault-standards-review.cts — the derived half of `/pp-vault-review`
12
+ * (FORK-ONLY, the standards-only vault).
13
+ *
14
+ * `/pp-vault-review` reviews a completed phase against the client vault's
15
+ * standards documents: one verdict per document that governed the phase
16
+ * (PASS / FAIL / N-A / INCONCLUSIVE), judged by the `pp-vault-reviewer` agent.
17
+ * This module gives that agent its facts and checks the artifact it writes;
18
+ * it judges nothing itself. It REPORTS and never gates: no envelope here has a
19
+ * `block` key, and no capability declares `check vault-review` as a gate.
20
+ *
21
+ * `deriveStandardsReview` reads, for one phase:
22
+ * - every PLAN's `<vault_standards>` block: which INDEX documents governed
23
+ * which plan, and why;
24
+ * - every SUMMARY's `## Standards Consulted` section: whether each document
25
+ * was read, and the plan's commit anchors;
26
+ * - per plan, the commit range those anchors bound (falling back to the
27
+ * directory's recorded start, as the seal does) and the source files
28
+ * changed in it (planning files excluded);
29
+ * - per document, where the cached copy is, so the agent can read it.
30
+ * It never fetches and never writes. Its only spawns are bounded, read-only
31
+ * git queries through `execGitLocal`.
32
+ *
33
+ * `validateReviewArtifact` checks the `{padded}-VAULT-REVIEW.md` the agent
34
+ * wrote against the derivation: every governing document appears exactly once
35
+ * in the verdict table, each verdict is one of the four, a non-PASS verdict
36
+ * states its reason, and the counts line agrees with the table.
37
+ *
38
+ * ADR-457 build-at-publish: this TypeScript file is the SOURCE OF TRUTH. It
39
+ * compiles to the git-ignored `pp-core/bin/lib/vault-standards-review.cjs`.
40
+ */
41
+ const node_fs_1 = __importDefault(require("node:fs"));
42
+ const node_path_1 = __importDefault(require("node:path"));
43
+ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
44
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
45
+ const vaultStandardsLeaf = require("./vault-standards.cjs");
46
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
47
+ const vaultSeal = require("./vault-seal.cjs");
48
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
49
+ const vaultIndexLeaf = require("./vault-index.cjs");
50
+ // ─── Types ───────────────────────────────────────────────────────────────────
51
+ /** The four verdicts, in the order the counts line states them. */
52
+ exports.REVIEW_VERDICTS = Object.freeze(['PASS', 'FAIL', 'N-A', 'INCONCLUSIVE']);
53
+ /** The artifact's file-name suffix and frontmatter marker. */
54
+ exports.REVIEW_ARTIFACT_SUFFIX = '-VAULT-REVIEW.md';
55
+ exports.REVIEW_ARTIFACT_MARKER = 'artifact: vault-review';
56
+ /** The verdict table's heading; the validator finds the table under it. */
57
+ exports.REVIEW_VERDICTS_HEADING = '## Standard Verdicts';
58
+ /** How many changed files one plan lists before it only counts. */
59
+ exports.REVIEW_FILES_CAP = 200;
60
+ /** Bound on a git query a review makes. */
61
+ const REVIEW_GIT_TIMEOUT_MS = 30_000;
62
+ // ─── Derivation ──────────────────────────────────────────────────────────────
63
+ const PLAN_RE = /(^|-)PLAN\.md$/;
64
+ const SUMMARY_RE = /(^|-)SUMMARY\.md$/;
65
+ function readBounded(abs) {
66
+ try {
67
+ const st = node_fs_1.default.statSync(abs);
68
+ if (!st.isFile() || st.size > vaultSeal.TRACE_ARTIFACT_BYTE_CAP)
69
+ return null;
70
+ return node_fs_1.default.readFileSync(abs, 'utf-8');
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ function consultedText(entry, hasSummary) {
77
+ if (!hasSummary)
78
+ return 'no summary';
79
+ if (entry === undefined)
80
+ return 'unfilled';
81
+ if (entry.status === 'read')
82
+ return vaultStandardsLeaf.STANDARDS_CONSULTED_READ;
83
+ if (entry.status === 'not-read')
84
+ return `${vaultStandardsLeaf.STANDARDS_CONSULTED_NOT_READ}${entry.reason ?? ''}`.trim();
85
+ return 'unfilled';
86
+ }
87
+ /**
88
+ * Derive the review's facts for one phase. Never throws: every fault is a
89
+ * status or a gap. Inert (with a statement) when the project has no
90
+ * `.platform-bindings.json` or the vault is off.
91
+ */
92
+ function deriveStandardsReview(root, cwd, positional, opts = {}) {
93
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
94
+ const vault = require('./vault.cjs');
95
+ if (!vault.hasPlatformBindings(root) || !vault.isVaultGateActive(cwd)) {
96
+ return {
97
+ status: 'inert',
98
+ statement: 'The client vault is off for this project, so there are no standards to review against.',
99
+ };
100
+ }
101
+ if (positional === undefined || positional === '') {
102
+ return {
103
+ status: 'no-argument',
104
+ statement: 'No phase was given. Re-run with the phase directory or the phase number.',
105
+ };
106
+ }
107
+ if (positional.startsWith('--')) {
108
+ return { status: 'usage', statement: 'The phase must come first: check vault-review <phase-dir|phase-number> [--raw].' };
109
+ }
110
+ const phase = vault.resolveTracePhaseDir(root, cwd, positional);
111
+ if (!phase) {
112
+ return {
113
+ status: 'unresolved',
114
+ statement: 'That argument is not a phase directory inside the project or a phase number the locator resolves.',
115
+ };
116
+ }
117
+ const exec = opts._exec ?? shell_command_projection_cjs_1.execGitLocal;
118
+ const gaps = [];
119
+ let names = [];
120
+ try {
121
+ names = node_fs_1.default.readdirSync(phase.abs).sort();
122
+ }
123
+ catch {
124
+ gaps.push(`the phase directory ${phase.rel} could not be listed`);
125
+ }
126
+ const summaries = new Map();
127
+ for (const name of names.filter((n) => SUMMARY_RE.test(n))) {
128
+ const text = readBounded(node_path_1.default.join(phase.abs, name));
129
+ if (text === null) {
130
+ gaps.push(`${name} could not be read (missing, unreadable or over the size cap)`);
131
+ continue;
132
+ }
133
+ const section = vaultStandardsLeaf.parseConsultedSection(text, { artifact: name });
134
+ if (section.present && section.planId !== null && !summaries.has(section.planId)) {
135
+ summaries.set(section.planId, { name, section, text });
136
+ }
137
+ }
138
+ const cacheDir = vault.resolveVaultCacheDir(cwd).dir;
139
+ const isAncestor = vaultSeal.sealIsAncestorVia(shell_command_projection_cjs_1.execGitLocal, root);
140
+ const plans = [];
141
+ const documents = new Map();
142
+ for (const name of names.filter((n) => PLAN_RE.test(n))) {
143
+ const text = readBounded(node_path_1.default.join(phase.abs, name));
144
+ if (text === null) {
145
+ gaps.push(`${name} could not be read (missing, unreadable or over the size cap)`);
146
+ continue;
147
+ }
148
+ const parsed = vaultStandardsLeaf.parseStandardsBlock(text, { artifact: name });
149
+ const record = parsed.record;
150
+ if (!parsed.present || record === null) {
151
+ gaps.push(`${name} carries no readable <vault_standards> block, so the documents that governed it are unknown`);
152
+ continue;
153
+ }
154
+ const summary = summaries.get(record.planId);
155
+ let range = null;
156
+ let files = [];
157
+ let filesTruncated = false;
158
+ if (summary !== undefined) {
159
+ const anchors = vaultSeal.resolveRecordAnchors(summary.text, vaultSeal.RECORDED_ARTIFACT_KIND.STANDARDS_CONSULTED, phase.abs, { isAncestor });
160
+ if (anchors.end !== undefined) {
161
+ range = { start: anchors.start ?? null, end: anchors.end };
162
+ if (anchors.start !== undefined) {
163
+ const diff = exec(['diff', '--name-only', '-z', '--no-renames', anchors.start, anchors.end], { cwd: root, timeout: REVIEW_GIT_TIMEOUT_MS });
164
+ if (diff.exitCode === 0) {
165
+ const all = diff.stdout.split('\0').filter((p) => p !== '' && !vaultSeal.isNonSourcePath(p));
166
+ filesTruncated = all.length > exports.REVIEW_FILES_CAP;
167
+ files = all.slice(0, exports.REVIEW_FILES_CAP);
168
+ }
169
+ else {
170
+ gaps.push(`the files changed by plan ${record.planId} could not be listed from its anchors`);
171
+ }
172
+ }
173
+ else {
174
+ gaps.push(`plan ${record.planId} records an end anchor but no start, so its changed files are unknown`);
175
+ }
176
+ }
177
+ else {
178
+ gaps.push(`plan ${record.planId}'s Standards Consulted section carries no anchor, so its commit range is unknown`);
179
+ }
180
+ }
181
+ else {
182
+ gaps.push(`plan ${record.planId} has no SUMMARY with a Standards Consulted section`);
183
+ }
184
+ plans.push({
185
+ planId: record.planId,
186
+ plan: `${phase.rel}/${name}`,
187
+ summary: summary === undefined ? null : `${phase.rel}/${summary.name}`,
188
+ range,
189
+ files,
190
+ filesTruncated,
191
+ degraded: record.degraded === null ? null : `${record.degraded.kind}: ${record.degraded.reason}`,
192
+ });
193
+ for (const doc of record.documents) {
194
+ let entry = documents.get(doc.path);
195
+ if (entry === undefined) {
196
+ const abs = node_path_1.default.join(cacheDir, ...doc.path.split('/'));
197
+ entry = { path: doc.path, cachePath: node_fs_1.default.existsSync(abs) ? abs : null, governed: [] };
198
+ if (entry.cachePath === null)
199
+ gaps.push(`${vaultIndexLeaf.displayVaultPath(doc.path)} is not in the vault cache; fetch the vault to judge it`);
200
+ documents.set(doc.path, entry);
201
+ }
202
+ entry.governed.push({
203
+ planId: record.planId,
204
+ reason: doc.reason,
205
+ consulted: consultedText(summary?.section.entries.find((e) => e.path === doc.path), summary !== undefined),
206
+ });
207
+ }
208
+ }
209
+ if (plans.length === 0 || documents.size === 0) {
210
+ return {
211
+ status: 'no-records',
212
+ statement: plans.length === 0
213
+ ? `No plan in ${phase.rel} carries a standards record, so there is nothing to review.`
214
+ : `No plan in ${phase.rel} was governed by a standards document, so there is nothing to review.`,
215
+ phaseDir: phase.rel,
216
+ plans,
217
+ documents: [],
218
+ gaps,
219
+ };
220
+ }
221
+ return { status: 'ok', phaseDir: phase.rel, plans, documents: [...documents.values()], gaps };
222
+ }
223
+ /** The counts line, in verdict order: `p PASS · f FAIL · n N-A · i INCONCLUSIVE (of N documents)`. */
224
+ function formatReviewCounts(counts) {
225
+ const total = exports.REVIEW_VERDICTS.reduce((n, v) => n + counts[v], 0);
226
+ return `${exports.REVIEW_VERDICTS.map((v) => `${counts[v]} ${v}`).join(' · ')} (of ${total} document${total === 1 ? '' : 's'})`;
227
+ }
228
+ /**
229
+ * Check a review artifact against the derivation. The verdict table is the
230
+ * first markdown table under `## Standard Verdicts`; its first column holds the
231
+ * document path in backticks, the second the verdict, the third the reason
232
+ * or evidence. Never throws; an empty list means the artifact is well formed.
233
+ */
234
+ function validateReviewArtifact(text, documents) {
235
+ const problems = [];
236
+ if (!text.includes(exports.REVIEW_ARTIFACT_MARKER)) {
237
+ problems.push({ kind: 'marker_missing', detail: `the frontmatter does not carry "${exports.REVIEW_ARTIFACT_MARKER}"` });
238
+ }
239
+ const lines = text.split(/\r?\n/);
240
+ const at = lines.findIndex((l) => l.trim() === exports.REVIEW_VERDICTS_HEADING);
241
+ const rows = [];
242
+ if (at === -1) {
243
+ problems.push({ kind: 'table_missing', detail: `there is no "${exports.REVIEW_VERDICTS_HEADING}" section` });
244
+ }
245
+ else {
246
+ for (let i = at + 1; i < lines.length && !lines[i].startsWith('## '); i += 1) {
247
+ const line = lines[i].trim();
248
+ if (!line.startsWith('|'))
249
+ continue;
250
+ const cells = line.slice(1, line.endsWith('|') ? -1 : undefined).split('|').map((c) => c.trim());
251
+ const m = /^`([^`]+)`$/.exec(cells[0] ?? '');
252
+ if (!m)
253
+ continue;
254
+ rows.push({ path: m[1], verdict: cells[1] ?? '', reason: cells[2] ?? '' });
255
+ }
256
+ if (rows.length === 0)
257
+ problems.push({ kind: 'table_missing', detail: 'the verdict table has no document rows' });
258
+ }
259
+ const known = new Set(documents.map((d) => d.path));
260
+ const seen = new Map();
261
+ for (const row of rows) {
262
+ seen.set(row.path, (seen.get(row.path) ?? 0) + 1);
263
+ if (!known.has(row.path))
264
+ problems.push({ kind: 'document_unknown', detail: `${row.path} did not govern this phase` });
265
+ if (!exports.REVIEW_VERDICTS.includes(row.verdict)) {
266
+ problems.push({ kind: 'verdict_invalid', detail: `${row.path} has verdict "${row.verdict}", not one of ${exports.REVIEW_VERDICTS.join(', ')}` });
267
+ }
268
+ else if (row.verdict !== 'PASS' && (row.reason === '' || row.reason === '-')) {
269
+ problems.push({ kind: 'reason_missing', detail: `${row.path} is ${row.verdict} without a stated reason` });
270
+ }
271
+ }
272
+ for (const doc of documents) {
273
+ const n = seen.get(doc.path) ?? 0;
274
+ if (n === 0)
275
+ problems.push({ kind: 'document_missing', detail: `${doc.path} has no verdict` });
276
+ if (n > 1)
277
+ problems.push({ kind: 'document_duplicated', detail: `${doc.path} has ${n} verdict rows` });
278
+ }
279
+ const counts = { PASS: 0, FAIL: 0, 'N-A': 0, INCONCLUSIVE: 0 };
280
+ for (const row of rows) {
281
+ if (exports.REVIEW_VERDICTS.includes(row.verdict))
282
+ counts[row.verdict] += 1;
283
+ }
284
+ const expected = formatReviewCounts(counts);
285
+ const countsLine = lines.find((l) => /\bPASS · /.test(l));
286
+ if (countsLine === undefined) {
287
+ problems.push({ kind: 'counts_missing', detail: `there is no counts line; it should read "${expected}"` });
288
+ }
289
+ else if (!countsLine.includes(expected)) {
290
+ problems.push({ kind: 'counts_mismatch', detail: `the counts line does not match the table; it should read "${expected}"` });
291
+ }
292
+ return problems;
293
+ }
@@ -10,7 +10,7 @@
10
10
  * `pp-core/references/planner-vault-standards.md` to this grammar in both
11
11
  * directions (D-10).
12
12
  *
13
- * In standards mode a plan records which INDEX-listed standards documents
13
+ * A plan records which INDEX-listed standards documents
14
14
  * governed it, in ONE body block the CLI emits (`vault standards block`), and
15
15
  * the plan's SUMMARY records which of them the executor read (`## Standards
16
16
  * Consulted`, rendered by `vault standards consulted`). This module is the ONE
@@ -33,8 +33,8 @@
33
33
  * (15-07) and the check router (15-08) both require this leaf, and a leaf whose
34
34
  * dependencies are themselves import-free at load time cannot close a require
35
35
  * cycle among them (the seal leaf requires this one back LAZILY, inside its
36
- * functions). The entry head split, the display
37
- * sanitiser and the fence markers are reused from there, never re-spelled
36
+ * functions). The entry head split and the display
37
+ * sanitiser are reused from there, never re-spelled
38
38
  * (Phase 14 D-06/D-17/D-32): the grammar for a `- \`path\` — …` line is one
39
39
  * grammar across INDEX.md, discovery's vault context and this record.
40
40
  *
@@ -45,8 +45,7 @@
45
45
  *
46
46
  * Message discipline (T-15-20, D-14): every text echoed from a record passes
47
47
  * `sanitiseVaultText`, and every path passes `displayVaultPath`, so a record
48
- * cannot smuggle terminal control bytes, bidi overrides or a live fence marker
49
- * into a gate message. The leaf never knows WHY a path is not a resolved INDEX
48
+ * cannot smuggle terminal control bytes or bidi overrides into a gate message. The leaf never knows WHY a path is not a resolved INDEX
50
49
  * entry (withheld, unsafe, unresolved and absent look the same to it), so no
51
50
  * detail can confirm that a refused path names a credential. Every detail ends
52
51
  * with a repair remedy and never names a destructive verb.
@@ -74,7 +73,7 @@ exports.judgeConsultedSummary = judgeConsultedSummary;
74
73
  exports.compareConsulted = compareConsulted;
75
74
  // eslint-disable-next-line @typescript-eslint/no-require-imports
76
75
  const vaultIndexLeaf = require("./vault-index.cjs");
77
- const { splitVaultEntryLine, displayVaultPath, sanitiseVaultText, DISPLAY_UNSAFE_TEXT_CHAR_RE, EXTERNAL_CONTENT_FENCE_START, EXTERNAL_CONTENT_FENCE_END, VAULT_ENTRY_PREFIX, VAULT_ENTRY_SEPARATOR, } = vaultIndexLeaf;
76
+ const { splitVaultEntryLine, displayVaultPath, sanitiseVaultText, DISPLAY_UNSAFE_TEXT_CHAR_RE, VAULT_ENTRY_PREFIX, VAULT_ENTRY_SEPARATOR, } = vaultIndexLeaf;
78
77
  // 16-06 (D-35): the anchor grammar has ONE owner, the seal leaf. Static and
79
78
  // cycle-free: its top level imports only Node built-ins (D-41).
80
79
  const vault_seal_cjs_1 = require("./vault-seal.cjs");
@@ -183,7 +182,7 @@ exports.STANDARDS_SHA_RE = /^[0-9a-f]{40}$/;
183
182
  */
184
183
  exports.STANDARDS_ANCHOR_PREFIX = '- anchors: ';
185
184
  /**
186
- * The plan-identifier form a record (and the practices scaffold) must carry,
185
+ * The plan-identifier form a record must carry,
187
186
  * one of two alternatives:
188
187
  *
189
188
  * - a PHASE plan id: one or more digits with an optional letter, optional
@@ -238,8 +237,8 @@ exports.STANDARDS_GRAMMAR = Object.freeze({
238
237
  });
239
238
  /**
240
239
  * The frozen standards problem kinds (D-17). Every value is `standards_` plus
241
- * the lower-snake key, so none can collide with a practices trace kind or a
242
- * trace handler kind (the leaf test asserts disjointness against both tables).
240
+ * the lower-snake key, so none can collide with a trace handler kind (the leaf
241
+ * test asserts disjointness).
243
242
  * The two pairing kinds are produced by the HANDLER (15-08); this leaf owns
244
243
  * their texts only.
245
244
  */
@@ -272,8 +271,8 @@ const TILDE_FENCE_RE = /~{3,}/;
272
271
  /**
273
272
  * Judge one reason clause: non-empty after trim; at most
274
273
  * `STANDARDS_REASON_MAX` code points (not UTF-16 units); no newline (CR, LF,
275
- * NEL, U+2028, U+2029); no backtick at all; no fence marker (either
276
- * external-content marker, or a run of three tildes); no block tag; nothing
274
+ * NEL, U+2028, U+2029); no backtick at all; no code fence (a run of three
275
+ * tildes); no block tag; nothing
277
276
  * the display sanitiser would rewrite (15-REVIEW WR-03: a control character
278
277
  * such as ESC or BEL, a bidi override, a line or paragraph separator), judged
279
278
  * by asking `sanitiseVaultText` itself, so the refused set has one owner. Runs
@@ -297,7 +296,7 @@ function validateReasonClause(text) {
297
296
  return { ok: false, why: 'too_long' };
298
297
  if (text.includes('`'))
299
298
  return { ok: false, why: 'backtick' };
300
- if (text.includes(EXTERNAL_CONTENT_FENCE_START) || text.includes(EXTERNAL_CONTENT_FENCE_END) || TILDE_FENCE_RE.test(text)) {
299
+ if (TILDE_FENCE_RE.test(text)) {
301
300
  return { ok: false, why: 'fence_marker' };
302
301
  }
303
302
  if (text.includes(exports.VAULT_STANDARDS_TAG_OPEN) || text.includes(exports.VAULT_STANDARDS_TAG_CLOSE)) {
@@ -431,7 +430,7 @@ function standardsProblemDetail(kind, ctx = {}) {
431
430
  const path = echoPath(c.path);
432
431
  switch (kind) {
433
432
  case exports.STANDARDS_PROBLEM_KIND.BLOCK_ABSENT:
434
- return composeDetail(`${subject} carries no ${exports.VAULT_STANDARDS_TAG_OPEN} block at column 0 outside code fences, comments and frontmatter; every standards-mode plan carries one, including a plan that writes no code.`, blockRemedy(c.planId));
433
+ return composeDetail(`${subject} carries no ${exports.VAULT_STANDARDS_TAG_OPEN} block at column 0 outside code fences, comments and frontmatter; every plan carries one while the vault is on, including a plan that writes no code.`, blockRemedy(c.planId));
435
434
  case exports.STANDARDS_PROBLEM_KIND.BLOCK_DUPLICATE:
436
435
  return composeDetail(`${subject} carries ${typeof c.count === 'number' ? c.count : 'more than one'} ${exports.VAULT_STANDARDS_TAG_OPEN} blocks; the gate reads exactly one. Replace them with a single regenerated block.`, blockRemedy(c.planId));
437
436
  case exports.STANDARDS_PROBLEM_KIND.BLOCK_UNTERMINATED:
@@ -731,7 +730,7 @@ function splitLines(text) {
731
730
  /**
732
731
  * The index of the first body line: past a YAML frontmatter that opens on
733
732
  * line 1 with `---` and closes with a later `---` line. An unclosed
734
- * frontmatter is not a frontmatter (the practices parser's rule).
733
+ * frontmatter is not a frontmatter.
735
734
  */
736
735
  function bodyStart(lines) {
737
736
  if (lines[0] !== '---')
@@ -786,8 +785,8 @@ function liveLineIndexes(lines, from, stop) {
786
785
  * a whole column-0 line outside the frontmatter, fenced code and HTML comments;
787
786
  * the section runs to the next ATX heading, the next thematic break or the end
788
787
  * of the text. This is the rule `parseConsultedSection` applies, and it is
789
- * exported so `src/vault-seal.cts` (the `## Practices Applied` presence test and
790
- * the door-record parser) asks this scanner instead of keeping a second one.
788
+ * exported so `src/vault-seal.cts` (the door-record parser) and the vault
789
+ * router ask this scanner instead of keeping a second one.
791
790
  * Never throws.
792
791
  */
793
792
  function findLiveSection(text, heading) {