@webpieces/rules-config 0.4.775 → 0.4.776
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-config.d.ts +37 -6
- package/src/checklist-config.js +55 -19
- package/src/checklist-config.js.map +1 -1
- package/src/checklist-docs-validator.d.ts +2 -2
- package/src/checklist-docs-validator.js +2 -2
- package/src/checklist-docs-validator.js.map +1 -1
- package/src/checklist-instructions.d.ts +11 -0
- package/src/checklist-instructions.js +43 -11
- package/src/checklist-instructions.js.map +1 -1
- package/src/checklist-validator.d.ts +13 -12
- package/src/checklist-validator.js +46 -34
- package/src/checklist-validator.js.map +1 -1
- package/src/constants.d.ts +6 -0
- package/src/constants.js +7 -1
- package/src/constants.js.map +1 -1
- package/src/index.d.ts +3 -3
- package/src/index.js +11 -6
- package/src/index.js.map +1 -1
- package/src/pr-gate-config.d.ts +9 -1
- package/src/pr-gate-config.js +15 -4
- package/src/pr-gate-config.js.map +1 -1
- package/src/pr-gate-section-validators.d.ts +17 -3
- package/src/pr-gate-section-validators.js +91 -30
- package/src/pr-gate-section-validators.js.map +1 -1
- package/src/retired-config-keys.js +8 -0
- package/src/retired-config-keys.js.map +1 -1
- package/src/review-json-data.d.ts +3 -2
- package/src/review-json-data.js +4 -4
- package/src/review-json-data.js.map +1 -1
- package/src/review-json.js +3 -3
- package/src/review-json.js.map +1 -1
- package/src/review-provenance.js +2 -2
- package/src/review-provenance.js.map +1 -1
- package/src/reviewer-instructions.d.ts +13 -9
- package/src/reviewer-instructions.js +27 -16
- package/src/reviewer-instructions.js.map +1 -1
- package/src/subagent-provenance.d.ts +30 -15
- package/src/subagent-provenance.js +100 -50
- package/src/subagent-provenance.js.map +1 -1
- package/src/validate-config.js +5 -2
- package/src/validate-config.js.map +1 -1
- package/templates/webpieces.review-checklists.md +50 -27
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.subagent}) wrote:\\n ` + finding;\n const retired = archivedPath === ''\n ? ' Fix it, then re-run.\\n'\n // Re-spawning is said only after the finding, because an instruction to spawn a subagent is the\n // one line an AI acts on first — see refusedChecklists for what that cost.\n : ` That verdict has been RETIRED to ${archivedPath} (audit only — it is not a live verdict).\\n` +\n ` A FRESH ${this.checklistFileName(req.id)} is now required. Fix the finding first, then have the ` +\n `\"${req.subagent}\" subagent review again and write a new verdict.\\n`;\n return head + retired + this.overrideRoute(req, reviewJsonFilePath);\n }\n\n /**\n * The ship-anyway paragraph: who may authorize, and the exact command that records it.\n *\n * Its own method because every refusal surface must say the identical thing about who may write an\n * override. A second copy of this paragraph is precisely how the previous one drifted into naming a\n * command that had since been deleted.\n */\n private overrideRoute(req: RequiredChecklist, reviewJsonFilePath: string): string {\n return ` To SHIP ANYWAY a human must decide it, and the decision is recorded in its own file — `\n + `${this.overrides.overrideFileName(req.id)}, never inside the reviewer's verdict.\\n`\n + ` ${this.overrides.writerRule()}\\n`\n + ' Run exactly this, replacing only the \"reason\" with what the human actually said:\\n\\n'\n + `${this.overrides.writeCommand(reviewJsonFilePath, req.id)}\\n`;\n }\n\n // Read the per-checklist verdict files `review-<id>.json` beside review.json — one per matched checklist.\n // A missing file is simply absent from the result (→ counts as MISSING for that checklist); a malformed\n // one is skipped (a stale review-<id>.json never wedges the branch).\n //\n // It looks up the EXACT `review-<id>.json` name per required id — never a directory scan, never a prefix\n // match. That is what guarantees an archived `review-<id>.json.old` can never resolve as a live verdict:\n // the retired file sits right beside the live path, and a scan that swept the directory would hand a\n // RETIRED refusal (or worse, a retired pass) back as the current state, undoing the whole point of the\n // move in {@link archiveChecklistResult}.\n loadChecklistResults(reviewJsonFilePath: string, required: readonly RequiredChecklist[]): ChecklistResult[] {\n const results: ChecklistResult[] = [];\n for (const req of required) {\n const p = this.checklistResultPath(reviewJsonFilePath, req.id);\n if (!fs.existsSync(p)) continue;\n // The human's authorization is read from its OWN file beside the verdict, in the same pass, so\n // resolveVerdict never touches disk and every command resolves one outcome from one read.\n const parsed = this.parseChecklistResult(p, req.id, this.overrides.load(reviewJsonFilePath, req.id));\n if (parsed) results.push(parsed);\n }\n return results;\n }\n\n // Resolve ONE checklist's verdict from its review-<id>.json. Central so review.json enforcement AND the\n // finish-command dashboard agree on the outcome. `problem` is checked FIRST: a file whose verdict cannot\n // be read must not fall through to any shipping outcome.\n resolveVerdict(req: RequiredChecklist, results: readonly ChecklistResult[]): ChecklistVerdict {\n const result = results.find((r: ChecklistResult): boolean => r.id === req.id);\n if (!result) return new ChecklistVerdict(req.id, CK_MISSING, '');\n if (result.problem !== '') return new ChecklistVerdict(req.id, CK_BAD_FORMAT, result.problem);\n if (result.status === VERDICT_GREEN) return new ChecklistVerdict(req.id, CK_PASS, result.output);\n if (result.status === VERDICT_YELLOW) return new ChecklistVerdict(req.id, CK_WARN, result.output);\n const override = result.override;\n // A malformed authorization is reported as a FORMAT problem, never treated as one: an override with\n // no stated reason authorizes nothing, and silently ignoring it would tell the reader their decision\n // was not recorded without ever saying why.\n if (override !== null && override.problem !== '') return new ChecklistVerdict(req.id, CK_BAD_FORMAT, override.problem);\n if (override !== null) return new ChecklistVerdict(req.id, CK_OVERRIDDEN, this.overrides.detail(override));\n return new ChecklistVerdict(req.id, CK_FAIL, result.output);\n }\n\n /**\n * One loud complaint per checklist whose verdict file EXISTS but cannot be read as a verdict — almost\n * always one still using the removed `success` field. Public and separate from\n * {@link requiredChecklistErrors} because `wp-finish-upsert-pr` refuses on missing reviewers BEFORE it\n * parses review.json: without this, a legacy file would surface as the generic \"no verdict yet\" block\n * and the AI would re-run a reviewer that already ran instead of fixing four characters of JSON.\n */\n checklistFormatErrors(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): string[] {\n const errors: string[] = [];\n for (const req of required) {\n const verdict = this.resolveVerdict(req, results);\n if (verdict.status === CK_BAD_FORMAT) errors.push(verdict.detail);\n }\n return errors;\n }\n\n // Every matched checklist whose verdict is FAIL (reviewed, found a problem, no override) or MISSING (no\n // review-<id>.json written) → one error each, printing the reviewer's `output` verbatim.\n private requiredChecklistErrors(\n required: readonly RequiredChecklist[], results: readonly ChecklistResult[], filePath: string,\n ): string[] {\n // Format complaints come from the ONE renderer, so wp-review-upsert-pr and wp-finish word them identically.\n const errors: string[] = this.checklistFormatErrors(required, results);\n for (const req of required) {\n const verdict = this.resolveVerdict(req, results);\n // CK_WARN ('yellow' — passed with concerns) is deliberately absent from this chain: it SHIPS.\n // The concern still reaches the PR, published in the checklist comment. Do not \"fix\" this.\n if (verdict.status === CK_FAIL) {\n // Through the ONE renderer, so this path and the command layer's refusal say the same thing.\n // No archive path here: this is validation, not the act of retiring the verdict.\n errors.push(this.refusalError(req, verdict, filePath));\n } else if (verdict.status === CK_MISSING) {\n // An OPTIONAL checklist with no verdict was legitimately not run — the human was offered it\n // and declined (or `--no-optional` skipped the offer). Demanding it here would make\n // `required: false` mean nothing. Note the CK_FAIL branch above deliberately has no such\n // exemption: once an optional reviewer RUNS, its refusal counts.\n if (!req.required) continue;\n const doc = req.doc.trim() !== '' ? ` Read: ${req.doc}.` : '';\n errors.push(\n `Checklist \"${req.id}\" MATCHED this diff but has no verdict. Spawn the \"${req.subagent}\" subagent to review it, ` +\n `then write ${this.checklistFileName(req.id)} with ` +\n `{\"id\":\"${req.id}\",\"status\":\"${VERDICT_GREEN}\",\"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 { 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"]}
|
package/src/review-provenance.js
CHANGED
|
@@ -25,7 +25,7 @@ const WHAT_THIS_IS = 'AUDIT RECORD — written by wp-finish-upsert-pr, never by
|
|
|
25
25
|
/** Where ONE reviewer's inputs and output live on disk. Data-only (per CLAUDE.md). */
|
|
26
26
|
class ReviewerPaths {
|
|
27
27
|
verdictFile; // the review-<id>.json this reviewer was told to write
|
|
28
|
-
instructionsFile; // its generated <
|
|
28
|
+
instructionsFile; // its checklist's generated <id>.instructions.md
|
|
29
29
|
docPath; // its checklist's guidance doc ('' when the checklist names none)
|
|
30
30
|
constructor(verdictFile, instructionsFile, docPath) {
|
|
31
31
|
this.verdictFile = verdictFile;
|
|
@@ -67,7 +67,7 @@ class ReviewerTranscript {
|
|
|
67
67
|
*/
|
|
68
68
|
models;
|
|
69
69
|
constructor(evidence, paths) {
|
|
70
|
-
this.id = evidence.
|
|
70
|
+
this.id = evidence.checklistId; // the checklist; agentType is the (shared) reviewer agent type
|
|
71
71
|
this.agentType = evidence.agentType;
|
|
72
72
|
this.agentId = evidence.agentId;
|
|
73
73
|
this.transcript = evidence.transcriptPath;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-provenance.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-provenance.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { toError } from './to-error';\nimport { ReviewerEvidence } from './subagent-provenance';\n\n// The audit record `wp-finish-upsert-pr` writes beside review.json, and where a consumed one is retired to.\nconst PROVENANCE_FILE = 'provenance.json';\nconst OLD_PROVENANCE_FILE = 'old-provenance.json';\n\n/**\n * Claude Code deletes transcripts after `cleanupPeriodDays`; 30 is its default and what applies when the\n * setting is absent, which is the common case. Recorded in every provenance file so a reader knows how\n * long the links it is holding remain resolvable rather than discovering it by following a dead path.\n */\nexport const DEFAULT_RETENTION_DAYS = 30;\n\nconst WHAT_THIS_IS =\n 'AUDIT RECORD — written by wp-finish-upsert-pr, never by an AI. It links each reviewer verdict to the ' +\n 'transcript of the subagent that produced it, and records what that reviewer was OFFERED versus what it ' +\n 'demonstrably READ. Open it to audit the review process itself: whether a verdict was written by an ' +\n 'agent that actually opened the diff and its checklist doc. Do not hand-edit it, and do not treat it as ' +\n 'a review — it says nothing about whether the code is good, only about how it was looked at. The linked ' +\n 'transcripts expire (see transcriptsExpireOn); the counters recorded here do not.';\n\n/** Where ONE reviewer's inputs and output live on disk. Data-only (per CLAUDE.md). */\nexport class ReviewerPaths {\n verdictFile: string; // the review-<id>.json this reviewer was told to write\n instructionsFile: string; // its generated <subagent>.instructions.md\n docPath: string; // its checklist's guidance doc ('' when the checklist names none)\n\n constructor(verdictFile: string, instructionsFile: string, docPath: string) {\n this.verdictFile = verdictFile;\n this.instructionsFile = instructionsFile;\n this.docPath = docPath;\n }\n}\n\n/**\n * ONE reviewer's provenance row: which agent ran, where its transcript is, and the evidence counters read\n * out of that transcript. Data-only, and its FIELD NAMES ARE THE JSON KEYS — the file is written by\n * serializing these objects directly, so renaming a field renames it in every consumer's audit record.\n *\n * The counters are copied here rather than left to be re-derived because the transcript they came from is a\n * wasting asset (~30 days). After it expires this row still answers \"did that reviewer read the diff?\".\n */\nexport class ReviewerTranscript {\n id: string;\n agentType: string;\n agentId: string;\n transcript: string; // absolute path to agent-<id>.jsonl, '' when it could not be resolved\n transcriptExists: boolean;\n verdictFile: string;\n instructionsFile: string;\n docPath: string;\n readDiff: boolean;\n readDoc: boolean;\n // Named its own verdict file in a tool input — the row's answer to \"who wrote this verdict?\", and the\n // strongest of the two channels that can credit a reviewer whose harness-stamped cwd points elsewhere.\n wroteVerdict: boolean;\n toolCallCount: number;\n offRepoSearches: number;\n /**\n * The model(s) that ACTUALLY served this reviewer, observed in its transcript — not the `model:` its\n * agent file requested, which can silently disagree, and not anything the reviewer claimed.\n *\n * Copied here for the reason the whole class exists: the transcript is a wasting asset (~30 days),\n * and after it expires this row is the only surviving answer to \"which model reviewed this?\".\n */\n models: string[];\n\n constructor(evidence: ReviewerEvidence, paths: ReviewerPaths) {\n this.id = evidence.agentType; // a checklist's id IS its subagent name (see ChecklistDefinition)\n this.agentType = evidence.agentType;\n this.agentId = evidence.agentId;\n this.transcript = evidence.transcriptPath;\n this.transcriptExists = evidence.transcriptPath !== '' && fs.existsSync(evidence.transcriptPath);\n this.verdictFile = paths.verdictFile;\n this.instructionsFile = paths.instructionsFile;\n this.docPath = paths.docPath;\n this.readDiff = evidence.readDiff;\n this.readDoc = evidence.readDoc;\n this.wroteVerdict = evidence.wroteVerdict;\n this.toolCallCount = evidence.toolCallCount;\n this.offRepoSearches = evidence.offRepoSearches;\n this.models = evidence.models;\n }\n}\n\n/** What the reviewers were handed, whether or not any of them opened it. Data-only. */\nexport class OfferedContext {\n diffDir: string;\n instructionsDir: string;\n\n constructor(diffDir: string, instructionsDir: string) {\n this.diffDir = diffDir;\n this.instructionsDir = instructionsDir;\n }\n}\n\n/**\n * What {@link ReviewProvenanceService.write} is asked to record. Data-only; `offered` and `reviewers` are\n * assigned after construction (the same shape ChecklistCommentRow uses) so this never becomes a 7-param\n * constructor.\n */\nexport class ProvenanceWriteRequest {\n prDir: string;\n /**\n * The dash-sanitized FEATURE SLUG, not a git branch — `AiBranchName.getFeatureName()`, which is\n * `baseBranchName(branch).replace(/\\//g, '-')`. It names the on-disk `pr-review/<slug>/` dir.\n *\n * It was called `branch`, and that mislabel cost a real investigation: a bug report against the\n * provenance gate reasoned from `provenance.json`'s dash-form `branch` that `sidechainOnBranch` must\n * be comparing a dash-form name against a slash-form git branch, and went looking for a missing\n * normalization. There is none — both sides of that comparison are slash-form\n * (`git branch --show-current`) and this field never reaches it. A field called `branch` that never\n * holds a branch is what sent the reader down that path; the name is now what it holds.\n */\n featureSlug: string;\n headSha: string;\n provenanceStatus: string; // PROVENANCE_OK | PROVENANCE_MISSING | PROVENANCE_SKIPPED\n offered: OfferedContext = new OfferedContext('', '');\n reviewers: ReviewerTranscript[] = [];\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(prDir: string, featureSlug: string, headSha: string, provenanceStatus: string) {\n this.prDir = prDir;\n this.featureSlug = featureSlug;\n this.headSha = headSha;\n this.provenanceStatus = provenanceStatus;\n }\n}\n\n/**\n * The written record. Field names are the JSON keys, in this order — `_WHAT_THIS_IS` is declared FIRST so\n * anything that opens the file reads what it is before it reads anything it might act on, exactly as\n * ReviewJsonService.archiveReviewJson stamps its note first.\n */\nexport class ReviewProvenance {\n // webpieces-disable naming-convention -- the leading underscore marks a note-to-the-reader key, not data\n _WHAT_THIS_IS = WHAT_THIS_IS;\n sessionId: string;\n mainTranscript: string;\n // The JSON key too: a reader of provenance.json sees `featureSlug`, matching the `pr-review/<slug>/`\n // dir it sits in. See ProvenanceWriteRequest.featureSlug for why the old `branch` key was a defect.\n featureSlug: string;\n headSha: string;\n stampedAt: string;\n transcriptRetentionDays: number;\n transcriptsExpireOn: string;\n provenanceStatus: string;\n offered: OfferedContext;\n reviewers: ReviewerTranscript[];\n\n constructor(request: ProvenanceWriteRequest) {\n this.sessionId = '';\n this.mainTranscript = '';\n this.featureSlug = request.featureSlug;\n this.headSha = request.headSha;\n this.stampedAt = '';\n this.transcriptRetentionDays = DEFAULT_RETENTION_DAYS;\n this.transcriptsExpireOn = '';\n this.provenanceStatus = request.provenanceStatus;\n this.offered = request.offered;\n this.reviewers = request.reviewers;\n }\n}\n\n/**\n * Records WHICH transcript produced which verdict, so the review process itself can be audited later.\n *\n * Why a service and not a field the AI writes: a reviewer subagent CANNOT know its own transcript path. The\n * environment exposes `CLAUDE_CODE_SESSION_ID` — the PARENT session — and no agent id, so a self-reported\n * link would be invented. Every path here is derived from the harness's own artifacts:\n * ~/.claude/projects/*/<sessionId>.jsonl → the main agent's transcript\n * ~/.claude/projects/*/<sessionId>/subagents/agent-<id>.jsonl → one reviewer's transcript\n * The subagent half is already resolved by {@link SubagentProvenanceService}; this carries it to disk and\n * adds the session-level facts (which session, how long the links live).\n *\n * Deliberately a SEPARATE file from review.json / review-<id>.json: those are AI-authored and stay\n * byte-untouched, so nothing here can be confused for something a reviewer claimed about itself.\n *\n * Best-effort throughout — an unreadable ~/.claude degrades the record to empty links, never fails a PR.\n * Same reasoning as SubagentProvenanceService: this reads undocumented Claude Code internals, and a format\n * change must not wedge a consumer's PR.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is drawn in the DI design and injected by type.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewProvenanceService {\n // Where the audit record for a branch lives, beside review.json.\n provenancePath(prDir: string): string {\n return path.join(prDir, PROVENANCE_FILE);\n }\n\n // Where a consumed record is retired to — the mirror of ReviewJsonService.oldReviewJsonPath, so an\n // archived old-review.json keeps the transcript links belonging to the round that produced it.\n oldProvenancePath(prDir: string): string {\n return path.join(prDir, OLD_PROVENANCE_FILE);\n }\n\n // The current Claude Code session id, or '' outside a Claude Code session (plain terminal / CI).\n sessionId(): string {\n return (process.env['CLAUDE_CODE_SESSION_ID'] ?? '').trim();\n }\n\n /**\n * The main agent's own transcript: `~/.claude/projects/<cwd-slug>/<sessionId>.jsonl`. Located by\n * scanning every project dir for the file named after the session, so the cwd-slug — which is a\n * mangling of the working directory we would otherwise have to reproduce exactly — never has to be\n * derived. '' when there is no session or the file is not there.\n */\n mainTranscript(): string {\n const session = this.sessionId();\n if (session === '') return '';\n const projects = path.join(os.homedir(), '.claude', 'projects');\n for (const proj of this.readDir(projects)) {\n const candidate = path.join(projects, proj, `${session}.jsonl`);\n if (fs.existsSync(candidate)) return candidate;\n }\n return '';\n }\n\n /**\n * How many days Claude Code keeps transcripts: `cleanupPeriodDays` from ~/.claude/settings.json (then\n * settings.local.json), else {@link DEFAULT_RETENTION_DAYS}. The setting is usually absent, which is\n * why the default is documented rather than left implicit.\n */\n retentionDays(): number {\n const dir = path.join(os.homedir(), '.claude');\n for (const file of ['settings.json', 'settings.local.json']) {\n const settings = this.readJson(path.join(dir, file));\n const days = settings?.['cleanupPeriodDays'];\n if (typeof days === 'number' && Number.isFinite(days) && days > 0) return days;\n }\n return DEFAULT_RETENTION_DAYS;\n }\n\n /**\n * The date the FIRST of these transcripts becomes unreadable: the oldest one's mtime + retentionDays,\n * as an ISO date. The oldest rather than the newest because that is when the audit trail starts losing\n * links, and a reader planning to follow them needs the pessimistic answer. '' when none exist.\n */\n expiresOn(transcripts: readonly string[], retentionDays: number): string {\n let oldest = 0;\n for (const file of transcripts) {\n const mtime = this.mtimeOf(file);\n if (mtime !== 0 && (oldest === 0 || mtime < oldest)) oldest = mtime;\n }\n if (oldest === 0) return '';\n const expiry = new Date(oldest + retentionDays * 24 * 60 * 60 * 1000);\n return expiry.toISOString().slice(0, 10);\n }\n\n /**\n * Write the record to `<prDir>/provenance.json` and return its path ('' if it could not be written).\n *\n * Written on EVERY finish, including one that refuses for a missing reviewer: a refused round is\n * precisely the one worth auditing, and a record that only ever appears on success cannot answer \"what\n * did the reviewers do the time this was rejected?\".\n */\n write(request: ProvenanceWriteRequest): string {\n const provenance = this.build(request);\n const target = this.provenancePath(request.prDir);\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: the audit record is never worth failing a PR over\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.mkdirSync(request.prDir, { recursive: true });\n fs.writeFileSync(target, JSON.stringify(provenance, null, 2) + '\\n');\n return target;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return '';\n }\n }\n\n // Retire the record for the round that just shipped: copy it to old-provenance.json beside the\n // old-review.json it belongs to. A COPY, not a move — unlike review.json this file is not an input to\n // anything, so leaving it in place cannot mislead a later reviewer, and the next finish overwrites it.\n archive(prDir: string): string {\n const source = this.provenancePath(prDir);\n if (!fs.existsSync(source)) return '';\n const target = this.oldProvenancePath(prDir);\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: a failed archive must not fail the command\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.copyFileSync(source, target);\n return target;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return '';\n }\n }\n\n // Fill in the session-level facts the caller cannot know: which session, which transcripts, how long.\n private build(request: ProvenanceWriteRequest): ReviewProvenance {\n const provenance = new ReviewProvenance(request);\n provenance.sessionId = this.sessionId();\n provenance.mainTranscript = this.mainTranscript();\n provenance.stampedAt = new Date().toISOString();\n provenance.transcriptRetentionDays = this.retentionDays();\n const linked = [provenance.mainTranscript, ...request.reviewers.map(\n (r: ReviewerTranscript): string => r.transcript)].filter((p: string): boolean => p !== '');\n provenance.transcriptsExpireOn = this.expiresOn(linked, provenance.transcriptRetentionDays);\n return provenance;\n }\n\n // Epoch millis of a file's mtime, or 0 when it cannot be read.\n private mtimeOf(filePath: string): number {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unstattable transcript contributes no expiry\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.statSync(filePath).mtime.getTime();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return 0;\n }\n }\n\n private readDir(dir: string): string[] {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unreadable dir yields [] (best-effort)\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.readdirSync(dir);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n }\n\n // webpieces-disable no-any-unknown -- opaque parsed JSON object, keys read by the caller\n private readJson(filePath: string): Record<string, unknown> | null {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: malformed settings → null (fall through to the default)\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(fs.readFileSync(filePath, 'utf8')) as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"review-provenance.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-provenance.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { toError } from './to-error';\nimport { ReviewerEvidence } from './subagent-provenance';\n\n// The audit record `wp-finish-upsert-pr` writes beside review.json, and where a consumed one is retired to.\nconst PROVENANCE_FILE = 'provenance.json';\nconst OLD_PROVENANCE_FILE = 'old-provenance.json';\n\n/**\n * Claude Code deletes transcripts after `cleanupPeriodDays`; 30 is its default and what applies when the\n * setting is absent, which is the common case. Recorded in every provenance file so a reader knows how\n * long the links it is holding remain resolvable rather than discovering it by following a dead path.\n */\nexport const DEFAULT_RETENTION_DAYS = 30;\n\nconst WHAT_THIS_IS =\n 'AUDIT RECORD — written by wp-finish-upsert-pr, never by an AI. It links each reviewer verdict to the ' +\n 'transcript of the subagent that produced it, and records what that reviewer was OFFERED versus what it ' +\n 'demonstrably READ. Open it to audit the review process itself: whether a verdict was written by an ' +\n 'agent that actually opened the diff and its checklist doc. Do not hand-edit it, and do not treat it as ' +\n 'a review — it says nothing about whether the code is good, only about how it was looked at. The linked ' +\n 'transcripts expire (see transcriptsExpireOn); the counters recorded here do not.';\n\n/** Where ONE reviewer's inputs and output live on disk. Data-only (per CLAUDE.md). */\nexport class ReviewerPaths {\n verdictFile: string; // the review-<id>.json this reviewer was told to write\n instructionsFile: string; // its checklist's generated <id>.instructions.md\n docPath: string; // its checklist's guidance doc ('' when the checklist names none)\n\n constructor(verdictFile: string, instructionsFile: string, docPath: string) {\n this.verdictFile = verdictFile;\n this.instructionsFile = instructionsFile;\n this.docPath = docPath;\n }\n}\n\n/**\n * ONE reviewer's provenance row: which agent ran, where its transcript is, and the evidence counters read\n * out of that transcript. Data-only, and its FIELD NAMES ARE THE JSON KEYS — the file is written by\n * serializing these objects directly, so renaming a field renames it in every consumer's audit record.\n *\n * The counters are copied here rather than left to be re-derived because the transcript they came from is a\n * wasting asset (~30 days). After it expires this row still answers \"did that reviewer read the diff?\".\n */\nexport class ReviewerTranscript {\n id: string;\n agentType: string;\n agentId: string;\n transcript: string; // absolute path to agent-<id>.jsonl, '' when it could not be resolved\n transcriptExists: boolean;\n verdictFile: string;\n instructionsFile: string;\n docPath: string;\n readDiff: boolean;\n readDoc: boolean;\n // Named its own verdict file in a tool input — the row's answer to \"who wrote this verdict?\", and the\n // strongest of the two channels that can credit a reviewer whose harness-stamped cwd points elsewhere.\n wroteVerdict: boolean;\n toolCallCount: number;\n offRepoSearches: number;\n /**\n * The model(s) that ACTUALLY served this reviewer, observed in its transcript — not the `model:` its\n * agent file requested, which can silently disagree, and not anything the reviewer claimed.\n *\n * Copied here for the reason the whole class exists: the transcript is a wasting asset (~30 days),\n * and after it expires this row is the only surviving answer to \"which model reviewed this?\".\n */\n models: string[];\n\n constructor(evidence: ReviewerEvidence, paths: ReviewerPaths) {\n this.id = evidence.checklistId; // the checklist; agentType is the (shared) reviewer agent type\n this.agentType = evidence.agentType;\n this.agentId = evidence.agentId;\n this.transcript = evidence.transcriptPath;\n this.transcriptExists = evidence.transcriptPath !== '' && fs.existsSync(evidence.transcriptPath);\n this.verdictFile = paths.verdictFile;\n this.instructionsFile = paths.instructionsFile;\n this.docPath = paths.docPath;\n this.readDiff = evidence.readDiff;\n this.readDoc = evidence.readDoc;\n this.wroteVerdict = evidence.wroteVerdict;\n this.toolCallCount = evidence.toolCallCount;\n this.offRepoSearches = evidence.offRepoSearches;\n this.models = evidence.models;\n }\n}\n\n/** What the reviewers were handed, whether or not any of them opened it. Data-only. */\nexport class OfferedContext {\n diffDir: string;\n instructionsDir: string;\n\n constructor(diffDir: string, instructionsDir: string) {\n this.diffDir = diffDir;\n this.instructionsDir = instructionsDir;\n }\n}\n\n/**\n * What {@link ReviewProvenanceService.write} is asked to record. Data-only; `offered` and `reviewers` are\n * assigned after construction (the same shape ChecklistCommentRow uses) so this never becomes a 7-param\n * constructor.\n */\nexport class ProvenanceWriteRequest {\n prDir: string;\n /**\n * The dash-sanitized FEATURE SLUG, not a git branch — `AiBranchName.getFeatureName()`, which is\n * `baseBranchName(branch).replace(/\\//g, '-')`. It names the on-disk `pr-review/<slug>/` dir.\n *\n * It was called `branch`, and that mislabel cost a real investigation: a bug report against the\n * provenance gate reasoned from `provenance.json`'s dash-form `branch` that `sidechainOnBranch` must\n * be comparing a dash-form name against a slash-form git branch, and went looking for a missing\n * normalization. There is none — both sides of that comparison are slash-form\n * (`git branch --show-current`) and this field never reaches it. A field called `branch` that never\n * holds a branch is what sent the reader down that path; the name is now what it holds.\n */\n featureSlug: string;\n headSha: string;\n provenanceStatus: string; // PROVENANCE_OK | PROVENANCE_MISSING | PROVENANCE_SKIPPED\n offered: OfferedContext = new OfferedContext('', '');\n reviewers: ReviewerTranscript[] = [];\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(prDir: string, featureSlug: string, headSha: string, provenanceStatus: string) {\n this.prDir = prDir;\n this.featureSlug = featureSlug;\n this.headSha = headSha;\n this.provenanceStatus = provenanceStatus;\n }\n}\n\n/**\n * The written record. Field names are the JSON keys, in this order — `_WHAT_THIS_IS` is declared FIRST so\n * anything that opens the file reads what it is before it reads anything it might act on, exactly as\n * ReviewJsonService.archiveReviewJson stamps its note first.\n */\nexport class ReviewProvenance {\n // webpieces-disable naming-convention -- the leading underscore marks a note-to-the-reader key, not data\n _WHAT_THIS_IS = WHAT_THIS_IS;\n sessionId: string;\n mainTranscript: string;\n // The JSON key too: a reader of provenance.json sees `featureSlug`, matching the `pr-review/<slug>/`\n // dir it sits in. See ProvenanceWriteRequest.featureSlug for why the old `branch` key was a defect.\n featureSlug: string;\n headSha: string;\n stampedAt: string;\n transcriptRetentionDays: number;\n transcriptsExpireOn: string;\n provenanceStatus: string;\n offered: OfferedContext;\n reviewers: ReviewerTranscript[];\n\n constructor(request: ProvenanceWriteRequest) {\n this.sessionId = '';\n this.mainTranscript = '';\n this.featureSlug = request.featureSlug;\n this.headSha = request.headSha;\n this.stampedAt = '';\n this.transcriptRetentionDays = DEFAULT_RETENTION_DAYS;\n this.transcriptsExpireOn = '';\n this.provenanceStatus = request.provenanceStatus;\n this.offered = request.offered;\n this.reviewers = request.reviewers;\n }\n}\n\n/**\n * Records WHICH transcript produced which verdict, so the review process itself can be audited later.\n *\n * Why a service and not a field the AI writes: a reviewer subagent CANNOT know its own transcript path. The\n * environment exposes `CLAUDE_CODE_SESSION_ID` — the PARENT session — and no agent id, so a self-reported\n * link would be invented. Every path here is derived from the harness's own artifacts:\n * ~/.claude/projects/*/<sessionId>.jsonl → the main agent's transcript\n * ~/.claude/projects/*/<sessionId>/subagents/agent-<id>.jsonl → one reviewer's transcript\n * The subagent half is already resolved by {@link SubagentProvenanceService}; this carries it to disk and\n * adds the session-level facts (which session, how long the links live).\n *\n * Deliberately a SEPARATE file from review.json / review-<id>.json: those are AI-authored and stay\n * byte-untouched, so nothing here can be confused for something a reviewer claimed about itself.\n *\n * Best-effort throughout — an unreadable ~/.claude degrades the record to empty links, never fails a PR.\n * Same reasoning as SubagentProvenanceService: this reads undocumented Claude Code internals, and a format\n * change must not wedge a consumer's PR.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is drawn in the DI design and injected by type.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewProvenanceService {\n // Where the audit record for a branch lives, beside review.json.\n provenancePath(prDir: string): string {\n return path.join(prDir, PROVENANCE_FILE);\n }\n\n // Where a consumed record is retired to — the mirror of ReviewJsonService.oldReviewJsonPath, so an\n // archived old-review.json keeps the transcript links belonging to the round that produced it.\n oldProvenancePath(prDir: string): string {\n return path.join(prDir, OLD_PROVENANCE_FILE);\n }\n\n // The current Claude Code session id, or '' outside a Claude Code session (plain terminal / CI).\n sessionId(): string {\n return (process.env['CLAUDE_CODE_SESSION_ID'] ?? '').trim();\n }\n\n /**\n * The main agent's own transcript: `~/.claude/projects/<cwd-slug>/<sessionId>.jsonl`. Located by\n * scanning every project dir for the file named after the session, so the cwd-slug — which is a\n * mangling of the working directory we would otherwise have to reproduce exactly — never has to be\n * derived. '' when there is no session or the file is not there.\n */\n mainTranscript(): string {\n const session = this.sessionId();\n if (session === '') return '';\n const projects = path.join(os.homedir(), '.claude', 'projects');\n for (const proj of this.readDir(projects)) {\n const candidate = path.join(projects, proj, `${session}.jsonl`);\n if (fs.existsSync(candidate)) return candidate;\n }\n return '';\n }\n\n /**\n * How many days Claude Code keeps transcripts: `cleanupPeriodDays` from ~/.claude/settings.json (then\n * settings.local.json), else {@link DEFAULT_RETENTION_DAYS}. The setting is usually absent, which is\n * why the default is documented rather than left implicit.\n */\n retentionDays(): number {\n const dir = path.join(os.homedir(), '.claude');\n for (const file of ['settings.json', 'settings.local.json']) {\n const settings = this.readJson(path.join(dir, file));\n const days = settings?.['cleanupPeriodDays'];\n if (typeof days === 'number' && Number.isFinite(days) && days > 0) return days;\n }\n return DEFAULT_RETENTION_DAYS;\n }\n\n /**\n * The date the FIRST of these transcripts becomes unreadable: the oldest one's mtime + retentionDays,\n * as an ISO date. The oldest rather than the newest because that is when the audit trail starts losing\n * links, and a reader planning to follow them needs the pessimistic answer. '' when none exist.\n */\n expiresOn(transcripts: readonly string[], retentionDays: number): string {\n let oldest = 0;\n for (const file of transcripts) {\n const mtime = this.mtimeOf(file);\n if (mtime !== 0 && (oldest === 0 || mtime < oldest)) oldest = mtime;\n }\n if (oldest === 0) return '';\n const expiry = new Date(oldest + retentionDays * 24 * 60 * 60 * 1000);\n return expiry.toISOString().slice(0, 10);\n }\n\n /**\n * Write the record to `<prDir>/provenance.json` and return its path ('' if it could not be written).\n *\n * Written on EVERY finish, including one that refuses for a missing reviewer: a refused round is\n * precisely the one worth auditing, and a record that only ever appears on success cannot answer \"what\n * did the reviewers do the time this was rejected?\".\n */\n write(request: ProvenanceWriteRequest): string {\n const provenance = this.build(request);\n const target = this.provenancePath(request.prDir);\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: the audit record is never worth failing a PR over\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.mkdirSync(request.prDir, { recursive: true });\n fs.writeFileSync(target, JSON.stringify(provenance, null, 2) + '\\n');\n return target;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return '';\n }\n }\n\n // Retire the record for the round that just shipped: copy it to old-provenance.json beside the\n // old-review.json it belongs to. A COPY, not a move — unlike review.json this file is not an input to\n // anything, so leaving it in place cannot mislead a later reviewer, and the next finish overwrites it.\n archive(prDir: string): string {\n const source = this.provenancePath(prDir);\n if (!fs.existsSync(source)) return '';\n const target = this.oldProvenancePath(prDir);\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: a failed archive must not fail the command\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.copyFileSync(source, target);\n return target;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return '';\n }\n }\n\n // Fill in the session-level facts the caller cannot know: which session, which transcripts, how long.\n private build(request: ProvenanceWriteRequest): ReviewProvenance {\n const provenance = new ReviewProvenance(request);\n provenance.sessionId = this.sessionId();\n provenance.mainTranscript = this.mainTranscript();\n provenance.stampedAt = new Date().toISOString();\n provenance.transcriptRetentionDays = this.retentionDays();\n const linked = [provenance.mainTranscript, ...request.reviewers.map(\n (r: ReviewerTranscript): string => r.transcript)].filter((p: string): boolean => p !== '');\n provenance.transcriptsExpireOn = this.expiresOn(linked, provenance.transcriptRetentionDays);\n return provenance;\n }\n\n // Epoch millis of a file's mtime, or 0 when it cannot be read.\n private mtimeOf(filePath: string): number {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unstattable transcript contributes no expiry\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.statSync(filePath).mtime.getTime();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return 0;\n }\n }\n\n private readDir(dir: string): string[] {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unreadable dir yields [] (best-effort)\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.readdirSync(dir);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n }\n\n // webpieces-disable no-any-unknown -- opaque parsed JSON object, keys read by the caller\n private readJson(filePath: string): Record<string, unknown> | null {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: malformed settings → null (fall through to the default)\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(fs.readFileSync(filePath, 'utf8')) as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n}\n"]}
|
|
@@ -31,7 +31,7 @@ export declare class BriefedFile {
|
|
|
31
31
|
* remove. Data-only.
|
|
32
32
|
*/
|
|
33
33
|
export declare class ReviewerBriefing {
|
|
34
|
-
|
|
34
|
+
agentName: string;
|
|
35
35
|
docPath: string;
|
|
36
36
|
repoRoot: string;
|
|
37
37
|
diffDir: string;
|
|
@@ -55,7 +55,7 @@ export declare class ReviewerBriefing {
|
|
|
55
55
|
hashMainHead: string;
|
|
56
56
|
ownAgentFileInDiff: string;
|
|
57
57
|
required: boolean;
|
|
58
|
-
constructor(
|
|
58
|
+
constructor(agentName: string, checklistId: string, repoRoot: string);
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
61
|
* The Read tool truncates at roughly this many lines, silently. Any path this file prints alongside a bigger
|
|
@@ -66,7 +66,8 @@ export declare const READ_TRUNCATION_LINES = 2000;
|
|
|
66
66
|
export declare const ALL_DIFF_ONE_READ_LINES = 1500;
|
|
67
67
|
/**
|
|
68
68
|
* Renders the per-reviewer instructions file that `wp-review-upsert-pr` writes to
|
|
69
|
-
* `.webpieces/pr-review/<feature>/instructions/<
|
|
69
|
+
* `.webpieces/pr-review/<feature>/instructions/<checklistId>.instructions.md` — ONE PER CHECKLIST, so a
|
|
70
|
+
* subagent covering several checklists (`commands.pr-gate.reviewerAgents`) is simply handed several files.
|
|
70
71
|
*
|
|
71
72
|
* WHY this file exists, measured rather than assumed: a reviewer subagent on consumer-monorepo2 spent **14 of its
|
|
72
73
|
* 26 tool calls** rediscovering context the tooling already had — three separate greps into
|
|
@@ -74,8 +75,9 @@ export declare const ALL_DIFF_ONE_READ_LINES = 1500;
|
|
|
74
75
|
* re-derivation of the dependency graph. It did not over-review; it was under-supplied. Everything below is
|
|
75
76
|
* chosen to delete a specific one of those calls.
|
|
76
77
|
*
|
|
77
|
-
* It is GENERATED per run, and the
|
|
78
|
-
*
|
|
78
|
+
* It is GENERATED per run, and the reviewer agent (`.claude/agents/<reviewerAgentName>.md`, by default the
|
|
79
|
+
* webpieces-owned `webpieces-reviewer.md`) is checklist-agnostic and points here, because content a human
|
|
80
|
+
* maintains goes stale and a reviewer follows the stale copy. The verdict
|
|
79
81
|
* schema in particular comes from {@link ReviewJsonService.verdictSchemaFor}.
|
|
80
82
|
*
|
|
81
83
|
* `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
|
|
@@ -85,12 +87,14 @@ export declare class ReviewerInstructionsService {
|
|
|
85
87
|
constructor(reviewJsonService: ReviewJsonService);
|
|
86
88
|
/** The dir holding this branch's generated instructions, beside its review.json. */
|
|
87
89
|
instructionsDirFor(repoRoot: string, featureName: string): string;
|
|
88
|
-
/** The file name for
|
|
89
|
-
fileNameFor(
|
|
90
|
-
/** Absolute path of one
|
|
91
|
-
pathFor(repoRoot: string, featureName: string,
|
|
90
|
+
/** The file name for one checklist's generated instructions. */
|
|
91
|
+
fileNameFor(checklistId: string): string;
|
|
92
|
+
/** Absolute path of one checklist's generated instructions file. */
|
|
93
|
+
pathFor(repoRoot: string, featureName: string, checklistId: string): string;
|
|
92
94
|
render(briefing: ReviewerBriefing): string;
|
|
93
95
|
private identity;
|
|
96
|
+
/** The absolute path of the reviewer agent's committed definition (`.claude/agents/<agentName>.md`). */
|
|
97
|
+
agentFileFor(b: ReviewerBriefing): string;
|
|
94
98
|
/**
|
|
95
99
|
* Every PR that edits the review gate hits this: on the measured run, 5 of 8 changed files were the
|
|
96
100
|
* reviewer agent files themselves, two reviewers reviewed their OWN definition, and neither flagged it —
|