@pareto4/pp-core 1.13.0 → 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 (204) hide show
  1. package/.claude-plugin/plugin.json +3 -3
  2. package/README.md +31 -12
  3. package/agents/pp-code-fixer.md +0 -11
  4. package/agents/pp-code-reviewer.md +1 -77
  5. package/agents/pp-debug-session-manager.md +9 -6
  6. package/agents/pp-executor.md +12 -29
  7. package/agents/pp-plan-checker.md +4 -4
  8. package/agents/pp-planner.md +34 -20
  9. package/agents/pp-vault-reviewer.md +100 -23
  10. package/agents/pp-verifier.md +1 -3
  11. package/bin/install.js +10 -3
  12. package/commands/pp-execute-phase.md +1 -3
  13. package/commands/pp-ns-manage.md +1 -1
  14. package/commands/pp-ns-review.md +1 -1
  15. package/commands/pp-plan-phase.md +1 -2
  16. package/commands/pp-vault-promote.md +7 -8
  17. package/commands/pp-vault-review.md +6 -5
  18. package/commands/pp-verify-work.md +1 -2
  19. package/gemini-extension.json +1 -1
  20. package/hooks/dist/pp-check-update.js +0 -0
  21. package/hooks/dist/pp-cursor-session-start.js +1 -1
  22. package/hooks/dist/pp-statusline.js +1 -1
  23. package/hooks/dist/pp-update-banner.js +0 -0
  24. package/hooks/pp-check-update.js +0 -0
  25. package/hooks/pp-cursor-session-start.js +1 -1
  26. package/hooks/pp-statusline.js +1 -1
  27. package/hooks/pp-update-banner.js +0 -0
  28. package/package.json +10 -18
  29. package/pareto-skills/_shared/create-project-board.sh +0 -0
  30. package/pareto-skills/pp-project-setup/SKILL.md +1 -1
  31. package/pp-core/bin/check-latest-version.cjs +0 -0
  32. package/pp-core/bin/detect-build-commands.sh +2 -2
  33. package/pp-core/bin/lib/capability-activation.cjs +83 -9
  34. package/pp-core/bin/lib/capability-registry.cjs +201 -229
  35. package/pp-core/bin/lib/capability-state.cjs +93 -1
  36. package/pp-core/bin/lib/capability-writer.cjs +3 -3
  37. package/pp-core/bin/lib/check-command-router.cjs +1906 -1514
  38. package/pp-core/bin/lib/clusters.cjs +3 -0
  39. package/pp-core/bin/lib/command-aliases.cjs +0 -35
  40. package/pp-core/bin/lib/commands.cjs +9 -4
  41. package/pp-core/bin/lib/compose-bindings.cjs +251 -88
  42. package/pp-core/bin/lib/config-loader.cjs +7 -103
  43. package/pp-core/bin/lib/config-schema.cjs +1 -1
  44. package/pp-core/bin/lib/config.cjs +151 -62
  45. package/pp-core/bin/lib/decisions.cjs +4 -4
  46. package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
  47. package/pp-core/bin/lib/frontmatter.cjs +1 -38
  48. package/pp-core/bin/lib/git-base-branch.cjs +11 -12
  49. package/pp-core/bin/lib/git-provider.cjs +9 -10
  50. package/pp-core/bin/lib/init-command-router.cjs +5 -8
  51. package/pp-core/bin/lib/init-contract.cjs +620 -738
  52. package/pp-core/bin/lib/init-interactive.cjs +62 -4
  53. package/pp-core/bin/lib/init.cjs +24 -51
  54. package/pp-core/bin/lib/install-profiles.cjs +1 -1
  55. package/pp-core/bin/lib/io.cjs +4 -2
  56. package/pp-core/bin/lib/loop-resolver.cjs +165 -129
  57. package/pp-core/bin/lib/org-profile.cjs +402 -161
  58. package/pp-core/bin/lib/package-identity.cjs +3 -3
  59. package/pp-core/bin/lib/phase-command-router.cjs +1 -29
  60. package/pp-core/bin/lib/phase-scope.cjs +1 -1
  61. package/pp-core/bin/lib/phase.cjs +11 -253
  62. package/pp-core/bin/lib/platform-bindings.cjs +40 -19
  63. package/pp-core/bin/lib/profile-output.cjs +1 -1
  64. package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
  65. package/pp-core/bin/lib/state.cjs +11 -85
  66. package/pp-core/bin/lib/template.cjs +1 -1
  67. package/pp-core/bin/lib/vault-command-router.cjs +1682 -477
  68. package/pp-core/bin/lib/vault-coordinates.cjs +16 -7
  69. package/pp-core/bin/lib/vault-index.cjs +656 -0
  70. package/pp-core/bin/lib/vault-seal.cjs +1851 -0
  71. package/pp-core/bin/lib/vault-standards-promote.cjs +577 -0
  72. package/pp-core/bin/lib/vault-standards-review.cjs +293 -0
  73. package/pp-core/bin/lib/vault-standards.cjs +1504 -0
  74. package/pp-core/bin/lib/vault-trace.cjs +155 -2778
  75. package/pp-core/bin/lib/vault-update.cjs +10 -59
  76. package/pp-core/bin/lib/vault.cjs +2570 -950
  77. package/pp-core/bin/lib/verify.cjs +0 -11
  78. package/pp-core/bin/portable-timeout.sh +1 -1
  79. package/pp-core/bin/pp-tools.cjs +157 -54
  80. package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
  81. package/pp-core/bin/shared/config-schema.manifest.json +0 -3
  82. package/pp-core/bin/shared/model-catalog.json +2 -2
  83. package/pp-core/bin/verify-reapply-patches.cjs +0 -0
  84. package/pp-core/org-profile.json +26 -0
  85. package/pp-core/references/autonomous-best-effort.md +8 -7
  86. package/pp-core/references/loop-hook-dispatch.md +0 -25
  87. package/pp-core/references/planner-gap-closure.md +0 -1
  88. package/pp-core/references/planner-tdd.md +0 -1
  89. package/pp-core/references/planner-vault-standards.md +311 -0
  90. package/pp-core/references/planning-config.md +0 -8
  91. package/pp-core/references/tdd.md +0 -1
  92. package/pp-core/templates/README.md +1 -0
  93. package/pp-core/templates/VAULT-REVIEW.md +56 -0
  94. package/pp-core/templates/phase-prompt.md +2 -6
  95. package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
  96. package/pp-core/workflows/add-tests.md +55 -4
  97. package/pp-core/workflows/audit-fix.md +63 -2
  98. package/pp-core/workflows/audit-milestone.md +1 -1
  99. package/pp-core/workflows/autonomous.md +85 -11
  100. package/pp-core/workflows/check-todos.md +13 -2
  101. package/pp-core/workflows/code-review-fix.md +59 -3
  102. package/pp-core/workflows/code-review.md +10 -57
  103. package/pp-core/workflows/complete-milestone.md +1 -1
  104. package/pp-core/workflows/debug.md +54 -4
  105. package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
  106. package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
  107. package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
  108. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
  109. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
  110. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
  111. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
  112. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
  113. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
  114. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
  115. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
  116. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
  117. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
  118. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
  119. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
  120. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
  121. package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
  122. package/pp-core/workflows/discovery-sprint/vault-context.md +207 -0
  123. package/pp-core/workflows/discovery-sprint.md +7 -3
  124. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
  125. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
  126. package/pp-core/workflows/discovery-to-build.md +10 -2
  127. package/pp-core/workflows/discuss-phase.md +3 -10
  128. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +327 -0
  129. package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
  130. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
  131. package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +78 -0
  132. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
  133. package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
  134. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
  135. package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +219 -20
  136. package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +147 -0
  137. package/pp-core/workflows/execute-phase.md +152 -128
  138. package/pp-core/workflows/execute-plan.md +54 -6
  139. package/pp-core/workflows/fast.md +53 -4
  140. package/pp-core/workflows/help/modes/full.md +3 -3
  141. package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
  142. package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
  143. package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
  144. package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
  145. package/pp-core/workflows/init.md +232 -272
  146. package/pp-core/workflows/new-project.md +31 -23
  147. package/pp-core/workflows/plan-phase.md +98 -45
  148. package/pp-core/workflows/quick.md +117 -3
  149. package/pp-core/workflows/secure-phase.md +1 -1
  150. package/pp-core/workflows/ship.md +57 -28
  151. package/pp-core/workflows/undo.md +1 -1
  152. package/pp-core/workflows/update.md +3 -3
  153. package/pp-core/workflows/validate-phase.md +58 -3
  154. package/pp-core/workflows/vault-review.md +161 -23
  155. package/pp-core/workflows/vault-write-back.md +73 -110
  156. package/pp-core/workflows/verify-work.md +12 -14
  157. package/scripts/changeset/cli.cjs +6 -5
  158. package/scripts/changeset/lint.cjs +0 -0
  159. package/scripts/changeset/new.cjs +33 -50
  160. package/scripts/dev-env.cjs +25 -85
  161. package/scripts/gen-capability-registry.cjs +120 -13
  162. package/scripts/lib/allowlist-ratchet.cjs +1 -103
  163. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
  164. package/scripts/lint-docs-required.cjs +0 -0
  165. package/scripts/lint-pr-check-project-dir.cjs +1 -3
  166. package/scripts/lint-test-file-count.allowlist.json +3 -19
  167. package/scripts/lint-test-file-count.cjs +5 -6
  168. package/scripts/prompt-injection-scan.sh +19 -17
  169. package/scripts/release-channel.cjs +0 -0
  170. package/scripts/sync-manifest-versions.cjs +5 -4
  171. package/scripts/sync-rulesets.sh +1 -1
  172. package/scripts/update-size-baseline.cjs +30 -274
  173. package/scripts/verify-opengsd-sweep.cjs +3 -5
  174. package/scripts/workflow-size.cjs +9 -61
  175. package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
  176. package/pp-core/bin/lib/install-run.cjs +0 -536
  177. package/pp-core/bin/lib/loop-gates.cjs +0 -213
  178. package/pp-core/bin/lib/phase-manifest.cjs +0 -527
  179. package/pp-core/bin/lib/phase-tier.cjs +0 -480
  180. package/pp-core/bin/lib/plan-graph.cjs +0 -368
  181. package/pp-core/bin/lib/practices-review-check.cjs +0 -938
  182. package/pp-core/bin/lib/vault-audit.cjs +0 -2070
  183. package/pp-core/bin/lib/vault-practices.cjs +0 -1990
  184. package/pp-core/bin/lib/vault-review.cjs +0 -2359
  185. package/pp-core/bin/lib/vault-write-back.cjs +0 -877
  186. package/pp-core/references/executor-vault-trace.md +0 -47
  187. package/pp-core/references/planner-plan-graph.md +0 -66
  188. package/pp-core/references/planner-vault-practices.md +0 -112
  189. package/pp-core/templates/practices/README.md +0 -251
  190. package/pp-core/templates/practices/error-handling.md +0 -18
  191. package/pp-core/templates/practices/minimal-comments.md +0 -14
  192. package/pp-core/templates/practices/naming-convention.md +0 -18
  193. package/pp-core/templates/practices/practice-template.md +0 -23
  194. package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
  195. package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
  196. package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
  197. package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
  198. package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
  199. package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
  200. package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
  201. package/scripts/lib/size-budget.cjs +0 -311
  202. package/scripts/lint-neutrality-names.cjs +0 -694
  203. package/scripts/lint-org-coordinates.allowlist.json +0 -35
  204. package/scripts/lint-org-coordinates.cjs +0 -426
@@ -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
+ }