@webpieces/rules-config 0.4.532 → 0.4.534
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/package.json +1 -1
- package/src/review-json.d.ts +31 -0
- package/src/review-json.js +87 -1
- package/src/review-json.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/rules-config",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.534",
|
|
4
4
|
"description": "Shared webpieces.config.json loader. Single source of truth for validation rule configuration consumed by @webpieces/ai-hook-rules, @webpieces/code-rules, and @webpieces/nx-webpieces-rules.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
package/src/review-json.d.ts
CHANGED
|
@@ -96,6 +96,31 @@ export declare class ReviewJsonService {
|
|
|
96
96
|
prDirFor(repoRoot: string, featureName: string): string;
|
|
97
97
|
reviewJsonPath(repoRoot: string, featureName: string): string;
|
|
98
98
|
prContextPath(repoRoot: string, featureName: string): string;
|
|
99
|
+
oldReviewJsonPath(reviewJsonFilePath: string): string;
|
|
100
|
+
/**
|
|
101
|
+
* Retire the review `wp-finish-upsert-pr` just used: move review.json to old-review.json, stamped with a
|
|
102
|
+
* note saying what it is. Returns the archive path, or '' when there was nothing to archive.
|
|
103
|
+
*
|
|
104
|
+
* The point is the MOVE, not the copy. review.json left in place after a PR is posted is a live-looking
|
|
105
|
+
* file describing a review that already happened, and the next run of stage ② on this branch finds it
|
|
106
|
+
* sitting there — so a reviewer subagent that judges the PR's stated intent (its title, summary or risk
|
|
107
|
+
* level) can read the previous run's review and return GREEN against a title that no longer exists.
|
|
108
|
+
* Nothing in the verdict distinguishes that from a real pass. Moving it means the only way to reach
|
|
109
|
+
* finish again is to write a fresh one, and {@link loadReviewJson} points at the archive when it is
|
|
110
|
+
* missing so the archive reads as an audit trail rather than as a lost file.
|
|
111
|
+
*
|
|
112
|
+
* Called only after the PR is actually up: a finish that failed before publishing must stay re-runnable.
|
|
113
|
+
*/
|
|
114
|
+
archiveReviewJson(reviewJsonFilePath: string): string;
|
|
115
|
+
/**
|
|
116
|
+
* The archived bytes: the original review with the AUDIT-ONLY note as its FIRST key, so anything that
|
|
117
|
+
* opens the file — human or AI — reads what it is before it reads any of its content.
|
|
118
|
+
*
|
|
119
|
+
* Falls back to the raw bytes when they do not parse. By here `loadReviewJson` has already accepted the
|
|
120
|
+
* file, so that is close to impossible; preserving the original beats losing it to a stamping failure.
|
|
121
|
+
*/
|
|
122
|
+
private archivedBody;
|
|
123
|
+
private tryParseObject;
|
|
99
124
|
/**
|
|
100
125
|
* Persist the PR's diff context so reviewer subagents can read the changed-file set + the exact base
|
|
101
126
|
* sha. Returns the file path written.
|
|
@@ -115,6 +140,12 @@ export declare class ReviewJsonService {
|
|
|
115
140
|
*/
|
|
116
141
|
reviewContextFor(repoRoot: string, featureName: string): ChecklistReviewContext;
|
|
117
142
|
reviewJsonSchemaHint(filePath: string): string;
|
|
143
|
+
/**
|
|
144
|
+
* The extra line the "no review.json" complaint carries when a PREVIOUS review was archived here. It
|
|
145
|
+
* turns a bare "not found" — which reads as data loss, and invites hunting for the file — into the fact:
|
|
146
|
+
* the last finish consumed it, and the archive is audit material, not a review to reuse.
|
|
147
|
+
*/
|
|
148
|
+
private archivedReviewHint;
|
|
118
149
|
checklistResultPath(reviewJsonFilePath: string, checklistId: string): string;
|
|
119
150
|
/**
|
|
120
151
|
* Load + validate the AI-authored review.json. Throws InformAiError (with the schema) when missing,
|
package/src/review-json.js
CHANGED
|
@@ -190,6 +190,16 @@ class PrContext {
|
|
|
190
190
|
exports.PrContext = PrContext;
|
|
191
191
|
const RISK_LEVELS = ['green', 'yellow', 'red'];
|
|
192
192
|
const EMOJI_FOR_LEVEL = { green: '🟢', yellow: '🟡', red: '🔴' };
|
|
193
|
+
// Where `wp-finish-upsert-pr` retires the review.json it just consumed, and the note it stamps on the way.
|
|
194
|
+
// The key sorts first in the written JSON because it is written first — an AI that opens the file to see
|
|
195
|
+
// whether it can reuse the review reads what the file IS before it reads a title it might be tempted to keep.
|
|
196
|
+
const OLD_REVIEW_FILE = 'old-review.json';
|
|
197
|
+
const ARCHIVE_NOTE_KEY = '_ARCHIVED_AUDIT_ONLY';
|
|
198
|
+
const ARCHIVE_NOTE = 'ARCHIVE — this is the review from the PREVIOUS wp-finish-upsert-pr run on this branch, kept for audit ' +
|
|
199
|
+
'purposes only. It is NOT the review for a new review round: it describes the code as of the last PR ' +
|
|
200
|
+
'update, which has since moved. If you are reviewing again, write a FRESH review.json at the path ' +
|
|
201
|
+
'pnpm wp-review-upsert-pr prints; do not copy this file\'s title, summary or risk level forward without ' +
|
|
202
|
+
're-deciding each one. Overwritten by every finish, so only the most recent review is ever here.';
|
|
193
203
|
/** Locates + loads/validates the AI-authored review.json. `@injectable(bindingScopeValues.Singleton)` so it's drawn in the design. */
|
|
194
204
|
let ReviewJsonService = class ReviewJsonService {
|
|
195
205
|
dotDir;
|
|
@@ -209,6 +219,69 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
209
219
|
prContextPath(repoRoot, featureName) {
|
|
210
220
|
return path.join(this.prDirFor(repoRoot, featureName), 'pr-context.json');
|
|
211
221
|
}
|
|
222
|
+
// Where a consumed review.json is archived to, beside it. Always the SAME path — it holds the last
|
|
223
|
+
// review and only the last one, so it can never be mistaken for a series that means something.
|
|
224
|
+
oldReviewJsonPath(reviewJsonFilePath) {
|
|
225
|
+
return path.join(path.dirname(reviewJsonFilePath), OLD_REVIEW_FILE);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Retire the review `wp-finish-upsert-pr` just used: move review.json to old-review.json, stamped with a
|
|
229
|
+
* note saying what it is. Returns the archive path, or '' when there was nothing to archive.
|
|
230
|
+
*
|
|
231
|
+
* The point is the MOVE, not the copy. review.json left in place after a PR is posted is a live-looking
|
|
232
|
+
* file describing a review that already happened, and the next run of stage ② on this branch finds it
|
|
233
|
+
* sitting there — so a reviewer subagent that judges the PR's stated intent (its title, summary or risk
|
|
234
|
+
* level) can read the previous run's review and return GREEN against a title that no longer exists.
|
|
235
|
+
* Nothing in the verdict distinguishes that from a real pass. Moving it means the only way to reach
|
|
236
|
+
* finish again is to write a fresh one, and {@link loadReviewJson} points at the archive when it is
|
|
237
|
+
* missing so the archive reads as an audit trail rather than as a lost file.
|
|
238
|
+
*
|
|
239
|
+
* Called only after the PR is actually up: a finish that failed before publishing must stay re-runnable.
|
|
240
|
+
*/
|
|
241
|
+
archiveReviewJson(reviewJsonFilePath) {
|
|
242
|
+
if (!fs.existsSync(reviewJsonFilePath))
|
|
243
|
+
return '';
|
|
244
|
+
const archivePath = this.oldReviewJsonPath(reviewJsonFilePath);
|
|
245
|
+
const raw = fs.readFileSync(reviewJsonFilePath, 'utf8');
|
|
246
|
+
fs.writeFileSync(archivePath, this.archivedBody(raw));
|
|
247
|
+
fs.rmSync(reviewJsonFilePath);
|
|
248
|
+
return archivePath;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The archived bytes: the original review with the AUDIT-ONLY note as its FIRST key, so anything that
|
|
252
|
+
* opens the file — human or AI — reads what it is before it reads any of its content.
|
|
253
|
+
*
|
|
254
|
+
* Falls back to the raw bytes when they do not parse. By here `loadReviewJson` has already accepted the
|
|
255
|
+
* file, so that is close to impossible; preserving the original beats losing it to a stamping failure.
|
|
256
|
+
*/
|
|
257
|
+
archivedBody(raw) {
|
|
258
|
+
const parsed = this.tryParseObject(raw);
|
|
259
|
+
if (parsed === null)
|
|
260
|
+
return raw;
|
|
261
|
+
// webpieces-disable no-any-unknown -- re-serializing opaque review fields verbatim; only the key ORDER is ours
|
|
262
|
+
const stamped = {};
|
|
263
|
+
stamped[ARCHIVE_NOTE_KEY] = ARCHIVE_NOTE;
|
|
264
|
+
for (const key of Object.keys(parsed))
|
|
265
|
+
stamped[key] = parsed[key];
|
|
266
|
+
return JSON.stringify(stamped, null, 2) + '\n';
|
|
267
|
+
}
|
|
268
|
+
// webpieces-disable no-any-unknown -- opaque parsed JSON; the caller only re-serializes it
|
|
269
|
+
tryParseObject(raw) {
|
|
270
|
+
// webpieces-disable no-unmanaged-exceptions -- chokepoint: unparseable bytes are archived verbatim, never fatal
|
|
271
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
272
|
+
try {
|
|
273
|
+
// webpieces-disable no-any-unknown -- parsed JSON is opaque; only its key order is used
|
|
274
|
+
const parsed = JSON.parse(raw);
|
|
275
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
276
|
+
return null;
|
|
277
|
+
return parsed;
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
const error = (0, to_error_1.toError)(err);
|
|
281
|
+
void error;
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
212
285
|
/**
|
|
213
286
|
* Persist the PR's diff context so reviewer subagents can read the changed-file set + the exact base
|
|
214
287
|
* sha. Returns the file path written.
|
|
@@ -278,6 +351,18 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
278
351
|
` "filesToReview": ["paths a human should look at (empty array if none)"]\n` +
|
|
279
352
|
`}`);
|
|
280
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* The extra line the "no review.json" complaint carries when a PREVIOUS review was archived here. It
|
|
356
|
+
* turns a bare "not found" — which reads as data loss, and invites hunting for the file — into the fact:
|
|
357
|
+
* the last finish consumed it, and the archive is audit material, not a review to reuse.
|
|
358
|
+
*/
|
|
359
|
+
archivedReviewHint(filePath) {
|
|
360
|
+
const archive = this.oldReviewJsonPath(filePath);
|
|
361
|
+
if (!fs.existsSync(archive))
|
|
362
|
+
return '';
|
|
363
|
+
return `\nA PREVIOUS review was archived to ${archive} when the last pnpm wp-finish-upsert-pr consumed it.\n` +
|
|
364
|
+
`That file is for AUDIT ONLY — it reviews code this branch has since moved past. Write a fresh one:`;
|
|
365
|
+
}
|
|
281
366
|
// The per-checklist review file path that sits beside review.json: review-<id>.json.
|
|
282
367
|
checklistResultPath(reviewJsonFilePath, checklistId) {
|
|
283
368
|
return path.join(path.dirname(reviewJsonFilePath), `review-${checklistId}.json`);
|
|
@@ -291,7 +376,8 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
291
376
|
// webpieces-disable max-lines-new-methods -- one cohesive load+validate pass over the review fields
|
|
292
377
|
loadReviewJson(filePath, required = []) {
|
|
293
378
|
if (!fs.existsSync(filePath)) {
|
|
294
|
-
throw new inform_ai_error_1.InformAiError(`Required review.json not found
|
|
379
|
+
throw new inform_ai_error_1.InformAiError(`Required review.json not found.${this.archivedReviewHint(filePath)}\n\n` +
|
|
380
|
+
`${this.reviewJsonSchemaHint(filePath)}\n\n` +
|
|
295
381
|
`Then re-run: pnpm wp-finish-upsert-pr`);
|
|
296
382
|
}
|
|
297
383
|
const raw = this.parseReviewJson(fs.readFileSync(filePath, 'utf8'), filePath);
|
package/src/review-json.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-json.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-json.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { PR_REVIEW_DIR } from './constants';\nimport { DotWebpieces, dotWebpieces } from './state-dir';\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\n// The three colors a reviewer subagent may report in `review-<id>.json`. A TRI-state, not a boolean,\n// because the boolean it replaced gave a reviewer no way to say \"this passes, but a human should look at\n// X\" — the only way to raise a concern was to FAIL the PR and then override your own failure, which reads\n// on the dashboard as a deliberately-accepted defect rather than as a note.\nexport const VERDICT_GREEN = 'green';\nexport const VERDICT_YELLOW = 'yellow';\nexport const VERDICT_RED = 'red';\nexport const VERDICT_STATUSES = [VERDICT_GREEN, VERDICT_YELLOW, VERDICT_RED] as const;\n\n// The verdict a reviewer SUBAGENT writes into `.webpieces/pr-review/<branch>/review-<id>.json`, one per\n// matched checklist. One file per checklist so N concurrent reviewer subagents never clobber a shared\n// file. It records the OUTCOME:\n// status:'green' → PASS\n// status:'yellow' → WARN (passes; the concern is published on the PR, nothing is blocked)\n// status:'red' + override non-empty → OVERRIDDEN (pass; the free-text justification reaches the PR)\n// status:'red' + no override → FAIL (refuse; `output` is printed verbatim)\n// `override` is deliberately free text, not a boolean — it forces the ship-anyway decision to be stated\n// in words and surfaces it on the dashboard, where a human sees it. Data-only (per CLAUDE.md).\nexport class ChecklistResult {\n id: string;\n status: string; // one of VERDICT_STATUSES; anything else is reported via `problem`\n output: string; // what the reviewer found; printed verbatim when the checklist fails\n override: string; // '' = no override; non-empty = ship-anyway justification (renders 🟠 overridden)\n // '' = a well-formed verdict. Non-empty = the file exists and parses but its verdict cannot be READ\n // (most often: it still uses the removed `success` field). Carried as data rather than thrown so the\n // complaint can be reported by BOTH wp-review-upsert-pr and wp-finish-upsert-pr in identical words, and so a\n // legacy file is never silently mistaken for a missing one.\n problem: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(id: string, status: string, output: string, override: string, problem = '') {\n this.id = id;\n this.status = status;\n this.output = output;\n this.override = override;\n this.problem = problem;\n }\n}\n\n// What the pr-gate command computed from the diff: a checklist this branch MATCHED (its patterns hit the\n// diff, so its reviewer subagent must run). Drives review-<id>.json enforcement, provenance, the schema\n// hint, and the dashboard. Data-only.\nexport class RequiredChecklist {\n id: string; // = subagent name; keys review-<id>.json\n subagent: string; // reviewer agent that must run (agentType the harness stamps)\n doc: string; // REPO-RELATIVE guidance doc the reviewer reads ('' → it just reads the diff)\n matchedFiles: string[]; // the changed files that matched it (for the dashboard + hint)\n // Which of the checklist's OWN globs actually fired. Printed so a reviewer can judge how coarse the\n // match was — a precise `db/migrations/**` hit means something different from a blanket `**` — and the\n // template tells reviewers that matching IS deliberately coarse. [] = no patterns (matches every PR).\n matchedPatterns: string[];\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(id: string, subagent: string, doc: string, matchedFiles: string[], matchedPatterns: string[] = []) {\n this.id = id;\n this.subagent = subagent;\n this.doc = doc;\n this.matchedFiles = matchedFiles;\n this.matchedPatterns = matchedPatterns;\n }\n}\n\n/**\n * The per-PR facts every reviewer subagent needs GIVEN to it, alongside its own checklist: the exact base\n * sha the gate diffs against and the file holding the complete changed-file set. Both used to live only in\n * a doc the printed instruction told the AI to go read, one indirection away from the instruction to hand\n * them over — so the printed block could not stand on its own. Data-only; empty = omit those lines.\n */\nexport class ChecklistReviewContext {\n baseSha: string; // the 3-point merge-base sha\n prContextPath: string; // path of pr-context.json — the AUTHORITATIVE full changed-file set\n /**\n * The exact command that reproduces ONE file's diff, with a `-- <file>` tail — NOT assembled by the\n * caller. This used to be hardcoded as `git diff <baseSha> HEAD -- <file>`, which returns NOTHING on a\n * dirty tree because the changed-file set is computed base→working-tree. See DiffBasis, which derives\n * this string from the same range the file set came from.\n */\n fileDiffCommand: string;\n diffDir: string; // dir of the MATERIALIZED diff (diff/ALL.diff + diff/files/…); '' when not written\n dirty: boolean; // true ⇒ the range includes uncommitted + untracked work, and must be said out loud\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(baseSha = '', prContextPath = '', fileDiffCommand = '', diffDir = '', dirty = false) {\n this.baseSha = baseSha;\n this.prContextPath = prContextPath;\n this.fileDiffCommand = fileDiffCommand;\n this.diffDir = diffDir;\n this.dirty = dirty;\n }\n}\n\n// The AI-authored review for a PR. The AI writes review.json itself between `wp-start-upsert-pr` (which\n// prints the schema) and `wp-finish-upsert-pr` (which reads it); reviewer subagents write the per-checklist\n// review-<id>.json files. Data-only (per CLAUDE.md).\nexport class ReviewJson {\n title: string; // human PR title describing the change; used as the `gh pr` title (empty → caller falls back)\n riskScore: number; // 0–100, drives the risk bar\n riskLevel: string; // 'green' | 'yellow' | 'red'\n riskEmoji: string; // '🟢' | '🟡' | '🔴' — derived from riskLevel when omitted\n summary: string; // rendered in the dashboard Summary section\n violations: string[]; // pattern/architecture violations; length = the Pattern Violations count\n risks: string[];\n filesToReview: string[];\n results: ChecklistResult[]; // resolved per-checklist verdicts (from review-<id>.json); [] when none\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n title: string,\n riskScore: number,\n riskLevel: string,\n riskEmoji: string,\n summary: string,\n violations: string[],\n risks: string[],\n filesToReview: string[],\n results: ChecklistResult[] = [],\n ) {\n this.title = title;\n this.riskScore = riskScore;\n this.riskLevel = riskLevel;\n this.riskEmoji = riskEmoji;\n this.summary = summary;\n this.violations = violations;\n this.risks = risks;\n this.filesToReview = filesToReview;\n this.results = results;\n }\n}\n\n// A checklist's resolved outcome, shared by review.json enforcement and the dashboard so both agree.\n// PASS, WARN and OVERRIDDEN all ship; FAIL, MISSING and BAD_FORMAT all refuse the PR.\nexport const CK_PASS = 'pass'; // review-<id>.json status:'green'\nexport const CK_WARN = 'warn'; // review-<id>.json status:'yellow' → 🟡 passes WITH concerns\nexport const CK_OVERRIDDEN = 'overridden'; // review-<id>.json status:'red' + non-empty override → 🟠\nexport const CK_FAIL = 'fail'; // review-<id>.json status:'red' + no override → refuse\nexport const CK_MISSING = 'missing'; // no review-<id>.json written → refuse\nexport const CK_BAD_FORMAT = 'bad-format'; // written, but its verdict is unreadable (e.g. legacy `success`)\n\nexport class ChecklistVerdict {\n id: string;\n status: string; // one of CK_PASS | CK_WARN | CK_OVERRIDDEN | CK_FAIL | CK_MISSING | CK_BAD_FORMAT\n detail: string; // reviewer output / override justification / format complaint (dashboard + errors)\n\n constructor(id: string, status: string, detail: string) {\n this.id = id;\n this.status = status;\n this.detail = detail;\n }\n}\n\n// The PR's diff context, written by wp-start-upsert-pr into `.webpieces/pr-review/<branch>/pr-context.json`\n// so a reviewer subagent knows the exact 3-point base the gate used and the full changed-file set — then\n// reads any file's actual diff with `git diff <base> HEAD -- <file>`. This is what lets a checklist match\n// coarsely by path (in the config) while the subagent makes the fine, content-level judgment. Data-only.\nexport class PrContext {\n base: string; // the 3-point merge-base sha the gate diffs against\n /**\n * The real HEAD sha. This was once the literal string 'HEAD', which is not a fact — it cannot be\n * compared later to detect that the tree moved under a review, and it reads as a range that was never\n * actually diffed. Its only reader (reviewContextFor) takes `base`, so recording the sha is free.\n */\n head: string;\n changedFiles: string[]; // every file changed in the range (NOT tsOnly — includes .sql/.gql/Dockerfile/…)\n dirty: boolean; // true ⇒ changedFiles includes uncommitted + untracked work\n dirtyFiles: string[]; // exactly which paths are uncommitted/untracked — why `dirty` is true\n diffCommand: string; // the command that reproduces the WHOLE diff (see DiffBasis; correct when dirty)\n diffDir: string; // dir holding the materialized per-file diffs + ALL.diff; '' when not materialized\n generatedAt: string; // ISO timestamp, so a stale context is detectable rather than silently trusted\n /**\n * Main's head as this clone last saw it — the THIRD hash point, matching the trio the 3-point merge\n * records in `merge-info/<branch>/updatemain-hashes.json`. `base`/`head` above are points A and B\n * under the review side's older names.\n *\n * The review side used to record only A and B, so nothing could answer \"did main move while this was\n * under review?\" — the question you most want answered when a review looks stale. '' when origin/main\n * is unresolvable. Purely informational; nothing gates on it.\n */\n hashMainHead: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n base: string, head: string, changedFiles: string[],\n dirty = false, dirtyFiles: string[] = [], diffCommand = '', diffDir = '', generatedAt = '',\n hashMainHead = '',\n ) {\n this.hashMainHead = hashMainHead;\n this.base = base;\n this.head = head;\n this.changedFiles = changedFiles;\n this.dirty = dirty;\n this.dirtyFiles = dirtyFiles;\n this.diffCommand = diffCommand;\n this.diffDir = diffDir;\n this.generatedAt = generatedAt;\n }\n}\n\nconst RISK_LEVELS = ['green', 'yellow', 'red'] as const;\nconst EMOJI_FOR_LEVEL: Record<string, string> = { green: '🟢', yellow: '🟡', red: '🔴' };\n\n/** Locates + loads/validates the AI-authored review.json. `@injectable(bindingScopeValues.Singleton)` so it's drawn in the design. */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewJsonService {\n constructor(private readonly dotDir: DotWebpieces = dotWebpieces) {}\n\n // The per-feature PR working dir: `<local .webpieces>/pr-review/<feature>`. LOCAL scope — a PR\n // review belongs to the worktree that is preparing it, and two worktrees never share one.\n prDirFor(repoRoot: string, featureName: string): string {\n return this.dotDir.localFile(repoRoot, PR_REVIEW_DIR, featureName);\n }\n\n // Absolute path of the review.json for a feature — beside pr-body.md, keyed by branch name.\n reviewJsonPath(repoRoot: string, featureName: string): string {\n return path.join(this.prDirFor(repoRoot, featureName), 'review.json');\n }\n\n // Absolute path of the pr-context.json for a feature (the diff base/head + changed files).\n prContextPath(repoRoot: string, featureName: string): string {\n return path.join(this.prDirFor(repoRoot, featureName), 'pr-context.json');\n }\n\n /**\n * Persist the PR's diff context so reviewer subagents can read the changed-file set + the exact base\n * sha. Returns the file path written.\n *\n * ALSO writes an immutable per-stage snapshot under `stages/<stage>.json` when `stage` is given.\n * `pr-context.json` is overwritten by each stage, so by the time anything goes wrong the earlier\n * states are gone — and \"what did the tooling think the diff was at stage ①, vs ②, vs ③?\" is exactly\n * the question you need answered when debugging a review that went sideways. The snapshots make the\n * review system auditable by an AI reviewing IT, which the single mutable file never could.\n */\n writePrContext(repoRoot: string, featureName: string, context: PrContext, stage = ''): string {\n const dir = this.prDirFor(repoRoot, featureName);\n fs.mkdirSync(dir, { recursive: true });\n const body = JSON.stringify(context, null, 2) + '\\n';\n const p = this.prContextPath(repoRoot, featureName);\n fs.writeFileSync(p, body);\n if (stage !== '') {\n const stagesDir = path.join(dir, 'stages');\n fs.mkdirSync(stagesDir, { recursive: true });\n fs.writeFileSync(path.join(stagesDir, `${stage}.json`), body);\n }\n return p;\n }\n\n /**\n * The review context for a feature, recovered from the pr-context.json wp-start-upsert-pr already wrote.\n * Lets wp-finish-upsert-pr's \"you still owe me review-<id>.json\" message inline the SAME self-sufficient\n * per-reviewer block start printed, instead of a checklist name and an indirection. Empty when the file\n * is absent or unreadable — the block then just omits those lines.\n */\n reviewContextFor(repoRoot: string, featureName: string): ChecklistReviewContext {\n const p = this.prContextPath(repoRoot, featureName);\n if (!fs.existsSync(p)) return new ChecklistReviewContext();\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unreadable context file degrades to fewer printed lines, never a crash\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed on the next line\n const raw = JSON.parse(fs.readFileSync(p, 'utf8')) as Record<string, unknown>;\n const base = typeof raw['base'] === 'string' ? (raw['base'] as string) : '';\n // Recover the REPRODUCE command rather than re-deriving it: a context written by an older\n // pr-gate has no diffCommand, and guessing `<base> HEAD` there would resurrect the exact\n // empty-on-a-dirty-tree bug this field exists to kill. Absent ⇒ omit the line entirely.\n const cmd = typeof raw['diffCommand'] === 'string' ? (raw['diffCommand'] as string) : '';\n const diffDir = typeof raw['diffDir'] === 'string' ? (raw['diffDir'] as string) : '';\n const dirty = raw['dirty'] === true;\n return new ChecklistReviewContext(base, p, cmd === '' ? '' : `${cmd} -- <file>`, diffDir, dirty);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return new ChecklistReviewContext('', p);\n }\n }\n\n // Copy-paste schema both commands print. `required` is the set of checklists the diff MATCHED; empty\n // ⇒ output identical to a repo with no checklists. Non-empty ⇒ appends per-checklist instructions\n // naming the reviewer subagent + doc + the review-<id>.json to write.\n reviewJsonSchemaHint(filePath: string): string {\n return (\n `Write your PR review to:\\n ${filePath}\\n\\n` +\n `with this exact JSON shape (riskEmoji optional — derived from riskLevel):\\n\\n` +\n `{\\n` +\n ` \"title\": \"concise PR title describing the change (imperative, no branch names)\",\\n` +\n ` \"riskScore\": 0, // integer 0–100 (higher = riskier)\\n` +\n ` \"riskLevel\": \"green | yellow | red\",\\n` +\n ` \"summary\": \"5–10 sentence review summary\",\\n` +\n ` \"violations\": [\"pattern/architecture violations you found (empty array if none)\"],\\n` +\n ` \"risks\": [\"notable risks (empty array if none)\"],\\n` +\n ` \"filesToReview\": [\"paths a human should look at (empty array if none)\"]\\n` +\n `}`\n );\n }\n\n // The per-checklist review file path that sits beside review.json: review-<id>.json.\n checklistResultPath(reviewJsonFilePath: string, checklistId: string): string {\n return path.join(path.dirname(reviewJsonFilePath), `review-${checklistId}.json`);\n }\n\n /**\n * Load + validate the AI-authored review.json. Throws InformAiError (with the schema) when missing,\n * unparseable, or structurally wrong. `required` is the set of checklists the diff matched: every one\n * must have a well-formed, passing (or overridden) review-<id>.json or a validation error is raised\n * alongside the usual ones so the AI gets ONE message.\n */\n // webpieces-disable max-lines-new-methods -- one cohesive load+validate pass over the review fields\n loadReviewJson(filePath: string, required: readonly RequiredChecklist[] = []): ReviewJson {\n if (!fs.existsSync(filePath)) {\n throw new InformAiError(\n `Required review.json not found.\\n\\n${this.reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n\n const raw = this.parseReviewJson(fs.readFileSync(filePath, 'utf8'), filePath);\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new InformAiError(`review.json must be a JSON object.\\n\\n${this.reviewJsonSchemaHint(filePath)}`);\n }\n\n const errors: string[] = [];\n\n const riskScore = raw['riskScore'];\n if (typeof riskScore !== 'number' || !Number.isFinite(riskScore) || riskScore < 0 || riskScore > 100) {\n errors.push(`\"riskScore\" must be a number 0–100, got ${JSON.stringify(riskScore)}.`);\n }\n\n const riskLevel = raw['riskLevel'];\n if (typeof riskLevel !== 'string' || !RISK_LEVELS.includes(riskLevel as typeof RISK_LEVELS[number])) {\n errors.push(`\"riskLevel\" must be one of: ${RISK_LEVELS.join(', ')}.`);\n }\n\n const title = typeof raw['title'] === 'string' ? (raw['title'] as string).trim() : '';\n if (title === '') {\n errors.push('\"title\" must be a non-empty, imperative PR title describing the change (no branch names).');\n }\n\n const results = this.loadChecklistResults(filePath, required);\n for (const err of this.requiredChecklistErrors(required, results)) errors.push(err);\n\n if (errors.length > 0) {\n throw new InformAiError(\n `review.json has ${errors.length} error(s) — fix ALL, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\n${this.reviewJsonSchemaHint(filePath)}`,\n );\n }\n\n const level = riskLevel as string;\n const emoji = typeof raw['riskEmoji'] === 'string' && raw['riskEmoji'] !== ''\n ? (raw['riskEmoji'] as string)\n : (EMOJI_FOR_LEVEL[level] ?? '🟡');\n const summary = typeof raw['summary'] === 'string' ? (raw['summary'] as string) : '';\n\n return new ReviewJson(\n title,\n riskScore as number,\n level,\n emoji,\n summary,\n this.asStringArray(raw['violations']),\n this.asStringArray(raw['risks']),\n this.asStringArray(raw['filesToReview']),\n results,\n );\n }\n\n /**\n * The checklists that still OWE a verdict: no review-<id>.json at all, a malformed one, or one whose\n * verdict is an un-overridden FAIL. This is the set every message lists — a checklist already PASSed or\n * OVERRIDDEN on this branch is deliberately NOT re-listed, because re-instructing it invites a redundant\n * second run and reads as though the earlier verdict did not count.\n */\n pendingChecklists(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[] {\n return required.filter((req: RequiredChecklist): boolean => {\n const status = this.resolveVerdict(req, results).status;\n // CK_WARN must be listed here beside PASS/OVERRIDDEN. A yellow verdict SHIPS — leaving it out\n // would mark the checklist owed forever, so `outstanding` never empties and the PR is refused\n // permanently no matter how many times the reviewer runs.\n return status !== CK_PASS && status !== CK_WARN && status !== CK_OVERRIDDEN;\n });\n }\n\n // Read the per-checklist verdict files `review-<id>.json` beside review.json — one per matched checklist.\n // A missing file is simply absent from the result (→ counts as MISSING for that checklist); a malformed\n // one is skipped (a stale review-<id>.json never wedges the branch).\n loadChecklistResults(reviewJsonFilePath: string, required: readonly RequiredChecklist[]): ChecklistResult[] {\n const results: ChecklistResult[] = [];\n for (const req of required) {\n const p = this.checklistResultPath(reviewJsonFilePath, req.id);\n if (!fs.existsSync(p)) continue;\n const parsed = this.parseChecklistResult(p, req.id);\n if (parsed) results.push(parsed);\n }\n return results;\n }\n\n // Resolve ONE checklist's verdict from its review-<id>.json. Central so review.json enforcement AND the\n // finish-command dashboard agree on the outcome. `problem` is checked FIRST: a file whose verdict cannot\n // be read must not fall through to any shipping outcome.\n resolveVerdict(req: RequiredChecklist, results: readonly ChecklistResult[]): ChecklistVerdict {\n const result = results.find((r: ChecklistResult): boolean => r.id === req.id);\n if (!result) return new ChecklistVerdict(req.id, CK_MISSING, '');\n if (result.problem !== '') return new ChecklistVerdict(req.id, CK_BAD_FORMAT, result.problem);\n if (result.status === VERDICT_GREEN) return new ChecklistVerdict(req.id, CK_PASS, result.output);\n if (result.status === VERDICT_YELLOW) return new ChecklistVerdict(req.id, CK_WARN, result.output);\n if (result.override.trim() !== '') return new ChecklistVerdict(req.id, CK_OVERRIDDEN, result.override.trim());\n return new ChecklistVerdict(req.id, CK_FAIL, result.output);\n }\n\n /**\n * One loud complaint per checklist whose verdict file EXISTS but cannot be read as a verdict — almost\n * always one still using the removed `success` field. Public and separate from\n * {@link requiredChecklistErrors} because `wp-finish-upsert-pr` refuses on missing reviewers BEFORE it\n * parses review.json: without this, a legacy file would surface as the generic \"no verdict yet\" block\n * and the AI would re-run a reviewer that already ran instead of fixing four characters of JSON.\n */\n checklistFormatErrors(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): string[] {\n const errors: string[] = [];\n for (const req of required) {\n const verdict = this.resolveVerdict(req, results);\n if (verdict.status === CK_BAD_FORMAT) errors.push(verdict.detail);\n }\n return errors;\n }\n\n // Every matched checklist whose verdict is FAIL (reviewed, found a problem, no override) or MISSING (no\n // review-<id>.json written) → one error each, printing the reviewer's `output` verbatim.\n private requiredChecklistErrors(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): string[] {\n // Format complaints come from the ONE renderer, so wp-review-upsert-pr and wp-finish word them identically.\n const errors: string[] = this.checklistFormatErrors(required, results);\n for (const req of required) {\n const verdict = this.resolveVerdict(req, results);\n // CK_WARN ('yellow' — passed with concerns) is deliberately absent from this chain: it SHIPS.\n // The concern still reaches the PR, published in the checklist comment. Do not \"fix\" this.\n if (verdict.status === CK_FAIL) {\n errors.push(\n `Checklist \"${req.id}\" FAILED review (status:\"${VERDICT_RED}\"). The reviewer (${req.subagent}) wrote:\\n ` +\n `${verdict.detail.split('\\n').join('\\n ')}\\n` +\n ` Fix it, then re-run; or set a non-empty \"override\" in ${this.checklistFileName(req.id)} to ship anyway with a stated justification.`,\n );\n } else if (verdict.status === CK_MISSING) {\n const doc = req.doc.trim() !== '' ? ` Read: ${req.doc}.` : '';\n errors.push(\n `Checklist \"${req.id}\" MATCHED this diff but has no verdict. Spawn the \"${req.subagent}\" subagent to review it, ` +\n `then write ${this.checklistFileName(req.id)} with ` +\n `{\"id\":\"${req.id}\",\"status\":\"${VERDICT_GREEN}\",\"output\":\"…\",\"override\":\"\"}.${doc}`,\n );\n }\n }\n return errors;\n }\n\n private checklistFileName(checklistId: string): string {\n return `review-${checklistId}.json`;\n }\n\n /**\n * THE renderer for a reviewer's verdict schema — with the reviewer's own `id` already filled in and,\n * when known, the exact file it must write.\n *\n * There is one because a verdict schema that lives anywhere a human maintains it goes stale, and a\n * reviewer follows the stale copy. That is not a hypothetical: when `success` was replaced by the\n * tri-state `status`, hand-written `.claude/agents/*.md` files kept documenting `success`, and a real\n * PR had to carry \"the verdict format in your own agent .md file is OUT OF DATE\" in the spawn prompt to\n * work around it. Every printed copy — the stage-② roster, the generated per-reviewer instructions\n * file, and the complaint raised against a malformed verdict — now comes from here.\n *\n * `verdictPath` may be '' when the caller is describing the shape rather than a specific file.\n */\n verdictSchemaFor(id: string, verdictPath = '', indent = ' '): string {\n const lines = [\n `${indent}{ \"id\": \"${id}\", \"status\": \"${VERDICT_GREEN} | ${VERDICT_YELLOW} | ${VERDICT_RED}\", ` +\n `\"output\": \"what you checked / found\", \"override\": \"\" }`,\n `${indent} ${VERDICT_GREEN} → passes, nothing to flag`,\n `${indent} ${VERDICT_YELLOW} → passes WITH CONCERNS; nothing is blocked and the concern is published on the PR`,\n `${indent} ${VERDICT_RED} → REFUSES the PR (set a non-empty \"override\" to ship anyway with a stated justification)`,\n `${indent}Prefer \"${VERDICT_YELLOW}\" over red-plus-override when the change is acceptable but worth a human's`,\n `${indent}attention — an override reads as a deliberately-accepted defect, a yellow reads as a note.`,\n ];\n if (verdictPath !== '') lines.push(`${indent}File: ${verdictPath}`);\n return lines.join('\\n');\n }\n\n /**\n * Parse one review-<id>.json into a ChecklistResult. `null` ONLY when the bytes do not parse as a JSON\n * object at all — that tolerance is why a half-written file never wedges a branch, and it degrades to\n * the same \"no verdict yet\" message as an absent file, which is honest (nothing readable is there).\n *\n * A file that DOES parse always yields a result, even when its verdict is unreadable, carrying the\n * complaint in `problem`. Returning `null` for those instead would collapse \"wrote a verdict in the old\n * format\" into \"never wrote a verdict\" and send the AI off to re-run a reviewer that already ran.\n */\n // webpieces-disable no-any-unknown -- opaque parsed JSON, narrowed field-by-field\n private parseChecklistResult(filePath: string, id: string): ChecklistResult | null {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unparseable per-checklist file is skipped, not fatal\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed below\n const raw = JSON.parse(fs.readFileSync(filePath, 'utf8')) as Record<string, unknown>;\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return null;\n const output = typeof raw['output'] === 'string' ? (raw['output'] as string) : '';\n const override = typeof raw['override'] === 'string' ? (raw['override'] as string) : '';\n const status = typeof raw['status'] === 'string' ? (raw['status'] as string).trim().toLowerCase() : '';\n return new ChecklistResult(id, status, output, override, this.statusProblem(filePath, id, status, raw));\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * '' when `status` is one of the three colors. Otherwise the complaint to show the AI verbatim. The\n * legacy-`success` case gets its OWN message: `success` was removed outright (no compatibility mode),\n * and a reviewer told only \"status must be green|yellow|red\" cannot tell whether it wrote the wrong\n * value or is using a field that no longer exists.\n */\n // webpieces-disable no-any-unknown -- opaque parsed JSON; only tested for key presence here\n private statusProblem(filePath: string, id: string, status: string, raw: Record<string, unknown>): string {\n // webpieces-disable no-any-unknown -- comparing against the readonly literal tuple of valid colors\n if ((VERDICT_STATUSES as readonly string[]).includes(status)) return '';\n // The ONE renderer — see verdictSchemaFor. A second copy here is what let the old `success` shape\n // survive in print after it was removed from the parser.\n const shape = this.verdictSchemaFor(id, filePath);\n if ('success' in raw) {\n return `Checklist \"${id}\" wrote its verdict with the REMOVED \"success\" field. It is now a tri-state ` +\n `\"status\" — there is no compatibility mode. Rewrite the file as:\\n${shape}`;\n }\n return `Checklist \"${id}\" wrote a verdict with no valid \"status\" (got ${JSON.stringify(status)}). ` +\n `It must be exactly one of ${VERDICT_STATUSES.join(', ')}:\\n${shape}`;\n }\n\n // webpieces-disable no-any-unknown -- opaque parsed JSON value, narrowed to string[] here\n private asStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n // webpieces-disable no-any-unknown -- element of an opaque JSON array, narrowed by the type guard\n return value.filter((v: unknown): v is string => typeof v === 'string');\n }\n\n // Parse opaque AI-authored JSON, converting a SyntaxError into a readable InformAiError.\n // webpieces-disable no-any-unknown -- returns the opaque parsed object; loadReviewJson narrows each field\n private parseReviewJson(raw: string, filePath: string): Record<string, unknown> {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: convert JSON.parse SyntaxError to an InformAiError for the AI\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed by the caller\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(\n `review.json is not valid JSON (${error.message}).\\n\\n${this.reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n }\n}\n\n// Temporary migration delegators to ReviewJsonService — removed once consumers inject it.\nconst reviewJsonSvc = new ReviewJsonService();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function prDirFor(repoRoot: string, featureName: string): string {\n return reviewJsonSvc.prDirFor(repoRoot, featureName);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function reviewJsonPath(repoRoot: string, featureName: string): string {\n return reviewJsonSvc.reviewJsonPath(repoRoot, featureName);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function reviewJsonSchemaHint(filePath: string): string {\n return reviewJsonSvc.reviewJsonSchemaHint(filePath);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function loadReviewJson(filePath: string, required: readonly RequiredChecklist[] = []): ReviewJson {\n return reviewJsonSvc.loadReviewJson(filePath, required);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"review-json.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-json.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { PR_REVIEW_DIR } from './constants';\nimport { DotWebpieces, dotWebpieces } from './state-dir';\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\n// The three colors a reviewer subagent may report in `review-<id>.json`. A TRI-state, not a boolean,\n// because the boolean it replaced gave a reviewer no way to say \"this passes, but a human should look at\n// X\" — the only way to raise a concern was to FAIL the PR and then override your own failure, which reads\n// on the dashboard as a deliberately-accepted defect rather than as a note.\nexport const VERDICT_GREEN = 'green';\nexport const VERDICT_YELLOW = 'yellow';\nexport const VERDICT_RED = 'red';\nexport const VERDICT_STATUSES = [VERDICT_GREEN, VERDICT_YELLOW, VERDICT_RED] as const;\n\n// The verdict a reviewer SUBAGENT writes into `.webpieces/pr-review/<branch>/review-<id>.json`, one per\n// matched checklist. One file per checklist so N concurrent reviewer subagents never clobber a shared\n// file. It records the OUTCOME:\n// status:'green' → PASS\n// status:'yellow' → WARN (passes; the concern is published on the PR, nothing is blocked)\n// status:'red' + override non-empty → OVERRIDDEN (pass; the free-text justification reaches the PR)\n// status:'red' + no override → FAIL (refuse; `output` is printed verbatim)\n// `override` is deliberately free text, not a boolean — it forces the ship-anyway decision to be stated\n// in words and surfaces it on the dashboard, where a human sees it. Data-only (per CLAUDE.md).\nexport class ChecklistResult {\n id: string;\n status: string; // one of VERDICT_STATUSES; anything else is reported via `problem`\n output: string; // what the reviewer found; printed verbatim when the checklist fails\n override: string; // '' = no override; non-empty = ship-anyway justification (renders 🟠 overridden)\n // '' = a well-formed verdict. Non-empty = the file exists and parses but its verdict cannot be READ\n // (most often: it still uses the removed `success` field). Carried as data rather than thrown so the\n // complaint can be reported by BOTH wp-review-upsert-pr and wp-finish-upsert-pr in identical words, and so a\n // legacy file is never silently mistaken for a missing one.\n problem: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(id: string, status: string, output: string, override: string, problem = '') {\n this.id = id;\n this.status = status;\n this.output = output;\n this.override = override;\n this.problem = problem;\n }\n}\n\n// What the pr-gate command computed from the diff: a checklist this branch MATCHED (its patterns hit the\n// diff, so its reviewer subagent must run). Drives review-<id>.json enforcement, provenance, the schema\n// hint, and the dashboard. Data-only.\nexport class RequiredChecklist {\n id: string; // = subagent name; keys review-<id>.json\n subagent: string; // reviewer agent that must run (agentType the harness stamps)\n doc: string; // REPO-RELATIVE guidance doc the reviewer reads ('' → it just reads the diff)\n matchedFiles: string[]; // the changed files that matched it (for the dashboard + hint)\n // Which of the checklist's OWN globs actually fired. Printed so a reviewer can judge how coarse the\n // match was — a precise `db/migrations/**` hit means something different from a blanket `**` — and the\n // template tells reviewers that matching IS deliberately coarse. [] = no patterns (matches every PR).\n matchedPatterns: string[];\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(id: string, subagent: string, doc: string, matchedFiles: string[], matchedPatterns: string[] = []) {\n this.id = id;\n this.subagent = subagent;\n this.doc = doc;\n this.matchedFiles = matchedFiles;\n this.matchedPatterns = matchedPatterns;\n }\n}\n\n/**\n * The per-PR facts every reviewer subagent needs GIVEN to it, alongside its own checklist: the exact base\n * sha the gate diffs against and the file holding the complete changed-file set. Both used to live only in\n * a doc the printed instruction told the AI to go read, one indirection away from the instruction to hand\n * them over — so the printed block could not stand on its own. Data-only; empty = omit those lines.\n */\nexport class ChecklistReviewContext {\n baseSha: string; // the 3-point merge-base sha\n prContextPath: string; // path of pr-context.json — the AUTHORITATIVE full changed-file set\n /**\n * The exact command that reproduces ONE file's diff, with a `-- <file>` tail — NOT assembled by the\n * caller. This used to be hardcoded as `git diff <baseSha> HEAD -- <file>`, which returns NOTHING on a\n * dirty tree because the changed-file set is computed base→working-tree. See DiffBasis, which derives\n * this string from the same range the file set came from.\n */\n fileDiffCommand: string;\n diffDir: string; // dir of the MATERIALIZED diff (diff/ALL.diff + diff/files/…); '' when not written\n dirty: boolean; // true ⇒ the range includes uncommitted + untracked work, and must be said out loud\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(baseSha = '', prContextPath = '', fileDiffCommand = '', diffDir = '', dirty = false) {\n this.baseSha = baseSha;\n this.prContextPath = prContextPath;\n this.fileDiffCommand = fileDiffCommand;\n this.diffDir = diffDir;\n this.dirty = dirty;\n }\n}\n\n// The AI-authored review for a PR. The AI writes review.json itself between `wp-start-upsert-pr` (which\n// prints the schema) and `wp-finish-upsert-pr` (which reads it); reviewer subagents write the per-checklist\n// review-<id>.json files. Data-only (per CLAUDE.md).\nexport class ReviewJson {\n title: string; // human PR title describing the change; used as the `gh pr` title (empty → caller falls back)\n riskScore: number; // 0–100, drives the risk bar\n riskLevel: string; // 'green' | 'yellow' | 'red'\n riskEmoji: string; // '🟢' | '🟡' | '🔴' — derived from riskLevel when omitted\n summary: string; // rendered in the dashboard Summary section\n violations: string[]; // pattern/architecture violations; length = the Pattern Violations count\n risks: string[];\n filesToReview: string[];\n results: ChecklistResult[]; // resolved per-checklist verdicts (from review-<id>.json); [] when none\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n title: string,\n riskScore: number,\n riskLevel: string,\n riskEmoji: string,\n summary: string,\n violations: string[],\n risks: string[],\n filesToReview: string[],\n results: ChecklistResult[] = [],\n ) {\n this.title = title;\n this.riskScore = riskScore;\n this.riskLevel = riskLevel;\n this.riskEmoji = riskEmoji;\n this.summary = summary;\n this.violations = violations;\n this.risks = risks;\n this.filesToReview = filesToReview;\n this.results = results;\n }\n}\n\n// A checklist's resolved outcome, shared by review.json enforcement and the dashboard so both agree.\n// PASS, WARN and OVERRIDDEN all ship; FAIL, MISSING and BAD_FORMAT all refuse the PR.\nexport const CK_PASS = 'pass'; // review-<id>.json status:'green'\nexport const CK_WARN = 'warn'; // review-<id>.json status:'yellow' → 🟡 passes WITH concerns\nexport const CK_OVERRIDDEN = 'overridden'; // review-<id>.json status:'red' + non-empty override → 🟠\nexport const CK_FAIL = 'fail'; // review-<id>.json status:'red' + no override → refuse\nexport const CK_MISSING = 'missing'; // no review-<id>.json written → refuse\nexport const CK_BAD_FORMAT = 'bad-format'; // written, but its verdict is unreadable (e.g. legacy `success`)\n\nexport class ChecklistVerdict {\n id: string;\n status: string; // one of CK_PASS | CK_WARN | CK_OVERRIDDEN | CK_FAIL | CK_MISSING | CK_BAD_FORMAT\n detail: string; // reviewer output / override justification / format complaint (dashboard + errors)\n\n constructor(id: string, status: string, detail: string) {\n this.id = id;\n this.status = status;\n this.detail = detail;\n }\n}\n\n// The PR's diff context, written by wp-start-upsert-pr into `.webpieces/pr-review/<branch>/pr-context.json`\n// so a reviewer subagent knows the exact 3-point base the gate used and the full changed-file set — then\n// reads any file's actual diff with `git diff <base> HEAD -- <file>`. This is what lets a checklist match\n// coarsely by path (in the config) while the subagent makes the fine, content-level judgment. Data-only.\nexport class PrContext {\n base: string; // the 3-point merge-base sha the gate diffs against\n /**\n * The real HEAD sha. This was once the literal string 'HEAD', which is not a fact — it cannot be\n * compared later to detect that the tree moved under a review, and it reads as a range that was never\n * actually diffed. Its only reader (reviewContextFor) takes `base`, so recording the sha is free.\n */\n head: string;\n changedFiles: string[]; // every file changed in the range (NOT tsOnly — includes .sql/.gql/Dockerfile/…)\n dirty: boolean; // true ⇒ changedFiles includes uncommitted + untracked work\n dirtyFiles: string[]; // exactly which paths are uncommitted/untracked — why `dirty` is true\n diffCommand: string; // the command that reproduces the WHOLE diff (see DiffBasis; correct when dirty)\n diffDir: string; // dir holding the materialized per-file diffs + ALL.diff; '' when not materialized\n generatedAt: string; // ISO timestamp, so a stale context is detectable rather than silently trusted\n /**\n * Main's head as this clone last saw it — the THIRD hash point, matching the trio the 3-point merge\n * records in `merge-info/<branch>/updatemain-hashes.json`. `base`/`head` above are points A and B\n * under the review side's older names.\n *\n * The review side used to record only A and B, so nothing could answer \"did main move while this was\n * under review?\" — the question you most want answered when a review looks stale. '' when origin/main\n * is unresolvable. Purely informational; nothing gates on it.\n */\n hashMainHead: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n base: string, head: string, changedFiles: string[],\n dirty = false, dirtyFiles: string[] = [], diffCommand = '', diffDir = '', generatedAt = '',\n hashMainHead = '',\n ) {\n this.hashMainHead = hashMainHead;\n this.base = base;\n this.head = head;\n this.changedFiles = changedFiles;\n this.dirty = dirty;\n this.dirtyFiles = dirtyFiles;\n this.diffCommand = diffCommand;\n this.diffDir = diffDir;\n this.generatedAt = generatedAt;\n }\n}\n\nconst RISK_LEVELS = ['green', 'yellow', 'red'] as const;\nconst EMOJI_FOR_LEVEL: Record<string, string> = { green: '🟢', yellow: '🟡', red: '🔴' };\n\n// Where `wp-finish-upsert-pr` retires the review.json it just consumed, and the note it stamps on the way.\n// The key sorts first in the written JSON because it is written first — an AI that opens the file to see\n// whether it can reuse the review reads what the file IS before it reads a title it might be tempted to keep.\nconst OLD_REVIEW_FILE = 'old-review.json';\nconst ARCHIVE_NOTE_KEY = '_ARCHIVED_AUDIT_ONLY';\nconst ARCHIVE_NOTE =\n 'ARCHIVE — this is the review from the PREVIOUS wp-finish-upsert-pr run on this branch, kept for audit ' +\n 'purposes only. It is NOT the review for a new review round: it describes the code as of the last PR ' +\n 'update, which has since moved. If you are reviewing again, write a FRESH review.json at the path ' +\n 'pnpm wp-review-upsert-pr prints; do not copy this file\\'s title, summary or risk level forward without ' +\n 're-deciding each one. Overwritten by every finish, so only the most recent review is ever here.';\n\n/** Locates + loads/validates the AI-authored review.json. `@injectable(bindingScopeValues.Singleton)` so it's drawn in the design. */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewJsonService {\n constructor(private readonly dotDir: DotWebpieces = dotWebpieces) {}\n\n // The per-feature PR working dir: `<local .webpieces>/pr-review/<feature>`. LOCAL scope — a PR\n // review belongs to the worktree that is preparing it, and two worktrees never share one.\n prDirFor(repoRoot: string, featureName: string): string {\n return this.dotDir.localFile(repoRoot, PR_REVIEW_DIR, featureName);\n }\n\n // Absolute path of the review.json for a feature — beside pr-body.md, keyed by branch name.\n reviewJsonPath(repoRoot: string, featureName: string): string {\n return path.join(this.prDirFor(repoRoot, featureName), 'review.json');\n }\n\n // Absolute path of the pr-context.json for a feature (the diff base/head + changed files).\n prContextPath(repoRoot: string, featureName: string): string {\n return path.join(this.prDirFor(repoRoot, featureName), 'pr-context.json');\n }\n\n // Where a consumed review.json is archived to, beside it. Always the SAME path — it holds the last\n // review and only the last one, so it can never be mistaken for a series that means something.\n oldReviewJsonPath(reviewJsonFilePath: string): string {\n return path.join(path.dirname(reviewJsonFilePath), OLD_REVIEW_FILE);\n }\n\n /**\n * Retire the review `wp-finish-upsert-pr` just used: move review.json to old-review.json, stamped with a\n * note saying what it is. Returns the archive path, or '' when there was nothing to archive.\n *\n * The point is the MOVE, not the copy. review.json left in place after a PR is posted is a live-looking\n * file describing a review that already happened, and the next run of stage ② on this branch finds it\n * sitting there — so a reviewer subagent that judges the PR's stated intent (its title, summary or risk\n * level) can read the previous run's review and return GREEN against a title that no longer exists.\n * Nothing in the verdict distinguishes that from a real pass. Moving it means the only way to reach\n * finish again is to write a fresh one, and {@link loadReviewJson} points at the archive when it is\n * missing so the archive reads as an audit trail rather than as a lost file.\n *\n * Called only after the PR is actually up: a finish that failed before publishing must stay re-runnable.\n */\n archiveReviewJson(reviewJsonFilePath: string): string {\n if (!fs.existsSync(reviewJsonFilePath)) return '';\n const archivePath = this.oldReviewJsonPath(reviewJsonFilePath);\n const raw = fs.readFileSync(reviewJsonFilePath, 'utf8');\n fs.writeFileSync(archivePath, this.archivedBody(raw));\n fs.rmSync(reviewJsonFilePath);\n return archivePath;\n }\n\n /**\n * The archived bytes: the original review with the AUDIT-ONLY note as its FIRST key, so anything that\n * opens the file — human or AI — reads what it is before it reads any of its content.\n *\n * Falls back to the raw bytes when they do not parse. By here `loadReviewJson` has already accepted the\n * file, so that is close to impossible; preserving the original beats losing it to a stamping failure.\n */\n private archivedBody(raw: string): string {\n const parsed = this.tryParseObject(raw);\n if (parsed === null) return raw;\n // webpieces-disable no-any-unknown -- re-serializing opaque review fields verbatim; only the key ORDER is ours\n const stamped: Record<string, unknown> = {};\n stamped[ARCHIVE_NOTE_KEY] = ARCHIVE_NOTE;\n for (const key of Object.keys(parsed)) stamped[key] = parsed[key];\n return JSON.stringify(stamped, null, 2) + '\\n';\n }\n\n // webpieces-disable no-any-unknown -- opaque parsed JSON; the caller only re-serializes it\n private tryParseObject(raw: string): Record<string, unknown> | null {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: unparseable bytes are archived verbatim, never fatal\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque; only its key order is used\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null;\n return parsed;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * Persist the PR's diff context so reviewer subagents can read the changed-file set + the exact base\n * sha. Returns the file path written.\n *\n * ALSO writes an immutable per-stage snapshot under `stages/<stage>.json` when `stage` is given.\n * `pr-context.json` is overwritten by each stage, so by the time anything goes wrong the earlier\n * states are gone — and \"what did the tooling think the diff was at stage ①, vs ②, vs ③?\" is exactly\n * the question you need answered when debugging a review that went sideways. The snapshots make the\n * review system auditable by an AI reviewing IT, which the single mutable file never could.\n */\n writePrContext(repoRoot: string, featureName: string, context: PrContext, stage = ''): string {\n const dir = this.prDirFor(repoRoot, featureName);\n fs.mkdirSync(dir, { recursive: true });\n const body = JSON.stringify(context, null, 2) + '\\n';\n const p = this.prContextPath(repoRoot, featureName);\n fs.writeFileSync(p, body);\n if (stage !== '') {\n const stagesDir = path.join(dir, 'stages');\n fs.mkdirSync(stagesDir, { recursive: true });\n fs.writeFileSync(path.join(stagesDir, `${stage}.json`), body);\n }\n return p;\n }\n\n /**\n * The review context for a feature, recovered from the pr-context.json wp-start-upsert-pr already wrote.\n * Lets wp-finish-upsert-pr's \"you still owe me review-<id>.json\" message inline the SAME self-sufficient\n * per-reviewer block start printed, instead of a checklist name and an indirection. Empty when the file\n * is absent or unreadable — the block then just omits those lines.\n */\n reviewContextFor(repoRoot: string, featureName: string): ChecklistReviewContext {\n const p = this.prContextPath(repoRoot, featureName);\n if (!fs.existsSync(p)) return new ChecklistReviewContext();\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unreadable context file degrades to fewer printed lines, never a crash\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed on the next line\n const raw = JSON.parse(fs.readFileSync(p, 'utf8')) as Record<string, unknown>;\n const base = typeof raw['base'] === 'string' ? (raw['base'] as string) : '';\n // Recover the REPRODUCE command rather than re-deriving it: a context written by an older\n // pr-gate has no diffCommand, and guessing `<base> HEAD` there would resurrect the exact\n // empty-on-a-dirty-tree bug this field exists to kill. Absent ⇒ omit the line entirely.\n const cmd = typeof raw['diffCommand'] === 'string' ? (raw['diffCommand'] as string) : '';\n const diffDir = typeof raw['diffDir'] === 'string' ? (raw['diffDir'] as string) : '';\n const dirty = raw['dirty'] === true;\n return new ChecklistReviewContext(base, p, cmd === '' ? '' : `${cmd} -- <file>`, diffDir, dirty);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return new ChecklistReviewContext('', p);\n }\n }\n\n // Copy-paste schema both commands print. `required` is the set of checklists the diff MATCHED; empty\n // ⇒ output identical to a repo with no checklists. Non-empty ⇒ appends per-checklist instructions\n // naming the reviewer subagent + doc + the review-<id>.json to write.\n reviewJsonSchemaHint(filePath: string): string {\n return (\n `Write your PR review to:\\n ${filePath}\\n\\n` +\n `with this exact JSON shape (riskEmoji optional — derived from riskLevel):\\n\\n` +\n `{\\n` +\n ` \"title\": \"concise PR title describing the change (imperative, no branch names)\",\\n` +\n ` \"riskScore\": 0, // integer 0–100 (higher = riskier)\\n` +\n ` \"riskLevel\": \"green | yellow | red\",\\n` +\n ` \"summary\": \"5–10 sentence review summary\",\\n` +\n ` \"violations\": [\"pattern/architecture violations you found (empty array if none)\"],\\n` +\n ` \"risks\": [\"notable risks (empty array if none)\"],\\n` +\n ` \"filesToReview\": [\"paths a human should look at (empty array if none)\"]\\n` +\n `}`\n );\n }\n\n /**\n * The extra line the \"no review.json\" complaint carries when a PREVIOUS review was archived here. It\n * turns a bare \"not found\" — which reads as data loss, and invites hunting for the file — into the fact:\n * the last finish consumed it, and the archive is audit material, not a review to reuse.\n */\n private archivedReviewHint(filePath: string): string {\n const archive = this.oldReviewJsonPath(filePath);\n if (!fs.existsSync(archive)) return '';\n return `\\nA PREVIOUS review was archived to ${archive} when the last pnpm wp-finish-upsert-pr consumed it.\\n` +\n `That file is for AUDIT ONLY — it reviews code this branch has since moved past. Write a fresh one:`;\n }\n\n // The per-checklist review file path that sits beside review.json: review-<id>.json.\n checklistResultPath(reviewJsonFilePath: string, checklistId: string): string {\n return path.join(path.dirname(reviewJsonFilePath), `review-${checklistId}.json`);\n }\n\n /**\n * Load + validate the AI-authored review.json. Throws InformAiError (with the schema) when missing,\n * unparseable, or structurally wrong. `required` is the set of checklists the diff matched: every one\n * must have a well-formed, passing (or overridden) review-<id>.json or a validation error is raised\n * alongside the usual ones so the AI gets ONE message.\n */\n // webpieces-disable max-lines-new-methods -- one cohesive load+validate pass over the review fields\n loadReviewJson(filePath: string, required: readonly RequiredChecklist[] = []): ReviewJson {\n if (!fs.existsSync(filePath)) {\n throw new InformAiError(\n `Required review.json not found.${this.archivedReviewHint(filePath)}\\n\\n` +\n `${this.reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n\n const raw = this.parseReviewJson(fs.readFileSync(filePath, 'utf8'), filePath);\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new InformAiError(`review.json must be a JSON object.\\n\\n${this.reviewJsonSchemaHint(filePath)}`);\n }\n\n const errors: string[] = [];\n\n const riskScore = raw['riskScore'];\n if (typeof riskScore !== 'number' || !Number.isFinite(riskScore) || riskScore < 0 || riskScore > 100) {\n errors.push(`\"riskScore\" must be a number 0–100, got ${JSON.stringify(riskScore)}.`);\n }\n\n const riskLevel = raw['riskLevel'];\n if (typeof riskLevel !== 'string' || !RISK_LEVELS.includes(riskLevel as typeof RISK_LEVELS[number])) {\n errors.push(`\"riskLevel\" must be one of: ${RISK_LEVELS.join(', ')}.`);\n }\n\n const title = typeof raw['title'] === 'string' ? (raw['title'] as string).trim() : '';\n if (title === '') {\n errors.push('\"title\" must be a non-empty, imperative PR title describing the change (no branch names).');\n }\n\n const results = this.loadChecklistResults(filePath, required);\n for (const err of this.requiredChecklistErrors(required, results)) errors.push(err);\n\n if (errors.length > 0) {\n throw new InformAiError(\n `review.json has ${errors.length} error(s) — fix ALL, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\n${this.reviewJsonSchemaHint(filePath)}`,\n );\n }\n\n const level = riskLevel as string;\n const emoji = typeof raw['riskEmoji'] === 'string' && raw['riskEmoji'] !== ''\n ? (raw['riskEmoji'] as string)\n : (EMOJI_FOR_LEVEL[level] ?? '🟡');\n const summary = typeof raw['summary'] === 'string' ? (raw['summary'] as string) : '';\n\n return new ReviewJson(\n title,\n riskScore as number,\n level,\n emoji,\n summary,\n this.asStringArray(raw['violations']),\n this.asStringArray(raw['risks']),\n this.asStringArray(raw['filesToReview']),\n results,\n );\n }\n\n /**\n * The checklists that still OWE a verdict: no review-<id>.json at all, a malformed one, or one whose\n * verdict is an un-overridden FAIL. This is the set every message lists — a checklist already PASSed or\n * OVERRIDDEN on this branch is deliberately NOT re-listed, because re-instructing it invites a redundant\n * second run and reads as though the earlier verdict did not count.\n */\n pendingChecklists(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[] {\n return required.filter((req: RequiredChecklist): boolean => {\n const status = this.resolveVerdict(req, results).status;\n // CK_WARN must be listed here beside PASS/OVERRIDDEN. A yellow verdict SHIPS — leaving it out\n // would mark the checklist owed forever, so `outstanding` never empties and the PR is refused\n // permanently no matter how many times the reviewer runs.\n return status !== CK_PASS && status !== CK_WARN && status !== CK_OVERRIDDEN;\n });\n }\n\n // Read the per-checklist verdict files `review-<id>.json` beside review.json — one per matched checklist.\n // A missing file is simply absent from the result (→ counts as MISSING for that checklist); a malformed\n // one is skipped (a stale review-<id>.json never wedges the branch).\n loadChecklistResults(reviewJsonFilePath: string, required: readonly RequiredChecklist[]): ChecklistResult[] {\n const results: ChecklistResult[] = [];\n for (const req of required) {\n const p = this.checklistResultPath(reviewJsonFilePath, req.id);\n if (!fs.existsSync(p)) continue;\n const parsed = this.parseChecklistResult(p, req.id);\n if (parsed) results.push(parsed);\n }\n return results;\n }\n\n // Resolve ONE checklist's verdict from its review-<id>.json. Central so review.json enforcement AND the\n // finish-command dashboard agree on the outcome. `problem` is checked FIRST: a file whose verdict cannot\n // be read must not fall through to any shipping outcome.\n resolveVerdict(req: RequiredChecklist, results: readonly ChecklistResult[]): ChecklistVerdict {\n const result = results.find((r: ChecklistResult): boolean => r.id === req.id);\n if (!result) return new ChecklistVerdict(req.id, CK_MISSING, '');\n if (result.problem !== '') return new ChecklistVerdict(req.id, CK_BAD_FORMAT, result.problem);\n if (result.status === VERDICT_GREEN) return new ChecklistVerdict(req.id, CK_PASS, result.output);\n if (result.status === VERDICT_YELLOW) return new ChecklistVerdict(req.id, CK_WARN, result.output);\n if (result.override.trim() !== '') return new ChecklistVerdict(req.id, CK_OVERRIDDEN, result.override.trim());\n return new ChecklistVerdict(req.id, CK_FAIL, result.output);\n }\n\n /**\n * One loud complaint per checklist whose verdict file EXISTS but cannot be read as a verdict — almost\n * always one still using the removed `success` field. Public and separate from\n * {@link requiredChecklistErrors} because `wp-finish-upsert-pr` refuses on missing reviewers BEFORE it\n * parses review.json: without this, a legacy file would surface as the generic \"no verdict yet\" block\n * and the AI would re-run a reviewer that already ran instead of fixing four characters of JSON.\n */\n checklistFormatErrors(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): string[] {\n const errors: string[] = [];\n for (const req of required) {\n const verdict = this.resolveVerdict(req, results);\n if (verdict.status === CK_BAD_FORMAT) errors.push(verdict.detail);\n }\n return errors;\n }\n\n // Every matched checklist whose verdict is FAIL (reviewed, found a problem, no override) or MISSING (no\n // review-<id>.json written) → one error each, printing the reviewer's `output` verbatim.\n private requiredChecklistErrors(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): string[] {\n // Format complaints come from the ONE renderer, so wp-review-upsert-pr and wp-finish word them identically.\n const errors: string[] = this.checklistFormatErrors(required, results);\n for (const req of required) {\n const verdict = this.resolveVerdict(req, results);\n // CK_WARN ('yellow' — passed with concerns) is deliberately absent from this chain: it SHIPS.\n // The concern still reaches the PR, published in the checklist comment. Do not \"fix\" this.\n if (verdict.status === CK_FAIL) {\n errors.push(\n `Checklist \"${req.id}\" FAILED review (status:\"${VERDICT_RED}\"). The reviewer (${req.subagent}) wrote:\\n ` +\n `${verdict.detail.split('\\n').join('\\n ')}\\n` +\n ` Fix it, then re-run; or set a non-empty \"override\" in ${this.checklistFileName(req.id)} to ship anyway with a stated justification.`,\n );\n } else if (verdict.status === CK_MISSING) {\n const doc = req.doc.trim() !== '' ? ` Read: ${req.doc}.` : '';\n errors.push(\n `Checklist \"${req.id}\" MATCHED this diff but has no verdict. Spawn the \"${req.subagent}\" subagent to review it, ` +\n `then write ${this.checklistFileName(req.id)} with ` +\n `{\"id\":\"${req.id}\",\"status\":\"${VERDICT_GREEN}\",\"output\":\"…\",\"override\":\"\"}.${doc}`,\n );\n }\n }\n return errors;\n }\n\n private checklistFileName(checklistId: string): string {\n return `review-${checklistId}.json`;\n }\n\n /**\n * THE renderer for a reviewer's verdict schema — with the reviewer's own `id` already filled in and,\n * when known, the exact file it must write.\n *\n * There is one because a verdict schema that lives anywhere a human maintains it goes stale, and a\n * reviewer follows the stale copy. That is not a hypothetical: when `success` was replaced by the\n * tri-state `status`, hand-written `.claude/agents/*.md` files kept documenting `success`, and a real\n * PR had to carry \"the verdict format in your own agent .md file is OUT OF DATE\" in the spawn prompt to\n * work around it. Every printed copy — the stage-② roster, the generated per-reviewer instructions\n * file, and the complaint raised against a malformed verdict — now comes from here.\n *\n * `verdictPath` may be '' when the caller is describing the shape rather than a specific file.\n */\n verdictSchemaFor(id: string, verdictPath = '', indent = ' '): string {\n const lines = [\n `${indent}{ \"id\": \"${id}\", \"status\": \"${VERDICT_GREEN} | ${VERDICT_YELLOW} | ${VERDICT_RED}\", ` +\n `\"output\": \"what you checked / found\", \"override\": \"\" }`,\n `${indent} ${VERDICT_GREEN} → passes, nothing to flag`,\n `${indent} ${VERDICT_YELLOW} → passes WITH CONCERNS; nothing is blocked and the concern is published on the PR`,\n `${indent} ${VERDICT_RED} → REFUSES the PR (set a non-empty \"override\" to ship anyway with a stated justification)`,\n `${indent}Prefer \"${VERDICT_YELLOW}\" over red-plus-override when the change is acceptable but worth a human's`,\n `${indent}attention — an override reads as a deliberately-accepted defect, a yellow reads as a note.`,\n ];\n if (verdictPath !== '') lines.push(`${indent}File: ${verdictPath}`);\n return lines.join('\\n');\n }\n\n /**\n * Parse one review-<id>.json into a ChecklistResult. `null` ONLY when the bytes do not parse as a JSON\n * object at all — that tolerance is why a half-written file never wedges a branch, and it degrades to\n * the same \"no verdict yet\" message as an absent file, which is honest (nothing readable is there).\n *\n * A file that DOES parse always yields a result, even when its verdict is unreadable, carrying the\n * complaint in `problem`. Returning `null` for those instead would collapse \"wrote a verdict in the old\n * format\" into \"never wrote a verdict\" and send the AI off to re-run a reviewer that already ran.\n */\n // webpieces-disable no-any-unknown -- opaque parsed JSON, narrowed field-by-field\n private parseChecklistResult(filePath: string, id: string): ChecklistResult | null {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unparseable per-checklist file is skipped, not fatal\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed below\n const raw = JSON.parse(fs.readFileSync(filePath, 'utf8')) as Record<string, unknown>;\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return null;\n const output = typeof raw['output'] === 'string' ? (raw['output'] as string) : '';\n const override = typeof raw['override'] === 'string' ? (raw['override'] as string) : '';\n const status = typeof raw['status'] === 'string' ? (raw['status'] as string).trim().toLowerCase() : '';\n return new ChecklistResult(id, status, output, override, this.statusProblem(filePath, id, status, raw));\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * '' when `status` is one of the three colors. Otherwise the complaint to show the AI verbatim. The\n * legacy-`success` case gets its OWN message: `success` was removed outright (no compatibility mode),\n * and a reviewer told only \"status must be green|yellow|red\" cannot tell whether it wrote the wrong\n * value or is using a field that no longer exists.\n */\n // webpieces-disable no-any-unknown -- opaque parsed JSON; only tested for key presence here\n private statusProblem(filePath: string, id: string, status: string, raw: Record<string, unknown>): string {\n // webpieces-disable no-any-unknown -- comparing against the readonly literal tuple of valid colors\n if ((VERDICT_STATUSES as readonly string[]).includes(status)) return '';\n // The ONE renderer — see verdictSchemaFor. A second copy here is what let the old `success` shape\n // survive in print after it was removed from the parser.\n const shape = this.verdictSchemaFor(id, filePath);\n if ('success' in raw) {\n return `Checklist \"${id}\" wrote its verdict with the REMOVED \"success\" field. It is now a tri-state ` +\n `\"status\" — there is no compatibility mode. Rewrite the file as:\\n${shape}`;\n }\n return `Checklist \"${id}\" wrote a verdict with no valid \"status\" (got ${JSON.stringify(status)}). ` +\n `It must be exactly one of ${VERDICT_STATUSES.join(', ')}:\\n${shape}`;\n }\n\n // webpieces-disable no-any-unknown -- opaque parsed JSON value, narrowed to string[] here\n private asStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n // webpieces-disable no-any-unknown -- element of an opaque JSON array, narrowed by the type guard\n return value.filter((v: unknown): v is string => typeof v === 'string');\n }\n\n // Parse opaque AI-authored JSON, converting a SyntaxError into a readable InformAiError.\n // webpieces-disable no-any-unknown -- returns the opaque parsed object; loadReviewJson narrows each field\n private parseReviewJson(raw: string, filePath: string): Record<string, unknown> {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: convert JSON.parse SyntaxError to an InformAiError for the AI\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed by the caller\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(\n `review.json is not valid JSON (${error.message}).\\n\\n${this.reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n }\n}\n\n// Temporary migration delegators to ReviewJsonService — removed once consumers inject it.\nconst reviewJsonSvc = new ReviewJsonService();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function prDirFor(repoRoot: string, featureName: string): string {\n return reviewJsonSvc.prDirFor(repoRoot, featureName);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function reviewJsonPath(repoRoot: string, featureName: string): string {\n return reviewJsonSvc.reviewJsonPath(repoRoot, featureName);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function reviewJsonSchemaHint(filePath: string): string {\n return reviewJsonSvc.reviewJsonSchemaHint(filePath);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function loadReviewJson(filePath: string, required: readonly RequiredChecklist[] = []): ReviewJson {\n return reviewJsonSvc.loadReviewJson(filePath, required);\n}\n"]}
|