@webpieces/rules-config 0.4.801 → 0.4.803
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/checklist-instructions.js +8 -7
- package/src/checklist-instructions.js.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +12 -4
- package/src/index.js.map +1 -1
- package/src/review-identity-stamp.d.ts +82 -0
- package/src/review-identity-stamp.js +172 -0
- package/src/review-identity-stamp.js.map +1 -0
- package/src/review-json.d.ts +6 -5
- package/src/review-json.js +16 -32
- package/src/review-json.js.map +1 -1
- package/src/reviewer-instructions.d.ts +1 -1
- package/src/reviewer-instructions.js +10 -5
- package/src/reviewer-instructions.js.map +1 -1
- package/src/subagent-provenance.d.ts +1 -0
- package/src/subagent-provenance.js +13 -13
- package/src/subagent-provenance.js.map +1 -1
- package/src/verdict-schema-renderer.d.ts +14 -0
- package/src/verdict-schema-renderer.js +44 -0
- package/src/verdict-schema-renderer.js.map +1 -0
- package/templates/webpieces.git-workflow.md +1 -1
- package/templates/webpieces.location-matrix.md +1 -1
- package/templates/webpieces.review-checklists.md +28 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-identity-stamp.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-identity-stamp.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AtomicFile } from './atomic-file';\nimport { DotWebpieces, dotWebpieces } from './state-dir';\nimport { toError } from './to-error';\n\n/** The ONE sanctioned way a reviewer submits a checklist verdict (issue #863). */\nexport const WRITE_REVIEW_BIN = 'wp-write-review';\n\n/** The flag that names the checklist a `wp-write-review` call submits for. */\nexport const WRITE_REVIEW_CHECKLIST_FLAG = '--checklist';\n\n/** Where the stamps live, beneath this worktree's tooling-owned state dir (never the AI-writable one). */\nexport const REVIEW_STAMP_DIR = 'review-stamps';\n\n/**\n * How long a stamp stays usable. The hook writes it a few milliseconds before the harness starts the very\n * Bash command it stamped, so anything older than this is left over from a call that never reached the bin\n * — and must not be available to lend its identity to a later caller.\n */\nexport const REVIEW_STAMP_MAX_AGE_MS = 120_000;\n\n/**\n * WHO is running a `pnpm wp-write-review` Bash call, as the HARNESS reported it to the PreToolUse hook.\n * Data-only (per CLAUDE.md), and its field names are the JSON keys on disk.\n *\n * WHY a hook stamp and not the environment (issue #863). A Bash-invoked bin sees only its env, and the env\n * cannot answer \"is this a subagent?\" in either harness:\n * - Claude Code exports `CLAUDE_CODE_SESSION_ID`, which is the PARENT session's id in a subagent too, and\n * no agent id at all — a reviewer and its coordinator have byte-identical identity env.\n * - Codex exports `CODEX_SESSION_ID` / `CODEX_THREAD_ID`, and nothing documents (or has been measured to\n * show) that a spawned subagent gets a thread id of its own rather than inheriting the coordinator's.\n * The hook payload DOES carry the answer, in both harnesses, on the same key: `agent_id` is empty for the\n * main loop and populated for a subagent. So the hook — which sees every Bash call before it runs — writes\n * that identity here for each checklist the command names, and the bin consumes it.\n *\n * NOT tamper-proof, and not claimed to be: an agent can write this file by hand. It moves forging a verdict\n * from \"write one JSON file\" (what a coordinator did on two merged PRs) to a deliberate, auditable forgery of\n * tooling state outside the AI-writable dir — the same bar SubagentProvenanceService sets.\n */\nexport class ReviewIdentityStamp {\n checklistId: string;\n aiType: string; // 'claude-code' | 'codex' — the adapter's answer, never the model's\n sessionId: string;\n agentId: string; // '' ⇒ the MAIN loop, i.e. the coordinating agent\n agentType: string;\n cwd: string;\n stampedAt: string; // ISO\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(checklistId: string, aiType: string, sessionId: string, agentId: string, agentType: string, cwd: string, stampedAt: string) {\n this.checklistId = checklistId;\n this.aiType = aiType;\n this.sessionId = sessionId;\n this.agentId = agentId;\n this.agentType = agentType;\n this.cwd = cwd;\n this.stampedAt = stampedAt;\n }\n}\n\n/**\n * Writes (from the PreToolUse hook) and consumes (from `wp-write-review`) {@link ReviewIdentityStamp}s.\n *\n * Both ends resolve the path from their OWN working directory through {@link DotWebpieces.local}, which is\n * keyed by git's answer for that tree — so the hook's payload cwd and the bin's process cwd land on the same\n * file for the same worktree, and two worktrees never share a stamp.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type; {@link reviewIdentityStamps} is the\n * instance the dependency-free hook adapter uses.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewIdentityStampService {\n constructor(\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly atomicFile: AtomicFile = new AtomicFile(),\n ) {}\n\n /**\n * The checklist ids a Bash command submits verdicts for, or [] when it does not invoke the bin at all.\n * Every `--checklist <id>` / `--checklist=<id>` is returned, because one reviewer covering several\n * checklists may chain several submissions into one command.\n */\n invokedChecklists(command: string): string[] {\n if (!new RegExp(`\\\\b${WRITE_REVIEW_BIN}\\\\b`).test(command)) return [];\n const ids: string[] = [];\n const flag = /--checklist(?:=|\\s+)([\"']?)([A-Za-z0-9._-]+)\\1/g;\n let match = flag.exec(command);\n while (match !== null) {\n if (!ids.includes(match[2])) ids.push(match[2]);\n match = flag.exec(command);\n }\n return ids;\n }\n\n /**\n * True when the stamp names the COORDINATING agent rather than a subagent: no agent id, or an agent id\n * equal to the session id (a harness that fills the main loop's agent id with the session id instead of\n * leaving it empty). The coordinator may never submit a reviewer's verdict — that is the whole point.\n */\n isCoordinator(stamp: ReviewIdentityStamp): boolean {\n return stamp.agentId.trim() === '' || stamp.agentId === stamp.sessionId;\n }\n\n stampPath(startDir: string, checklistId: string): string {\n return this.dotDir.localFile(startDir, REVIEW_STAMP_DIR, `${checklistId.replace(/[^A-Za-z0-9._-]/g, '_')}.json`);\n }\n\n /** Best-effort: a stamp that cannot be written makes the bin refuse, which is the safe direction. */\n write(startDir: string, stamp: ReviewIdentityStamp): void {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: the hook must never fail a tool call over its own bookkeeping\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n this.atomicFile.writeJsonAtomic(this.stampPath(startDir, stamp.checklistId), stamp);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n /**\n * Read AND delete the stamp for one checklist. null when there is none, it is unreadable, or it is\n * older than {@link REVIEW_STAMP_MAX_AGE_MS}. Deleted on read so one hook call vouches for one\n * submission; a delete that fails (a sandbox that denies it) is harmless, because age bounds it anyway.\n */\n take(startDir: string, checklistId: string, nowMs: number = Date.now()): ReviewIdentityStamp | null {\n const file = this.stampPath(startDir, checklistId);\n const stamp = this.read(file);\n this.remove(file);\n if (stamp === null || stamp.checklistId !== checklistId) return null;\n const age = nowMs - Date.parse(stamp.stampedAt);\n if (!Number.isFinite(age) || age < 0 || age > REVIEW_STAMP_MAX_AGE_MS) return null;\n return stamp;\n }\n\n private read(file: string): ReviewIdentityStamp | null {\n if (!fs.existsSync(file)) return null;\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unreadable stamp is no stamp\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- opaque parsed JSON, narrowed field-by-field below\n const raw = JSON.parse(fs.readFileSync(file, 'utf8')) as Record<string, unknown>;\n // webpieces-disable no-any-unknown -- one opaque JSON value, narrowed to a string\n const str = (v: unknown): string => (typeof v === 'string' ? v : '');\n return new ReviewIdentityStamp(\n str(raw['checklistId']), str(raw['aiType']), str(raw['sessionId']), str(raw['agentId']),\n str(raw['agentType']), str(raw['cwd']), str(raw['stampedAt']));\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n private remove(file: string): void {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: a stamp that cannot be deleted still expires\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.rmSync(file, { force: true });\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n}\n\n/** The instance the hook adapter uses — it runs without a DI container (see hook-core.ts). */\nexport const reviewIdentityStamps = new ReviewIdentityStampService();\n"]}
|
package/src/review-json.d.ts
CHANGED
|
@@ -177,18 +177,19 @@ export declare class ReviewJsonService {
|
|
|
177
177
|
private requiredChecklistErrors;
|
|
178
178
|
private checklistFileName;
|
|
179
179
|
/**
|
|
180
|
-
* THE renderer for a reviewer's verdict schema — with the reviewer's own `id` already filled in
|
|
181
|
-
* when known, the exact file it must write.
|
|
180
|
+
* THE renderer for a reviewer's verdict schema — with the reviewer's own `id` already filled in.
|
|
182
181
|
*
|
|
183
182
|
* There is one because a verdict schema that lives anywhere a human maintains it goes stale, and a
|
|
184
183
|
* reviewer follows the stale copy. That is not a hypothetical: when `success` was replaced by the
|
|
185
184
|
* tri-state `status`, hand-written `.claude/agents/*.md` files kept documenting `success`, and a real
|
|
186
185
|
* PR had to carry "the verdict format in your own agent .md file is OUT OF DATE" in the spawn prompt to
|
|
187
186
|
* work around it. Every printed copy — the stage-② roster, the generated per-reviewer instructions
|
|
188
|
-
* file, and the complaint raised against a malformed verdict — now comes from here.
|
|
189
|
-
*
|
|
187
|
+
* file, and the complaint raised against a malformed verdict — now comes from here. It ends with the
|
|
188
|
+
* ONE way a verdict is submitted (`wp-write-review`, issue #863), never a file path to write.
|
|
190
189
|
*/
|
|
191
|
-
|
|
190
|
+
renderVerdictSchema(id: string, indent?: string): string;
|
|
191
|
+
/** THE command that submits one checklist's verdict (issue #863) — see VerdictSchemaRenderer. */
|
|
192
|
+
submitCommand(id: string): string;
|
|
192
193
|
private parseChecklistResult;
|
|
193
194
|
/**
|
|
194
195
|
* '' when the verdict can be READ. Otherwise the complaint to show the AI verbatim.
|
package/src/review-json.js
CHANGED
|
@@ -13,6 +13,7 @@ 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
15
|
const review_json_schema_renderer_1 = require("./review-json-schema-renderer");
|
|
16
|
+
const verdict_schema_renderer_1 = require("./verdict-schema-renderer");
|
|
16
17
|
const checklist_override_1 = require("./checklist-override");
|
|
17
18
|
Object.defineProperty(exports, "ChecklistOverride", { enumerable: true, get: function () { return checklist_override_1.ChecklistOverride; } });
|
|
18
19
|
Object.defineProperty(exports, "ChecklistOverrideService", { enumerable: true, get: function () { return checklist_override_1.ChecklistOverrideService; } });
|
|
@@ -494,9 +495,8 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
494
495
|
if (!req.required)
|
|
495
496
|
continue;
|
|
496
497
|
const doc = req.doc.trim() !== '' ? ` Read: ${req.doc}.` : '';
|
|
497
|
-
errors.push(`Checklist "${req.id}" MATCHED this diff but has no verdict. Spawn the "${req.reviewer.agentName}" subagent to review it
|
|
498
|
-
`
|
|
499
|
-
`{"id":"${req.id}","status":"${review_json_data_1.VERDICT_GREEN}","agent":"unknown","model":"unknown","output":"…"}.${doc}`);
|
|
498
|
+
errors.push(`Checklist "${req.id}" MATCHED this diff but has no verdict. Spawn the "${req.reviewer.agentName}" subagent to review it; ` +
|
|
499
|
+
`IT submits the verdict with ${this.submitCommand(req.id)} — nobody writes ${this.checklistFileName(req.id)} by hand.${doc}`);
|
|
500
500
|
}
|
|
501
501
|
}
|
|
502
502
|
return errors;
|
|
@@ -505,38 +505,22 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
505
505
|
return `review-${checklistId}.json`;
|
|
506
506
|
}
|
|
507
507
|
/**
|
|
508
|
-
* THE renderer for a reviewer's verdict schema — with the reviewer's own `id` already filled in
|
|
509
|
-
* when known, the exact file it must write.
|
|
508
|
+
* THE renderer for a reviewer's verdict schema — with the reviewer's own `id` already filled in.
|
|
510
509
|
*
|
|
511
510
|
* There is one because a verdict schema that lives anywhere a human maintains it goes stale, and a
|
|
512
511
|
* reviewer follows the stale copy. That is not a hypothetical: when `success` was replaced by the
|
|
513
512
|
* tri-state `status`, hand-written `.claude/agents/*.md` files kept documenting `success`, and a real
|
|
514
513
|
* PR had to carry "the verdict format in your own agent .md file is OUT OF DATE" in the spawn prompt to
|
|
515
514
|
* work around it. Every printed copy — the stage-② roster, the generated per-reviewer instructions
|
|
516
|
-
* file, and the complaint raised against a malformed verdict — now comes from here.
|
|
517
|
-
*
|
|
515
|
+
* file, and the complaint raised against a malformed verdict — now comes from here. It ends with the
|
|
516
|
+
* ONE way a verdict is submitted (`wp-write-review`, issue #863), never a file path to write.
|
|
518
517
|
*/
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
`${indent}Use the literal "unknown" when unavailable; never guess. These are self-reported, not provenance.`,
|
|
526
|
-
`${indent} ${review_json_data_1.VERDICT_GREEN} → passes, nothing to flag`,
|
|
527
|
-
`${indent} ${review_json_data_1.VERDICT_YELLOW} → passes WITH CONCERNS; nothing is blocked and the concern is published on the PR`,
|
|
528
|
-
`${indent} ${review_json_data_1.VERDICT_RED} → REFUSES the PR; your "output" is printed verbatim`,
|
|
529
|
-
`${indent}Prefer "${review_json_data_1.VERDICT_YELLOW}" over red when the change is acceptable but worth a human's attention —`,
|
|
530
|
-
`${indent}a red a human then authorizes reads as a deliberately-accepted defect, a yellow reads as a note.`,
|
|
531
|
-
// The one sentence that stops a reviewer doing what a reviewer did once: telling the human to run
|
|
532
|
-
// a command, on its own authority, to get past its own finding.
|
|
533
|
-
`${indent}THERE IS NO "override" FIELD HERE, and you NEVER write one. A reviewer does not authorize`,
|
|
534
|
-
`${indent}shipping past its own finding: if this needs a human's decision, SAY SO in "output" and STOP.`,
|
|
535
|
-
`${indent}The coordinating agent is the one with the human, and records that decision in override-${id}.json.`,
|
|
536
|
-
];
|
|
537
|
-
if (verdictPath !== '')
|
|
538
|
-
lines.push(`${indent}File: ${verdictPath}`);
|
|
539
|
-
return lines.join('\n');
|
|
518
|
+
renderVerdictSchema(id, indent = ' ') {
|
|
519
|
+
return new verdict_schema_renderer_1.VerdictSchemaRenderer().render(id, indent);
|
|
520
|
+
}
|
|
521
|
+
/** THE command that submits one checklist's verdict (issue #863) — see VerdictSchemaRenderer. */
|
|
522
|
+
submitCommand(id) {
|
|
523
|
+
return new verdict_schema_renderer_1.VerdictSchemaRenderer().submitCommand(id);
|
|
540
524
|
}
|
|
541
525
|
// Unreadable objects retain their format complaint; absent/unparseable verdicts return null.
|
|
542
526
|
// webpieces-disable no-any-unknown -- opaque parsed JSON, narrowed field-by-field
|
|
@@ -576,22 +560,22 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
576
560
|
*/
|
|
577
561
|
// webpieces-disable no-any-unknown -- opaque parsed JSON; only tested for key presence here
|
|
578
562
|
verdictProblem(filePath, id, status, raw) {
|
|
579
|
-
// The ONE renderer — see
|
|
563
|
+
// The ONE renderer — see renderVerdictSchema. A second copy here is what let the old `success` shape
|
|
580
564
|
// survive in print after it was removed from the parser.
|
|
581
|
-
const shape = this.
|
|
565
|
+
const shape = this.renderVerdictSchema(id);
|
|
582
566
|
if ('override' in raw) {
|
|
583
567
|
return `Checklist "${id}" wrote its verdict with the MOVED "override" field. A ship-anyway `
|
|
584
568
|
+ 'authorization is no longer part of a reviewer\'s verdict: it MOVED to its own file, '
|
|
585
569
|
+ `${this.overrides.overrideFileName(id)}, which only the coordinating agent writes and only on a `
|
|
586
570
|
+ 'human\'s in-session instruction. There is no compatibility mode — DELETE the "override" key from '
|
|
587
|
-
+ `${filePath}
|
|
571
|
+
+ `${filePath} by resubmitting it:\n${shape}`;
|
|
588
572
|
}
|
|
589
573
|
// webpieces-disable no-any-unknown -- comparing against the readonly literal tuple of valid colors
|
|
590
574
|
if (review_json_data_1.VERDICT_STATUSES.includes(status))
|
|
591
575
|
return '';
|
|
592
576
|
if ('success' in raw) {
|
|
593
577
|
return `Checklist "${id}" wrote its verdict with the REMOVED "success" field. It is now a tri-state ` +
|
|
594
|
-
`"status" — there is no compatibility mode.
|
|
578
|
+
`"status" — there is no compatibility mode. Resubmit it as:\n${shape}`;
|
|
595
579
|
}
|
|
596
580
|
return `Checklist "${id}" wrote a verdict with no valid "status" (got ${JSON.stringify(status)}). ` +
|
|
597
581
|
`It must be exactly one of ${review_json_data_1.VERDICT_STATUSES.join(', ')}:\n${shape}`;
|
package/src/review-json.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-json.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-json.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { PR_REVIEW_DIR } from './constants';\nimport { DotWebpieces, dotWebpieces } from './state-dir';\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\nimport { ReviewJsonSchemaRenderer } from './review-json-schema-renderer';\nimport { ChecklistOverride, ChecklistOverrideService, checklistOverrideService } from './checklist-override';\nimport {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n SINGLE_ROUND_MAIN_AGENT_INSTRUCTIONS,\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 review vocabulary.\nexport { ChecklistOverride, ChecklistOverrideService, checklistOverrideService };\nexport {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n SINGLE_ROUND_MAIN_AGENT_INSTRUCTIONS,\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 review.json schema shared by every command that asks the coordinating AI to write it.\n reviewJsonSchemaHint(filePath: string, mainAgentInstructions = ''): string {\n return new ReviewJsonSchemaRenderer().render(filePath, mainAgentInstructions);\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(\n filePath: string, required: readonly RequiredChecklist[] = [], expectedMainAgentInstructions = '',\n ): 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, expectedMainAgentInstructions)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n const raw = this.parseReviewJson(fs.readFileSync(filePath, 'utf8'), filePath, expectedMainAgentInstructions);\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new InformAiError(\n `review.json must be a JSON object.\\n\\n${this.reviewJsonSchemaHint(filePath, expectedMainAgentInstructions)}`);\n }\n\n const errors: string[] = [];\n for (const field of ['agent', 'model']) {\n if (typeof raw[field] !== 'string' || raw[field].trim() === '') {\n errors.push(`\"${field}\" must be a non-empty string; use \"unknown\" when unavailable.`);\n }\n }\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 const mainAgentInstructions = this.validateMainAgentInstructions(raw, expectedMainAgentInstructions, errors);\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, expectedMainAgentInstructions)}`,\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((raw['agent'] as string).trim(), (raw['model'] as string).trim(),\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 mainAgentInstructions,\n );\n }\n\n // webpieces-disable no-any-unknown -- AI-authored JSON boundary is validated before use\n private validateMainAgentInstructions(raw: Record<string, unknown>, expected: string, errors: string[]): string {\n const actual = typeof raw['main_agent_instructions'] === 'string'\n ? (raw['main_agent_instructions'] as string).trim() : '';\n if (expected !== '' && actual !== expected)\n errors.push('\"main_agent_instructions\" must exactly preserve the single-round instructions printed by stage ②.');\n return actual;\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.reviewer.agentName}) 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.reviewer.agentName}\" 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.reviewer.agentName}\" subagent to review it, ` +\n `then write ${this.checklistFileName(req.id)} with ` +\n `{\"id\":\"${req.id}\",\"status\":\"${VERDICT_GREEN}\",\"agent\":\"unknown\",\"model\":\"unknown\",\"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 */\n verdictSchemaFor(id: string, verdictPath = '', indent = ' '): string {\n const lines = [\n `${indent}{ \"id\": \"${id}\", \"status\": \"${VERDICT_GREEN} | ${VERDICT_YELLOW} | ${VERDICT_RED}\", ` +\n `\"agent\": \"claude | codex | unknown\", \"model\": \"opus | sonnet | actual readable model name | unknown\", ` +\n `\"output\": \"what you checked / found\" }`,\n `${indent}Identity fields are REQUIRED non-empty strings. Use your own harness and model, never the parent's.`,\n `${indent}Use the literal \"unknown\" when unavailable; never guess. These are self-reported, not provenance.`,\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 // Unreadable objects retain their format complaint; absent/unparseable verdicts return null.\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 const agent = typeof raw['agent'] === 'string' ? raw['agent'].trim() : '';\n const model = typeof raw['model'] === 'string' ? raw['model'].trim() : '';\n const identityProblem = agent === '' || model === ''\n ? 'Both \"agent\" and \"model\" must be non-empty strings; use \"unknown\" when unavailable.' : '';\n return new ChecklistResult(agent, model, id, status, output, override,\n this.verdictProblem(filePath, id, status, raw) || identityProblem);\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(\n raw: string, filePath: string, expectedMainAgentInstructions = '',\n ): 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` +\n `${this.reviewJsonSchemaHint(filePath, expectedMainAgentInstructions)}\\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, mainAgentInstructions = ''): string {\n return reviewJsonSvc.reviewJsonSchemaHint(filePath, mainAgentInstructions);\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 { ReviewJsonSchemaRenderer } from './review-json-schema-renderer';\nimport { VerdictSchemaRenderer } from './verdict-schema-renderer';\nimport { ChecklistOverride, ChecklistOverrideService, checklistOverrideService } from './checklist-override';\nimport {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n SINGLE_ROUND_MAIN_AGENT_INSTRUCTIONS,\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 review vocabulary.\nexport { ChecklistOverride, ChecklistOverrideService, checklistOverrideService };\nexport {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n SINGLE_ROUND_MAIN_AGENT_INSTRUCTIONS,\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 review.json schema shared by every command that asks the coordinating AI to write it.\n reviewJsonSchemaHint(filePath: string, mainAgentInstructions = ''): string {\n return new ReviewJsonSchemaRenderer().render(filePath, mainAgentInstructions);\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(\n filePath: string, required: readonly RequiredChecklist[] = [], expectedMainAgentInstructions = '',\n ): 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, expectedMainAgentInstructions)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n const raw = this.parseReviewJson(fs.readFileSync(filePath, 'utf8'), filePath, expectedMainAgentInstructions);\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new InformAiError(\n `review.json must be a JSON object.\\n\\n${this.reviewJsonSchemaHint(filePath, expectedMainAgentInstructions)}`);\n }\n\n const errors: string[] = [];\n for (const field of ['agent', 'model']) {\n if (typeof raw[field] !== 'string' || raw[field].trim() === '') {\n errors.push(`\"${field}\" must be a non-empty string; use \"unknown\" when unavailable.`);\n }\n }\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 const mainAgentInstructions = this.validateMainAgentInstructions(raw, expectedMainAgentInstructions, errors);\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, expectedMainAgentInstructions)}`,\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((raw['agent'] as string).trim(), (raw['model'] as string).trim(),\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 mainAgentInstructions,\n );\n }\n\n // webpieces-disable no-any-unknown -- AI-authored JSON boundary is validated before use\n private validateMainAgentInstructions(raw: Record<string, unknown>, expected: string, errors: string[]): string {\n const actual = typeof raw['main_agent_instructions'] === 'string'\n ? (raw['main_agent_instructions'] as string).trim() : '';\n if (expected !== '' && actual !== expected)\n errors.push('\"main_agent_instructions\" must exactly preserve the single-round instructions printed by stage ②.');\n return actual;\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.reviewer.agentName}) 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.reviewer.agentName}\" 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.reviewer.agentName}\" subagent to review it; ` +\n `IT submits the verdict with ${this.submitCommand(req.id)} — nobody writes ${this.checklistFileName(req.id)} by hand.${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.\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. It ends with the\n * ONE way a verdict is submitted (`wp-write-review`, issue #863), never a file path to write.\n */\n renderVerdictSchema(id: string, indent = ' '): string {\n return new VerdictSchemaRenderer().render(id, indent);\n }\n\n /** THE command that submits one checklist's verdict (issue #863) — see VerdictSchemaRenderer. */\n submitCommand(id: string): string {\n return new VerdictSchemaRenderer().submitCommand(id);\n }\n\n // Unreadable objects retain their format complaint; absent/unparseable verdicts return null.\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 const agent = typeof raw['agent'] === 'string' ? raw['agent'].trim() : '';\n const model = typeof raw['model'] === 'string' ? raw['model'].trim() : '';\n const identityProblem = agent === '' || model === ''\n ? 'Both \"agent\" and \"model\" must be non-empty strings; use \"unknown\" when unavailable.' : '';\n return new ChecklistResult(agent, model, id, status, output, override,\n this.verdictProblem(filePath, id, status, raw) || identityProblem);\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 renderVerdictSchema. 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.renderVerdictSchema(id);\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} by resubmitting it:\\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. Resubmit it 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(\n raw: string, filePath: string, expectedMainAgentInstructions = '',\n ): 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` +\n `${this.reviewJsonSchemaHint(filePath, expectedMainAgentInstructions)}\\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, mainAgentInstructions = ''): string {\n return reviewJsonSvc.reviewJsonSchemaHint(filePath, mainAgentInstructions);\n}\n"]}
|
|
@@ -78,7 +78,7 @@ export declare const ALL_DIFF_ONE_READ_LINES = 1500;
|
|
|
78
78
|
* It is GENERATED per run, and the reviewer agent (`.claude/agents/<name>.md`, by default the
|
|
79
79
|
* webpieces-owned `webpieces-reviewer.md`) is checklist-agnostic and points here, because content a human
|
|
80
80
|
* maintains goes stale and a reviewer follows the stale copy. The verdict
|
|
81
|
-
* schema in particular comes from {@link ReviewJsonService.
|
|
81
|
+
* schema in particular comes from {@link ReviewJsonService.renderVerdictSchema}.
|
|
82
82
|
*
|
|
83
83
|
* `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
|
|
84
84
|
*/
|
|
@@ -135,7 +135,7 @@ exports.ALL_DIFF_ONE_READ_LINES = 1500;
|
|
|
135
135
|
* It is GENERATED per run, and the reviewer agent (`.claude/agents/<name>.md`, by default the
|
|
136
136
|
* webpieces-owned `webpieces-reviewer.md`) is checklist-agnostic and points here, because content a human
|
|
137
137
|
* maintains goes stale and a reviewer follows the stale copy. The verdict
|
|
138
|
-
* schema in particular comes from {@link ReviewJsonService.
|
|
138
|
+
* schema in particular comes from {@link ReviewJsonService.renderVerdictSchema}.
|
|
139
139
|
*
|
|
140
140
|
* `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
|
|
141
141
|
*/
|
|
@@ -383,12 +383,16 @@ let ReviewerInstructionsService = class ReviewerInstructionsService {
|
|
|
383
383
|
*/
|
|
384
384
|
verdictSection(b) {
|
|
385
385
|
return [
|
|
386
|
-
'##
|
|
386
|
+
'## Submit your verdict',
|
|
387
387
|
'',
|
|
388
|
-
`
|
|
388
|
+
`Submit EXACTLY this shape through \`${this.reviewJsonService.submitCommand(b.checklistId)}\` —`,
|
|
389
|
+
'the JSON on stdin (a heredoc is simplest), or `--file <path>` naming a scratch file. Do NOT write',
|
|
390
|
+
`\`${b.verdictPath}\` yourself: the bin validates the verdict, writes that file, and records that YOU`,
|
|
391
|
+
'submitted it, and `wp-finish-upsert-pr` REJECTS a verdict file written any other way. The bin also',
|
|
392
|
+
'refuses the coordinating agent, so nobody can submit this on your behalf.',
|
|
389
393
|
'',
|
|
390
394
|
'```',
|
|
391
|
-
this.reviewJsonService.
|
|
395
|
+
this.reviewJsonService.renderVerdictSchema(b.checklistId, ''),
|
|
392
396
|
'```',
|
|
393
397
|
'',
|
|
394
398
|
];
|
|
@@ -418,7 +422,8 @@ let ReviewerInstructionsService = class ReviewerInstructionsService {
|
|
|
418
422
|
'Open your diff files before writing a verdict. `wp-finish-upsert-pr` reads your own transcript and',
|
|
419
423
|
'reports a verdict written without opening the diff. It also records the path of that transcript,',
|
|
420
424
|
'beside what you were offered and what you read, in `provenance.json` next to your verdict file —',
|
|
421
|
-
'so the review is auditable after the fact. You do not write that file
|
|
425
|
+
'so the review is auditable after the fact. You do not write that file, or the',
|
|
426
|
+
'`review-<id>.provenance.json` wp-write-review writes beside your verdict; the tooling does.',
|
|
422
427
|
];
|
|
423
428
|
}
|
|
424
429
|
};
|