@webpieces/rules-config 0.4.563 → 0.4.565
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 +24 -1
- package/src/checklist-config.js +26 -2
- package/src/checklist-config.js.map +1 -1
- package/src/cli-args.d.ts +39 -6
- package/src/cli-args.js +72 -13
- package/src/cli-args.js.map +1 -1
- package/src/index.d.ts +1 -1
- package/src/index.js +9 -7
- package/src/index.js.map +1 -1
- package/src/pr-gate-section-validators.js +40 -2
- package/src/pr-gate-section-validators.js.map +1 -1
- package/src/review-json-data.d.ts +11 -1
- package/src/review-json-data.js +18 -4
- package/src/review-json-data.js.map +1 -1
- package/src/review-json.d.ts +13 -0
- package/src/review-json.js +21 -0
- package/src/review-json.js.map +1 -1
- package/src/reviewer-instructions.d.ts +1 -0
- package/src/reviewer-instructions.js +9 -0
- package/src/reviewer-instructions.js.map +1 -1
- package/templates/webpieces.review-checklists.md +58 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-gate-section-validators.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/pr-gate-section-validators.ts"],"names":[],"mappings":";;AA6BA,kEASC;AA4BD,8DAIC;AA4DD,sDAkBC;AApJD,uDAK2B;AAC3B,+DAA2D;AAC3D,yDAAwF;AAExF,yGAAyG;AACzG,sGAAsG;AACtG,oEAAoE;AAEpE,2GAA2G;AAC3G,wGAAwG;AACxG,MAAM,aAAa,GAAG,aAAa,CAAC;AAEpC;;;;;;;;;GASG;AACH,6GAA6G;AAC7G,8GAA8G;AAC9G,SAAgB,2BAA2B,CAAC,CAA0B;IAClE,IAAI,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,OAAO;QACH,yBAAyB,aAAa,mEAAmE;YACzG,wGAAwG;YACxG,yGAAyG;YACzG,sGAAsG;YACtG,0EAA0E;KAC7E,CAAC;AACN,CAAC;AAED,MAAM,iBAAiB,GAAG,CACtB,YAAY;IACZ,uBAAuB;IACvB,gDAAgD;IAChD,qDAAqD;IACrD,0DAA0D;IAC1D,SAAS;IACT,2GAA2G;IAC3G,+FAA+F,CAClG,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,2FAA2F;AAC3F,8GAA8G;AAC9G,SAAgB,yBAAyB,CAAC,KAAc,EAAE,QAAiB;IACvE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,sBAAsB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACzE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK;QAAE,OAAO,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;IACvG,OAAO,CAAC,mFAAmF,iBAAiB,EAAE,CAAC,CAAC;AACpH,CAAC;AAED;;;;GAIG;AACH,wGAAwG;AACxG,8GAA8G;AAC9G,SAAS,mBAAmB,CAAC,KAAa;IACtC,MAAM,GAAG,GAAI,KAAiC,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,yBAAyB,CAAC;IAC9F,OAAO,CACH,mDAAmD,MAAM,qDAAqD;QAC9G,uFAAuF;QACvF,gBAAgB,MAAM,qFAAqF;QAC3G,2CAA2C,MAAM,0CAA0C;QAC3F,4FAA4F;QAC5F,WAAW,MAAM,mFAAmF;QACpG,qEAAqE,MAAM,sBAAsB;QACjG,KAAK,iBAAiB,EAAE,CAC3B,CAAC;AACN,CAAC;AAED,wGAAwG;AACxG,6GAA6G;AAC7G,kFAAkF;AAClF,6FAA6F;AAC7F,8GAA8G;AAC9G,SAAS,sBAAsB,CAAC,KAAyB,EAAE,QAAiB;IACxE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,8GAA8G;IAC9G,KAAK,CAAC,OAAO,CAAC,CAAC,KAAc,EAAE,CAAS,EAAQ,EAAE;QAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACtE,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,0DAA0D,CAAC,CAAC;YACjG,OAAO;QACX,CAAC;QACD,2EAA2E;QAC3E,MAAM,CAAC,GAAG,KAAgC,CAAC;QAC3C,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;YACzD,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,8HAA8H,CAAC,CAAC;QACzK,CAAC;QACD,yFAAyF;QACzF,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,CAAU,EAAW,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,EAAE,CAAC;YACxI,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,8EAA8E,CAAC,CAAC;QACzH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,CAAqB,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IACH,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAsB,EAAuB,EAAE,CAAC,IAAA,8BAAW,EAAC,IAAI,CAAC,CAAC,CAAC;IAC3F,OAAO,CAAC,GAAG,MAAM,EAAE,GAAG,IAAI,wCAAkB,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,kGAAkG;AAClG,oGAAoG;AACpG,sGAAsG;AACtG,iGAAiG;AACjG,wGAAwG;AACxG,8GAA8G;AAC9G,SAAgB,qBAAqB,CAAC,KAAc;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACtE,OAAO,CAAC,oEAAoE,8CAA4B,MAAM,CAAC,CAAC;IACpH,CAAC;IACD,4FAA4F;IAC5F,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,IAAI,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC;IACvC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,mCAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IACtF,OAAO;QACH,yCAAyC,MAAM,CAAC,SAAS,CAAC,kBAAkB;YAC5E,mBAAmB,mCAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;YACpD,MAAM,8CAA4B,oFAAoF;YACtH,+FAA+F;YAC/F,oFAAoF;YACpF,MAAM,yCAAuB,8EAA8E;YAC3G,MAAM,uCAAqB,uFAAuF;KACrH,CAAC;AACN,CAAC","sourcesContent":["import {\n BRANCH_RETENTIONS,\n BRANCH_RETENTION_ARCHIVE_TAG,\n BRANCH_RETENTION_DELETE,\n BRANCH_RETENTION_KEEP,\n} from './branch-archiver';\nimport { ChecklistValidator } from './checklist-validator';\nimport { ChecklistDefinition, RawChecklistItem, toChecklist } from './checklist-config';\n\n// The two `pr-gate` sub-sections whose validation is bulky enough to own a file: the review `checklists`\n// and the one rationale key that is rejected outright. Split out of validate-config.ts only for size;\n// loadAndValidate still reaches both through validatePrGateSection.\n\n// The `*Why` convention (buildCommandWhy, mergeModeWhy, gatesWhy…) is free-form rationale a consumer keeps\n// beside a field, and pr-gate tolerates any of them — EXCEPT this one. See validateNoGateSaltRationale.\nconst GATE_SALT_WHY = 'gateSaltWhy';\n\n/**\n * Reject `gateSaltWhy` outright, and say why, so the next validate on upgrade FORCES its removal.\n *\n * webpieces.config.json is one of the first files a coding agent reads. A rationale note next to `gateSalt`\n * necessarily explains what the token protects, that the salt is committed, and therefore how to forge it —\n * i.e. it is a bypass how-to, sitting in the most-read file in the repo, defeating the only thing an\n * obscurity-grade mechanism has going for it. The rationale belongs in the webpieces source (pr-gate-config.ts\n * documents it in full for humans reading the tooling), never in consumer config. Every other `*Why` key\n * stays allowed; this is not a general ban on documenting your config.\n */\n// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; only key PRESENCE is read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateNoGateSaltRationale(s: Record<string, unknown>): string[] {\n if (!(GATE_SALT_WHY in s)) return [];\n return [\n `[pr-gate] DELETE the \"${GATE_SALT_WHY}\" key from webpieces.config.json. It is a rationale note next to ` +\n `\"gateSalt\", which means it spells out what the gate token protects and that the salt is committed — a ` +\n `bypass how-to in the file a coding agent reads first. The mechanism is obscurity-grade; documenting it ` +\n `here removes the obscurity. Nothing else needs changing: the reasoning is already documented in the ` +\n `webpieces source (PrGateConfig.gateSalt) for humans reading the tooling.`,\n ];\n}\n\nconst CHECKLIST_EXAMPLE = (\n 'Example:\\n' +\n ' \"checklists\": [\\n' +\n ' { \"subagent\": \"db-migration-reviewer\",\\n' +\n ' \"doc\": \".claude/review/db-migrations.md\",\\n' +\n ' \"patterns\": [\"**/migrations/**\", \"**/*.sql\"] }\\n' +\n ' ]\\n' +\n ' Each entry needs its OWN reviewer subagent (a .claude/agents/<subagent>.md) — that is how independent\\n' +\n ' review is enforced. \"doc\" is REPO-relative. Omit \"patterns\" (or use []) to run on every PR.'\n);\n\n/**\n * The `checklists` section of a pr-gate config: an ARRAY of { subagent, doc?, patterns? }, and nothing else.\n *\n * The previous `{ \"doc\": \"...\" }` shape — which hid the same array in a `<!-- webpieces:checklists -->` HTML\n * comment inside a markdown doc — is REMOVED, not deprecated. It is rejected with the exact edit to make.\n * There is deliberately no back-compat branch: two accepted shapes means two code paths, two doc-resolution\n * rules and two sets of error messages to keep honest forever, while the migration itself is a mechanical\n * config edit that the coding agent reading this error applies in one pass. A hard failure naming the fix is\n * cheaper than permanent duality.\n *\n * Exported so the isolated validate-checklist-docs target reuses it. `repoRoot` (when known) lets the doc +\n * reviewer-agent existence checks run.\n */\n// webpieces-disable no-any-unknown -- `value` is opaque consumer JSON until narrowed below\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateChecklistsSection(value: unknown, repoRoot?: string): string[] {\n if (Array.isArray(value)) return validateChecklistArray(value, repoRoot);\n if (typeof value === 'object' && value !== null && 'doc' in value) return [legacyManifestError(value)];\n return [`[pr-gate] \"checklists\" must be an ARRAY of { \"subagent\", \"doc\"?, \"patterns\"? }. ${CHECKLIST_EXAMPLE}`];\n}\n\n/**\n * The migration message for the removed `{ doc }` manifest shape. It names the doc the consumer pointed at,\n * because that is the file holding the array they must move, and spells out the one non-obvious part of the\n * move: entry `doc` paths used to resolve relative to that manifest doc and are now REPO-relative.\n */\n// webpieces-disable no-any-unknown -- narrowing the opaque checklists section to read the old `doc` key\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction legacyManifestError(value: object): string {\n const doc = (value as Record<string, unknown>)['doc'];\n const docRel = typeof doc === 'string' && doc.trim() !== '' ? doc : '<your review index doc>';\n return (\n `[pr-gate] \"checklists\" is the REMOVED { \"doc\": \"${docRel}\" } shape. The checklist array no longer lives in\\n` +\n ` an HTML comment inside a markdown doc — put it directly in webpieces.config.json:\\n` +\n ` 1. Open \"${docRel}\" and copy the JSON array out of its <!-- webpieces:checklists [...] --> comment.\\n` +\n ` 2. Replace \"checklists\": { \"doc\": \"${docRel}\" } with \"checklists\": <that array>.\\n` +\n ` 3. Rewrite each entry's \"doc\" to be REPO-relative — they used to resolve relative to\\n` +\n ` \"${docRel}\", so a bare \"db-migrations.md\" becomes e.g. \".claude/review/db-migrations.md\".\\n` +\n ` 4. Delete the <!-- webpieces:checklists ... --> comment from \"${docRel}\"; keep the prose.\\n` +\n ` ${CHECKLIST_EXAMPLE}`\n );\n}\n\n// Structurally check each entry HERE — a bad `patterns` or a non-object entry is a config-file typo and\n// deserves a `checklists[i]` message — then hand the narrowed defs to ChecklistValidator for the checks only\n// the filesystem can answer (the guidance doc exists, the reviewer agent exists).\n// webpieces-disable no-any-unknown -- opaque consumer JSON entries, narrowed per-field below\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction validateChecklistArray(value: readonly unknown[], repoRoot?: string): string[] {\n const errors: string[] = [];\n const items: RawChecklistItem[] = [];\n // webpieces-disable no-any-unknown -- each array entry is opaque consumer JSON, narrowed field-by-field below\n value.forEach((entry: unknown, i: number): void => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n errors.push(`[pr-gate] checklists[${i}] must be an object { \"subagent\", \"doc\"?, \"patterns\"? }.`);\n return;\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque checklist entry\n const e = entry as Record<string, unknown>;\n if (e['doc'] !== undefined && typeof e['doc'] !== 'string') {\n errors.push(`[pr-gate] checklists[${i}].doc must be a string — a REPO-relative path to the reviewer's guidance doc (omit it and the reviewer just reads the diff).`);\n }\n // webpieces-disable no-any-unknown -- opaque array element, narrowed by the typeof guard\n if (e['patterns'] !== undefined && !(Array.isArray(e['patterns']) && e['patterns'].every((p: unknown): boolean => typeof p === 'string'))) {\n errors.push(`[pr-gate] checklists[${i}].patterns must be a string[] of path globs (omit or [] to run on every PR).`);\n }\n items.push(e as RawChecklistItem);\n });\n if (repoRoot === undefined) return errors;\n const defs = items.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item));\n return [...errors, ...new ChecklistValidator().validate(repoRoot, defs)];\n}\n\n// The `landPr` block: what happens to the LOCAL branch once its PR is in main. Optional — omitted\n// means \"archive-tag\", which is deliberately the DEFAULT so a consumer gets the branch-accumulation\n// fix without editing config at all. `branchRetentionWhy` (and any other `*Why` sibling) is free-form\n// rationale prose and is tolerated, per the repo's convention for documenting comment-less JSON.\n// webpieces-disable no-any-unknown -- `value` is the opaque consumer `landPr` value until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateLandPrSection(value: unknown): string[] {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return [`[pr-gate] \"landPr\" must be an object, e.g. { \"branchRetention\": \"${BRANCH_RETENTION_ARCHIVE_TAG}\" }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque landPr object from consumer JSON\n const s = value as Record<string, unknown>;\n if (!('branchRetention' in s)) return [];\n const retention = s['branchRetention'];\n if (typeof retention === 'string' && BRANCH_RETENTIONS.includes(retention)) return [];\n return [\n `[pr-gate] \"landPr.branchRetention\" = \"${String(retention)}\" is not valid. ` +\n `Must be one of: ${BRANCH_RETENTIONS.join(', ')}.\\n` +\n ` \"${BRANCH_RETENTION_ARCHIVE_TAG}\" — (default) tag the branch tip as archive/<date>/<branch>, THEN delete it. The\\n` +\n ` history stays byte-identical and restorable, but the branch stops counting\\n` +\n ` toward the branch cap and cannot be committed onto by accident.\\n` +\n ` \"${BRANCH_RETENTION_DELETE}\" — delete outright; recoverable only from the reflog, which expires.\\n` +\n ` \"${BRANCH_RETENTION_KEEP}\" — do not delete. Branches then accumulate until branch-creation-guard trips.`,\n ];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pr-gate-section-validators.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/pr-gate-section-validators.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BRANCH_RETENTIONS,\n BRANCH_RETENTION_ARCHIVE_TAG,\n BRANCH_RETENTION_DELETE,\n BRANCH_RETENTION_KEEP,\n} from './branch-archiver';\nimport { ChecklistValidator } from './checklist-validator';\nimport { ChecklistDefinition, RawChecklistItem, toChecklist } from './checklist-config';\n\n// The two `pr-gate` sub-sections whose validation is bulky enough to own a file: the review `checklists`\n// and the one rationale key that is rejected outright. Split out of validate-config.ts only for size;\n// loadAndValidate still reaches both through validatePrGateSection.\n\n// The `*Why` convention (buildCommandWhy, mergeModeWhy, gatesWhy…) is free-form rationale a consumer keeps\n// beside a field, and pr-gate tolerates any of them — EXCEPT this one. See validateNoGateSaltRationale.\nconst GATE_SALT_WHY = 'gateSaltWhy';\n\n/**\n * Reject `gateSaltWhy` outright, and say why, so the next validate on upgrade FORCES its removal.\n *\n * webpieces.config.json is one of the first files a coding agent reads. A rationale note next to `gateSalt`\n * necessarily explains what the token protects, that the salt is committed, and therefore how to forge it —\n * i.e. it is a bypass how-to, sitting in the most-read file in the repo, defeating the only thing an\n * obscurity-grade mechanism has going for it. The rationale belongs in the webpieces source (pr-gate-config.ts\n * documents it in full for humans reading the tooling), never in consumer config. Every other `*Why` key\n * stays allowed; this is not a general ban on documenting your config.\n */\n// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; only key PRESENCE is read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateNoGateSaltRationale(s: Record<string, unknown>): string[] {\n if (!(GATE_SALT_WHY in s)) return [];\n return [\n `[pr-gate] DELETE the \"${GATE_SALT_WHY}\" key from webpieces.config.json. It is a rationale note next to ` +\n `\"gateSalt\", which means it spells out what the gate token protects and that the salt is committed — a ` +\n `bypass how-to in the file a coding agent reads first. The mechanism is obscurity-grade; documenting it ` +\n `here removes the obscurity. Nothing else needs changing: the reasoning is already documented in the ` +\n `webpieces source (PrGateConfig.gateSalt) for humans reading the tooling.`,\n ];\n}\n\nconst CHECKLIST_EXAMPLE = (\n 'Example:\\n' +\n ' \"checklists\": [\\n' +\n ' { \"subagent\": \"db-migration-reviewer\",\\n' +\n ' \"doc\": \".claude/review/db-migrations.md\",\\n' +\n ' \"patterns\": [\"**/migrations/**\", \"**/*.sql\"],\\n' +\n ' \"required\": true }\\n' +\n ' ]\\n' +\n ' Each entry needs its OWN reviewer subagent (a .claude/agents/<subagent>.md) — that is how independent\\n' +\n ' review is enforced. \"doc\" is REPO-relative. Omit \"patterns\" (or use []) to run on every PR.\\n' +\n ' \"required\" is MANDATORY on every entry: true blocks the PR until the reviewer passes; false makes it\\n' +\n ' an OPTIONAL review the human is offered and may decline (but if they DO run it, a red verdict still\\n' +\n ' blocks).'\n);\n\n/**\n * The `checklists` section of a pr-gate config: an ARRAY of { subagent, doc?, patterns? }, and nothing else.\n *\n * The previous `{ \"doc\": \"...\" }` shape — which hid the same array in a `<!-- webpieces:checklists -->` HTML\n * comment inside a markdown doc — is REMOVED, not deprecated. It is rejected with the exact edit to make.\n * There is deliberately no back-compat branch: two accepted shapes means two code paths, two doc-resolution\n * rules and two sets of error messages to keep honest forever, while the migration itself is a mechanical\n * config edit that the coding agent reading this error applies in one pass. A hard failure naming the fix is\n * cheaper than permanent duality.\n *\n * Exported so the isolated validate-checklist-docs target reuses it. `repoRoot` (when known) lets the doc +\n * reviewer-agent existence checks run.\n */\n// webpieces-disable no-any-unknown -- `value` is opaque consumer JSON until narrowed below\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateChecklistsSection(value: unknown, repoRoot?: string): string[] {\n if (Array.isArray(value)) return validateChecklistArray(value, repoRoot);\n if (typeof value === 'object' && value !== null && 'doc' in value) return [legacyManifestError(value)];\n return [`[pr-gate] \"checklists\" must be an ARRAY of { \"subagent\", \"doc\"?, \"patterns\"? }. ${CHECKLIST_EXAMPLE}`];\n}\n\n/**\n * The migration message for the removed `{ doc }` manifest shape. It names the doc the consumer pointed at,\n * because that is the file holding the array they must move, and spells out the one non-obvious part of the\n * move: entry `doc` paths used to resolve relative to that manifest doc and are now REPO-relative.\n */\n// webpieces-disable no-any-unknown -- narrowing the opaque checklists section to read the old `doc` key\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction legacyManifestError(value: object): string {\n const doc = (value as Record<string, unknown>)['doc'];\n const docRel = typeof doc === 'string' && doc.trim() !== '' ? doc : '<your review index doc>';\n return (\n `[pr-gate] \"checklists\" is the REMOVED { \"doc\": \"${docRel}\" } shape. The checklist array no longer lives in\\n` +\n ` an HTML comment inside a markdown doc — put it directly in webpieces.config.json:\\n` +\n ` 1. Open \"${docRel}\" and copy the JSON array out of its <!-- webpieces:checklists [...] --> comment.\\n` +\n ` 2. Replace \"checklists\": { \"doc\": \"${docRel}\" } with \"checklists\": <that array>.\\n` +\n ` 3. Rewrite each entry's \"doc\" to be REPO-relative — they used to resolve relative to\\n` +\n ` \"${docRel}\", so a bare \"db-migrations.md\" becomes e.g. \".claude/review/db-migrations.md\".\\n` +\n ` 4. Delete the <!-- webpieces:checklists ... --> comment from \"${docRel}\"; keep the prose.\\n` +\n ` ${CHECKLIST_EXAMPLE}`\n );\n}\n\n/**\n * `required` is MANDATORY on every checklist entry — omitting it is an error, never a default.\n *\n * The error names the entry's own subagent, because that is what the consumer recognizes in a\n * twelve-entry array; `checklists[7]` alone means counting braces. It also states BOTH edits, because the\n * whole point of the key is that the answer differs per checklist and only the consumer knows which.\n *\n * Why a hard rejection instead of `?? true`: an accepted shape is never migrated. Defaulting to true\n * silently keeps the all-blocking behavior this key exists to relieve, and every consumer that would have\n * benefited stays on the old behavior forever without ever being told the dial exists. Defaulting to false\n * is worse — it would silently DOWNGRADE a live review gate on upgrade. Per CLAUDE.md the reader of this\n * message is a coding agent, so the migration is one mechanical pass.\n */\n// webpieces-disable no-any-unknown -- one opaque checklist entry, narrowed by the typeof guards here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction requiredKeyErrors(e: Record<string, unknown>, i: number): string[] {\n const name = typeof e['subagent'] === 'string' && e['subagent'].trim() !== '' ? ` (\"${e['subagent']}\")` : '';\n if (e['required'] === undefined) {\n return [\n `[pr-gate] checklists[${i}]${name} is missing \"required\". Every checklist must state one — there is\\n` +\n ` no default, in either direction.\\n` +\n ` \"required\": true → BLOCKING. wp-finish-upsert-pr refuses the PR until this reviewer passes.\\n` +\n ` This is what every checklist did before this key existed.\\n` +\n ` \"required\": false → OPTIONAL. When it matches the diff, wp-review-upsert-pr offers it and the\\n` +\n ` human may decline it. If they DO run it, a red verdict still blocks.\\n` +\n ` ${CHECKLIST_EXAMPLE}`,\n ];\n }\n if (typeof e['required'] !== 'boolean') {\n return [`[pr-gate] checklists[${i}]${name}.required must be a boolean (true = blocking, false = optional) — not a string or number.`];\n }\n return [];\n}\n\n// Structurally check each entry HERE — a bad `patterns` or a non-object entry is a config-file typo and\n// deserves a `checklists[i]` message — then hand the narrowed defs to ChecklistValidator for the checks only\n// the filesystem can answer (the guidance doc exists, the reviewer agent exists).\n// webpieces-disable no-any-unknown -- opaque consumer JSON entries, narrowed per-field below\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction validateChecklistArray(value: readonly unknown[], repoRoot?: string): string[] {\n const errors: string[] = [];\n const items: RawChecklistItem[] = [];\n // webpieces-disable no-any-unknown -- each array entry is opaque consumer JSON, narrowed field-by-field below\n value.forEach((entry: unknown, i: number): void => {\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n errors.push(`[pr-gate] checklists[${i}] must be an object { \"subagent\", \"doc\"?, \"patterns\"? }.`);\n return;\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque checklist entry\n const e = entry as Record<string, unknown>;\n if (e['doc'] !== undefined && typeof e['doc'] !== 'string') {\n errors.push(`[pr-gate] checklists[${i}].doc must be a string — a REPO-relative path to the reviewer's guidance doc (omit it and the reviewer just reads the diff).`);\n }\n // webpieces-disable no-any-unknown -- opaque array element, narrowed by the typeof guard\n if (e['patterns'] !== undefined && !(Array.isArray(e['patterns']) && e['patterns'].every((p: unknown): boolean => typeof p === 'string'))) {\n errors.push(`[pr-gate] checklists[${i}].patterns must be a string[] of path globs (omit or [] to run on every PR).`);\n }\n errors.push(...requiredKeyErrors(e, i));\n items.push(e as RawChecklistItem);\n });\n if (repoRoot === undefined) return errors;\n const defs = items.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item));\n return [...errors, ...new ChecklistValidator().validate(repoRoot, defs)];\n}\n\n// The `landPr` block: what happens to the LOCAL branch once its PR is in main. Optional — omitted\n// means \"archive-tag\", which is deliberately the DEFAULT so a consumer gets the branch-accumulation\n// fix without editing config at all. `branchRetentionWhy` (and any other `*Why` sibling) is free-form\n// rationale prose and is tolerated, per the repo's convention for documenting comment-less JSON.\n// webpieces-disable no-any-unknown -- `value` is the opaque consumer `landPr` value until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateLandPrSection(value: unknown): string[] {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return [`[pr-gate] \"landPr\" must be an object, e.g. { \"branchRetention\": \"${BRANCH_RETENTION_ARCHIVE_TAG}\" }.`];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque landPr object from consumer JSON\n const s = value as Record<string, unknown>;\n if (!('branchRetention' in s)) return [];\n const retention = s['branchRetention'];\n if (typeof retention === 'string' && BRANCH_RETENTIONS.includes(retention)) return [];\n return [\n `[pr-gate] \"landPr.branchRetention\" = \"${String(retention)}\" is not valid. ` +\n `Must be one of: ${BRANCH_RETENTIONS.join(', ')}.\\n` +\n ` \"${BRANCH_RETENTION_ARCHIVE_TAG}\" — (default) tag the branch tip as archive/<date>/<branch>, THEN delete it. The\\n` +\n ` history stays byte-identical and restorable, but the branch stops counting\\n` +\n ` toward the branch cap and cannot be committed onto by accident.\\n` +\n ` \"${BRANCH_RETENTION_DELETE}\" — delete outright; recoverable only from the reflog, which expires.\\n` +\n ` \"${BRANCH_RETENTION_KEEP}\" — do not delete. Branches then accumulate until branch-creation-guard trips.`,\n ];\n}\n"]}
|
|
@@ -18,13 +18,23 @@ export declare class ChecklistResult {
|
|
|
18
18
|
problem: string;
|
|
19
19
|
constructor(id: string, status: string, output: string, override: string, problem?: string);
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* What the pr-gate command computed from the diff: a checklist this branch MATCHED (its patterns hit the
|
|
23
|
+
* diff, so its reviewer subagent is in scope). Drives review-<id>.json enforcement, provenance, the schema
|
|
24
|
+
* hint, and the dashboard. Data-only.
|
|
25
|
+
*
|
|
26
|
+
* NAME NOTE: "Required" here means MATCHED, not mandatory — it predates `required` by a long way and is
|
|
27
|
+
* the shared shape across review-json, the detector, the briefing builder, provenance and the dashboard.
|
|
28
|
+
* Whether the reviewer must actually run is the {@link RequiredChecklist.required} field below.
|
|
29
|
+
*/
|
|
21
30
|
export declare class RequiredChecklist {
|
|
22
31
|
id: string;
|
|
23
32
|
subagent: string;
|
|
24
33
|
doc: string;
|
|
25
34
|
matchedFiles: string[];
|
|
26
35
|
matchedPatterns: string[];
|
|
27
|
-
|
|
36
|
+
required: boolean;
|
|
37
|
+
constructor(id: string, subagent: string, doc: string, matchedFiles: string[], matchedPatterns?: string[], required?: boolean);
|
|
28
38
|
}
|
|
29
39
|
/**
|
|
30
40
|
* The per-PR facts every reviewer subagent needs GIVEN to it, alongside its own checklist: the exact base
|
package/src/review-json-data.js
CHANGED
|
@@ -46,9 +46,15 @@ class ChecklistResult {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
exports.ChecklistResult = ChecklistResult;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
/**
|
|
50
|
+
* What the pr-gate command computed from the diff: a checklist this branch MATCHED (its patterns hit the
|
|
51
|
+
* diff, so its reviewer subagent is in scope). Drives review-<id>.json enforcement, provenance, the schema
|
|
52
|
+
* hint, and the dashboard. Data-only.
|
|
53
|
+
*
|
|
54
|
+
* NAME NOTE: "Required" here means MATCHED, not mandatory — it predates `required` by a long way and is
|
|
55
|
+
* the shared shape across review-json, the detector, the briefing builder, provenance and the dashboard.
|
|
56
|
+
* Whether the reviewer must actually run is the {@link RequiredChecklist.required} field below.
|
|
57
|
+
*/
|
|
52
58
|
class RequiredChecklist {
|
|
53
59
|
id; // = subagent name; keys review-<id>.json
|
|
54
60
|
subagent; // reviewer agent that must run (agentType the harness stamps)
|
|
@@ -58,13 +64,21 @@ class RequiredChecklist {
|
|
|
58
64
|
// match was — a precise `db/migrations/**` hit means something different from a blanket `**` — and the
|
|
59
65
|
// template tells reviewers that matching IS deliberately coarse. [] = no patterns (matches every PR).
|
|
60
66
|
matchedPatterns;
|
|
67
|
+
// Straight from the checklist's config `required`. true = blocking; false = the human is offered it and
|
|
68
|
+
// may decline. Carried on the MATCH rather than looked up from config downstream so the set that gates
|
|
69
|
+
// and the set that is reported cannot disagree about which of the two a checklist is.
|
|
70
|
+
required;
|
|
61
71
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
62
|
-
constructor(id, subagent, doc, matchedFiles, matchedPatterns = []
|
|
72
|
+
constructor(id, subagent, doc, matchedFiles, matchedPatterns = [],
|
|
73
|
+
// Defaulted to the BLOCKING value so any construction that forgets it fails closed — a test or a
|
|
74
|
+
// future call site that silently produced an optional checklist would be a hole in the gate.
|
|
75
|
+
required = true) {
|
|
63
76
|
this.id = id;
|
|
64
77
|
this.subagent = subagent;
|
|
65
78
|
this.doc = doc;
|
|
66
79
|
this.matchedFiles = matchedFiles;
|
|
67
80
|
this.matchedPatterns = matchedPatterns;
|
|
81
|
+
this.required = required;
|
|
68
82
|
}
|
|
69
83
|
}
|
|
70
84
|
exports.RequiredChecklist = RequiredChecklist;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-json-data.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-json-data.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The DATA-ONLY classes and status constants of the PR review system — the verdict a reviewer writes, the\n * checklist that demanded it, the resolved outcome, and the diff context handed to reviewers.\n *\n * Split out of review-json.ts, which holds the SERVICE that reads and writes them. The split is purely by\n * kind (data vs behaviour): review-json.ts re-exports every name below, so `from './review-json'` and\n * `from '@webpieces/rules-config'` keep resolving exactly as before and no consumer changes.\n */\n\n// The three colors a reviewer subagent may report in `review-<id>.json`. A TRI-state, not a boolean,\n// because the boolean it replaced gave a reviewer no way to say \"this passes, but a human should look at\n// X\" — the only way to raise a concern was to FAIL the PR and then override your own failure, which reads\n// on the dashboard as a deliberately-accepted defect rather than as a note.\nexport const VERDICT_GREEN = 'green';\nexport const VERDICT_YELLOW = 'yellow';\nexport const VERDICT_RED = 'red';\nexport const VERDICT_STATUSES = [VERDICT_GREEN, VERDICT_YELLOW, VERDICT_RED] as const;\n\n// The verdict a reviewer SUBAGENT writes into `.webpieces/pr-review/<branch>/review-<id>.json`, one per\n// matched checklist. One file per checklist so N concurrent reviewer subagents never clobber a shared\n// file. It records the OUTCOME:\n// status:'green' → PASS\n// status:'yellow' → WARN (passes; the concern is published on the PR, nothing is blocked)\n// status:'red' + override non-empty → OVERRIDDEN (pass; the free-text justification reaches the PR)\n// status:'red' + no override → FAIL (refuse; `output` is printed verbatim)\n// `override` is deliberately free text, not a boolean — it forces the ship-anyway decision to be stated\n// in words and surfaces it on the dashboard, where a human sees it. Data-only (per CLAUDE.md).\nexport class ChecklistResult {\n id: string;\n status: string; // one of VERDICT_STATUSES; anything else is reported via `problem`\n output: string; // what the reviewer found; printed verbatim when the checklist fails\n override: string; // '' = no override; non-empty = ship-anyway justification (renders 🟠 overridden)\n // '' = a well-formed verdict. Non-empty = the file exists and parses but its verdict cannot be READ\n // (most often: it still uses the removed `success` field). Carried as data rather than thrown so the\n // complaint can be reported by BOTH wp-review-upsert-pr and wp-finish-upsert-pr in identical words, and so a\n // legacy file is never silently mistaken for a missing one.\n problem: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(id: string, status: string, output: string, override: string, problem = '') {\n this.id = id;\n this.status = status;\n this.output = output;\n this.override = override;\n this.problem = problem;\n }\n}\n\n// What the pr-gate command computed from the diff: a checklist this branch MATCHED (its patterns hit the\n// diff, so its reviewer subagent must run). Drives review-<id>.json enforcement, provenance, the schema\n// hint, and the dashboard. Data-only.\nexport class RequiredChecklist {\n id: string; // = subagent name; keys review-<id>.json\n subagent: string; // reviewer agent that must run (agentType the harness stamps)\n doc: string; // REPO-RELATIVE guidance doc the reviewer reads ('' → it just reads the diff)\n matchedFiles: string[]; // the changed files that matched it (for the dashboard + hint)\n // Which of the checklist's OWN globs actually fired. Printed so a reviewer can judge how coarse the\n // match was — a precise `db/migrations/**` hit means something different from a blanket `**` — and the\n // template tells reviewers that matching IS deliberately coarse. [] = no patterns (matches every PR).\n matchedPatterns: string[];\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(id: string, subagent: string, doc: string, matchedFiles: string[], matchedPatterns: string[] = []) {\n this.id = id;\n this.subagent = subagent;\n this.doc = doc;\n this.matchedFiles = matchedFiles;\n this.matchedPatterns = matchedPatterns;\n }\n}\n\n/**\n * The per-PR facts every reviewer subagent needs GIVEN to it, alongside its own checklist: the exact base\n * sha the gate diffs against and the file holding the complete changed-file set. Both used to live only in\n * a doc the printed instruction told the AI to go read, one indirection away from the instruction to hand\n * them over — so the printed block could not stand on its own. Data-only; empty = omit those lines.\n */\nexport class ChecklistReviewContext {\n baseSha: string; // the 3-point merge-base sha\n prContextPath: string; // path of pr-context.json — the AUTHORITATIVE full changed-file set\n /**\n * The exact command that reproduces ONE file's diff, with a `-- <file>` tail — NOT assembled by the\n * caller. This used to be hardcoded as `git diff <baseSha> HEAD -- <file>`, which returns NOTHING on a\n * dirty tree because the changed-file set is computed base→working-tree. See DiffBasis, which derives\n * this string from the same range the file set came from.\n */\n fileDiffCommand: string;\n diffDir: string; // dir of the MATERIALIZED diff (diff/ALL.diff + diff/files/…); '' when not written\n dirty: boolean; // true ⇒ the range includes uncommitted + untracked work, and must be said out loud\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(baseSha = '', prContextPath = '', fileDiffCommand = '', diffDir = '', dirty = false) {\n this.baseSha = baseSha;\n this.prContextPath = prContextPath;\n this.fileDiffCommand = fileDiffCommand;\n this.diffDir = diffDir;\n this.dirty = dirty;\n }\n}\n\n// The AI-authored review for a PR. The AI writes review.json itself between `wp-start-upsert-pr` (which\n// prints the schema) and `wp-finish-upsert-pr` (which reads it); reviewer subagents write the per-checklist\n// review-<id>.json files. Data-only (per CLAUDE.md).\nexport class ReviewJson {\n title: string; // human PR title describing the change; used as the `gh pr` title (empty → caller falls back)\n riskScore: number; // 0–100, drives the risk bar\n riskLevel: string; // 'green' | 'yellow' | 'red'\n riskEmoji: string; // '🟢' | '🟡' | '🔴' — derived from riskLevel when omitted\n summary: string; // rendered in the dashboard Summary section\n violations: string[]; // pattern/architecture violations; length = the Pattern Violations count\n risks: string[];\n filesToReview: string[];\n results: ChecklistResult[]; // resolved per-checklist verdicts (from review-<id>.json); [] when none\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n title: string,\n riskScore: number,\n riskLevel: string,\n riskEmoji: string,\n summary: string,\n violations: string[],\n risks: string[],\n filesToReview: string[],\n results: ChecklistResult[] = [],\n ) {\n this.title = title;\n this.riskScore = riskScore;\n this.riskLevel = riskLevel;\n this.riskEmoji = riskEmoji;\n this.summary = summary;\n this.violations = violations;\n this.risks = risks;\n this.filesToReview = filesToReview;\n this.results = results;\n }\n}\n\n// A checklist's resolved outcome, shared by review.json enforcement and the dashboard so both agree.\n// PASS, WARN and OVERRIDDEN all ship; FAIL, MISSING and BAD_FORMAT all refuse the PR.\nexport const CK_PASS = 'pass'; // review-<id>.json status:'green'\nexport const CK_WARN = 'warn'; // review-<id>.json status:'yellow' → 🟡 passes WITH concerns\nexport const CK_OVERRIDDEN = 'overridden'; // review-<id>.json status:'red' + non-empty override → 🟠\nexport const CK_FAIL = 'fail'; // review-<id>.json status:'red' + no override → refuse\nexport const CK_MISSING = 'missing'; // no review-<id>.json written → refuse\nexport const CK_BAD_FORMAT = 'bad-format'; // written, but its verdict is unreadable (e.g. legacy `success`)\n\nexport class ChecklistVerdict {\n id: string;\n status: string; // one of CK_PASS | CK_WARN | CK_OVERRIDDEN | CK_FAIL | CK_MISSING | CK_BAD_FORMAT\n detail: string; // reviewer output / override justification / format complaint (dashboard + errors)\n\n constructor(id: string, status: string, detail: string) {\n this.id = id;\n this.status = status;\n this.detail = detail;\n }\n}\n\n// The PR's diff context, written by wp-start-upsert-pr into `.webpieces/pr-review/<branch>/pr-context.json`\n// so a reviewer subagent knows the exact 3-point base the gate used and the full changed-file set — then\n// reads any file's actual diff with `git diff <base> HEAD -- <file>`. This is what lets a checklist match\n// coarsely by path (in the config) while the subagent makes the fine, content-level judgment. Data-only.\nexport class PrContext {\n base: string; // the 3-point merge-base sha the gate diffs against\n /**\n * The real HEAD sha. This was once the literal string 'HEAD', which is not a fact — it cannot be\n * compared later to detect that the tree moved under a review, and it reads as a range that was never\n * actually diffed. Its only reader (reviewContextFor) takes `base`, so recording the sha is free.\n */\n head: string;\n changedFiles: string[]; // every file changed in the range (NOT tsOnly — includes .sql/.gql/Dockerfile/…)\n dirty: boolean; // true ⇒ changedFiles includes uncommitted + untracked work\n dirtyFiles: string[]; // exactly which paths are uncommitted/untracked — why `dirty` is true\n diffCommand: string; // the command that reproduces the WHOLE diff (see DiffBasis; correct when dirty)\n diffDir: string; // dir holding the materialized per-file diffs + ALL.diff; '' when not materialized\n generatedAt: string; // ISO timestamp, so a stale context is detectable rather than silently trusted\n /**\n * Main's head as this clone last saw it — the THIRD hash point, matching the trio the 3-point merge\n * records in `merge-info/<branch>/updatemain-hashes.json`. `base`/`head` above are points A and B\n * under the review side's older names.\n *\n * The review side used to record only A and B, so nothing could answer \"did main move while this was\n * under review?\" — the question you most want answered when a review looks stale. '' when origin/main\n * is unresolvable. Purely informational; nothing gates on it.\n */\n hashMainHead: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n base: string, head: string, changedFiles: string[],\n dirty = false, dirtyFiles: string[] = [], diffCommand = '', diffDir = '', generatedAt = '',\n hashMainHead = '',\n ) {\n this.hashMainHead = hashMainHead;\n this.base = base;\n this.head = head;\n this.changedFiles = changedFiles;\n this.dirty = dirty;\n this.dirtyFiles = dirtyFiles;\n this.diffCommand = diffCommand;\n this.diffDir = diffDir;\n this.generatedAt = generatedAt;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"review-json-data.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-json-data.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The DATA-ONLY classes and status constants of the PR review system — the verdict a reviewer writes, the\n * checklist that demanded it, the resolved outcome, and the diff context handed to reviewers.\n *\n * Split out of review-json.ts, which holds the SERVICE that reads and writes them. The split is purely by\n * kind (data vs behaviour): review-json.ts re-exports every name below, so `from './review-json'` and\n * `from '@webpieces/rules-config'` keep resolving exactly as before and no consumer changes.\n */\n\n// The three colors a reviewer subagent may report in `review-<id>.json`. A TRI-state, not a boolean,\n// because the boolean it replaced gave a reviewer no way to say \"this passes, but a human should look at\n// X\" — the only way to raise a concern was to FAIL the PR and then override your own failure, which reads\n// on the dashboard as a deliberately-accepted defect rather than as a note.\nexport const VERDICT_GREEN = 'green';\nexport const VERDICT_YELLOW = 'yellow';\nexport const VERDICT_RED = 'red';\nexport const VERDICT_STATUSES = [VERDICT_GREEN, VERDICT_YELLOW, VERDICT_RED] as const;\n\n// The verdict a reviewer SUBAGENT writes into `.webpieces/pr-review/<branch>/review-<id>.json`, one per\n// matched checklist. One file per checklist so N concurrent reviewer subagents never clobber a shared\n// file. It records the OUTCOME:\n// status:'green' → PASS\n// status:'yellow' → WARN (passes; the concern is published on the PR, nothing is blocked)\n// status:'red' + override non-empty → OVERRIDDEN (pass; the free-text justification reaches the PR)\n// status:'red' + no override → FAIL (refuse; `output` is printed verbatim)\n// `override` is deliberately free text, not a boolean — it forces the ship-anyway decision to be stated\n// in words and surfaces it on the dashboard, where a human sees it. Data-only (per CLAUDE.md).\nexport class ChecklistResult {\n id: string;\n status: string; // one of VERDICT_STATUSES; anything else is reported via `problem`\n output: string; // what the reviewer found; printed verbatim when the checklist fails\n override: string; // '' = no override; non-empty = ship-anyway justification (renders 🟠 overridden)\n // '' = a well-formed verdict. Non-empty = the file exists and parses but its verdict cannot be READ\n // (most often: it still uses the removed `success` field). Carried as data rather than thrown so the\n // complaint can be reported by BOTH wp-review-upsert-pr and wp-finish-upsert-pr in identical words, and so a\n // legacy file is never silently mistaken for a missing one.\n problem: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(id: string, status: string, output: string, override: string, problem = '') {\n this.id = id;\n this.status = status;\n this.output = output;\n this.override = override;\n this.problem = problem;\n }\n}\n\n/**\n * What the pr-gate command computed from the diff: a checklist this branch MATCHED (its patterns hit the\n * diff, so its reviewer subagent is in scope). Drives review-<id>.json enforcement, provenance, the schema\n * hint, and the dashboard. Data-only.\n *\n * NAME NOTE: \"Required\" here means MATCHED, not mandatory — it predates `required` by a long way and is\n * the shared shape across review-json, the detector, the briefing builder, provenance and the dashboard.\n * Whether the reviewer must actually run is the {@link RequiredChecklist.required} field below.\n */\nexport class RequiredChecklist {\n id: string; // = subagent name; keys review-<id>.json\n subagent: string; // reviewer agent that must run (agentType the harness stamps)\n doc: string; // REPO-RELATIVE guidance doc the reviewer reads ('' → it just reads the diff)\n matchedFiles: string[]; // the changed files that matched it (for the dashboard + hint)\n // Which of the checklist's OWN globs actually fired. Printed so a reviewer can judge how coarse the\n // match was — a precise `db/migrations/**` hit means something different from a blanket `**` — and the\n // template tells reviewers that matching IS deliberately coarse. [] = no patterns (matches every PR).\n matchedPatterns: string[];\n // Straight from the checklist's config `required`. true = blocking; false = the human is offered it and\n // may decline. Carried on the MATCH rather than looked up from config downstream so the set that gates\n // and the set that is reported cannot disagree about which of the two a checklist is.\n required: boolean;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n id: string, subagent: string, doc: string, matchedFiles: string[], matchedPatterns: string[] = [],\n // Defaulted to the BLOCKING value so any construction that forgets it fails closed — a test or a\n // future call site that silently produced an optional checklist would be a hole in the gate.\n required = true,\n ) {\n this.id = id;\n this.subagent = subagent;\n this.doc = doc;\n this.matchedFiles = matchedFiles;\n this.matchedPatterns = matchedPatterns;\n this.required = required;\n }\n}\n\n/**\n * The per-PR facts every reviewer subagent needs GIVEN to it, alongside its own checklist: the exact base\n * sha the gate diffs against and the file holding the complete changed-file set. Both used to live only in\n * a doc the printed instruction told the AI to go read, one indirection away from the instruction to hand\n * them over — so the printed block could not stand on its own. Data-only; empty = omit those lines.\n */\nexport class ChecklistReviewContext {\n baseSha: string; // the 3-point merge-base sha\n prContextPath: string; // path of pr-context.json — the AUTHORITATIVE full changed-file set\n /**\n * The exact command that reproduces ONE file's diff, with a `-- <file>` tail — NOT assembled by the\n * caller. This used to be hardcoded as `git diff <baseSha> HEAD -- <file>`, which returns NOTHING on a\n * dirty tree because the changed-file set is computed base→working-tree. See DiffBasis, which derives\n * this string from the same range the file set came from.\n */\n fileDiffCommand: string;\n diffDir: string; // dir of the MATERIALIZED diff (diff/ALL.diff + diff/files/…); '' when not written\n dirty: boolean; // true ⇒ the range includes uncommitted + untracked work, and must be said out loud\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(baseSha = '', prContextPath = '', fileDiffCommand = '', diffDir = '', dirty = false) {\n this.baseSha = baseSha;\n this.prContextPath = prContextPath;\n this.fileDiffCommand = fileDiffCommand;\n this.diffDir = diffDir;\n this.dirty = dirty;\n }\n}\n\n// The AI-authored review for a PR. The AI writes review.json itself between `wp-start-upsert-pr` (which\n// prints the schema) and `wp-finish-upsert-pr` (which reads it); reviewer subagents write the per-checklist\n// review-<id>.json files. Data-only (per CLAUDE.md).\nexport class ReviewJson {\n title: string; // human PR title describing the change; used as the `gh pr` title (empty → caller falls back)\n riskScore: number; // 0–100, drives the risk bar\n riskLevel: string; // 'green' | 'yellow' | 'red'\n riskEmoji: string; // '🟢' | '🟡' | '🔴' — derived from riskLevel when omitted\n summary: string; // rendered in the dashboard Summary section\n violations: string[]; // pattern/architecture violations; length = the Pattern Violations count\n risks: string[];\n filesToReview: string[];\n results: ChecklistResult[]; // resolved per-checklist verdicts (from review-<id>.json); [] when none\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n title: string,\n riskScore: number,\n riskLevel: string,\n riskEmoji: string,\n summary: string,\n violations: string[],\n risks: string[],\n filesToReview: string[],\n results: ChecklistResult[] = [],\n ) {\n this.title = title;\n this.riskScore = riskScore;\n this.riskLevel = riskLevel;\n this.riskEmoji = riskEmoji;\n this.summary = summary;\n this.violations = violations;\n this.risks = risks;\n this.filesToReview = filesToReview;\n this.results = results;\n }\n}\n\n// A checklist's resolved outcome, shared by review.json enforcement and the dashboard so both agree.\n// PASS, WARN and OVERRIDDEN all ship; FAIL, MISSING and BAD_FORMAT all refuse the PR.\nexport const CK_PASS = 'pass'; // review-<id>.json status:'green'\nexport const CK_WARN = 'warn'; // review-<id>.json status:'yellow' → 🟡 passes WITH concerns\nexport const CK_OVERRIDDEN = 'overridden'; // review-<id>.json status:'red' + non-empty override → 🟠\nexport const CK_FAIL = 'fail'; // review-<id>.json status:'red' + no override → refuse\nexport const CK_MISSING = 'missing'; // no review-<id>.json written → refuse\nexport const CK_BAD_FORMAT = 'bad-format'; // written, but its verdict is unreadable (e.g. legacy `success`)\n\nexport class ChecklistVerdict {\n id: string;\n status: string; // one of CK_PASS | CK_WARN | CK_OVERRIDDEN | CK_FAIL | CK_MISSING | CK_BAD_FORMAT\n detail: string; // reviewer output / override justification / format complaint (dashboard + errors)\n\n constructor(id: string, status: string, detail: string) {\n this.id = id;\n this.status = status;\n this.detail = detail;\n }\n}\n\n// The PR's diff context, written by wp-start-upsert-pr into `.webpieces/pr-review/<branch>/pr-context.json`\n// so a reviewer subagent knows the exact 3-point base the gate used and the full changed-file set — then\n// reads any file's actual diff with `git diff <base> HEAD -- <file>`. This is what lets a checklist match\n// coarsely by path (in the config) while the subagent makes the fine, content-level judgment. Data-only.\nexport class PrContext {\n base: string; // the 3-point merge-base sha the gate diffs against\n /**\n * The real HEAD sha. This was once the literal string 'HEAD', which is not a fact — it cannot be\n * compared later to detect that the tree moved under a review, and it reads as a range that was never\n * actually diffed. Its only reader (reviewContextFor) takes `base`, so recording the sha is free.\n */\n head: string;\n changedFiles: string[]; // every file changed in the range (NOT tsOnly — includes .sql/.gql/Dockerfile/…)\n dirty: boolean; // true ⇒ changedFiles includes uncommitted + untracked work\n dirtyFiles: string[]; // exactly which paths are uncommitted/untracked — why `dirty` is true\n diffCommand: string; // the command that reproduces the WHOLE diff (see DiffBasis; correct when dirty)\n diffDir: string; // dir holding the materialized per-file diffs + ALL.diff; '' when not materialized\n generatedAt: string; // ISO timestamp, so a stale context is detectable rather than silently trusted\n /**\n * Main's head as this clone last saw it — the THIRD hash point, matching the trio the 3-point merge\n * records in `merge-info/<branch>/updatemain-hashes.json`. `base`/`head` above are points A and B\n * under the review side's older names.\n *\n * The review side used to record only A and B, so nothing could answer \"did main move while this was\n * under review?\" — the question you most want answered when a review looks stale. '' when origin/main\n * is unresolvable. Purely informational; nothing gates on it.\n */\n hashMainHead: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n base: string, head: string, changedFiles: string[],\n dirty = false, dirtyFiles: string[] = [], diffCommand = '', diffDir = '', generatedAt = '',\n hashMainHead = '',\n ) {\n this.hashMainHead = hashMainHead;\n this.base = base;\n this.head = head;\n this.changedFiles = changedFiles;\n this.dirty = dirty;\n this.dirtyFiles = dirtyFiles;\n this.diffCommand = diffCommand;\n this.diffDir = diffDir;\n this.generatedAt = generatedAt;\n }\n}\n"]}
|
package/src/review-json.d.ts
CHANGED
|
@@ -106,6 +106,19 @@ export declare class ReviewJsonService {
|
|
|
106
106
|
* second run and reads as though the earlier verdict did not count.
|
|
107
107
|
*/
|
|
108
108
|
pendingChecklists(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[];
|
|
109
|
+
/**
|
|
110
|
+
* The OPTIONAL checklists (`required: false`) that matched the diff but have no verdict file at all.
|
|
111
|
+
*
|
|
112
|
+
* This is the ONE set that separates "nobody ran it" from "it failed", and the distinction is the whole
|
|
113
|
+
* feature: an optional checklist with no verdict was legitimately not run — declined by the human, or
|
|
114
|
+
* skipped via `--no-optional` — so it must NOT block. An optional checklist with a RED verdict is not in
|
|
115
|
+
* here (it resolves to CK_FAIL) and blocks exactly like a required one: choosing to run a reviewer and
|
|
116
|
+
* then ignoring its answer would make the whole thing theater.
|
|
117
|
+
*
|
|
118
|
+
* A strict subset of {@link pendingChecklists}, computed here rather than at each call site so the
|
|
119
|
+
* command that gates and the dashboard that reports cannot disagree about which checklists were skipped.
|
|
120
|
+
*/
|
|
121
|
+
optionalWithoutVerdict(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[];
|
|
109
122
|
/**
|
|
110
123
|
* The checklists that REFUSED: a reviewer ran, judged the change, and said no (CK_FAIL — status red with
|
|
111
124
|
* no override). A strict subset of {@link pendingChecklists}, split out because it demands a completely
|
package/src/review-json.js
CHANGED
|
@@ -322,6 +322,21 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
322
322
|
return status !== review_json_data_1.CK_PASS && status !== review_json_data_1.CK_WARN && status !== review_json_data_1.CK_OVERRIDDEN;
|
|
323
323
|
});
|
|
324
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* The OPTIONAL checklists (`required: false`) that matched the diff but have no verdict file at all.
|
|
327
|
+
*
|
|
328
|
+
* This is the ONE set that separates "nobody ran it" from "it failed", and the distinction is the whole
|
|
329
|
+
* feature: an optional checklist with no verdict was legitimately not run — declined by the human, or
|
|
330
|
+
* skipped via `--no-optional` — so it must NOT block. An optional checklist with a RED verdict is not in
|
|
331
|
+
* here (it resolves to CK_FAIL) and blocks exactly like a required one: choosing to run a reviewer and
|
|
332
|
+
* then ignoring its answer would make the whole thing theater.
|
|
333
|
+
*
|
|
334
|
+
* A strict subset of {@link pendingChecklists}, computed here rather than at each call site so the
|
|
335
|
+
* command that gates and the dashboard that reports cannot disagree about which checklists were skipped.
|
|
336
|
+
*/
|
|
337
|
+
optionalWithoutVerdict(required, results) {
|
|
338
|
+
return required.filter((req) => !req.required && this.resolveVerdict(req, results).status === review_json_data_1.CK_MISSING);
|
|
339
|
+
}
|
|
325
340
|
/**
|
|
326
341
|
* The checklists that REFUSED: a reviewer ran, judged the change, and said no (CK_FAIL — status red with
|
|
327
342
|
* no override). A strict subset of {@link pendingChecklists}, split out because it demands a completely
|
|
@@ -436,6 +451,12 @@ let ReviewJsonService = class ReviewJsonService {
|
|
|
436
451
|
errors.push(this.refusalError(req, verdict));
|
|
437
452
|
}
|
|
438
453
|
else if (verdict.status === review_json_data_1.CK_MISSING) {
|
|
454
|
+
// An OPTIONAL checklist with no verdict was legitimately not run — the human was offered it
|
|
455
|
+
// and declined (or `--no-optional` skipped the offer). Demanding it here would make
|
|
456
|
+
// `required: false` mean nothing. Note the CK_FAIL branch above deliberately has no such
|
|
457
|
+
// exemption: once an optional reviewer RUNS, its refusal counts.
|
|
458
|
+
if (!req.required)
|
|
459
|
+
continue;
|
|
439
460
|
const doc = req.doc.trim() !== '' ? ` Read: ${req.doc}.` : '';
|
|
440
461
|
errors.push(`Checklist "${req.id}" MATCHED this diff but has no verdict. Spawn the "${req.subagent}" subagent to review it, ` +
|
|
441
462
|
`then write ${this.checklistFileName(req.id)} with ` +
|
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 {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n ChecklistResult,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJson,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n ChecklistVerdict,\n PrContext,\n} from './review-json-data';\n\n// Re-exported so review-json.ts stays the single import site for the whole review vocabulary: the data\n// classes moved out to keep this file under the file-size limit, NOT to give callers a second module to\n// learn. Every existing `from './review-json'` import keeps resolving.\nexport {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n ChecklistResult,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJson,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n ChecklistVerdict,\n PrContext,\n};\n\nconst RISK_LEVELS = ['green', 'yellow', 'red'] as const;\nconst EMOJI_FOR_LEVEL: Record<string, string> = { green: '🟢', yellow: '🟡', red: '🔴' };\n\n// Where `wp-finish-upsert-pr` retires the review.json it just consumed, and the note it stamps on the way.\n// The key sorts first in the written JSON because it is written first — an AI that opens the file to see\n// whether it can reuse the review reads what the file IS before it reads a title it might be tempted to keep.\nconst OLD_REVIEW_FILE = 'old-review.json';\nconst ARCHIVE_NOTE_KEY = '_ARCHIVED_AUDIT_ONLY';\nconst ARCHIVE_NOTE =\n 'ARCHIVE — this is the review from the PREVIOUS wp-finish-upsert-pr run on this branch, kept for audit ' +\n 'purposes only. It is NOT the review for a new review round: it describes the code as of the last PR ' +\n 'update, which has since moved. If you are reviewing again, write a FRESH review.json at the path ' +\n 'pnpm wp-review-upsert-pr prints; do not copy this file\\'s title, summary or risk level forward without ' +\n 're-deciding each one. Overwritten by every finish, so only the most recent review is ever here.';\n\n// The same stamp, for a retired per-checklist verdict. Verdict files get their OWN wording because the two\n// archives answer different questions: old-review.json holds a description of the code, this holds a\n// REVIEWER'S DECISION. The one thing that must not happen is a reader treating an archived red as the live\n// verdict — the whole reason the file was moved rather than copied — so the note says that outright.\nconst CHECKLIST_ARCHIVE_NOTE =\n 'ARCHIVE — this is a checklist verdict from a PREVIOUS reviewer run on this branch, kept for audit ' +\n 'purposes only. It is NOT a live verdict and must never be read back as one: it was RETIRED because it ' +\n 'refused the PR, and the gate moved it here so the only way forward is a FRESH review-<id>.json written ' +\n 'by a real reviewer run. Do not copy its status back onto the live path to get past the gate. ' +\n 'Overwritten by every retirement, so only the most recently retired verdict is ever here.';\n\n/** Locates + loads/validates the AI-authored review.json. `@injectable(bindingScopeValues.Singleton)` so it's drawn in the design. */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewJsonService {\n constructor(private readonly dotDir: DotWebpieces = dotWebpieces) {}\n\n // The per-feature PR working dir: `<local .webpieces>/pr-review/<feature>`. LOCAL scope — a PR\n // review belongs to the worktree that is preparing it, and two worktrees never share one.\n prDirFor(repoRoot: string, featureName: string): string {\n return this.dotDir.localFile(repoRoot, PR_REVIEW_DIR, featureName);\n }\n\n // Absolute path of the review.json for a feature — beside pr-body.md, keyed by branch name.\n reviewJsonPath(repoRoot: string, featureName: string): string {\n return path.join(this.prDirFor(repoRoot, featureName), 'review.json');\n }\n\n // Absolute path of the pr-context.json for a feature (the diff base/head + changed files).\n prContextPath(repoRoot: string, featureName: string): string {\n return path.join(this.prDirFor(repoRoot, featureName), 'pr-context.json');\n }\n\n // Where a consumed review.json is archived to, beside it. Always the SAME path — it holds the last\n // review and only the last one, so it can never be mistaken for a series that means something.\n oldReviewJsonPath(reviewJsonFilePath: string): string {\n return path.join(path.dirname(reviewJsonFilePath), OLD_REVIEW_FILE);\n }\n\n /**\n * Retire the review `wp-finish-upsert-pr` just used: move review.json to old-review.json, stamped with a\n * note saying what it is. Returns the archive path, or '' when there was nothing to archive.\n *\n * The point is the MOVE, not the copy. review.json left in place after a PR is posted is a live-looking\n * file describing a review that already happened, and the next run of stage ② on this branch finds it\n * sitting there — so a reviewer subagent that judges the PR's stated intent (its title, summary or risk\n * level) can read the previous run's review and return GREEN against a title that no longer exists.\n * Nothing in the verdict distinguishes that from a real pass. Moving it means the only way to reach\n * finish again is to write a fresh one, and {@link loadReviewJson} points at the archive when it is\n * missing so the archive reads as an audit trail rather than as a lost file.\n *\n * Called only after the PR is actually up: a finish that failed before publishing must stay re-runnable.\n */\n archiveReviewJson(reviewJsonFilePath: string): string {\n if (!fs.existsSync(reviewJsonFilePath)) return '';\n const archivePath = this.oldReviewJsonPath(reviewJsonFilePath);\n const raw = fs.readFileSync(reviewJsonFilePath, 'utf8');\n fs.writeFileSync(archivePath, this.archivedBody(raw, ARCHIVE_NOTE));\n fs.rmSync(reviewJsonFilePath);\n return archivePath;\n }\n\n /**\n * The archived bytes: the original JSON with an AUDIT-ONLY note as its FIRST key, so anything that opens\n * the file — human or AI — reads what it is before it reads any of its content.\n *\n * `note` is a parameter rather than a constant because two different files are archived here (review.json\n * and review-<id>.json) and they need to say different things, while the stamping MECHANICS — parse,\n * note first, original keys in order, fall back to raw — are identical. One implementation, two texts;\n * a second copy of this method would be the thing that drifts.\n *\n * Falls back to the raw bytes when they do not parse. For review.json `loadReviewJson` has already\n * accepted the file so that is close to impossible, but a verdict file is written by a subagent and may\n * be half-written or not an object at all — and preserving the original always beats losing it to a\n * stamping failure, since the archive exists precisely to be the record.\n */\n private archivedBody(raw: string, note: string): string {\n const parsed = this.tryParseObject(raw);\n if (parsed === null) return raw;\n // webpieces-disable no-any-unknown -- re-serializing opaque review fields verbatim; only the key ORDER is ours\n const stamped: Record<string, unknown> = {};\n stamped[ARCHIVE_NOTE_KEY] = note;\n for (const key of Object.keys(parsed)) stamped[key] = parsed[key];\n return JSON.stringify(stamped, null, 2) + '\\n';\n }\n\n // webpieces-disable no-any-unknown -- opaque parsed JSON; the caller only re-serializes it\n private tryParseObject(raw: string): Record<string, unknown> | null {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: unparseable bytes are archived verbatim, never fatal\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque; only its key order is used\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null;\n return parsed;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * Persist the PR's diff context so reviewer subagents can read the changed-file set + the exact base\n * sha. Returns the file path written.\n *\n * ALSO writes an immutable per-stage snapshot under `stages/<stage>.json` when `stage` is given.\n * `pr-context.json` is overwritten by each stage, so by the time anything goes wrong the earlier\n * states are gone — and \"what did the tooling think the diff was at stage ①, vs ②, vs ③?\" is exactly\n * the question you need answered when debugging a review that went sideways. The snapshots make the\n * review system auditable by an AI reviewing IT, which the single mutable file never could.\n */\n writePrContext(repoRoot: string, featureName: string, context: PrContext, stage = ''): string {\n const dir = this.prDirFor(repoRoot, featureName);\n fs.mkdirSync(dir, { recursive: true });\n const body = JSON.stringify(context, null, 2) + '\\n';\n const p = this.prContextPath(repoRoot, featureName);\n fs.writeFileSync(p, body);\n if (stage !== '') {\n const stagesDir = path.join(dir, 'stages');\n fs.mkdirSync(stagesDir, { recursive: true });\n fs.writeFileSync(path.join(stagesDir, `${stage}.json`), body);\n }\n return p;\n }\n\n /**\n * The review context for a feature, recovered from the pr-context.json wp-start-upsert-pr already wrote.\n * Lets wp-finish-upsert-pr's \"you still owe me review-<id>.json\" message inline the SAME self-sufficient\n * per-reviewer block start printed, instead of a checklist name and an indirection. Empty when the file\n * is absent or unreadable — the block then just omits those lines.\n */\n reviewContextFor(repoRoot: string, featureName: string): ChecklistReviewContext {\n const p = this.prContextPath(repoRoot, featureName);\n if (!fs.existsSync(p)) return new ChecklistReviewContext();\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unreadable context file degrades to fewer printed lines, never a crash\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed on the next line\n const raw = JSON.parse(fs.readFileSync(p, 'utf8')) as Record<string, unknown>;\n const base = typeof raw['base'] === 'string' ? (raw['base'] as string) : '';\n // Recover the REPRODUCE command rather than re-deriving it: a context written by an older\n // pr-gate has no diffCommand, and guessing `<base> HEAD` there would resurrect the exact\n // empty-on-a-dirty-tree bug this field exists to kill. Absent ⇒ omit the line entirely.\n const cmd = typeof raw['diffCommand'] === 'string' ? (raw['diffCommand'] as string) : '';\n const diffDir = typeof raw['diffDir'] === 'string' ? (raw['diffDir'] as string) : '';\n const dirty = raw['dirty'] === true;\n return new ChecklistReviewContext(base, p, cmd === '' ? '' : `${cmd} -- <file>`, diffDir, dirty);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return new ChecklistReviewContext('', p);\n }\n }\n\n // Copy-paste schema both commands print. `required` is the set of checklists the diff MATCHED; empty\n // ⇒ output identical to a repo with no checklists. Non-empty ⇒ appends per-checklist instructions\n // naming the reviewer subagent + doc + the review-<id>.json to write.\n reviewJsonSchemaHint(filePath: string): string {\n return (\n `Write your PR review to:\\n ${filePath}\\n\\n` +\n `with this exact JSON shape (riskEmoji optional — derived from riskLevel):\\n\\n` +\n `{\\n` +\n ` \"title\": \"concise PR title describing the change (imperative, no branch names)\",\\n` +\n ` \"riskScore\": 0, // integer 0–100 (higher = riskier)\\n` +\n ` \"riskLevel\": \"green | yellow | red\",\\n` +\n ` \"summary\": \"5–10 sentence review summary\",\\n` +\n ` \"violations\": [\"pattern/architecture violations you found (empty array if none)\"],\\n` +\n ` \"risks\": [\"notable risks (empty array if none)\"],\\n` +\n ` \"filesToReview\": [\"paths a human should look at (empty array if none)\"]\\n` +\n `}`\n );\n }\n\n /**\n * The extra line the \"no review.json\" complaint carries when a PREVIOUS review was archived here. It\n * turns a bare \"not found\" — which reads as data loss, and invites hunting for the file — into the fact:\n * the last finish consumed it, and the archive is audit material, not a review to reuse.\n */\n private archivedReviewHint(filePath: string): string {\n const archive = this.oldReviewJsonPath(filePath);\n if (!fs.existsSync(archive)) return '';\n return `\\nA PREVIOUS review was archived to ${archive} when the last pnpm wp-finish-upsert-pr consumed it.\\n` +\n `That file is for AUDIT ONLY — it reviews code this branch has since moved past. Write a fresh one:`;\n }\n\n // The per-checklist review file path that sits beside review.json: review-<id>.json.\n checklistResultPath(reviewJsonFilePath: string, checklistId: string): string {\n return path.join(path.dirname(reviewJsonFilePath), `review-${checklistId}.json`);\n }\n\n /**\n * Where a RETIRED verdict for one checklist goes: `review-<id>.json.old`, beside the live path.\n *\n * Mirrors {@link oldReviewJsonPath} deliberately, including its single-slot rule: ALWAYS the same path,\n * so it holds the last retired verdict and only the last one. A series (`.old.old`, `.old.1`) would read\n * as though the number of retirements meant something, and nothing downstream can interpret that — the\n * one fact worth keeping is \"this checklist refused before, here is what it said\".\n */\n oldChecklistResultPath(reviewJsonFilePath: string, checklistId: string): string {\n return `${this.checklistResultPath(reviewJsonFilePath, checklistId)}.old`;\n }\n\n /**\n * Retire one checklist's verdict: MOVE review-<id>.json to review-<id>.json.old, stamped with a note\n * saying what it is. Returns the archive path, or '' when there was nothing to archive.\n *\n * The point is the MOVE, exactly as in {@link archiveReviewJson}. A red verdict left on the live path is\n * re-read by the next run and re-reported as the CURRENT state of the branch, so the branch keeps being\n * refused for a finding that may already be fixed — and the fix, when it comes, silently overwrites the\n * only record that the gate ever refused anything. Moving it makes the refusal durable and makes a fresh\n * reviewer run the only way forward, which is the honest requirement: the old verdict judged code that\n * has since changed.\n *\n * Safe by construction for RED verdicts specifically, which is why the caller only archives on CK_FAIL:\n * a red verdict is never reusable — it always blocks — so nothing is lost by moving it. Green and yellow\n * verdicts ARE deliberately reused across finish attempts, and retiring one would force a needless (and\n * expensive) subagent re-run.\n */\n archiveChecklistResult(reviewJsonFilePath: string, checklistId: string): string {\n const livePath = this.checklistResultPath(reviewJsonFilePath, checklistId);\n if (!fs.existsSync(livePath)) return '';\n const archivePath = this.oldChecklistResultPath(reviewJsonFilePath, checklistId);\n const raw = fs.readFileSync(livePath, 'utf8');\n fs.writeFileSync(archivePath, this.archivedBody(raw, CHECKLIST_ARCHIVE_NOTE));\n fs.rmSync(livePath);\n return archivePath;\n }\n\n /**\n * Load + validate the AI-authored review.json. Throws InformAiError (with the schema) when missing,\n * unparseable, or structurally wrong. `required` is the set of checklists the diff matched: every one\n * must have a well-formed, passing (or overridden) review-<id>.json or a validation error is raised\n * alongside the usual ones so the AI gets ONE message.\n */\n // webpieces-disable max-lines-new-methods -- one cohesive load+validate pass over the review fields\n loadReviewJson(filePath: string, required: readonly RequiredChecklist[] = []): ReviewJson {\n if (!fs.existsSync(filePath)) {\n throw new InformAiError(\n `Required review.json not found.${this.archivedReviewHint(filePath)}\\n\\n` +\n `${this.reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n\n const raw = this.parseReviewJson(fs.readFileSync(filePath, 'utf8'), filePath);\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new InformAiError(`review.json must be a JSON object.\\n\\n${this.reviewJsonSchemaHint(filePath)}`);\n }\n\n const errors: string[] = [];\n\n const riskScore = raw['riskScore'];\n if (typeof riskScore !== 'number' || !Number.isFinite(riskScore) || riskScore < 0 || riskScore > 100) {\n errors.push(`\"riskScore\" must be a number 0–100, got ${JSON.stringify(riskScore)}.`);\n }\n\n const riskLevel = raw['riskLevel'];\n if (typeof riskLevel !== 'string' || !RISK_LEVELS.includes(riskLevel as typeof RISK_LEVELS[number])) {\n errors.push(`\"riskLevel\" must be one of: ${RISK_LEVELS.join(', ')}.`);\n }\n\n const title = typeof raw['title'] === 'string' ? (raw['title'] as string).trim() : '';\n if (title === '') {\n errors.push('\"title\" must be a non-empty, imperative PR title describing the change (no branch names).');\n }\n\n const results = this.loadChecklistResults(filePath, required);\n for (const err of this.requiredChecklistErrors(required, results)) errors.push(err);\n\n if (errors.length > 0) {\n throw new InformAiError(\n `review.json has ${errors.length} error(s) — fix ALL, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\n${this.reviewJsonSchemaHint(filePath)}`,\n );\n }\n\n const level = riskLevel as string;\n const emoji = typeof raw['riskEmoji'] === 'string' && raw['riskEmoji'] !== ''\n ? (raw['riskEmoji'] as string)\n : (EMOJI_FOR_LEVEL[level] ?? '🟡');\n const summary = typeof raw['summary'] === 'string' ? (raw['summary'] as string) : '';\n\n return new ReviewJson(\n title,\n riskScore as number,\n level,\n emoji,\n summary,\n this.asStringArray(raw['violations']),\n this.asStringArray(raw['risks']),\n this.asStringArray(raw['filesToReview']),\n results,\n );\n }\n\n /**\n * The checklists that still OWE a verdict: no review-<id>.json at all, a malformed one, or one whose\n * verdict is an un-overridden FAIL. This is the set every message lists — a checklist already PASSed or\n * OVERRIDDEN on this branch is deliberately NOT re-listed, because re-instructing it invites a redundant\n * second run and reads as though the earlier verdict did not count.\n */\n pendingChecklists(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[] {\n return required.filter((req: RequiredChecklist): boolean => {\n const status = this.resolveVerdict(req, results).status;\n // CK_WARN must be listed here beside PASS/OVERRIDDEN. A yellow verdict SHIPS — leaving it out\n // would mark the checklist owed forever, so `outstanding` never empties and the PR is refused\n // permanently no matter how many times the reviewer runs.\n return status !== CK_PASS && status !== CK_WARN && status !== CK_OVERRIDDEN;\n });\n }\n\n /**\n * The checklists that REFUSED: a reviewer ran, judged the change, and said no (CK_FAIL — status red with\n * no override). A strict subset of {@link pendingChecklists}, split out because it demands a completely\n * different action from the reader.\n *\n * Public so every command agrees on the set. When \"refused\" was computed ad hoc, a refusal and a\n * never-ran reviewer landed in one bucket and produced one message — \"you MUST run these N reviewer\n * subagent(s)\" — handed to an AI, which obediently re-spawned a reviewer that had already answered. It\n * refused again for the same reason, and the loop cost a full subagent run per pass while the reviewer's\n * actual finding was never shown to anyone. A refusal is a RESULT, not a missing step.\n */\n refusedChecklists(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[] {\n return required.filter((req: RequiredChecklist): boolean =>\n this.resolveVerdict(req, results).status === CK_FAIL);\n }\n\n /**\n * THE renderer for \"this reviewer refused\" — one wording, wherever the refusal surfaces. It exists as a\n * method because the text was previously inlined in {@link requiredChecklistErrors}, reachable only\n * through review.json validation, while the command layer refused earlier with its own generic message.\n * Two messages for one event is how the useful one became unreachable; there is now exactly one.\n *\n * It always quotes the reviewer's own `output` verbatim: the finding is the whole point, and an error\n * that names a checklist without saying what it objected to gives the reader nothing to fix.\n *\n * `archivedPath` non-empty ⇒ the verdict has just been RETIRED (moved) to that path, so the message must\n * change in two ways. It says where the record went — otherwise the move reads as data loss — and,\n * critically, it must NOT tell the reader to \"set override in review-<id>.json\", because that file no\n * longer exists. The escape hatch is therefore worded as writing a FRESH verdict file (the body can be\n * copied back out of the archive) with a human-authored override.\n */\n refusalError(req: RequiredChecklist, verdict: ChecklistVerdict, archivedPath = ''): string {\n const finding = `${verdict.detail.split('\\n').join('\\n ')}\\n`;\n const head = `Checklist \"${req.id}\" FAILED review (status:\"${VERDICT_RED}\"). The reviewer (${req.subagent}) wrote:\\n ` + finding;\n if (archivedPath === '') {\n return head +\n ` Fix it, then re-run; or set a non-empty \"override\" in ${this.checklistFileName(req.id)} to ship anyway with a stated justification.`;\n }\n // Re-spawning is the LAST thing said, and only after the finding, because an instruction to spawn a\n // subagent is the one line an AI acts on first — see refusedChecklists for what that cost.\n return head +\n ` That verdict has been RETIRED to ${archivedPath} (audit only — it is not a live verdict).\\n` +\n ` A FRESH ${this.checklistFileName(req.id)} is now required. Fix the finding first, then have the ` +\n `\"${req.subagent}\" subagent review again and write a new verdict.\\n` +\n ` To ship anyway, a HUMAN must decide it: write a fresh ${this.checklistFileName(req.id)} (you may copy the ` +\n `body back from the archive) carrying a non-empty, human-authored \"override\" justification.`;\n }\n\n // Read the per-checklist verdict files `review-<id>.json` beside review.json — one per matched checklist.\n // A missing file is simply absent from the result (→ counts as MISSING for that checklist); a malformed\n // one is skipped (a stale review-<id>.json never wedges the branch).\n //\n // It looks up the EXACT `review-<id>.json` name per required id — never a directory scan, never a prefix\n // match. That is what guarantees an archived `review-<id>.json.old` can never resolve as a live verdict:\n // the retired file sits right beside the live path, and a scan that swept the directory would hand a\n // RETIRED refusal (or worse, a retired pass) back as the current state, undoing the whole point of the\n // move in {@link archiveChecklistResult}.\n loadChecklistResults(reviewJsonFilePath: string, required: readonly RequiredChecklist[]): ChecklistResult[] {\n const results: ChecklistResult[] = [];\n for (const req of required) {\n const p = this.checklistResultPath(reviewJsonFilePath, req.id);\n if (!fs.existsSync(p)) continue;\n const parsed = this.parseChecklistResult(p, req.id);\n if (parsed) results.push(parsed);\n }\n return results;\n }\n\n // Resolve ONE checklist's verdict from its review-<id>.json. Central so review.json enforcement AND the\n // finish-command dashboard agree on the outcome. `problem` is checked FIRST: a file whose verdict cannot\n // be read must not fall through to any shipping outcome.\n resolveVerdict(req: RequiredChecklist, results: readonly ChecklistResult[]): ChecklistVerdict {\n const result = results.find((r: ChecklistResult): boolean => r.id === req.id);\n if (!result) return new ChecklistVerdict(req.id, CK_MISSING, '');\n if (result.problem !== '') return new ChecklistVerdict(req.id, CK_BAD_FORMAT, result.problem);\n if (result.status === VERDICT_GREEN) return new ChecklistVerdict(req.id, CK_PASS, result.output);\n if (result.status === VERDICT_YELLOW) return new ChecklistVerdict(req.id, CK_WARN, result.output);\n if (result.override.trim() !== '') return new ChecklistVerdict(req.id, CK_OVERRIDDEN, result.override.trim());\n return new ChecklistVerdict(req.id, CK_FAIL, result.output);\n }\n\n /**\n * One loud complaint per checklist whose verdict file EXISTS but cannot be read as a verdict — almost\n * always one still using the removed `success` field. Public and separate from\n * {@link requiredChecklistErrors} because `wp-finish-upsert-pr` refuses on missing reviewers BEFORE it\n * parses review.json: without this, a legacy file would surface as the generic \"no verdict yet\" block\n * and the AI would re-run a reviewer that already ran instead of fixing four characters of JSON.\n */\n checklistFormatErrors(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): string[] {\n const errors: string[] = [];\n for (const req of required) {\n const verdict = this.resolveVerdict(req, results);\n if (verdict.status === CK_BAD_FORMAT) errors.push(verdict.detail);\n }\n return errors;\n }\n\n // Every matched checklist whose verdict is FAIL (reviewed, found a problem, no override) or MISSING (no\n // review-<id>.json written) → one error each, printing the reviewer's `output` verbatim.\n private requiredChecklistErrors(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): string[] {\n // Format complaints come from the ONE renderer, so wp-review-upsert-pr and wp-finish word them identically.\n const errors: string[] = this.checklistFormatErrors(required, results);\n for (const req of required) {\n const verdict = this.resolveVerdict(req, results);\n // CK_WARN ('yellow' — passed with concerns) is deliberately absent from this chain: it SHIPS.\n // The concern still reaches the PR, published in the checklist comment. Do not \"fix\" this.\n if (verdict.status === CK_FAIL) {\n // Through the ONE renderer, so this path and the command layer's refusal say the same thing.\n // No archive path here: this is validation, not the act of retiring the verdict.\n errors.push(this.refusalError(req, verdict));\n } else if (verdict.status === CK_MISSING) {\n const doc = req.doc.trim() !== '' ? ` Read: ${req.doc}.` : '';\n errors.push(\n `Checklist \"${req.id}\" MATCHED this diff but has no verdict. Spawn the \"${req.subagent}\" subagent to review it, ` +\n `then write ${this.checklistFileName(req.id)} with ` +\n `{\"id\":\"${req.id}\",\"status\":\"${VERDICT_GREEN}\",\"output\":\"…\",\"override\":\"\"}.${doc}`,\n );\n }\n }\n return errors;\n }\n\n private checklistFileName(checklistId: string): string {\n return `review-${checklistId}.json`;\n }\n\n /**\n * THE renderer for a reviewer's verdict schema — with the reviewer's own `id` already filled in and,\n * when known, the exact file it must write.\n *\n * There is one because a verdict schema that lives anywhere a human maintains it goes stale, and a\n * reviewer follows the stale copy. That is not a hypothetical: when `success` was replaced by the\n * tri-state `status`, hand-written `.claude/agents/*.md` files kept documenting `success`, and a real\n * PR had to carry \"the verdict format in your own agent .md file is OUT OF DATE\" in the spawn prompt to\n * work around it. Every printed copy — the stage-② roster, the generated per-reviewer instructions\n * file, and the complaint raised against a malformed verdict — now comes from here.\n *\n * `verdictPath` may be '' when the caller is describing the shape rather than a specific file.\n */\n verdictSchemaFor(id: string, verdictPath = '', indent = ' '): string {\n const lines = [\n `${indent}{ \"id\": \"${id}\", \"status\": \"${VERDICT_GREEN} | ${VERDICT_YELLOW} | ${VERDICT_RED}\", ` +\n `\"output\": \"what you checked / found\", \"override\": \"\" }`,\n `${indent} ${VERDICT_GREEN} → passes, nothing to flag`,\n `${indent} ${VERDICT_YELLOW} → passes WITH CONCERNS; nothing is blocked and the concern is published on the PR`,\n `${indent} ${VERDICT_RED} → REFUSES the PR (set a non-empty \"override\" to ship anyway with a stated justification)`,\n `${indent}Prefer \"${VERDICT_YELLOW}\" over red-plus-override when the change is acceptable but worth a human's`,\n `${indent}attention — an override reads as a deliberately-accepted defect, a yellow reads as a note.`,\n ];\n if (verdictPath !== '') lines.push(`${indent}File: ${verdictPath}`);\n return lines.join('\\n');\n }\n\n /**\n * Parse one review-<id>.json into a ChecklistResult. `null` ONLY when the bytes do not parse as a JSON\n * object at all — that tolerance is why a half-written file never wedges a branch, and it degrades to\n * the same \"no verdict yet\" message as an absent file, which is honest (nothing readable is there).\n *\n * A file that DOES parse always yields a result, even when its verdict is unreadable, carrying the\n * complaint in `problem`. Returning `null` for those instead would collapse \"wrote a verdict in the old\n * format\" into \"never wrote a verdict\" and send the AI off to re-run a reviewer that already ran.\n */\n // webpieces-disable no-any-unknown -- opaque parsed JSON, narrowed field-by-field\n private parseChecklistResult(filePath: string, id: string): ChecklistResult | null {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unparseable per-checklist file is skipped, not fatal\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed below\n const raw = JSON.parse(fs.readFileSync(filePath, 'utf8')) as Record<string, unknown>;\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return null;\n const output = typeof raw['output'] === 'string' ? (raw['output'] as string) : '';\n const override = typeof raw['override'] === 'string' ? (raw['override'] as string) : '';\n const status = typeof raw['status'] === 'string' ? (raw['status'] as string).trim().toLowerCase() : '';\n return new ChecklistResult(id, status, output, override, this.statusProblem(filePath, id, status, raw));\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * '' when `status` is one of the three colors. Otherwise the complaint to show the AI verbatim. The\n * legacy-`success` case gets its OWN message: `success` was removed outright (no compatibility mode),\n * and a reviewer told only \"status must be green|yellow|red\" cannot tell whether it wrote the wrong\n * value or is using a field that no longer exists.\n */\n // webpieces-disable no-any-unknown -- opaque parsed JSON; only tested for key presence here\n private statusProblem(filePath: string, id: string, status: string, raw: Record<string, unknown>): string {\n // webpieces-disable no-any-unknown -- comparing against the readonly literal tuple of valid colors\n if ((VERDICT_STATUSES as readonly string[]).includes(status)) return '';\n // The ONE renderer — see verdictSchemaFor. A second copy here is what let the old `success` shape\n // survive in print after it was removed from the parser.\n const shape = this.verdictSchemaFor(id, filePath);\n if ('success' in raw) {\n return `Checklist \"${id}\" wrote its verdict with the REMOVED \"success\" field. It is now a tri-state ` +\n `\"status\" — there is no compatibility mode. Rewrite the file as:\\n${shape}`;\n }\n return `Checklist \"${id}\" wrote a verdict with no valid \"status\" (got ${JSON.stringify(status)}). ` +\n `It must be exactly one of ${VERDICT_STATUSES.join(', ')}:\\n${shape}`;\n }\n\n // webpieces-disable no-any-unknown -- opaque parsed JSON value, narrowed to string[] here\n private asStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n // webpieces-disable no-any-unknown -- element of an opaque JSON array, narrowed by the type guard\n return value.filter((v: unknown): v is string => typeof v === 'string');\n }\n\n // Parse opaque AI-authored JSON, converting a SyntaxError into a readable InformAiError.\n // webpieces-disable no-any-unknown -- returns the opaque parsed object; loadReviewJson narrows each field\n private parseReviewJson(raw: string, filePath: string): Record<string, unknown> {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: convert JSON.parse SyntaxError to an InformAiError for the AI\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed by the caller\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(\n `review.json is not valid JSON (${error.message}).\\n\\n${this.reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n }\n}\n\n// Temporary migration delegators to ReviewJsonService — removed once consumers inject it.\nconst reviewJsonSvc = new ReviewJsonService();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function prDirFor(repoRoot: string, featureName: string): string {\n return reviewJsonSvc.prDirFor(repoRoot, featureName);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function reviewJsonPath(repoRoot: string, featureName: string): string {\n return reviewJsonSvc.reviewJsonPath(repoRoot, featureName);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function reviewJsonSchemaHint(filePath: string): string {\n return reviewJsonSvc.reviewJsonSchemaHint(filePath);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function loadReviewJson(filePath: string, required: readonly RequiredChecklist[] = []): ReviewJson {\n return reviewJsonSvc.loadReviewJson(filePath, required);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"review-json.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-json.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { PR_REVIEW_DIR } from './constants';\nimport { DotWebpieces, dotWebpieces } from './state-dir';\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\nimport {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n ChecklistResult,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJson,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n ChecklistVerdict,\n PrContext,\n} from './review-json-data';\n\n// Re-exported so review-json.ts stays the single import site for the whole review vocabulary: the data\n// classes moved out to keep this file under the file-size limit, NOT to give callers a second module to\n// learn. Every existing `from './review-json'` import keeps resolving.\nexport {\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n ChecklistResult,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJson,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n ChecklistVerdict,\n PrContext,\n};\n\nconst RISK_LEVELS = ['green', 'yellow', 'red'] as const;\nconst EMOJI_FOR_LEVEL: Record<string, string> = { green: '🟢', yellow: '🟡', red: '🔴' };\n\n// Where `wp-finish-upsert-pr` retires the review.json it just consumed, and the note it stamps on the way.\n// The key sorts first in the written JSON because it is written first — an AI that opens the file to see\n// whether it can reuse the review reads what the file IS before it reads a title it might be tempted to keep.\nconst OLD_REVIEW_FILE = 'old-review.json';\nconst ARCHIVE_NOTE_KEY = '_ARCHIVED_AUDIT_ONLY';\nconst ARCHIVE_NOTE =\n 'ARCHIVE — this is the review from the PREVIOUS wp-finish-upsert-pr run on this branch, kept for audit ' +\n 'purposes only. It is NOT the review for a new review round: it describes the code as of the last PR ' +\n 'update, which has since moved. If you are reviewing again, write a FRESH review.json at the path ' +\n 'pnpm wp-review-upsert-pr prints; do not copy this file\\'s title, summary or risk level forward without ' +\n 're-deciding each one. Overwritten by every finish, so only the most recent review is ever here.';\n\n// The same stamp, for a retired per-checklist verdict. Verdict files get their OWN wording because the two\n// archives answer different questions: old-review.json holds a description of the code, this holds a\n// REVIEWER'S DECISION. The one thing that must not happen is a reader treating an archived red as the live\n// verdict — the whole reason the file was moved rather than copied — so the note says that outright.\nconst CHECKLIST_ARCHIVE_NOTE =\n 'ARCHIVE — this is a checklist verdict from a PREVIOUS reviewer run on this branch, kept for audit ' +\n 'purposes only. It is NOT a live verdict and must never be read back as one: it was RETIRED because it ' +\n 'refused the PR, and the gate moved it here so the only way forward is a FRESH review-<id>.json written ' +\n 'by a real reviewer run. Do not copy its status back onto the live path to get past the gate. ' +\n 'Overwritten by every retirement, so only the most recently retired verdict is ever here.';\n\n/** Locates + loads/validates the AI-authored review.json. `@injectable(bindingScopeValues.Singleton)` so it's drawn in the design. */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewJsonService {\n constructor(private readonly dotDir: DotWebpieces = dotWebpieces) {}\n\n // The per-feature PR working dir: `<local .webpieces>/pr-review/<feature>`. LOCAL scope — a PR\n // review belongs to the worktree that is preparing it, and two worktrees never share one.\n prDirFor(repoRoot: string, featureName: string): string {\n return this.dotDir.localFile(repoRoot, PR_REVIEW_DIR, featureName);\n }\n\n // Absolute path of the review.json for a feature — beside pr-body.md, keyed by branch name.\n reviewJsonPath(repoRoot: string, featureName: string): string {\n return path.join(this.prDirFor(repoRoot, featureName), 'review.json');\n }\n\n // Absolute path of the pr-context.json for a feature (the diff base/head + changed files).\n prContextPath(repoRoot: string, featureName: string): string {\n return path.join(this.prDirFor(repoRoot, featureName), 'pr-context.json');\n }\n\n // Where a consumed review.json is archived to, beside it. Always the SAME path — it holds the last\n // review and only the last one, so it can never be mistaken for a series that means something.\n oldReviewJsonPath(reviewJsonFilePath: string): string {\n return path.join(path.dirname(reviewJsonFilePath), OLD_REVIEW_FILE);\n }\n\n /**\n * Retire the review `wp-finish-upsert-pr` just used: move review.json to old-review.json, stamped with a\n * note saying what it is. Returns the archive path, or '' when there was nothing to archive.\n *\n * The point is the MOVE, not the copy. review.json left in place after a PR is posted is a live-looking\n * file describing a review that already happened, and the next run of stage ② on this branch finds it\n * sitting there — so a reviewer subagent that judges the PR's stated intent (its title, summary or risk\n * level) can read the previous run's review and return GREEN against a title that no longer exists.\n * Nothing in the verdict distinguishes that from a real pass. Moving it means the only way to reach\n * finish again is to write a fresh one, and {@link loadReviewJson} points at the archive when it is\n * missing so the archive reads as an audit trail rather than as a lost file.\n *\n * Called only after the PR is actually up: a finish that failed before publishing must stay re-runnable.\n */\n archiveReviewJson(reviewJsonFilePath: string): string {\n if (!fs.existsSync(reviewJsonFilePath)) return '';\n const archivePath = this.oldReviewJsonPath(reviewJsonFilePath);\n const raw = fs.readFileSync(reviewJsonFilePath, 'utf8');\n fs.writeFileSync(archivePath, this.archivedBody(raw, ARCHIVE_NOTE));\n fs.rmSync(reviewJsonFilePath);\n return archivePath;\n }\n\n /**\n * The archived bytes: the original JSON with an AUDIT-ONLY note as its FIRST key, so anything that opens\n * the file — human or AI — reads what it is before it reads any of its content.\n *\n * `note` is a parameter rather than a constant because two different files are archived here (review.json\n * and review-<id>.json) and they need to say different things, while the stamping MECHANICS — parse,\n * note first, original keys in order, fall back to raw — are identical. One implementation, two texts;\n * a second copy of this method would be the thing that drifts.\n *\n * Falls back to the raw bytes when they do not parse. For review.json `loadReviewJson` has already\n * accepted the file so that is close to impossible, but a verdict file is written by a subagent and may\n * be half-written or not an object at all — and preserving the original always beats losing it to a\n * stamping failure, since the archive exists precisely to be the record.\n */\n private archivedBody(raw: string, note: string): string {\n const parsed = this.tryParseObject(raw);\n if (parsed === null) return raw;\n // webpieces-disable no-any-unknown -- re-serializing opaque review fields verbatim; only the key ORDER is ours\n const stamped: Record<string, unknown> = {};\n stamped[ARCHIVE_NOTE_KEY] = note;\n for (const key of Object.keys(parsed)) stamped[key] = parsed[key];\n return JSON.stringify(stamped, null, 2) + '\\n';\n }\n\n // webpieces-disable no-any-unknown -- opaque parsed JSON; the caller only re-serializes it\n private tryParseObject(raw: string): Record<string, unknown> | null {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: unparseable bytes are archived verbatim, never fatal\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque; only its key order is used\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null;\n return parsed;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * Persist the PR's diff context so reviewer subagents can read the changed-file set + the exact base\n * sha. Returns the file path written.\n *\n * ALSO writes an immutable per-stage snapshot under `stages/<stage>.json` when `stage` is given.\n * `pr-context.json` is overwritten by each stage, so by the time anything goes wrong the earlier\n * states are gone — and \"what did the tooling think the diff was at stage ①, vs ②, vs ③?\" is exactly\n * the question you need answered when debugging a review that went sideways. The snapshots make the\n * review system auditable by an AI reviewing IT, which the single mutable file never could.\n */\n writePrContext(repoRoot: string, featureName: string, context: PrContext, stage = ''): string {\n const dir = this.prDirFor(repoRoot, featureName);\n fs.mkdirSync(dir, { recursive: true });\n const body = JSON.stringify(context, null, 2) + '\\n';\n const p = this.prContextPath(repoRoot, featureName);\n fs.writeFileSync(p, body);\n if (stage !== '') {\n const stagesDir = path.join(dir, 'stages');\n fs.mkdirSync(stagesDir, { recursive: true });\n fs.writeFileSync(path.join(stagesDir, `${stage}.json`), body);\n }\n return p;\n }\n\n /**\n * The review context for a feature, recovered from the pr-context.json wp-start-upsert-pr already wrote.\n * Lets wp-finish-upsert-pr's \"you still owe me review-<id>.json\" message inline the SAME self-sufficient\n * per-reviewer block start printed, instead of a checklist name and an indirection. Empty when the file\n * is absent or unreadable — the block then just omits those lines.\n */\n reviewContextFor(repoRoot: string, featureName: string): ChecklistReviewContext {\n const p = this.prContextPath(repoRoot, featureName);\n if (!fs.existsSync(p)) return new ChecklistReviewContext();\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unreadable context file degrades to fewer printed lines, never a crash\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed on the next line\n const raw = JSON.parse(fs.readFileSync(p, 'utf8')) as Record<string, unknown>;\n const base = typeof raw['base'] === 'string' ? (raw['base'] as string) : '';\n // Recover the REPRODUCE command rather than re-deriving it: a context written by an older\n // pr-gate has no diffCommand, and guessing `<base> HEAD` there would resurrect the exact\n // empty-on-a-dirty-tree bug this field exists to kill. Absent ⇒ omit the line entirely.\n const cmd = typeof raw['diffCommand'] === 'string' ? (raw['diffCommand'] as string) : '';\n const diffDir = typeof raw['diffDir'] === 'string' ? (raw['diffDir'] as string) : '';\n const dirty = raw['dirty'] === true;\n return new ChecklistReviewContext(base, p, cmd === '' ? '' : `${cmd} -- <file>`, diffDir, dirty);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return new ChecklistReviewContext('', p);\n }\n }\n\n // Copy-paste schema both commands print. `required` is the set of checklists the diff MATCHED; empty\n // ⇒ output identical to a repo with no checklists. Non-empty ⇒ appends per-checklist instructions\n // naming the reviewer subagent + doc + the review-<id>.json to write.\n reviewJsonSchemaHint(filePath: string): string {\n return (\n `Write your PR review to:\\n ${filePath}\\n\\n` +\n `with this exact JSON shape (riskEmoji optional — derived from riskLevel):\\n\\n` +\n `{\\n` +\n ` \"title\": \"concise PR title describing the change (imperative, no branch names)\",\\n` +\n ` \"riskScore\": 0, // integer 0–100 (higher = riskier)\\n` +\n ` \"riskLevel\": \"green | yellow | red\",\\n` +\n ` \"summary\": \"5–10 sentence review summary\",\\n` +\n ` \"violations\": [\"pattern/architecture violations you found (empty array if none)\"],\\n` +\n ` \"risks\": [\"notable risks (empty array if none)\"],\\n` +\n ` \"filesToReview\": [\"paths a human should look at (empty array if none)\"]\\n` +\n `}`\n );\n }\n\n /**\n * The extra line the \"no review.json\" complaint carries when a PREVIOUS review was archived here. It\n * turns a bare \"not found\" — which reads as data loss, and invites hunting for the file — into the fact:\n * the last finish consumed it, and the archive is audit material, not a review to reuse.\n */\n private archivedReviewHint(filePath: string): string {\n const archive = this.oldReviewJsonPath(filePath);\n if (!fs.existsSync(archive)) return '';\n return `\\nA PREVIOUS review was archived to ${archive} when the last pnpm wp-finish-upsert-pr consumed it.\\n` +\n `That file is for AUDIT ONLY — it reviews code this branch has since moved past. Write a fresh one:`;\n }\n\n // The per-checklist review file path that sits beside review.json: review-<id>.json.\n checklistResultPath(reviewJsonFilePath: string, checklistId: string): string {\n return path.join(path.dirname(reviewJsonFilePath), `review-${checklistId}.json`);\n }\n\n /**\n * Where a RETIRED verdict for one checklist goes: `review-<id>.json.old`, beside the live path.\n *\n * Mirrors {@link oldReviewJsonPath} deliberately, including its single-slot rule: ALWAYS the same path,\n * so it holds the last retired verdict and only the last one. A series (`.old.old`, `.old.1`) would read\n * as though the number of retirements meant something, and nothing downstream can interpret that — the\n * one fact worth keeping is \"this checklist refused before, here is what it said\".\n */\n oldChecklistResultPath(reviewJsonFilePath: string, checklistId: string): string {\n return `${this.checklistResultPath(reviewJsonFilePath, checklistId)}.old`;\n }\n\n /**\n * Retire one checklist's verdict: MOVE review-<id>.json to review-<id>.json.old, stamped with a note\n * saying what it is. Returns the archive path, or '' when there was nothing to archive.\n *\n * The point is the MOVE, exactly as in {@link archiveReviewJson}. A red verdict left on the live path is\n * re-read by the next run and re-reported as the CURRENT state of the branch, so the branch keeps being\n * refused for a finding that may already be fixed — and the fix, when it comes, silently overwrites the\n * only record that the gate ever refused anything. Moving it makes the refusal durable and makes a fresh\n * reviewer run the only way forward, which is the honest requirement: the old verdict judged code that\n * has since changed.\n *\n * Safe by construction for RED verdicts specifically, which is why the caller only archives on CK_FAIL:\n * a red verdict is never reusable — it always blocks — so nothing is lost by moving it. Green and yellow\n * verdicts ARE deliberately reused across finish attempts, and retiring one would force a needless (and\n * expensive) subagent re-run.\n */\n archiveChecklistResult(reviewJsonFilePath: string, checklistId: string): string {\n const livePath = this.checklistResultPath(reviewJsonFilePath, checklistId);\n if (!fs.existsSync(livePath)) return '';\n const archivePath = this.oldChecklistResultPath(reviewJsonFilePath, checklistId);\n const raw = fs.readFileSync(livePath, 'utf8');\n fs.writeFileSync(archivePath, this.archivedBody(raw, CHECKLIST_ARCHIVE_NOTE));\n fs.rmSync(livePath);\n return archivePath;\n }\n\n /**\n * Load + validate the AI-authored review.json. Throws InformAiError (with the schema) when missing,\n * unparseable, or structurally wrong. `required` is the set of checklists the diff matched: every one\n * must have a well-formed, passing (or overridden) review-<id>.json or a validation error is raised\n * alongside the usual ones so the AI gets ONE message.\n */\n // webpieces-disable max-lines-new-methods -- one cohesive load+validate pass over the review fields\n loadReviewJson(filePath: string, required: readonly RequiredChecklist[] = []): ReviewJson {\n if (!fs.existsSync(filePath)) {\n throw new InformAiError(\n `Required review.json not found.${this.archivedReviewHint(filePath)}\\n\\n` +\n `${this.reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n\n const raw = this.parseReviewJson(fs.readFileSync(filePath, 'utf8'), filePath);\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new InformAiError(`review.json must be a JSON object.\\n\\n${this.reviewJsonSchemaHint(filePath)}`);\n }\n\n const errors: string[] = [];\n\n const riskScore = raw['riskScore'];\n if (typeof riskScore !== 'number' || !Number.isFinite(riskScore) || riskScore < 0 || riskScore > 100) {\n errors.push(`\"riskScore\" must be a number 0–100, got ${JSON.stringify(riskScore)}.`);\n }\n\n const riskLevel = raw['riskLevel'];\n if (typeof riskLevel !== 'string' || !RISK_LEVELS.includes(riskLevel as typeof RISK_LEVELS[number])) {\n errors.push(`\"riskLevel\" must be one of: ${RISK_LEVELS.join(', ')}.`);\n }\n\n const title = typeof raw['title'] === 'string' ? (raw['title'] as string).trim() : '';\n if (title === '') {\n errors.push('\"title\" must be a non-empty, imperative PR title describing the change (no branch names).');\n }\n\n const results = this.loadChecklistResults(filePath, required);\n for (const err of this.requiredChecklistErrors(required, results)) errors.push(err);\n\n if (errors.length > 0) {\n throw new InformAiError(\n `review.json has ${errors.length} error(s) — fix ALL, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\n${this.reviewJsonSchemaHint(filePath)}`,\n );\n }\n\n const level = riskLevel as string;\n const emoji = typeof raw['riskEmoji'] === 'string' && raw['riskEmoji'] !== ''\n ? (raw['riskEmoji'] as string)\n : (EMOJI_FOR_LEVEL[level] ?? '🟡');\n const summary = typeof raw['summary'] === 'string' ? (raw['summary'] as string) : '';\n\n return new ReviewJson(\n title,\n riskScore as number,\n level,\n emoji,\n summary,\n this.asStringArray(raw['violations']),\n this.asStringArray(raw['risks']),\n this.asStringArray(raw['filesToReview']),\n results,\n );\n }\n\n /**\n * The checklists that still OWE a verdict: no review-<id>.json at all, a malformed one, or one whose\n * verdict is an un-overridden FAIL. This is the set every message lists — a checklist already PASSed or\n * OVERRIDDEN on this branch is deliberately NOT re-listed, because re-instructing it invites a redundant\n * second run and reads as though the earlier verdict did not count.\n */\n pendingChecklists(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[] {\n return required.filter((req: RequiredChecklist): boolean => {\n const status = this.resolveVerdict(req, results).status;\n // CK_WARN must be listed here beside PASS/OVERRIDDEN. A yellow verdict SHIPS — leaving it out\n // would mark the checklist owed forever, so `outstanding` never empties and the PR is refused\n // permanently no matter how many times the reviewer runs.\n return status !== CK_PASS && status !== CK_WARN && status !== CK_OVERRIDDEN;\n });\n }\n\n /**\n * The OPTIONAL checklists (`required: false`) that matched the diff but have no verdict file at all.\n *\n * This is the ONE set that separates \"nobody ran it\" from \"it failed\", and the distinction is the whole\n * feature: an optional checklist with no verdict was legitimately not run — declined by the human, or\n * skipped via `--no-optional` — so it must NOT block. An optional checklist with a RED verdict is not in\n * here (it resolves to CK_FAIL) and blocks exactly like a required one: choosing to run a reviewer and\n * then ignoring its answer would make the whole thing theater.\n *\n * A strict subset of {@link pendingChecklists}, computed here rather than at each call site so the\n * command that gates and the dashboard that reports cannot disagree about which checklists were skipped.\n */\n optionalWithoutVerdict(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[] {\n return required.filter((req: RequiredChecklist): boolean =>\n !req.required && this.resolveVerdict(req, results).status === CK_MISSING);\n }\n\n /**\n * The checklists that REFUSED: a reviewer ran, judged the change, and said no (CK_FAIL — status red with\n * no override). A strict subset of {@link pendingChecklists}, split out because it demands a completely\n * different action from the reader.\n *\n * Public so every command agrees on the set. When \"refused\" was computed ad hoc, a refusal and a\n * never-ran reviewer landed in one bucket and produced one message — \"you MUST run these N reviewer\n * subagent(s)\" — handed to an AI, which obediently re-spawned a reviewer that had already answered. It\n * refused again for the same reason, and the loop cost a full subagent run per pass while the reviewer's\n * actual finding was never shown to anyone. A refusal is a RESULT, not a missing step.\n */\n refusedChecklists(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): RequiredChecklist[] {\n return required.filter((req: RequiredChecklist): boolean =>\n this.resolveVerdict(req, results).status === CK_FAIL);\n }\n\n /**\n * THE renderer for \"this reviewer refused\" — one wording, wherever the refusal surfaces. It exists as a\n * method because the text was previously inlined in {@link requiredChecklistErrors}, reachable only\n * through review.json validation, while the command layer refused earlier with its own generic message.\n * Two messages for one event is how the useful one became unreachable; there is now exactly one.\n *\n * It always quotes the reviewer's own `output` verbatim: the finding is the whole point, and an error\n * that names a checklist without saying what it objected to gives the reader nothing to fix.\n *\n * `archivedPath` non-empty ⇒ the verdict has just been RETIRED (moved) to that path, so the message must\n * change in two ways. It says where the record went — otherwise the move reads as data loss — and,\n * critically, it must NOT tell the reader to \"set override in review-<id>.json\", because that file no\n * longer exists. The escape hatch is therefore worded as writing a FRESH verdict file (the body can be\n * copied back out of the archive) with a human-authored override.\n */\n refusalError(req: RequiredChecklist, verdict: ChecklistVerdict, archivedPath = ''): string {\n const finding = `${verdict.detail.split('\\n').join('\\n ')}\\n`;\n const head = `Checklist \"${req.id}\" FAILED review (status:\"${VERDICT_RED}\"). The reviewer (${req.subagent}) wrote:\\n ` + finding;\n if (archivedPath === '') {\n return head +\n ` Fix it, then re-run; or set a non-empty \"override\" in ${this.checklistFileName(req.id)} to ship anyway with a stated justification.`;\n }\n // Re-spawning is the LAST thing said, and only after the finding, because an instruction to spawn a\n // subagent is the one line an AI acts on first — see refusedChecklists for what that cost.\n return head +\n ` That verdict has been RETIRED to ${archivedPath} (audit only — it is not a live verdict).\\n` +\n ` A FRESH ${this.checklistFileName(req.id)} is now required. Fix the finding first, then have the ` +\n `\"${req.subagent}\" subagent review again and write a new verdict.\\n` +\n ` To ship anyway, a HUMAN must decide it: write a fresh ${this.checklistFileName(req.id)} (you may copy the ` +\n `body back from the archive) carrying a non-empty, human-authored \"override\" justification.`;\n }\n\n // Read the per-checklist verdict files `review-<id>.json` beside review.json — one per matched checklist.\n // A missing file is simply absent from the result (→ counts as MISSING for that checklist); a malformed\n // one is skipped (a stale review-<id>.json never wedges the branch).\n //\n // It looks up the EXACT `review-<id>.json` name per required id — never a directory scan, never a prefix\n // match. That is what guarantees an archived `review-<id>.json.old` can never resolve as a live verdict:\n // the retired file sits right beside the live path, and a scan that swept the directory would hand a\n // RETIRED refusal (or worse, a retired pass) back as the current state, undoing the whole point of the\n // move in {@link archiveChecklistResult}.\n loadChecklistResults(reviewJsonFilePath: string, required: readonly RequiredChecklist[]): ChecklistResult[] {\n const results: ChecklistResult[] = [];\n for (const req of required) {\n const p = this.checklistResultPath(reviewJsonFilePath, req.id);\n if (!fs.existsSync(p)) continue;\n const parsed = this.parseChecklistResult(p, req.id);\n if (parsed) results.push(parsed);\n }\n return results;\n }\n\n // Resolve ONE checklist's verdict from its review-<id>.json. Central so review.json enforcement AND the\n // finish-command dashboard agree on the outcome. `problem` is checked FIRST: a file whose verdict cannot\n // be read must not fall through to any shipping outcome.\n resolveVerdict(req: RequiredChecklist, results: readonly ChecklistResult[]): ChecklistVerdict {\n const result = results.find((r: ChecklistResult): boolean => r.id === req.id);\n if (!result) return new ChecklistVerdict(req.id, CK_MISSING, '');\n if (result.problem !== '') return new ChecklistVerdict(req.id, CK_BAD_FORMAT, result.problem);\n if (result.status === VERDICT_GREEN) return new ChecklistVerdict(req.id, CK_PASS, result.output);\n if (result.status === VERDICT_YELLOW) return new ChecklistVerdict(req.id, CK_WARN, result.output);\n if (result.override.trim() !== '') return new ChecklistVerdict(req.id, CK_OVERRIDDEN, result.override.trim());\n return new ChecklistVerdict(req.id, CK_FAIL, result.output);\n }\n\n /**\n * One loud complaint per checklist whose verdict file EXISTS but cannot be read as a verdict — almost\n * always one still using the removed `success` field. Public and separate from\n * {@link requiredChecklistErrors} because `wp-finish-upsert-pr` refuses on missing reviewers BEFORE it\n * parses review.json: without this, a legacy file would surface as the generic \"no verdict yet\" block\n * and the AI would re-run a reviewer that already ran instead of fixing four characters of JSON.\n */\n checklistFormatErrors(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): string[] {\n const errors: string[] = [];\n for (const req of required) {\n const verdict = this.resolveVerdict(req, results);\n if (verdict.status === CK_BAD_FORMAT) errors.push(verdict.detail);\n }\n return errors;\n }\n\n // Every matched checklist whose verdict is FAIL (reviewed, found a problem, no override) or MISSING (no\n // review-<id>.json written) → one error each, printing the reviewer's `output` verbatim.\n private requiredChecklistErrors(required: readonly RequiredChecklist[], results: readonly ChecklistResult[]): string[] {\n // Format complaints come from the ONE renderer, so wp-review-upsert-pr and wp-finish word them identically.\n const errors: string[] = this.checklistFormatErrors(required, results);\n for (const req of required) {\n const verdict = this.resolveVerdict(req, results);\n // CK_WARN ('yellow' — passed with concerns) is deliberately absent from this chain: it SHIPS.\n // The concern still reaches the PR, published in the checklist comment. Do not \"fix\" this.\n if (verdict.status === CK_FAIL) {\n // Through the ONE renderer, so this path and the command layer's refusal say the same thing.\n // No archive path here: this is validation, not the act of retiring the verdict.\n errors.push(this.refusalError(req, verdict));\n } else if (verdict.status === CK_MISSING) {\n // An OPTIONAL checklist with no verdict was legitimately not run — the human was offered it\n // and declined (or `--no-optional` skipped the offer). Demanding it here would make\n // `required: false` mean nothing. Note the CK_FAIL branch above deliberately has no such\n // exemption: once an optional reviewer RUNS, its refusal counts.\n if (!req.required) continue;\n const doc = req.doc.trim() !== '' ? ` Read: ${req.doc}.` : '';\n errors.push(\n `Checklist \"${req.id}\" MATCHED this diff but has no verdict. Spawn the \"${req.subagent}\" subagent to review it, ` +\n `then write ${this.checklistFileName(req.id)} with ` +\n `{\"id\":\"${req.id}\",\"status\":\"${VERDICT_GREEN}\",\"output\":\"…\",\"override\":\"\"}.${doc}`,\n );\n }\n }\n return errors;\n }\n\n private checklistFileName(checklistId: string): string {\n return `review-${checklistId}.json`;\n }\n\n /**\n * THE renderer for a reviewer's verdict schema — with the reviewer's own `id` already filled in and,\n * when known, the exact file it must write.\n *\n * There is one because a verdict schema that lives anywhere a human maintains it goes stale, and a\n * reviewer follows the stale copy. That is not a hypothetical: when `success` was replaced by the\n * tri-state `status`, hand-written `.claude/agents/*.md` files kept documenting `success`, and a real\n * PR had to carry \"the verdict format in your own agent .md file is OUT OF DATE\" in the spawn prompt to\n * work around it. Every printed copy — the stage-② roster, the generated per-reviewer instructions\n * file, and the complaint raised against a malformed verdict — now comes from here.\n *\n * `verdictPath` may be '' when the caller is describing the shape rather than a specific file.\n */\n verdictSchemaFor(id: string, verdictPath = '', indent = ' '): string {\n const lines = [\n `${indent}{ \"id\": \"${id}\", \"status\": \"${VERDICT_GREEN} | ${VERDICT_YELLOW} | ${VERDICT_RED}\", ` +\n `\"output\": \"what you checked / found\", \"override\": \"\" }`,\n `${indent} ${VERDICT_GREEN} → passes, nothing to flag`,\n `${indent} ${VERDICT_YELLOW} → passes WITH CONCERNS; nothing is blocked and the concern is published on the PR`,\n `${indent} ${VERDICT_RED} → REFUSES the PR (set a non-empty \"override\" to ship anyway with a stated justification)`,\n `${indent}Prefer \"${VERDICT_YELLOW}\" over red-plus-override when the change is acceptable but worth a human's`,\n `${indent}attention — an override reads as a deliberately-accepted defect, a yellow reads as a note.`,\n ];\n if (verdictPath !== '') lines.push(`${indent}File: ${verdictPath}`);\n return lines.join('\\n');\n }\n\n /**\n * Parse one review-<id>.json into a ChecklistResult. `null` ONLY when the bytes do not parse as a JSON\n * object at all — that tolerance is why a half-written file never wedges a branch, and it degrades to\n * the same \"no verdict yet\" message as an absent file, which is honest (nothing readable is there).\n *\n * A file that DOES parse always yields a result, even when its verdict is unreadable, carrying the\n * complaint in `problem`. Returning `null` for those instead would collapse \"wrote a verdict in the old\n * format\" into \"never wrote a verdict\" and send the AI off to re-run a reviewer that already ran.\n */\n // webpieces-disable no-any-unknown -- opaque parsed JSON, narrowed field-by-field\n private parseChecklistResult(filePath: string, id: string): ChecklistResult | null {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: an unparseable per-checklist file is skipped, not fatal\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed below\n const raw = JSON.parse(fs.readFileSync(filePath, 'utf8')) as Record<string, unknown>;\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return null;\n const output = typeof raw['output'] === 'string' ? (raw['output'] as string) : '';\n const override = typeof raw['override'] === 'string' ? (raw['override'] as string) : '';\n const status = typeof raw['status'] === 'string' ? (raw['status'] as string).trim().toLowerCase() : '';\n return new ChecklistResult(id, status, output, override, this.statusProblem(filePath, id, status, raw));\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * '' when `status` is one of the three colors. Otherwise the complaint to show the AI verbatim. The\n * legacy-`success` case gets its OWN message: `success` was removed outright (no compatibility mode),\n * and a reviewer told only \"status must be green|yellow|red\" cannot tell whether it wrote the wrong\n * value or is using a field that no longer exists.\n */\n // webpieces-disable no-any-unknown -- opaque parsed JSON; only tested for key presence here\n private statusProblem(filePath: string, id: string, status: string, raw: Record<string, unknown>): string {\n // webpieces-disable no-any-unknown -- comparing against the readonly literal tuple of valid colors\n if ((VERDICT_STATUSES as readonly string[]).includes(status)) return '';\n // The ONE renderer — see verdictSchemaFor. A second copy here is what let the old `success` shape\n // survive in print after it was removed from the parser.\n const shape = this.verdictSchemaFor(id, filePath);\n if ('success' in raw) {\n return `Checklist \"${id}\" wrote its verdict with the REMOVED \"success\" field. It is now a tri-state ` +\n `\"status\" — there is no compatibility mode. Rewrite the file as:\\n${shape}`;\n }\n return `Checklist \"${id}\" wrote a verdict with no valid \"status\" (got ${JSON.stringify(status)}). ` +\n `It must be exactly one of ${VERDICT_STATUSES.join(', ')}:\\n${shape}`;\n }\n\n // webpieces-disable no-any-unknown -- opaque parsed JSON value, narrowed to string[] here\n private asStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n // webpieces-disable no-any-unknown -- element of an opaque JSON array, narrowed by the type guard\n return value.filter((v: unknown): v is string => typeof v === 'string');\n }\n\n // Parse opaque AI-authored JSON, converting a SyntaxError into a readable InformAiError.\n // webpieces-disable no-any-unknown -- returns the opaque parsed object; loadReviewJson narrows each field\n private parseReviewJson(raw: string, filePath: string): Record<string, unknown> {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: convert JSON.parse SyntaxError to an InformAiError for the AI\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed by the caller\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(\n `review.json is not valid JSON (${error.message}).\\n\\n${this.reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n }\n}\n\n// Temporary migration delegators to ReviewJsonService — removed once consumers inject it.\nconst reviewJsonSvc = new ReviewJsonService();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function prDirFor(repoRoot: string, featureName: string): string {\n return reviewJsonSvc.prDirFor(repoRoot, featureName);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function reviewJsonPath(repoRoot: string, featureName: string): string {\n return reviewJsonSvc.reviewJsonPath(repoRoot, featureName);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function reviewJsonSchemaHint(filePath: string): string {\n return reviewJsonSvc.reviewJsonSchemaHint(filePath);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ReviewJsonService; removed once consumers inject it\nexport function loadReviewJson(filePath: string, required: readonly RequiredChecklist[] = []): ReviewJson {\n return reviewJsonSvc.loadReviewJson(filePath, required);\n}\n"]}
|
|
@@ -77,6 +77,12 @@ class ReviewerBriefing {
|
|
|
77
77
|
hashFeatureHead;
|
|
78
78
|
hashMainHead;
|
|
79
79
|
ownAgentFileInDiff; // ABSOLUTE path when this diff edits THIS reviewer's own agent file; '' otherwise
|
|
80
|
+
// From the checklist's config `required`. Carried so the stage-② report can group the spawn blocks
|
|
81
|
+
// (must run) apart from the ones the human is merely OFFERED, without a second lookup that could
|
|
82
|
+
// disagree with the scan. The reviewer's OWN instructions do not mention it: a reviewer that has been
|
|
83
|
+
// spawned reviews the same way either way, and telling an optional one it was optional invites it to
|
|
84
|
+
// grade itself softer.
|
|
85
|
+
required;
|
|
80
86
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
81
87
|
constructor(subagent, checklistId, repoRoot) {
|
|
82
88
|
this.subagent = subagent;
|
|
@@ -102,6 +108,9 @@ class ReviewerBriefing {
|
|
|
102
108
|
this.hashFeatureHead = '';
|
|
103
109
|
this.hashMainHead = '';
|
|
104
110
|
this.ownAgentFileInDiff = '';
|
|
111
|
+
// Fails CLOSED, like RequiredChecklist.required: a briefing built without the flag being copied is
|
|
112
|
+
// treated as a blocking reviewer, never as a skippable one.
|
|
113
|
+
this.required = true;
|
|
105
114
|
}
|
|
106
115
|
}
|
|
107
116
|
exports.ReviewerBriefing = ReviewerBriefing;
|