@webpieces/rules-config 0.4.731 → 0.4.733

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ReviewJsonService = exports.PrContext = exports.ChecklistVerdict = exports.CK_BAD_FORMAT = exports.CK_MISSING = exports.CK_FAIL = exports.CK_OVERRIDDEN = exports.CK_WARN = exports.CK_PASS = exports.ReviewJson = exports.ChecklistReviewContext = exports.RequiredChecklist = exports.ChecklistResult = exports.VERDICT_STATUSES = exports.VERDICT_RED = exports.VERDICT_YELLOW = exports.VERDICT_GREEN = void 0;
3
+ exports.ReviewJsonService = exports.PrContext = exports.ChecklistVerdict = exports.CK_BAD_FORMAT = exports.CK_MISSING = exports.CK_FAIL = exports.CK_OVERRIDDEN = exports.CK_WARN = exports.CK_PASS = exports.ReviewJson = exports.ChecklistReviewContext = exports.RequiredChecklist = exports.ChecklistResult = exports.VERDICT_STATUSES = exports.VERDICT_RED = exports.VERDICT_YELLOW = exports.VERDICT_GREEN = exports.checklistOverrideService = exports.ChecklistOverrideService = exports.ChecklistOverride = void 0;
4
4
  exports.prDirFor = prDirFor;
5
5
  exports.reviewJsonPath = reviewJsonPath;
6
6
  exports.reviewJsonSchemaHint = reviewJsonSchemaHint;
@@ -12,6 +12,10 @@ const constants_1 = require("./constants");
12
12
  const state_dir_1 = require("./state-dir");
13
13
  const inform_ai_error_1 = require("./inform-ai-error");
14
14
  const to_error_1 = require("./to-error");
15
+ const checklist_override_1 = require("./checklist-override");
16
+ Object.defineProperty(exports, "ChecklistOverride", { enumerable: true, get: function () { return checklist_override_1.ChecklistOverride; } });
17
+ Object.defineProperty(exports, "ChecklistOverrideService", { enumerable: true, get: function () { return checklist_override_1.ChecklistOverrideService; } });
18
+ Object.defineProperty(exports, "checklistOverrideService", { enumerable: true, get: function () { return checklist_override_1.checklistOverrideService; } });
15
19
  const review_json_data_1 = require("./review-json-data");
16
20
  Object.defineProperty(exports, "VERDICT_GREEN", { enumerable: true, get: function () { return review_json_data_1.VERDICT_GREEN; } });
17
21
  Object.defineProperty(exports, "VERDICT_YELLOW", { enumerable: true, get: function () { return review_json_data_1.VERDICT_YELLOW; } });
@@ -53,8 +57,10 @@ const CHECKLIST_ARCHIVE_NOTE = 'ARCHIVE — this is a checklist verdict from a P
53
57
  /** Locates + loads/validates the AI-authored review.json. `@injectable(bindingScopeValues.Singleton)` so it's drawn in the design. */
