@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.
- package/.claude-plugin/plugin.json +3 -3
- package/README.md +31 -12
- package/agents/pp-code-fixer.md +0 -11
- package/agents/pp-code-reviewer.md +1 -77
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +12 -29
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +34 -20
- package/agents/pp-vault-reviewer.md +100 -23
- package/agents/pp-verifier.md +1 -3
- package/bin/install.js +10 -3
- package/commands/pp-execute-phase.md +1 -3
- package/commands/pp-ns-manage.md +1 -1
- package/commands/pp-ns-review.md +1 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-promote.md +7 -8
- package/commands/pp-vault-review.md +6 -5
- package/commands/pp-verify-work.md +1 -2
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-check-update.js +0 -0
- package/hooks/dist/pp-cursor-session-start.js +1 -1
- package/hooks/dist/pp-statusline.js +1 -1
- package/hooks/dist/pp-update-banner.js +0 -0
- package/hooks/pp-check-update.js +0 -0
- package/hooks/pp-cursor-session-start.js +1 -1
- package/hooks/pp-statusline.js +1 -1
- package/hooks/pp-update-banner.js +0 -0
- package/package.json +10 -18
- package/pareto-skills/_shared/create-project-board.sh +0 -0
- package/pareto-skills/pp-project-setup/SKILL.md +1 -1
- package/pp-core/bin/check-latest-version.cjs +0 -0
- package/pp-core/bin/detect-build-commands.sh +2 -2
- package/pp-core/bin/lib/capability-activation.cjs +83 -9
- package/pp-core/bin/lib/capability-registry.cjs +201 -229
- package/pp-core/bin/lib/capability-state.cjs +93 -1
- package/pp-core/bin/lib/capability-writer.cjs +3 -3
- package/pp-core/bin/lib/check-command-router.cjs +1906 -1514
- package/pp-core/bin/lib/clusters.cjs +3 -0
- package/pp-core/bin/lib/command-aliases.cjs +0 -35
- package/pp-core/bin/lib/commands.cjs +9 -4
- package/pp-core/bin/lib/compose-bindings.cjs +251 -88
- package/pp-core/bin/lib/config-loader.cjs +7 -103
- package/pp-core/bin/lib/config-schema.cjs +1 -1
- package/pp-core/bin/lib/config.cjs +151 -62
- package/pp-core/bin/lib/decisions.cjs +4 -4
- package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
- package/pp-core/bin/lib/frontmatter.cjs +1 -38
- package/pp-core/bin/lib/git-base-branch.cjs +11 -12
- package/pp-core/bin/lib/git-provider.cjs +9 -10
- package/pp-core/bin/lib/init-command-router.cjs +5 -8
- package/pp-core/bin/lib/init-contract.cjs +620 -738
- package/pp-core/bin/lib/init-interactive.cjs +62 -4
- package/pp-core/bin/lib/init.cjs +24 -51
- package/pp-core/bin/lib/install-profiles.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +4 -2
- package/pp-core/bin/lib/loop-resolver.cjs +165 -129
- package/pp-core/bin/lib/org-profile.cjs +402 -161
- package/pp-core/bin/lib/package-identity.cjs +3 -3
- package/pp-core/bin/lib/phase-command-router.cjs +1 -29
- package/pp-core/bin/lib/phase-scope.cjs +1 -1
- package/pp-core/bin/lib/phase.cjs +11 -253
- package/pp-core/bin/lib/platform-bindings.cjs +40 -19
- package/pp-core/bin/lib/profile-output.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
- package/pp-core/bin/lib/state.cjs +11 -85
- package/pp-core/bin/lib/template.cjs +1 -1
- package/pp-core/bin/lib/vault-command-router.cjs +1682 -477
- package/pp-core/bin/lib/vault-coordinates.cjs +16 -7
- package/pp-core/bin/lib/vault-index.cjs +656 -0
- package/pp-core/bin/lib/vault-seal.cjs +1851 -0
- package/pp-core/bin/lib/vault-standards-promote.cjs +577 -0
- package/pp-core/bin/lib/vault-standards-review.cjs +293 -0
- package/pp-core/bin/lib/vault-standards.cjs +1504 -0
- package/pp-core/bin/lib/vault-trace.cjs +155 -2778
- package/pp-core/bin/lib/vault-update.cjs +10 -59
- package/pp-core/bin/lib/vault.cjs +2570 -950
- package/pp-core/bin/lib/verify.cjs +0 -11
- package/pp-core/bin/portable-timeout.sh +1 -1
- package/pp-core/bin/pp-tools.cjs +157 -54
- package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
- package/pp-core/bin/shared/config-schema.manifest.json +0 -3
- package/pp-core/bin/shared/model-catalog.json +2 -2
- package/pp-core/bin/verify-reapply-patches.cjs +0 -0
- package/pp-core/org-profile.json +26 -0
- package/pp-core/references/autonomous-best-effort.md +8 -7
- package/pp-core/references/loop-hook-dispatch.md +0 -25
- package/pp-core/references/planner-gap-closure.md +0 -1
- package/pp-core/references/planner-tdd.md +0 -1
- package/pp-core/references/planner-vault-standards.md +311 -0
- package/pp-core/references/planning-config.md +0 -8
- package/pp-core/references/tdd.md +0 -1
- package/pp-core/templates/README.md +1 -0
- package/pp-core/templates/VAULT-REVIEW.md +56 -0
- package/pp-core/templates/phase-prompt.md +2 -6
- package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
- package/pp-core/workflows/add-tests.md +55 -4
- package/pp-core/workflows/audit-fix.md +63 -2
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/autonomous.md +85 -11
- package/pp-core/workflows/check-todos.md +13 -2
- package/pp-core/workflows/code-review-fix.md +59 -3
- package/pp-core/workflows/code-review.md +10 -57
- package/pp-core/workflows/complete-milestone.md +1 -1
- package/pp-core/workflows/debug.md +54 -4
- package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
- package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
- package/pp-core/workflows/discovery-sprint/vault-context.md +207 -0
- package/pp-core/workflows/discovery-sprint.md +7 -3
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
- package/pp-core/workflows/discovery-to-build.md +10 -2
- package/pp-core/workflows/discuss-phase.md +3 -10
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +327 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
- package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +78 -0
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +219 -20
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +147 -0
- package/pp-core/workflows/execute-phase.md +152 -128
- package/pp-core/workflows/execute-plan.md +54 -6
- package/pp-core/workflows/fast.md +53 -4
- package/pp-core/workflows/help/modes/full.md +3 -3
- package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
- package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
- package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
- package/pp-core/workflows/init.md +232 -272
- package/pp-core/workflows/new-project.md +31 -23
- package/pp-core/workflows/plan-phase.md +98 -45
- package/pp-core/workflows/quick.md +117 -3
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +57 -28
- package/pp-core/workflows/undo.md +1 -1
- package/pp-core/workflows/update.md +3 -3
- package/pp-core/workflows/validate-phase.md +58 -3
- package/pp-core/workflows/vault-review.md +161 -23
- package/pp-core/workflows/vault-write-back.md +73 -110
- package/pp-core/workflows/verify-work.md +12 -14
- package/scripts/changeset/cli.cjs +6 -5
- package/scripts/changeset/lint.cjs +0 -0
- package/scripts/changeset/new.cjs +33 -50
- package/scripts/dev-env.cjs +25 -85
- package/scripts/gen-capability-registry.cjs +120 -13
- package/scripts/lib/allowlist-ratchet.cjs +1 -103
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
- package/scripts/lint-docs-required.cjs +0 -0
- package/scripts/lint-pr-check-project-dir.cjs +1 -3
- package/scripts/lint-test-file-count.allowlist.json +3 -19
- package/scripts/lint-test-file-count.cjs +5 -6
- package/scripts/prompt-injection-scan.sh +19 -17
- package/scripts/release-channel.cjs +0 -0
- package/scripts/sync-manifest-versions.cjs +5 -4
- package/scripts/sync-rulesets.sh +1 -1
- package/scripts/update-size-baseline.cjs +30 -274
- package/scripts/verify-opengsd-sweep.cjs +3 -5
- package/scripts/workflow-size.cjs +9 -61
- package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
- package/pp-core/bin/lib/install-run.cjs +0 -536
- package/pp-core/bin/lib/loop-gates.cjs +0 -213
- package/pp-core/bin/lib/phase-manifest.cjs +0 -527
- package/pp-core/bin/lib/phase-tier.cjs +0 -480
- package/pp-core/bin/lib/plan-graph.cjs +0 -368
- package/pp-core/bin/lib/practices-review-check.cjs +0 -938
- package/pp-core/bin/lib/vault-audit.cjs +0 -2070
- package/pp-core/bin/lib/vault-practices.cjs +0 -1990
- package/pp-core/bin/lib/vault-review.cjs +0 -2359
- package/pp-core/bin/lib/vault-write-back.cjs +0 -877
- package/pp-core/references/executor-vault-trace.md +0 -47
- package/pp-core/references/planner-plan-graph.md +0 -66
- package/pp-core/references/planner-vault-practices.md +0 -112
- package/pp-core/templates/practices/README.md +0 -251
- package/pp-core/templates/practices/error-handling.md +0 -18
- package/pp-core/templates/practices/minimal-comments.md +0 -14
- package/pp-core/templates/practices/naming-convention.md +0 -18
- package/pp-core/templates/practices/practice-template.md +0 -23
- package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
- package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
- package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
- package/scripts/lib/size-budget.cjs +0 -311
- package/scripts/lint-neutrality-names.cjs +0 -694
- package/scripts/lint-org-coordinates.allowlist.json +0 -35
- 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
|
+
}
|