@pareto4/pp-core 1.17.1 → 1.19.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 +1 -1
- package/README.md +5 -3
- package/agents/pp-code-fixer.md +9 -16
- package/agents/pp-code-reviewer.md +2 -78
- package/agents/pp-codebase-mapper.md +3 -0
- package/agents/pp-debugger.md +3 -2
- package/agents/pp-executor.md +25 -30
- package/agents/pp-integration-checker.md +1 -0
- package/agents/pp-phase-researcher.md +3 -1
- package/agents/pp-plan-checker.md +3 -0
- package/agents/pp-planner.md +16 -12
- package/agents/pp-research-synthesizer.md +1 -1
- package/agents/pp-ui-auditor.md +1 -0
- package/agents/pp-ui-researcher.md +3 -1
- package/agents/pp-vault-reviewer.md +43 -189
- package/agents/pp-verifier.md +10 -8
- package/commands/pp-add-tests.md +1 -1
- package/commands/pp-debug.md +1 -0
- package/commands/pp-init.md +4 -3
- package/commands/pp-ns-manage.md +1 -1
- package/commands/pp-ns-review.md +1 -1
- package/commands/pp-quick.md +3 -0
- package/commands/pp-review-backlog.md +2 -1
- package/commands/pp-vault-promote.md +7 -8
- package/commands/pp-vault-review.md +6 -7
- package/gemini-extension.json +1 -1
- package/package.json +3 -6
- package/pp-core/bin/lib/capability-registry.cjs +15 -357
- package/pp-core/bin/lib/capability-state.cjs +15 -79
- package/pp-core/bin/lib/capability-writer.cjs +2 -16
- package/pp-core/bin/lib/check-command-router.cjs +342 -2298
- package/pp-core/bin/lib/clusters.cjs +2 -2
- package/pp-core/bin/lib/commands.cjs +79 -4
- package/pp-core/bin/lib/compose-bindings.cjs +99 -16
- package/pp-core/bin/lib/config.cjs +3 -6
- package/pp-core/bin/lib/discovery-rollup.cjs +9 -6
- package/pp-core/bin/lib/discovery-screens.cjs +20 -2
- package/pp-core/bin/lib/discovery-sweep.cjs +12 -3
- package/pp-core/bin/lib/discovery-vault-check.cjs +37 -3
- package/pp-core/bin/lib/git-base-branch.cjs +10 -13
- package/pp-core/bin/lib/git-provider.cjs +5 -6
- package/pp-core/bin/lib/init-contract.cjs +55 -42
- package/pp-core/bin/lib/init-interactive.cjs +6 -20
- package/pp-core/bin/lib/io.cjs +2 -8
- package/pp-core/bin/lib/loop-resolver.cjs +8 -32
- package/pp-core/bin/lib/org-profile.cjs +16 -92
- package/pp-core/bin/lib/vault-command-router.cjs +174 -1206
- package/pp-core/bin/lib/vault-coordinates.cjs +4 -4
- package/pp-core/bin/lib/vault-index.cjs +44 -74
- package/pp-core/bin/lib/vault-seal.cjs +37 -122
- 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 +15 -16
- package/pp-core/bin/lib/vault-trace.cjs +40 -3016
- package/pp-core/bin/lib/vault-update.cjs +5 -26
- package/pp-core/bin/lib/vault.cjs +122 -316
- package/pp-core/bin/pp-tools.cjs +15 -36
- package/pp-core/org-profile.json +1 -1
- package/pp-core/references/autonomous-best-effort.md +4 -3
- package/pp-core/references/autonomous-lifecycle.md +1 -0
- package/pp-core/references/autonomous-smart-discuss.md +5 -1
- package/pp-core/references/discovery-id-conventions.md +35 -2
- package/pp-core/references/execute-mvp-tdd.md +3 -3
- package/pp-core/references/git-integration.md +57 -33
- package/pp-core/references/git-planning-commit.md +31 -9
- package/pp-core/references/phase-argument-parsing.md +1 -0
- package/pp-core/references/planner-gap-closure.md +1 -0
- package/pp-core/references/planner-revision.md +3 -1
- package/pp-core/references/planner-tdd.md +4 -4
- package/pp-core/references/planner-vault-standards.md +16 -25
- package/pp-core/references/planning-config.md +3 -3
- package/pp-core/references/shell-portability.md +28 -0
- package/pp-core/references/tdd.md +20 -16
- package/pp-core/references/worktree-path-safety.md +1 -0
- package/pp-core/templates/README.md +1 -1
- package/pp-core/templates/VAULT-REVIEW.md +24 -170
- package/pp-core/templates/milestone.md +5 -5
- package/pp-core/workflows/add-backlog.md +1 -1
- package/pp-core/workflows/add-tests.md +8 -5
- package/pp-core/workflows/add-todo.md +3 -1
- package/pp-core/workflows/ai-integration-phase.md +2 -1
- package/pp-core/workflows/audit-fix.md +7 -6
- package/pp-core/workflows/audit-milestone.md +1 -0
- package/pp-core/workflows/autonomous.md +6 -0
- package/pp-core/workflows/check-todos.md +2 -1
- package/pp-core/workflows/cleanup.md +3 -1
- package/pp-core/workflows/code-review-fix.md +15 -8
- package/pp-core/workflows/code-review.md +16 -58
- package/pp-core/workflows/complete-milestone.md +13 -3
- package/pp-core/workflows/debug.md +4 -2
- package/pp-core/workflows/diagnose-issues.md +1 -1
- package/pp-core/workflows/discovery-sprint/prototype.md +2 -2
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +1 -1
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +51 -7
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +5 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +10 -8
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +11 -2
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +5 -5
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +3 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +2 -2
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +14 -2
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +2 -2
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +10 -10
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +11 -8
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +21 -18
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +5 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +9 -3
- package/pp-core/workflows/discovery-sprint/vault-context.md +17 -12
- package/pp-core/workflows/discovery-sprint.md +11 -8
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +1 -0
- package/pp-core/workflows/discuss-phase-assumptions.md +5 -3
- package/pp-core/workflows/discuss-phase.md +8 -3
- package/pp-core/workflows/docs-update.md +1 -0
- package/pp-core/workflows/eval-review.md +2 -1
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +13 -14
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -0
- package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +5 -25
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -0
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +18 -6
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +6 -109
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +1 -17
- package/pp-core/workflows/execute-phase.md +21 -12
- package/pp-core/workflows/execute-plan.md +24 -15
- package/pp-core/workflows/explore.md +1 -1
- package/pp-core/workflows/forensics.md +4 -2
- package/pp-core/workflows/health.md +1 -0
- package/pp-core/workflows/help/modes/full.md +1 -1
- package/pp-core/workflows/import.md +2 -1
- package/pp-core/workflows/ingest-docs.md +1 -0
- package/pp-core/workflows/init/steps/client-context-capture.md +1 -0
- package/pp-core/workflows/init/steps/github-provisioning.md +145 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +18 -4
- package/pp-core/workflows/init/steps/reachability-outcome.md +3 -1
- package/pp-core/workflows/init.md +56 -66
- package/pp-core/workflows/map-codebase.md +5 -1
- package/pp-core/workflows/milestone-summary.md +2 -1
- package/pp-core/workflows/mvp-phase.md +1 -0
- package/pp-core/workflows/new-milestone.md +9 -5
- package/pp-core/workflows/new-project.md +9 -8
- package/pp-core/workflows/new-workspace.md +1 -0
- package/pp-core/workflows/next.md +2 -1
- package/pp-core/workflows/pause-work.md +6 -4
- package/pp-core/workflows/plan-milestone-gaps.md +3 -1
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +1 -1
- package/pp-core/workflows/plan-phase.md +21 -6
- package/pp-core/workflows/plan-review-convergence.md +2 -0
- package/pp-core/workflows/plant-seed.md +4 -2
- package/pp-core/workflows/pr-branch.md +2 -0
- package/pp-core/workflows/progress.md +5 -0
- package/pp-core/workflows/quick.md +19 -12
- package/pp-core/workflows/reapply-patches.md +3 -0
- package/pp-core/workflows/remove-phase.md +1 -1
- package/pp-core/workflows/resume-project.md +3 -0
- package/pp-core/workflows/review.md +7 -1
- package/pp-core/workflows/secure-phase.md +2 -1
- package/pp-core/workflows/session-report.md +1 -0
- package/pp-core/workflows/ship.md +8 -1
- package/pp-core/workflows/sketch-wrap-up.md +1 -1
- package/pp-core/workflows/sketch.md +3 -2
- package/pp-core/workflows/spec-phase.md +2 -1
- package/pp-core/workflows/spike-wrap-up.md +1 -1
- package/pp-core/workflows/spike.md +4 -3
- package/pp-core/workflows/sync-skills.md +5 -2
- package/pp-core/workflows/thread.md +3 -2
- package/pp-core/workflows/transition.md +6 -0
- package/pp-core/workflows/ui-phase.md +3 -1
- package/pp-core/workflows/ui-review.md +2 -1
- package/pp-core/workflows/ultraplan-phase.md +1 -0
- package/pp-core/workflows/undo.md +14 -10
- package/pp-core/workflows/update.md +5 -0
- package/pp-core/workflows/validate-phase.md +9 -6
- package/pp-core/workflows/vault-review.md +65 -186
- package/pp-core/workflows/vault-write-back.md +78 -110
- package/pp-core/workflows/verify-phase.md +5 -0
- package/pp-core/workflows/verify-work.md +9 -2
- package/scripts/gen-capability-registry.cjs +12 -93
- package/scripts/lint-test-file-count.allowlist.json +1 -19
- package/scripts/lint-test-file-count.cjs +5 -6
- package/scripts/prompt-injection-scan.sh +1 -4
- package/scripts/sync-runtime-launcher.cjs +115 -5
- package/pp-core/bin/lib/practices-review-check.cjs +0 -944
- package/pp-core/bin/lib/vault-audit.cjs +0 -2594
- package/pp-core/bin/lib/vault-mode.cjs +0 -235
- package/pp-core/bin/lib/vault-practices.cjs +0 -2057
- package/pp-core/bin/lib/vault-review.cjs +0 -2102
- package/pp-core/bin/lib/vault-write-back.cjs +0 -905
- package/pp-core/references/planner-vault-practices.md +0 -95
- package/pp-core/references/practice-arithmetic-contract.md +0 -121
- package/pp-core/references/verifier-practice-verdicts.md +0 -263
- package/pp-core/templates/practices/README.md +0 -248
- 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/execute-phase/steps/verifier-practice-payload.md +0 -165
- package/scripts/gen-practice-arithmetic-contract.cjs +0 -412
- package/scripts/practice-arithmetic-contract.allow.json +0 -35
- package/scripts/record-vault-mode-golden.cjs +0 -89
- 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
|
-
*
|
|
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
|
|
37
|
-
* sanitiser
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
242
|
-
*
|
|
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
|
|
276
|
-
*
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
790
|
-
*
|
|
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) {
|