54
58
  let ReviewJsonService = class ReviewJsonService {
55
59
  dotDir;
56
- constructor(dotDir = state_dir_1.dotWebpieces) {
60
+ overrides;
61
+ constructor(dotDir = state_dir_1.dotWebpieces, overrides = checklist_override_1.checklistOverrideService) {
57
62
  this.dotDir = dotDir;
63
+ this.overrides = overrides;
58
64
  }
59
65
  // The per-feature PR working dir: `<worktree>/.webpieces/pr-review/<feature>`. AI-WRITABLE scope,
60
66
  // not local() — an agent AUTHORS review.json here, and each reviewer subagent authors its own
@@ -295,7 +301,7 @@ let ReviewJsonService = class ReviewJsonService {
295
301
  errors.push('"title" must be a non-empty, imperative PR title describing the change (no branch names).');
296
302
  }
297
303
  const results = this.loadChecklistResults(filePath, required);
298
- for (const err of this.requiredChecklistErrors(required, results))
304
+ for (const err of this.requiredChecklistErrors(required, results, filePath))
299
305
  errors.push(err);
300
306
  if (errors.length > 0) {
301
307
  throw new inform_ai_error_1.InformAiError(`review.json has ${errors.length} error(s) — fix ALL, then re-run pnpm wp-finish-upsert-pr:\n\n` +
@@ -362,27 +368,43 @@ let ReviewJsonService = class ReviewJsonService {
362
368
  * It always quotes the reviewer's own `output` verbatim: the finding is the whole point, and an error
363
369
  * that names a checklist without saying what it objected to gives the reader nothing to fix.
364
370
  *
365
- * `archivedPath` non-empty the verdict has just been RETIRED (moved) to that path, so the message must
366
- * change in two ways. It says where the record went otherwise the move reads as data loss — and,
367
- * critically, it must NOT tell the reader to "set override in review-<id>.json", because that file no
368
- * longer exists. The escape hatch is therefore worded as writing a FRESH verdict file (the body can be
369
- * copied back out of the archive) with a human-authored override.
371
+ * IT NAMES THE WRITER, which is the half that was missing. The old text said WHAT to write ("set a
372
+ * non-empty override") and WHERE, but never WHO MAYso the only reachable move was an agent editing a
373
+ * reviewer's verdict file in place, which the harness denies, which is how a human ended up hand-editing
374
+ * JSON. The ship-anyway route is now a SEPARATE file the coordinating agent may write, and the command
375
+ * that writes it is printed ready to run. See {@link ChecklistOverrideService.writerRule}.
376
+ *
377
+ * `archivedPath` non-empty ⇒ the verdict has just been RETIRED (moved) to that path, so the message says
378
+ * where the record went — otherwise the move reads as data loss — and that a FRESH verdict is required.
379
+ * The AUTHORIZATION is unaffected by that move: `override-<id>.json` is a different file, it survives the
380
+ * retirement, and a human who already decided to accept this checklist is never asked again.
370
381
  */
371
- refusalError(req, verdict, archivedPath = '') {
382
+ // eslint-disable-next-line @typescript-eslint/max-params
383
+ refusalError(req, verdict, reviewJsonFilePath, archivedPath = '') {
372
384
  const finding = `${verdict.detail.split('\n').join('\n ')}\n`;
373
385
  const head = `Checklist "${req.id}" FAILED review (status:"${review_json_data_1.VERDICT_RED}"). The reviewer (${req.subagent}) wrote:\n ` + finding;
374
- if (archivedPath === '') {
375
- return head +
376
- ` Fix it, then re-run; or set a non-empty "override" in ${this.checklistFileName(req.id)} to ship anyway with a stated justification.`;
377
- }
378
- // Re-spawning is the LAST thing said, and only after the finding, because an instruction to spawn a
379
- // subagent is the one line an AI acts on first see refusedChecklists for what that cost.
380
- return head +
381
- ` That verdict has been RETIRED to ${archivedPath} (audit only — it is not a live verdict).\n` +
382
- ` A FRESH ${this.checklistFileName(req.id)} is now required. Fix the finding first, then have the ` +
383
- `"${req.subagent}" subagent review again and write a new verdict.\n` +
384
- ` To ship anyway, a HUMAN must decide it: write a fresh ${this.checklistFileName(req.id)} (you may copy the ` +
385
- `body back from the archive) carrying a non-empty, human-authored "override" justification.`;
386
+ const retired = archivedPath === ''
387
+ ? ' Fix it, then re-run.\n'
388
+ // Re-spawning is said only after the finding, because an instruction to spawn a subagent is the
389
+ // one line an AI acts on first — see refusedChecklists for what that cost.
390
+ : ` That verdict has been RETIRED to ${archivedPath} (audit only it is not a live verdict).\n` +
391
+ ` A FRESH ${this.checklistFileName(req.id)} is now required. Fix the finding first, then have the ` +
392
+ `"${req.subagent}" subagent review again and write a new verdict.\n`;
393
+ return head + retired + this.overrideRoute(req, reviewJsonFilePath);
394
+ }
395
+ /**
396
+ * The ship-anyway paragraph: who may authorize, and the exact command that records it.
397
+ *
398
+ * Its own method because every refusal surface must say the identical thing about who may write an
399
+ * override. A second copy of this paragraph is precisely how the previous one drifted into naming a
400
+ * command that had since been deleted.
401
+ */
402
+ overrideRoute(req, reviewJsonFilePath) {
403
+ return ` To SHIP ANYWAY a human must decide it, and the decision is recorded in its own file — `
404
+ + `${this.overrides.overrideFileName(req.id)}, never inside the reviewer's verdict.\n`
405
+ + ` ${this.overrides.writerRule()}\n`
406
+ + ' Run exactly this, replacing only the "reason" with what the human actually said:\n\n'
407
+ + `${this.overrides.writeCommand(reviewJsonFilePath, req.id)}\n`;
386
408
  }
387
409
  // Read the per-checklist verdict files `review-<id>.json` beside review.json — one per matched checklist.
388
410
  // A missing file is simply absent from the result (→ counts as MISSING for that checklist); a malformed
@@ -399,7 +421,9 @@ let ReviewJsonService = class ReviewJsonService {
399
421
  const p = this.checklistResultPath(reviewJsonFilePath, req.id);
400
422
  if (!fs.existsSync(p))
401
423
  continue;
402
- const parsed = this.parseChecklistResult(p, req.id);
424
+ // The human's authorization is read from its OWN file beside the verdict, in the same pass, so
425
+ // resolveVerdict never touches disk and every command resolves one outcome from one read.
426
+ const parsed = this.parseChecklistResult(p, req.id, this.overrides.load(reviewJsonFilePath, req.id));
403
427
  if (parsed)
404
428
  results.push(parsed);
405
429
  }
@@ -418,8 +442,14 @@ let ReviewJsonService = class ReviewJsonService {
418
442
  return new review_json_data_1.ChecklistVerdict(req.id, review_json_data_1.CK_PASS, result.output);
419
443
  if (result.status === review_json_data_1.VERDICT_YELLOW)
420
444
  return new review_json_data_1.ChecklistVerdict(req.id, review_json_data_1.CK_WARN, result.output);
421
- if (result.override.trim() !== '')
422
- return new review_json_data_1.ChecklistVerdict(req.id, review_json_data_1.CK_OVERRIDDEN, result.override.trim());
445
+ const override = result.override;
446
+ // A malformed authorization is reported as a FORMAT problem, never treated as one: an override with
447
+ // no stated reason authorizes nothing, and silently ignoring it would tell the reader their decision
448
+ // was not recorded without ever saying why.
449
+ if (override !== null && override.problem !== '')
450
+ return new review_json_data_1.ChecklistVerdict(req.id, review_json_data_1.CK_BAD_FORMAT, override.problem);
451
+ if (override !== null)
452
+ return new review_json_data_1.ChecklistVerdict(req.id, review_json_data_1.CK_OVERRIDDEN, this.overrides.detail(override));
423
453
  return new review_json_data_1.ChecklistVerdict(req.id, review_json_data_1.CK_FAIL, result.output);
424
454
  }
425
455
  /**
@@ -440,7 +470,7 @@ let ReviewJsonService = class ReviewJsonService {
440
470
  }
441
471
  // Every matched checklist whose verdict is FAIL (reviewed, found a problem, no override) or MISSING (no
442
472
  // review-<id>.json written) → one error each, printing the reviewer's `output` verbatim.
443
- requiredChecklistErrors(required, results) {
473
+ requiredChecklistErrors(required, results, filePath) {
444
474
  // Format complaints come from the ONE renderer, so wp-review-upsert-pr and wp-finish word them identically.
445
475
  const errors = this.checklistFormatErrors(required, results);
446
476
  for (const req of required) {
@@ -450,7 +480,7 @@ let ReviewJsonService = class ReviewJsonService {
450
480
  if (verdict.status === review_json_data_1.CK_FAIL) {
451
481
  // Through the ONE renderer, so this path and the command layer's refusal say the same thing.
452
482
  // No archive path here: this is validation, not the act of retiring the verdict.
453
- errors.push(this.refusalError(req, verdict));
483
+ errors.push(this.refusalError(req, verdict, filePath));
454
484
  }
455
485
  else if (verdict.status === review_json_data_1.CK_MISSING) {
456
486
  // An OPTIONAL checklist with no verdict was legitimately not run — the human was offered it
@@ -462,7 +492,7 @@ let ReviewJsonService = class ReviewJsonService {
462
492
  const doc = req.doc.trim() !== '' ? ` Read: ${req.doc}.` : '';
463
493
  errors.push(`Checklist "${req.id}" MATCHED this diff but has no verdict. Spawn the "${req.subagent}" subagent to review it, ` +
464
494
  `then write ${this.checklistFileName(req.id)} with ` +
465
- `{"id":"${req.id}","status":"${review_json_data_1.VERDICT_GREEN}","output":"…","override":""}.${doc}`);
495
+ `{"id":"${req.id}","status":"${review_json_data_1.VERDICT_GREEN}","output":"…"}.${doc}`);
466
496
  }
467
497
  }
468
498
  return errors;
@@ -486,12 +516,17 @@ let ReviewJsonService = class ReviewJsonService {
486
516
  verdictSchemaFor(id, verdictPath = '', indent = ' ') {
487
517
  const lines = [
488
518
  `${indent}{ "id": "${id}", "status": "${review_json_data_1.VERDICT_GREEN} | ${review_json_data_1.VERDICT_YELLOW} | ${review_json_data_1.VERDICT_RED}", ` +
489
- `"output": "what you checked / found", "override": "" }`,
519
+ `"output": "what you checked / found" }`,
490
520
  `${indent} ${review_json_data_1.VERDICT_GREEN} → passes, nothing to flag`,
491
521
  `${indent} ${review_json_data_1.VERDICT_YELLOW} → passes WITH CONCERNS; nothing is blocked and the concern is published on the PR`,
492
- `${indent} ${review_json_data_1.VERDICT_RED} → REFUSES the PR (set a non-empty "override" to ship anyway with a stated justification)`,
493
- `${indent}Prefer "${review_json_data_1.VERDICT_YELLOW}" over red-plus-override when the change is acceptable but worth a human's`,
494
- `${indent}attention an override reads as a deliberately-accepted defect, a yellow reads as a note.`,
522
+ `${indent} ${review_json_data_1.VERDICT_RED} → REFUSES the PR; your "output" is printed verbatim`,
523
+ `${indent}Prefer "${review_json_data_1.VERDICT_YELLOW}" over red when the change is acceptable but worth a human's attention —`,
524
+ `${indent}a red a human then authorizes reads as a deliberately-accepted defect, a yellow reads as a note.`,
525
+ // The one sentence that stops a reviewer doing what a reviewer did once: telling the human to run
526
+ // a command, on its own authority, to get past its own finding.
527
+ `${indent}THERE IS NO "override" FIELD HERE, and you NEVER write one. A reviewer does not authorize`,
528
+ `${indent}shipping past its own finding: if this needs a human's decision, SAY SO in "output" and STOP.`,
529
+ `${indent}The coordinating agent is the one with the human, and records that decision in override-${id}.json.`,
495
530
  ];
496
531
  if (verdictPath !== '')
497
532
  lines.push(`${indent}File: ${verdictPath}`);
@@ -507,7 +542,7 @@ let ReviewJsonService = class ReviewJsonService {
507
542
  * format" into "never wrote a verdict" and send the AI off to re-run a reviewer that already ran.
508
543
  */
509
544
  // webpieces-disable no-any-unknown -- opaque parsed JSON, narrowed field-by-field
510
- parseChecklistResult(filePath, id) {
545
+ parseChecklistResult(filePath, id, override) {
511
546
  // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unparseable per-checklist file is skipped, not fatal
512
547
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
513
548
  try {
@@ -516,9 +551,8 @@ let ReviewJsonService = class ReviewJsonService {
516
551
  if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
517
552
  return null;
518
553
  const output = typeof raw['output'] === 'string' ? raw['output'] : '';
519
- const override = typeof raw['override'] === 'string' ? raw['override'] : '';
520
554
  const status = typeof raw['status'] === 'string' ? raw['status'].trim().toLowerCase() : '';
521
- return new review_json_data_1.ChecklistResult(id, status, output, override, this.statusProblem(filePath, id, status, raw));
555
+ return new review_json_data_1.ChecklistResult(id, status, output, override, this.verdictProblem(filePath, id, status, raw));
522
556
  }
523
557
  catch (err) {
524
558
  const error = (0, to_error_1.toError)(err);
@@ -527,19 +561,32 @@ let ReviewJsonService = class ReviewJsonService {
527
561
  }
528
562
  }
529
563
  /**
530
- * '' when `status` is one of the three colors. Otherwise the complaint to show the AI verbatim. The
531
- * legacy-`success` case gets its OWN message: `success` was removed outright (no compatibility mode),
532
- * and a reviewer told only "status must be green|yellow|red" cannot tell whether it wrote the wrong
533
- * value or is using a field that no longer exists.
564
+ * '' when the verdict can be READ. Otherwise the complaint to show the AI verbatim.
565
+ *
566
+ * The MOVED `override` field is checked FIRST, ahead of `status`, because it is the more specific fact: a
567
+ * file carrying it was written against a schema that no longer exists, and a reader told only "status
568
+ * must be green|yellow|red" would fix the wrong thing. It is rejected even when EMPTY — an accepted shape
569
+ * is never migrated, and `"override": ""` sitting in a reviewer's file is the copy that teaches the next
570
+ * reviewer the field still exists.
571
+ *
572
+ * The legacy-`success` case keeps its OWN message for the same reason: `success` was removed outright
573
+ * (no compatibility mode), and a reviewer cannot tell a wrong value from a field that no longer exists.
534
574
  */
535
575
  // webpieces-disable no-any-unknown -- opaque parsed JSON; only tested for key presence here
536
- statusProblem(filePath, id, status, raw) {
537
- // webpieces-disable no-any-unknown -- comparing against the readonly literal tuple of valid colors
538
- if (review_json_data_1.VERDICT_STATUSES.includes(status))
539
- return '';
576
+ verdictProblem(filePath, id, status, raw) {
540
577
  // The ONE renderer — see verdictSchemaFor. A second copy here is what let the old `success` shape
541
578
  // survive in print after it was removed from the parser.
542
579
  const shape = this.verdictSchemaFor(id, filePath);
580
+ if ('override' in raw) {
581
+ return `Checklist "${id}" wrote its verdict with the MOVED "override" field. A ship-anyway `
582
+ + 'authorization is no longer part of a reviewer\'s verdict: it MOVED to its own file, '
583
+ + `${this.overrides.overrideFileName(id)}, which only the coordinating agent writes and only on a `
584
+ + 'human\'s in-session instruction. There is no compatibility mode — DELETE the "override" key from '
585
+ + `${filePath}. Rewrite the file as:\n${shape}`;
586
+ }
587
+ // webpieces-disable no-any-unknown -- comparing against the readonly literal tuple of valid colors
588
+ if (review_json_data_1.VERDICT_STATUSES.includes(status))
589
+ return '';
543
590
  if ('success' in raw) {
544
591
  return `Checklist "${id}" wrote its verdict with the REMOVED "success" field. It is now a tri-state ` +
545
592
  `"status" — there is no compatibility mode. Rewrite the file as:\n${shape}`;
@@ -573,7 +620,8 @@ let ReviewJsonService = class ReviewJsonService {
573
620
  exports.ReviewJsonService = ReviewJsonService;
574
621
  exports.ReviewJsonService = ReviewJsonService = tslib_1.__decorate([
575
622
  (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
576
- tslib_1.__metadata("design:paramtypes", [state_dir_1.DotWebpieces])
623
+ tslib_1.__metadata("design:paramtypes", [state_dir_1.DotWebpieces,
624
+ checklist_override_1.ChecklistOverrideService])
577
625
  ], ReviewJsonService);
578
626
  // Temporary migration delegators to ReviewJsonService — removed once consumers inject it.
579
627
  const reviewJsonSvc = new ReviewJsonService();
@@ -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';\nimport {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n ChecklistResult,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJson,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n ChecklistVerdict,\n PrContext,\n} from './review-json-data';\n\n// Re-exported so review-json.ts stays the single import site for the whole review vocabulary: the data\n// classes moved out to keep this file under the file-size limit, NOT to give callers a second module to\n// learn. Every existing `from './review-json'` import keeps resolving.\nexport {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n ChecklistResult,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJson,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n ChecklistVerdict,\n PrContext,\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// The same stamp, for a retired per-checklist verdict. Verdict files get their OWN wording because the two\n// archives answer different questions: old-review.json holds a description of the code, this holds a\n// REVIEWER'S DECISION. The one thing that must not happen is a reader treating an archived red as the live\n// verdict — the whole reason the file was moved rather than copied — so the note says that outright.\nconst CHECKLIST_ARCHIVE_NOTE =\n 'ARCHIVE — this is a checklist verdict from a PREVIOUS reviewer run on this branch, kept for audit ' +\n 'purposes only. It is NOT a live verdict and must never be read back as one: it was RETIRED because it ' +\n 'refused the PR, and the gate moved it here so the only way forward is a FRESH review-<id>.json written ' +\n 'by a real reviewer run. Do not copy its status back onto the live path to get past the gate. ' +\n 'Overwritten by every retirement, so only the most recently retired verdict 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: `<worktree>/.webpieces/pr-review/<feature>`. AI-WRITABLE scope,\n // not local() — an agent AUTHORS review.json here, and each reviewer subagent authors its own\n // review-<id>.json beside it. A worktree-isolated agent's Write is refused for any path under the\n // shared checkout, which is where local() puts this, so local() made both files unwritable by the\n // very agents the flow instructs to write them. See DotWebpieces.aiWritable() for the full account.\n prDirFor(repoRoot: string, featureName: string): string {\n return this.dotDir.aiWritableFile(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, ARCHIVE_NOTE));\n fs.rmSync(reviewJsonFilePath);\n return archivePath;\n }\n\n /**\n * The archived bytes: the original JSON with an AUDIT-ONLY note as its FIRST key, so anything that opens\n * the file — human or AI — reads what it is before it reads any of its content.\n *\n * `note` is a parameter rather than a constant because two different files are archived here (review.json\n * and review-<id>.json) and they need to say different things, while the stamping MECHANICS — parse,\n * note first, original keys in order, fall back to raw — are identical. One implementation, two texts;\n * a second copy of this method would be the thing that drifts.\n *\n * Falls back to the raw bytes when they do not parse. For review.json `loadReviewJson` has already\n * accepted the file so that is close to impossible, but a verdict file is written by a subagent and may\n * be half-written or not an object at all — and preserving the original always beats losing it to a\n * stamping failure, since the archive exists precisely to be the record.\n */\n private archivedBody(raw: string, note: 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] = 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 * Where a RETIRED verdict for one checklist goes: `review-<id>.json.old`, beside the live path.\n *\n * Mirrors {@link oldReviewJsonPath} deliberately, including its single-slot rule: ALWAYS the same path,\n * so it holds the last retired verdict and only the last one. A series (`.old.old`, `.old.1`) would read\n * as though the number of retirements meant something, and nothing downstream can interpret that — the\n * one fact worth keeping is \"this checklist refused before, here is what it said\".\n */\n oldChecklistResultPath(reviewJsonFilePath: string, checklistId: string): string {\n return `${this.checklistResultPath(reviewJsonFilePath, checklistId)}.old`;\n }\n\n /**\n * Retire one checklist's verdict: MOVE review-<id>.json to review-<id>.json.old, stamped with a note\n * saying what it is. Returns the archive path, or '' when there was nothing to archive.\n *\n * The point is the MOVE, exactly as in {@link archiveReviewJson}. A red verdict left on the live path is\n * re-read by the next run and re-reported as the CURRENT state of the branch, so the branch keeps being\n * refused for a finding that may already be fixed — and the fix, when it comes, silently overwrites the\n * only record that the gate ever refused anything. Moving it makes the refusal durable and makes a fresh\n * reviewer run the only way forward, which is the honest requirement: the old verdict judged code that\n * has since changed.\n *\n * Safe by construction for RED verdicts specifically, which is why the caller only archives on CK_FAIL:\n * a red verdict is never reusable — it always blocks — so nothing is lost by moving it. Green and yellow\n * verdicts ARE deliberately reused across finish attempts, and retiring one would force a needless (and\n * expensive) subagent re-run.\n */\n archiveChecklistResult(reviewJsonFilePath: string, checklistId: string): string {\n const livePath = this.checklistResultPath(reviewJsonFilePath, checklistId);\n if (!fs.existsSync(livePath)) return '';\n const archivePath = this.oldChecklistResultPath(reviewJsonFilePath, checklistId);\n const raw = fs.readFileSync(livePath, 'utf8');\n fs.writeFileSync(archivePath, this.archivedBody(raw, CHECKLIST_ARCHIVE_NOTE));\n fs.rmSync(livePath);\n return archivePath;\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 /**\n * The OPTIONAL checklists (`required: false`) that matched the diff but have no verdict file at all.\n *\n * This is the ONE set that separates \"nobody ran it\" from \"it failed\", and the distinction is the whole\n * feature: an optional checklist with no verdict was legitimately not run — declined by the human, or\n * skipped via `--no-optional` — so it must NOT block. An optional checklist with a RED verdict is not in\n * here (it resolves to CK_FAIL) and blocks exactly like a required one: choosing to run a reviewer and\n * then ignoring its answer would make the whole thing theater.\n *\n * A strict subset of {@link pendingChecklists}, computed here rather than at each call site so the\n * command that gates and the dashboard that reports cannot disagree about which checklists were skipped.\n */\n optionalWithoutVerdict(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[] {\n return required.filter((req: RequiredChecklist): boolean =>\n !req.required && this.resolveVerdict(req, results).status === CK_MISSING);\n }\n\n /**\n * The checklists that REFUSED: a reviewer ran, judged the change, and said no (CK_FAIL — status red with\n * no override). A strict subset of {@link pendingChecklists}, split out because it demands a completely\n * different action from the reader.\n *\n * Public so every command agrees on the set. When \"refused\" was computed ad hoc, a refusal and a\n * never-ran reviewer landed in one bucket and produced one message — \"you MUST run these N reviewer\n * subagent(s)\" — handed to an AI, which obediently re-spawned a reviewer that had already answered. It\n * refused again for the same reason, and the loop cost a full subagent run per pass while the reviewer's\n * actual finding was never shown to anyone. A refusal is a RESULT, not a missing step.\n */\n refusedChecklists(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[] {\n return required.filter((req: RequiredChecklist): boolean =>\n this.resolveVerdict(req, results).status === CK_FAIL);\n }\n\n /**\n * THE renderer for \"this reviewer refused\" — one wording, wherever the refusal surfaces. It exists as a\n * method because the text was previously inlined in {@link requiredChecklistErrors}, reachable only\n * through review.json validation, while the command layer refused earlier with its own generic message.\n * Two messages for one event is how the useful one became unreachable; there is now exactly one.\n *\n * It always quotes the reviewer's own `output` verbatim: the finding is the whole point, and an error\n * that names a checklist without saying what it objected to gives the reader nothing to fix.\n *\n * `archivedPath` non-empty ⇒ the verdict has just been RETIRED (moved) to that path, so the message must\n * change in two ways. It says where the record went — otherwise the move reads as data loss — and,\n * critically, it must NOT tell the reader to \"set override in review-<id>.json\", because that file no\n * longer exists. The escape hatch is therefore worded as writing a FRESH verdict file (the body can be\n * copied back out of the archive) with a human-authored override.\n */\n refusalError(req: RequiredChecklist, verdict: ChecklistVerdict, archivedPath = ''): string {\n const finding = `${verdict.detail.split('\\n').join('\\n ')}\\n`;\n const head = `Checklist \"${req.id}\" FAILED review (status:\"${VERDICT_RED}\"). The reviewer (${req.subagent}) wrote:\\n ` + finding;\n if (archivedPath === '') {\n return head +\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 // Re-spawning is the LAST thing said, and only after the finding, because an instruction to spawn a\n // subagent is the one line an AI acts on first — see refusedChecklists for what that cost.\n return head +\n ` That verdict has been RETIRED to ${archivedPath} (audit only — it is not a live verdict).\\n` +\n ` A FRESH ${this.checklistFileName(req.id)} is now required. Fix the finding first, then have the ` +\n `\"${req.subagent}\" subagent review again and write a new verdict.\\n` +\n ` To ship anyway, a HUMAN must decide it: write a fresh ${this.checklistFileName(req.id)} (you may copy the ` +\n `body back from the archive) carrying a non-empty, human-authored \"override\" justification.`;\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 //\n // It looks up the EXACT `review-<id>.json` name per required id — never a directory scan, never a prefix\n // match. That is what guarantees an archived `review-<id>.json.old` can never resolve as a live verdict:\n // the retired file sits right beside the live path, and a scan that swept the directory would hand a\n // RETIRED refusal (or worse, a retired pass) back as the current state, undoing the whole point of the\n // move in {@link archiveChecklistResult}.\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 // Through the ONE renderer, so this path and the command layer's refusal say the same thing.\n // No archive path here: this is validation, not the act of retiring the verdict.\n errors.push(this.refusalError(req, verdict));\n } else if (verdict.status === CK_MISSING) {\n // An OPTIONAL checklist with no verdict was legitimately not run — the human was offered it\n // and declined (or `--no-optional` skipped the offer). Demanding it here would make\n // `required: false` mean nothing. Note the CK_FAIL branch above deliberately has no such\n // exemption: once an optional reviewer RUNS, its refusal counts.\n if (!req.required) continue;\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"]}
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';\nimport { ChecklistOverride, ChecklistOverrideService, checklistOverrideService } from './checklist-override';\nimport {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n ChecklistResult,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJson,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n ChecklistVerdict,\n PrContext,\n} from './review-json-data';\n\n// Re-exported so review-json.ts stays the single import site for the whole review vocabulary: the data\n// classes moved out to keep this file under the file-size limit, NOT to give callers a second module to\n// learn. Every existing `from './review-json'` import keeps resolving.\nexport { ChecklistOverride, ChecklistOverrideService, checklistOverrideService };\n\nexport {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n ChecklistResult,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJson,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n ChecklistVerdict,\n PrContext,\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// The same stamp, for a retired per-checklist verdict. Verdict files get their OWN wording because the two\n// archives answer different questions: old-review.json holds a description of the code, this holds a\n// REVIEWER'S DECISION. The one thing that must not happen is a reader treating an archived red as the live\n// verdict — the whole reason the file was moved rather than copied — so the note says that outright.\nconst CHECKLIST_ARCHIVE_NOTE =\n 'ARCHIVE — this is a checklist verdict from a PREVIOUS reviewer run on this branch, kept for audit ' +\n 'purposes only. It is NOT a live verdict and must never be read back as one: it was RETIRED because it ' +\n 'refused the PR, and the gate moved it here so the only way forward is a FRESH review-<id>.json written ' +\n 'by a real reviewer run. Do not copy its status back onto the live path to get past the gate. ' +\n 'Overwritten by every retirement, so only the most recently retired verdict 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(\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly overrides: ChecklistOverrideService = checklistOverrideService,\n ) {}\n\n // The per-feature PR working dir: `<worktree>/.webpieces/pr-review/<feature>`. AI-WRITABLE scope,\n // not local() — an agent AUTHORS review.json here, and each reviewer subagent authors its own\n // review-<id>.json beside it. A worktree-isolated agent's Write is refused for any path under the\n // shared checkout, which is where local() puts this, so local() made both files unwritable by the\n // very agents the flow instructs to write them. See DotWebpieces.aiWritable() for the full account.\n prDirFor(repoRoot: string, featureName: string): string {\n return this.dotDir.aiWritableFile(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, ARCHIVE_NOTE));\n fs.rmSync(reviewJsonFilePath);\n return archivePath;\n }\n\n /**\n * The archived bytes: the original JSON with an AUDIT-ONLY note as its FIRST key, so anything that opens\n * the file — human or AI — reads what it is before it reads any of its content.\n *\n * `note` is a parameter rather than a constant because two different files are archived here (review.json\n * and review-<id>.json) and they need to say different things, while the stamping MECHANICS — parse,\n * note first, original keys in order, fall back to raw — are identical. One implementation, two texts;\n * a second copy of this method would be the thing that drifts.\n *\n * Falls back to the raw bytes when they do not parse. For review.json `loadReviewJson` has already\n * accepted the file so that is close to impossible, but a verdict file is written by a subagent and may\n * be half-written or not an object at all — and preserving the original always beats losing it to a\n * stamping failure, since the archive exists precisely to be the record.\n */\n private archivedBody(raw: string, note: 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] = 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 * Where a RETIRED verdict for one checklist goes: `review-<id>.json.old`, beside the live path.\n *\n * Mirrors {@link oldReviewJsonPath} deliberately, including its single-slot rule: ALWAYS the same path,\n * so it holds the last retired verdict and only the last one. A series (`.old.old`, `.old.1`) would read\n * as though the number of retirements meant something, and nothing downstream can interpret that — the\n * one fact worth keeping is \"this checklist refused before, here is what it said\".\n */\n oldChecklistResultPath(reviewJsonFilePath: string, checklistId: string): string {\n return `${this.checklistResultPath(reviewJsonFilePath, checklistId)}.old`;\n }\n\n /**\n * Retire one checklist's verdict: MOVE review-<id>.json to review-<id>.json.old, stamped with a note\n * saying what it is. Returns the archive path, or '' when there was nothing to archive.\n *\n * The point is the MOVE, exactly as in {@link archiveReviewJson}. A red verdict left on the live path is\n * re-read by the next run and re-reported as the CURRENT state of the branch, so the branch keeps being\n * refused for a finding that may already be fixed — and the fix, when it comes, silently overwrites the\n * only record that the gate ever refused anything. Moving it makes the refusal durable and makes a fresh\n * reviewer run the only way forward, which is the honest requirement: the old verdict judged code that\n * has since changed.\n *\n * Safe by construction for RED verdicts specifically, which is why the caller only archives on CK_FAIL:\n * a red verdict is never reusable — it always blocks — so nothing is lost by moving it. Green and yellow\n * verdicts ARE deliberately reused across finish attempts, and retiring one would force a needless (and\n * expensive) subagent re-run.\n */\n archiveChecklistResult(reviewJsonFilePath: string, checklistId: string): string {\n const livePath = this.checklistResultPath(reviewJsonFilePath, checklistId);\n if (!fs.existsSync(livePath)) return '';\n const archivePath = this.oldChecklistResultPath(reviewJsonFilePath, checklistId);\n const raw = fs.readFileSync(livePath, 'utf8');\n fs.writeFileSync(archivePath, this.archivedBody(raw, CHECKLIST_ARCHIVE_NOTE));\n fs.rmSync(livePath);\n return archivePath;\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, filePath)) 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 /**\n * The OPTIONAL checklists (`required: false`) that matched the diff but have no verdict file at all.\n *\n * This is the ONE set that separates \"nobody ran it\" from \"it failed\", and the distinction is the whole\n * feature: an optional checklist with no verdict was legitimately not run — declined by the human, or\n * skipped via `--no-optional` — so it must NOT block. An optional checklist with a RED verdict is not in\n * here (it resolves to CK_FAIL) and blocks exactly like a required one: choosing to run a reviewer and\n * then ignoring its answer would make the whole thing theater.\n *\n * A strict subset of {@link pendingChecklists}, computed here rather than at each call site so the\n * command that gates and the dashboard that reports cannot disagree about which checklists were skipped.\n */\n optionalWithoutVerdict(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[] {\n return required.filter((req: RequiredChecklist): boolean =>\n !req.required && this.resolveVerdict(req, results).status === CK_MISSING);\n }\n\n /**\n * The checklists that REFUSED: a reviewer ran, judged the change, and said no (CK_FAIL — status red with\n * no override). A strict subset of {@link pendingChecklists}, split out because it demands a completely\n * different action from the reader.\n *\n * Public so every command agrees on the set. When \"refused\" was computed ad hoc, a refusal and a\n * never-ran reviewer landed in one bucket and produced one message — \"you MUST run these N reviewer\n * subagent(s)\" — handed to an AI, which obediently re-spawned a reviewer that had already answered. It\n * refused again for the same reason, and the loop cost a full subagent run per pass while the reviewer's\n * actual finding was never shown to anyone. A refusal is a RESULT, not a missing step.\n */\n refusedChecklists(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[] {\n return required.filter((req: RequiredChecklist): boolean =>\n this.resolveVerdict(req, results).status === CK_FAIL);\n }\n\n /**\n * THE renderer for \"this reviewer refused\" — one wording, wherever the refusal surfaces. It exists as a\n * method because the text was previously inlined in {@link requiredChecklistErrors}, reachable only\n * through review.json validation, while the command layer refused earlier with its own generic message.\n * Two messages for one event is how the useful one became unreachable; there is now exactly one.\n *\n * It always quotes the reviewer's own `output` verbatim: the finding is the whole point, and an error\n * that names a checklist without saying what it objected to gives the reader nothing to fix.\n *\n * IT NAMES THE WRITER, which is the half that was missing. The old text said WHAT to write (\"set a\n * non-empty override\") and WHERE, but never WHO MAY — so the only reachable move was an agent editing a\n * reviewer's verdict file in place, which the harness denies, which is how a human ended up hand-editing\n * JSON. The ship-anyway route is now a SEPARATE file the coordinating agent may write, and the command\n * that writes it is printed ready to run. See {@link ChecklistOverrideService.writerRule}.\n *\n * `archivedPath` non-empty ⇒ the verdict has just been RETIRED (moved) to that path, so the message says\n * where the record went — otherwise the move reads as data loss — and that a FRESH verdict is required.\n * The AUTHORIZATION is unaffected by that move: `override-<id>.json` is a different file, it survives the\n * retirement, and a human who already decided to accept this checklist is never asked again.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n refusalError(req: RequiredChecklist, verdict: ChecklistVerdict, reviewJsonFilePath: string, archivedPath = ''): string {\n const finding = `${verdict.detail.split('\\n').join('\\n ')}\\n`;\n const head = `Checklist \"${req.id}\" FAILED review (status:\"${VERDICT_RED}\"). The reviewer (${req.subagent}) wrote:\\n ` + finding;\n const retired = archivedPath === ''\n ? ' Fix it, then re-run.\\n'\n // Re-spawning is said only after the finding, because an instruction to spawn a subagent is the\n // one line an AI acts on first — see refusedChecklists for what that cost.\n : ` That verdict has been RETIRED to ${archivedPath} (audit only — it is not a live verdict).\\n` +\n ` A FRESH ${this.checklistFileName(req.id)} is now required. Fix the finding first, then have the ` +\n `\"${req.subagent}\" subagent review again and write a new verdict.\\n`;\n return head + retired + this.overrideRoute(req, reviewJsonFilePath);\n }\n\n /**\n * The ship-anyway paragraph: who may authorize, and the exact command that records it.\n *\n * Its own method because every refusal surface must say the identical thing about who may write an\n * override. A second copy of this paragraph is precisely how the previous one drifted into naming a\n * command that had since been deleted.\n */\n private overrideRoute(req: RequiredChecklist, reviewJsonFilePath: string): string {\n return ` To SHIP ANYWAY a human must decide it, and the decision is recorded in its own file — `\n + `${this.overrides.overrideFileName(req.id)}, never inside the reviewer's verdict.\\n`\n + ` ${this.overrides.writerRule()}\\n`\n + ' Run exactly this, replacing only the \"reason\" with what the human actually said:\\n\\n'\n + `${this.overrides.writeCommand(reviewJsonFilePath, req.id)}\\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 //\n // It looks up the EXACT `review-<id>.json` name per required id — never a directory scan, never a prefix\n // match. That is what guarantees an archived `review-<id>.json.old` can never resolve as a live verdict:\n // the retired file sits right beside the live path, and a scan that swept the directory would hand a\n // RETIRED refusal (or worse, a retired pass) back as the current state, undoing the whole point of the\n // move in {@link archiveChecklistResult}.\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 // The human's authorization is read from its OWN file beside the verdict, in the same pass, so\n // resolveVerdict never touches disk and every command resolves one outcome from one read.\n const parsed = this.parseChecklistResult(p, req.id, this.overrides.load(reviewJsonFilePath, 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 const override = result.override;\n // A malformed authorization is reported as a FORMAT problem, never treated as one: an override with\n // no stated reason authorizes nothing, and silently ignoring it would tell the reader their decision\n // was not recorded without ever saying why.\n if (override !== null && override.problem !== '') return new ChecklistVerdict(req.id, CK_BAD_FORMAT, override.problem);\n if (override !== null) return new ChecklistVerdict(req.id, CK_OVERRIDDEN, this.overrides.detail(override));\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(\n required: readonly RequiredChecklist[], results: readonly ChecklistResult[], filePath: string,\n ): 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 // Through the ONE renderer, so this path and the command layer's refusal say the same thing.\n // No archive path here: this is validation, not the act of retiring the verdict.\n errors.push(this.refusalError(req, verdict, filePath));\n } else if (verdict.status === CK_MISSING) {\n // An OPTIONAL checklist with no verdict was legitimately not run — the human was offered it\n // and declined (or `--no-optional` skipped the offer). Demanding it here would make\n // `required: false` mean nothing. Note the CK_FAIL branch above deliberately has no such\n // exemption: once an optional reviewer RUNS, its refusal counts.\n if (!req.required) continue;\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\":\"…\"}.${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\" }`,\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; your \"output\" is printed verbatim`,\n `${indent}Prefer \"${VERDICT_YELLOW}\" over red when the change is acceptable but worth a human's attention —`,\n `${indent}a red a human then authorizes reads as a deliberately-accepted defect, a yellow reads as a note.`,\n // The one sentence that stops a reviewer doing what a reviewer did once: telling the human to run\n // a command, on its own authority, to get past its own finding.\n `${indent}THERE IS NO \"override\" FIELD HERE, and you NEVER write one. A reviewer does not authorize`,\n `${indent}shipping past its own finding: if this needs a human's decision, SAY SO in \"output\" and STOP.`,\n `${indent}The coordinating agent is the one with the human, and records that decision in override-${id}.json.`,\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, override: ChecklistOverride | null): 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 status = typeof raw['status'] === 'string' ? (raw['status'] as string).trim().toLowerCase() : '';\n return new ChecklistResult(id, status, output, override, this.verdictProblem(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 the verdict can be READ. Otherwise the complaint to show the AI verbatim.\n *\n * The MOVED `override` field is checked FIRST, ahead of `status`, because it is the more specific fact: a\n * file carrying it was written against a schema that no longer exists, and a reader told only \"status\n * must be green|yellow|red\" would fix the wrong thing. It is rejected even when EMPTY — an accepted shape\n * is never migrated, and `\"override\": \"\"` sitting in a reviewer's file is the copy that teaches the next\n * reviewer the field still exists.\n *\n * The legacy-`success` case keeps its OWN message for the same reason: `success` was removed outright\n * (no compatibility mode), and a reviewer cannot tell a wrong value from a field that no longer exists.\n */\n // webpieces-disable no-any-unknown -- opaque parsed JSON; only tested for key presence here\n private verdictProblem(filePath: string, id: string, status: string, raw: Record<string, unknown>): string {\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 ('override' in raw) {\n return `Checklist \"${id}\" wrote its verdict with the MOVED \"override\" field. A ship-anyway `\n + 'authorization is no longer part of a reviewer\\'s verdict: it MOVED to its own file, '\n + `${this.overrides.overrideFileName(id)}, which only the coordinating agent writes and only on a `\n + 'human\\'s in-session instruction. There is no compatibility mode — DELETE the \"override\" key from '\n + `${filePath}. Rewrite the file as:\\n${shape}`;\n }\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 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"]}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * One dangling entry the sweep ACTED ON: what was linked, the target that was not there, and — when the
3
+ * removal itself failed — why. Data-only (per CLAUDE.md).
4
+ *
5
+ * `failure` exists because the removal is an `fs.rmSync`, not just a probe: an unwritable `.bin` (EACCES,
6
+ * a read-only mount) would otherwise heal silently-never while the tree kept advertising a command that
7
+ * does not exist, which is the precise defect this module exists to remove. Not thrown — the sweep is a
8
+ * courtesy and must never fail the `wp-*` command that called it — so the diagnostic rides back here and
9
+ * {@link StaleBinSweeper.report} states both outcomes.
10
+ */
11
+ export declare class StaleBinRemoval {
12
+ name: string;
13
+ target: string;
14
+ failure: string;
15
+ constructor(name: string, target: string, failure?: string);
16
+ }
17
+ /**
18
+ * Removes `node_modules/.bin/wp-*` entries whose symlink target is gone.
19
+ *
20
+ * Deliberately NOT `@injectable`: decorating it would import inversify, and this module must stay loadable
21
+ * by `wp-upgrade-shim` on a tree that cannot build a DI container (see the header). Callers use the shared
22
+ * {@link staleBinSweeper} instance, which is also what makes {@link sweepOnce}'s memo process-wide.
23
+ */
24
+ export declare class StaleBinSweeper {
25
+ private readonly swept;
26
+ /** Where the bins live for a tree. Public so a test can point at a fixture without guessing the layout. */
27
+ binDir(repoRoot: string): string;
28
+ /**
29
+ * Sweep once per root per process, returning what was removed ([] on every later call for the same
30
+ * root, and [] when there was nothing to remove — the two are indistinguishable to a caller ON PURPOSE,
31
+ * because both mean "say nothing").
32
+ */
33
+ sweepOnce(repoRoot: string): StaleBinRemoval[];
34
+ /**
35
+ * Remove every dangling `wp-*` symlink under the tree's `.bin`, returning what went. [] when the
36
+ * directory does not exist (a linked worktree with no install of its own is the common case) or when
37
+ * everything there resolves.
38
+ *
39
+ * BEST EFFORT, PER ENTRY. A `.bin` that cannot be read, or one entry that cannot be removed, must never
40
+ * take down the `wp-*` command that called this — the sweep is a courtesy, not the command's job.
41
+ */
42
+ sweep(repoRoot: string): StaleBinRemoval[];
43
+ /**
44
+ * The lines a caller prints for a sweep. [] for an empty sweep, so REMOVING NOTHING IS SILENT — this
45
+ * runs on every `wp-*` command and the common case must add no noise at all.
46
+ *
47
+ * Rendered here rather than at each call site so the two callers cannot describe the same act
48
+ * differently; each still chooses its own output channel and its own leading icon convention.
49
+ */
50
+ report(removed: readonly StaleBinRemoval[]): string[];
51
+ private entries;
52
+ /**
53
+ * Remove ONE entry if — and only if — it is a symlink whose target does not exist. Returns what was
54
+ * acted on, with `failure` set when the entry was dangling but could not be removed; `null` when the
55
+ * entry was not a dangling `wp-*` link at all.
56
+ */
57
+ private removeIfDangling;
58
+ /**
59
+ * The link target of a DANGLING symlink, or `null` when this entry is not one — a real file, a live
60
+ * link, or something we cannot stat. `existsSync` FOLLOWS symlinks, so a false answer on a path `lstat`
61
+ * calls a link is exactly the dangling case, with no need to resolve the target ourselves.
62
+ *
63
+ * Split from the removal so the two catches mean different things: an unreadable entry is simply not
64
+ * ours to touch, while a failed REMOVAL is a fact worth printing.
65
+ */
66
+ private danglingTarget;
67
+ }
68
+ export declare const staleBinSweeper: StaleBinSweeper;