@webpieces/rules-config 0.4.780 → 0.4.781
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 +4 -3
- package/src/checklist-config.js +5 -4
- package/src/checklist-config.js.map +1 -1
- package/src/checklist-validator.d.ts +2 -1
- package/src/checklist-validator.js +10 -4
- package/src/checklist-validator.js.map +1 -1
- package/src/pr-gate-config.d.ts +5 -5
- package/src/pr-gate-config.js +13 -8
- package/src/pr-gate-config.js.map +1 -1
- package/src/pr-gate-section-validators.d.ts +8 -4
- package/src/pr-gate-section-validators.js +41 -21
- package/src/pr-gate-section-validators.js.map +1 -1
- package/src/retired-config-keys.js +3 -3
- package/src/retired-config-keys.js.map +1 -1
- package/src/reviewer-instructions.d.ts +1 -1
- package/src/reviewer-instructions.js +2 -2
- package/src/reviewer-instructions.js.map +1 -1
- package/src/subagent-provenance.d.ts +1 -1
- package/src/subagent-provenance.js +1 -1
- package/src/subagent-provenance.js.map +1 -1
- package/src/validate-config.js +0 -1
- package/src/validate-config.js.map +1 -1
- package/templates/webpieces.review-checklists.md +13 -10
|
@@ -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":"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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 {\n ChecklistDefinition, DEFAULT_REVIEWER_AGENT_NAME, RawChecklistItem, REVIEWER_AGENTS_ONE_PER_CHECKLIST,\n ReviewerAgentPolicy, toChecklist,\n} from './checklist-config';\nimport { UPGRADE_SHIM_COMMAND } from './constants';\nimport { retiredKeyErrorsIn } from './retired-config-keys';\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 ' \"reviewerAgentName\": \"webpieces-reviewer\",\\n' +\n ' \"checklists\": [\\n' +\n ' { \"id\": \"db-migrations\",\\n' +\n ' \"doc\": \".claude/review/db-migrations.md\",\\n' +\n ' \"patterns\": [\"**/migrations/**\", \"**/*.sql\"],\\n' +\n ' \"required\": true }\\n' +\n ' ]\\n' +\n ' \"id\" names the checklist and keys its review-<id>.json. Every checklist is reviewed by the agent\\n' +\n ' \"reviewerAgentName\" names, against its own \"doc\" (REQUIRED, REPO-relative). Omit \"patterns\" (or use\\n' +\n ' []) to run on every PR. \"required\" is MANDATORY on every entry: true blocks the PR until the\\n' +\n ' reviewer passes; false makes it an OPTIONAL review the human is offered and may decline (but if they\\n' +\n ' DO run it, a red verdict still blocks).'\n);\n\n// The label every RETIRED checklist-entry key is filed under in RETIRED_CONFIG_KEYS.\nexport const CHECKLIST_ENTRY_LABEL = '[pr-gate.checklists]';\n\n/**\n * The `checklists` section of a pr-gate config: an ARRAY of { id, doc, patterns?, required }, 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 * 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 { \"id\", \"doc\", \"patterns\"?, \"required\" }. ${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 id, because that is what the consumer recognizes in a twelve-entry\n * array; `checklists[7]` alone means counting braces. It also states BOTH edits, because the whole point of\n * 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['id'] === 'string' && e['id'].trim() !== '' ? ` (\"${e['id']}\")` : '';\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 ONE entry — a bad `patterns` or a non-string `doc` is a config-file typo and deserves a\n// `checklists[i]` message. A RETIRED key (the per-entry `subagent`) is reported with its migration edit.\n// webpieces-disable no-any-unknown -- one opaque checklist entry, narrowed per-field below\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction checklistEntryErrors(e: Record<string, unknown>, i: number): string[] {\n const errors = retiredKeyErrorsIn(e, CHECKLIST_ENTRY_LABEL).map((m: string): string => `checklists[${i}] ${m}`);\n if (e['id'] !== undefined && typeof e['id'] !== 'string') {\n errors.push(`[pr-gate] checklists[${i}].id must be a string — the checklist's name; it keys review-<id>.json.`);\n }\n if (e['doc'] !== undefined && typeof e['doc'] !== 'string') {\n errors.push(`[pr-gate] checklists[${i}].doc must be a string — the REPO-relative path to the checklist's guidance doc.`);\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 return errors;\n}\n\n// Structurally check each entry, then hand the narrowed defs to ChecklistValidator for the id rules and the\n// checks only the filesystem can answer (the guidance doc exists). The reviewer agent itself is repo-wide\n// and is validated once, beside `reviewerAgentName`, by validateReviewerAgentKeys.\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 { \"id\", \"doc\", \"patterns\"?, \"required\" }.`);\n return;\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque checklist entry\n const e = entry as Record<string, unknown>;\n errors.push(...checklistEntryErrors(e, i));\n // Only the fields that passed their type check reach toChecklist — the wrong-typed ones already\n // have their own error above, and must not crash the id/doc checks below.\n items.push({\n id: typeof e['id'] === 'string' ? e['id'] : undefined,\n doc: typeof e['doc'] === 'string' ? e['doc'] : undefined,\n required: e['required'] === true,\n });\n });\n // The reviewer policy plays no part in these checks (ids and docs), so a placeholder is bound here.\n const placeholder = new ReviewerAgentPolicy('', REVIEWER_AGENTS_ONE_PER_CHECKLIST);\n const defs = items.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item, placeholder));\n return [...errors, ...new ChecklistValidator().validate(repoRoot, defs)];\n}\n\nexport const REVIEWER_AGENT_NAME_KEY = 'reviewerAgentName';\nexport const REVIEWER_AGENTS_KEY = 'reviewerAgents';\n\n/**\n * `reviewerAgentName` (REQUIRED while the gate is active) and `reviewerAgents` (optional positive integer).\n *\n * `reviewerAgentName` has no default for the same reason `mergeMode` has none: it names a file the repo\n * commits, and a silent default would brief every reviewer with an agent type the repo may not have. The\n * error hands the reader the exact line, which an agent applies in one pass.\n *\n * `reviewerAgents` absent is the documented \"one subagent per checklist\" contract — the count every repo had\n * before the key existed, so it widens nothing; present, it CAPS the subagents one round may use.\n */\n// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; two keys are read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateReviewerAgentKeys(s: Record<string, unknown>, repoRoot?: string): string[] {\n const errors: string[] = [];\n const name = s[REVIEWER_AGENT_NAME_KEY];\n const named = typeof name === 'string' && name.trim() !== '';\n if (!named) {\n errors.push(\n `[pr-gate] Missing required field \"${REVIEWER_AGENT_NAME_KEY}\" — the agent type every reviewer subagent is ` +\n `spawned as. Add this line to commands.pr-gate in webpieces.config.json:\\n` +\n ` \"${REVIEWER_AGENT_NAME_KEY}\": \"${DEFAULT_REVIEWER_AGENT_NAME}\",\\n` +\n ` ${DEFAULT_REVIEWER_AGENT_NAME} is the generic reviewer webpieces ships as .claude/agents/${DEFAULT_REVIEWER_AGENT_NAME}.md ` +\n `(\\`${UPGRADE_SHIM_COMMAND}\\` writes it). Point the key at your own agent to use that one instead.`);\n }\n const max = s[REVIEWER_AGENTS_KEY];\n if (REVIEWER_AGENTS_KEY in s && !(typeof max === 'number' && Number.isInteger(max) && max >= 1)) {\n errors.push(\n `[pr-gate] \"${REVIEWER_AGENTS_KEY}\" = ${JSON.stringify(max)} is not valid — it must be a positive integer: the MOST ` +\n `reviewer subagents one review round may use, with the checklists grouped across them (1 = a single ` +\n `subagent reviews every checklist). Delete the key to keep one subagent per checklist.`);\n }\n if (repoRoot !== undefined && named) {\n errors.push(...new ChecklistValidator().validateReviewerAgent(\n repoRoot, new ReviewerAgentPolicy((name as string).trim(), REVIEWER_AGENTS_ONE_PER_CHECKLIST)));\n }\n return errors;\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\n// A git ref COMPONENT this flow is willing to build a ref name out of. Deliberately much narrower than\n// git's own check-ref-format: these two values are concatenated into a ref that a command then\n// force-pushes, so anything that could be read as a flag, a path escape, or a glob is rejected outright\n// rather than trusted to `git push` argument order.\nconst REF_COMPONENT = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;\n\n/**\n * Validate the optional `pr-gate.devDeploy` block — where `wp-push-dev` publishes the disposable copy of\n * a feature branch, and which ref is the shared dev branch itself. Absent ⇒ `dev-include` / `dev`.\n *\n * `devBranch` must NOT contain a slash and must not sit inside `branchNamespace`: the whole point of the\n * namespace is that the composed dev branch is written by CI and the copies are written by developers, so\n * a config where one contains the other makes `--list` enumerate the deploy branch and makes the\n * \"refused as a source branch\" check ambiguous.\n */\n// webpieces-disable no-any-unknown -- `value` is the opaque consumer devDeploy value until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateDevDeploySection(value: unknown): string[] {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return ['[pr-gate] \"devDeploy\" must be an object, e.g. { \"branchNamespace\": \"dev-include\", \"devBranch\": \"dev\" }.'];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque devDeploy object from consumer JSON\n const s = value as Record<string, unknown>;\n const errors: string[] = [];\n errors.push(...validateRefComponent(s, 'branchNamespace', 'dev-include'));\n errors.push(...validateRefComponent(s, 'devBranch', 'dev'));\n if (errors.length > 0) return errors;\n\n const namespace = typeof s['branchNamespace'] === 'string' ? s['branchNamespace'].trim() : 'dev-include';\n const devBranch = typeof s['devBranch'] === 'string' ? s['devBranch'].trim() : 'dev';\n if (devBranch.includes('/')) {\n errors.push(\n `[pr-gate] \"devDeploy.devBranch\" = \"${devBranch}\" must be a single ref name with no \"/\" — it is the ` +\n `branch your CI composes and deploys, not a namespace.`);\n }\n if (devBranch === namespace || devBranch.startsWith(`${namespace}/`) || namespace.startsWith(`${devBranch}/`)) {\n errors.push(\n `[pr-gate] \"devDeploy.devBranch\" (\"${devBranch}\") and \"devDeploy.branchNamespace\" (\"${namespace}\") must not ` +\n `contain one another. The namespace holds the DISPOSABLE per-developer copies (written by wp-push-dev); ` +\n `devBranch is the COMPOSED branch your CI rebuilds from origin/main. Overlapping them makes wp-push-dev ` +\n `--list enumerate the deploy branch and makes \"refused as a source branch\" ambiguous.`);\n }\n return errors;\n}\n\n// One `devDeploy` string field: present ⇒ must be a non-empty, ref-safe string. Absent ⇒ the default.\n// webpieces-disable no-any-unknown -- the already-narrowed opaque devDeploy object\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction validateRefComponent(s: Record<string, unknown>, key: string, example: string): string[] {\n if (!(key in s)) return [];\n const raw = s[key];\n if (typeof raw !== 'string' || raw.trim() === '') {\n return [`[pr-gate] \"devDeploy.${key}\" must be a non-empty string, e.g. \"${example}\". Omit the key for the \"${example}\" default.`];\n }\n if (!REF_COMPONENT.test(raw.trim())) {\n return [\n `[pr-gate] \"devDeploy.${key}\" = \"${raw}\" is not a usable git ref name. Use letters, digits, \".\", \"_\", ` +\n `\"-\" and \"/\" only, starting with a letter or digit (e.g. \"${example}\").`,\n ];\n }\n return [];\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 {\n ChecklistDefinition, DEFAULT_REVIEWER_AGENT_NAME, RawChecklistItem, REVIEWER_AGENTS_ONE_PER_CHECKLIST,\n ReviewerAgentPolicy, toChecklist,\n} from './checklist-config';\nimport { UPGRADE_SHIM_COMMAND } from './constants';\nimport { retiredKeyErrorsIn } from './retired-config-keys';\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 ' { \"id\": \"db-migrations\",\\n' +\n ' \"doc\": \".claude/review/db-migrations.md\",\\n' +\n ' \"patterns\": [\"**/migrations/**\", \"**/*.sql\"],\\n' +\n ' \"required\": true }\\n' +\n ' ]\\n' +\n ' \"id\" names the checklist and keys its review-<id>.json. Every checklist is reviewed by the\\n' +\n ' webpieces-reviewer agent (or your own: \"overrideReviewerAgent\": true + \"reviewerAgentName\"), against\\n' +\n ' its own \"doc\" (REQUIRED, 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\\n' +\n ' it an OPTIONAL review the human is offered and may decline (but if they DO run it, a red verdict\\n' +\n ' still blocks).'\n);\n\n// The label every RETIRED checklist-entry key is filed under in RETIRED_CONFIG_KEYS.\nexport const CHECKLIST_ENTRY_LABEL = '[pr-gate.checklists]';\n\n/**\n * The `checklists` section of a pr-gate config: an ARRAY of { id, doc, patterns?, required }, 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 * 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 { \"id\", \"doc\", \"patterns\"?, \"required\" }. ${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 id, because that is what the consumer recognizes in a twelve-entry\n * array; `checklists[7]` alone means counting braces. It also states BOTH edits, because the whole point of\n * 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['id'] === 'string' && e['id'].trim() !== '' ? ` (\"${e['id']}\")` : '';\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 ONE entry — a bad `patterns` or a non-string `doc` is a config-file typo and deserves a\n// `checklists[i]` message. A RETIRED key (the per-entry `subagent`) is reported with its migration edit.\n// webpieces-disable no-any-unknown -- one opaque checklist entry, narrowed per-field below\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction checklistEntryErrors(e: Record<string, unknown>, i: number): string[] {\n const errors = retiredKeyErrorsIn(e, CHECKLIST_ENTRY_LABEL).map((m: string): string => `checklists[${i}] ${m}`);\n if (e['id'] !== undefined && typeof e['id'] !== 'string') {\n errors.push(`[pr-gate] checklists[${i}].id must be a string — the checklist's name; it keys review-<id>.json.`);\n }\n if (e['doc'] !== undefined && typeof e['doc'] !== 'string') {\n errors.push(`[pr-gate] checklists[${i}].doc must be a string — the REPO-relative path to the checklist's guidance doc.`);\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 return errors;\n}\n\n// Structurally check each entry, then hand the narrowed defs to ChecklistValidator for the id rules and the\n// checks only the filesystem can answer (the guidance doc exists). The reviewer agent itself is repo-wide\n// and is validated once, beside `overrideReviewerAgent` / `reviewerAgentName`, by validateReviewerAgentKeys.\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 { \"id\", \"doc\", \"patterns\"?, \"required\" }.`);\n return;\n }\n // webpieces-disable no-any-unknown -- narrowing one opaque checklist entry\n const e = entry as Record<string, unknown>;\n errors.push(...checklistEntryErrors(e, i));\n // Only the fields that passed their type check reach toChecklist — the wrong-typed ones already\n // have their own error above, and must not crash the id/doc checks below.\n items.push({\n id: typeof e['id'] === 'string' ? e['id'] : undefined,\n doc: typeof e['doc'] === 'string' ? e['doc'] : undefined,\n required: e['required'] === true,\n });\n });\n // The reviewer policy plays no part in these checks (ids and docs), so a placeholder is bound here.\n const placeholder = new ReviewerAgentPolicy('', REVIEWER_AGENTS_ONE_PER_CHECKLIST);\n const defs = items.map((item: RawChecklistItem): ChecklistDefinition => toChecklist(item, placeholder));\n return [...errors, ...new ChecklistValidator().validate(repoRoot, defs)];\n}\n\nexport const REVIEWER_AGENT_NAME_KEY = 'reviewerAgentName';\nexport const OVERRIDE_REVIEWER_AGENT_KEY = 'overrideReviewerAgent';\nexport const REVIEWER_AGENTS_KEY = 'reviewerAgents';\n\n/**\n * `overrideReviewerAgent` (optional boolean, absent == false), `reviewerAgentName` (only with the override)\n * and `reviewerAgents` (optional positive integer).\n *\n * The reviewer agent defaults to {@link DEFAULT_REVIEWER_AGENT_NAME} — the generic reviewer webpieces ships\n * and manages — so a new repo writes NOTHING here (issue #947). Replacing it changes how every PR is\n * reviewed, so it must be said out loud with `\"overrideReviewerAgent\": true` (greppable) AND a\n * `reviewerAgentName`. A name WITHOUT the override is rejected rather than silently ignored: a key that looks\n * meaningful but controls nothing is a second spelling of a decision (.claude/rules/no-backwards-compat.md).\n *\n * `reviewerAgents` absent is the documented \"one subagent per checklist\" contract — the count every repo had\n * before the key existed, so it widens nothing; present, it CAPS the subagents one round may use.\n */\n// webpieces-disable no-any-unknown -- the already-narrowed opaque pr-gate section; three keys are read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateReviewerAgentKeys(s: Record<string, unknown>, repoRoot?: string): string[] {\n const errors: string[] = [];\n const override = s[OVERRIDE_REVIEWER_AGENT_KEY];\n const hasName = REVIEWER_AGENT_NAME_KEY in s;\n const name = s[REVIEWER_AGENT_NAME_KEY];\n const named = typeof name === 'string' && name.trim() !== '';\n if (OVERRIDE_REVIEWER_AGENT_KEY in s && typeof override !== 'boolean') {\n errors.push(\n `[pr-gate] \"${OVERRIDE_REVIEWER_AGENT_KEY}\" = ${JSON.stringify(override)} is not valid — it must be true or ` +\n `false. Delete the key to use the webpieces reviewer (${DEFAULT_REVIEWER_AGENT_NAME}); set it to true, together ` +\n `with \"${REVIEWER_AGENT_NAME_KEY}\", to use an agent of your own.`);\n } else if (override === true && !named) {\n errors.push(\n `[pr-gate] \"${OVERRIDE_REVIEWER_AGENT_KEY}\": true needs \"${REVIEWER_AGENT_NAME_KEY}\" — add the agent type every ` +\n `reviewer subagent is spawned as (its .claude/agents/<name>.md must exist) to commands.pr-gate in ` +\n `webpieces.config.json:\\n` +\n ` \"${REVIEWER_AGENT_NAME_KEY}\": \"my-reviewer\",\\n` +\n ` Or delete \"${OVERRIDE_REVIEWER_AGENT_KEY}\" to use the webpieces reviewer (${DEFAULT_REVIEWER_AGENT_NAME}).`);\n } else if (override !== true && hasName) {\n errors.push(\n `[pr-gate] \"${REVIEWER_AGENT_NAME_KEY}\" is set but \"${OVERRIDE_REVIEWER_AGENT_KEY}\" is not true, so it would be ` +\n `ignored. The reviewer agent defaults to ${DEFAULT_REVIEWER_AGENT_NAME} (.claude/agents/${DEFAULT_REVIEWER_AGENT_NAME}.md, ` +\n `written by \\`${UPGRADE_SHIM_COMMAND}\\`). Either:\\n` +\n ` • remove \"${REVIEWER_AGENT_NAME_KEY}\" from commands.pr-gate in webpieces.config.json to use that default, or\\n` +\n ` • set \"${OVERRIDE_REVIEWER_AGENT_KEY}\": true beside it to review with your own agent.`);\n }\n const agentKeysValid = errors.length === 0;\n const max = s[REVIEWER_AGENTS_KEY];\n if (REVIEWER_AGENTS_KEY in s && !(typeof max === 'number' && Number.isInteger(max) && max >= 1)) {\n errors.push(\n `[pr-gate] \"${REVIEWER_AGENTS_KEY}\" = ${JSON.stringify(max)} is not valid — it must be a positive integer: the MOST ` +\n `reviewer subagents one review round may use, with the checklists grouped across them (1 = a single ` +\n `subagent reviews every checklist). Delete the key to keep one subagent per checklist.`);\n }\n if (repoRoot !== undefined && agentKeysValid) {\n const agentName = override === true ? (name as string).trim() : DEFAULT_REVIEWER_AGENT_NAME;\n errors.push(...new ChecklistValidator().validateReviewerAgent(\n repoRoot, new ReviewerAgentPolicy(agentName, REVIEWER_AGENTS_ONE_PER_CHECKLIST)));\n }\n return errors;\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\n// A git ref COMPONENT this flow is willing to build a ref name out of. Deliberately much narrower than\n// git's own check-ref-format: these two values are concatenated into a ref that a command then\n// force-pushes, so anything that could be read as a flag, a path escape, or a glob is rejected outright\n// rather than trusted to `git push` argument order.\nconst REF_COMPONENT = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;\n\n/**\n * Validate the optional `pr-gate.devDeploy` block — where `wp-push-dev` publishes the disposable copy of\n * a feature branch, and which ref is the shared dev branch itself. Absent ⇒ `dev-include` / `dev`.\n *\n * `devBranch` must NOT contain a slash and must not sit inside `branchNamespace`: the whole point of the\n * namespace is that the composed dev branch is written by CI and the copies are written by developers, so\n * a config where one contains the other makes `--list` enumerate the deploy branch and makes the\n * \"refused as a source branch\" check ambiguous.\n */\n// webpieces-disable no-any-unknown -- `value` is the opaque consumer devDeploy value until narrowed here\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nexport function validateDevDeploySection(value: unknown): string[] {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return ['[pr-gate] \"devDeploy\" must be an object, e.g. { \"branchNamespace\": \"dev-include\", \"devBranch\": \"dev\" }.'];\n }\n // webpieces-disable no-any-unknown -- narrowing the opaque devDeploy object from consumer JSON\n const s = value as Record<string, unknown>;\n const errors: string[] = [];\n errors.push(...validateRefComponent(s, 'branchNamespace', 'dev-include'));\n errors.push(...validateRefComponent(s, 'devBranch', 'dev'));\n if (errors.length > 0) return errors;\n\n const namespace = typeof s['branchNamespace'] === 'string' ? s['branchNamespace'].trim() : 'dev-include';\n const devBranch = typeof s['devBranch'] === 'string' ? s['devBranch'].trim() : 'dev';\n if (devBranch.includes('/')) {\n errors.push(\n `[pr-gate] \"devDeploy.devBranch\" = \"${devBranch}\" must be a single ref name with no \"/\" — it is the ` +\n `branch your CI composes and deploys, not a namespace.`);\n }\n if (devBranch === namespace || devBranch.startsWith(`${namespace}/`) || namespace.startsWith(`${devBranch}/`)) {\n errors.push(\n `[pr-gate] \"devDeploy.devBranch\" (\"${devBranch}\") and \"devDeploy.branchNamespace\" (\"${namespace}\") must not ` +\n `contain one another. The namespace holds the DISPOSABLE per-developer copies (written by wp-push-dev); ` +\n `devBranch is the COMPOSED branch your CI rebuilds from origin/main. Overlapping them makes wp-push-dev ` +\n `--list enumerate the deploy branch and makes \"refused as a source branch\" ambiguous.`);\n }\n return errors;\n}\n\n// One `devDeploy` string field: present ⇒ must be a non-empty, ref-safe string. Absent ⇒ the default.\n// webpieces-disable no-any-unknown -- the already-narrowed opaque devDeploy object\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this file\nfunction validateRefComponent(s: Record<string, unknown>, key: string, example: string): string[] {\n if (!(key in s)) return [];\n const raw = s[key];\n if (typeof raw !== 'string' || raw.trim() === '') {\n return [`[pr-gate] \"devDeploy.${key}\" must be a non-empty string, e.g. \"${example}\". Omit the key for the \"${example}\" default.`];\n }\n if (!REF_COMPONENT.test(raw.trim())) {\n return [\n `[pr-gate] \"devDeploy.${key}\" = \"${raw}\" is not a usable git ref name. Use letters, digits, \".\", \"_\", ` +\n `\"-\" and \"/\" only, starting with a letter or digit (e.g. \"${example}\").`,\n ];\n }\n return [];\n}\n"]}
|
|
@@ -159,12 +159,12 @@ exports.RETIRED_CONFIG_KEYS = [
|
|
|
159
159
|
...prLifecycleRetirements(),
|
|
160
160
|
// --- The per-checklist reviewer agent (issue #938). A checklist used to name its own agent type in
|
|
161
161
|
// `subagent`, which also served as its id; every checklist is now reviewed by the ONE agent type that
|
|
162
|
-
//
|
|
162
|
+
// webpieces-reviewer (or an overrideReviewerAgent name) is, so the key that is left is only the checklist's NAME.
|
|
163
163
|
// A rename, so `prunable: false` — deleting it would discard the id every verdict file is keyed by.
|
|
164
164
|
new RetiredConfigKey(exports.RETIRED_SCOPE_KEY, 'subagent', 'id', 'Rename "subagent" to "id" in EVERY commands.pr-gate.checklists entry, keeping its value (it still ' +
|
|
165
165
|
'names the checklist and keys review-<id>.json). Checklists no longer choose an agent type: every one ' +
|
|
166
|
-
'is reviewed by the agent
|
|
167
|
-
'"reviewerAgentName"
|
|
166
|
+
'is reviewed by the webpieces-reviewer agent (or your own, via "overrideReviewerAgent": true + ' +
|
|
167
|
+
'"reviewerAgentName" in commands.pr-gate).', '[pr-gate.checklists]', false),
|
|
168
168
|
];
|
|
169
169
|
// The four branch-state classes. Split into a helper purely to keep the table above readable; the
|
|
170
170
|
// instruction is per-key because the fields that carry over differ (only feature-branch-guard had
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retired-config-keys.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/retired-config-keys.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Retired webpieces.config.json keys — the ONE place in the codebase where a dead config key may be named.\n *\n * ## THE POLICY: webpieces.config.json is never released backwards-compatible\n *\n * When a config key moves, is renamed, or is deleted, the loader REJECTS the old shape with an error that\n * names the destination. It does NOT quietly accept both. There is no fallback, no alias table applied\n * before validation, no \"still accepted for back-compat until every consumer migrates\".\n *\n * This is safe *here* in a way it is not for a normal library, and the reason is the consumer: every reader\n * of this file is a coding agent. The config is validated on startup, the agent is handed the exact edit,\n * and it applies it in one pass — so the upgrade is seamless without shipping a compatibility layer. That\n * makes a hard failure strictly cheaper than duality: two accepted shapes means two code paths, two sets of\n * defaults and two sets of error messages to keep honest forever, and the stale shape then lives in\n * consumer configs indefinitely because nothing ever forces the edit.\n *\n * A hard rejection here is always self-recoverable, which is what makes it safe to do:\n * - editing webpieces.config.json is ALWAYS permitted, even while the config is invalid (see the banner\n * in config-file.ts and the guard matrix — a Write/Edit targeting this file is an unconditional PASS),\n * - and `PRUNE_UNKNOWN_COMMAND` is an L0 CURE, so the mechanical cleanup of keys no validator knows runs\n * from inside the block too.\n * So rejecting an old shape can never wedge a repo. \"It would deadlock the consumer\" is not a reason to add\n * a fallback; it is not true.\n *\n * NOT `pnpm install`. This paragraph used to offer it as the escape hatch that \"fixes the far more common\n * cause of a validation failure: an installed validator lagging the config by a release\". It is permitted\n * (installer bypass), but it is not the cure for anything you can read here: the shim's version-drift guard\n * compares the pin against the installed version and denies every tool call BEFORE the validator runs, so a\n * validation error on screen is proof that package.json and node_modules already agree. The banner from this\n * same package now says \"Do NOT run `pnpm install` — it cannot help\", and a docstring teaching the opposite\n * is how that advice leaked back out.\n *\n * ## Adding an entry\n *\n * When you retire a key, add it here and DELETE its read path — do not leave a `??` fallback behind. The\n * `instruction` is the whole product: it is read by an agent that will act on it verbatim, so give the\n * mechanical edit (\"rename X to Y\", \"move A into B\"), not a description of the change. Saying where a thing\n * moved to is exactly the point of this table; it is what makes the fallback unnecessary.\n *\n * retired-config-keys.spec.ts asserts every entry below actually FAILS the load. That spec is the guard: a\n * future fallback that silently swallows a retired key turns it red.\n */\n\nimport { RETIRED_KEY_MARKER } from './config-error-banner';\nimport { PRUNE_UNKNOWN_COMMAND } from './constants';\n\n// A retired key is matched one of two ways, because config keys live at two very different levels.\n// RULE — a rule/guard NAME, which may sit under either `rules` or `hookGuards`, so it is matched by bare\n// name rather than by a fixed path.\n// KEY — a plain key inside a known section, matched by name within that section.\nexport const RETIRED_SCOPE_RULE = 'rule';\nexport const RETIRED_SCOPE_KEY = 'key';\n\n/** One retired config key and the mechanical edit that replaces it. Data-only (per CLAUDE.md). */\nexport class RetiredConfigKey {\n scope: string;\n // The key exactly as it appears in webpieces.config.json.\n key: string;\n // Where the value goes now. Empty string when the key is deleted outright with no replacement.\n movedTo: string;\n // The imperative fix, written for the agent that will apply it.\n instruction: string;\n // Bracketed label leading the error, matching the `[rule-name]` / `[section]` convention in this package.\n label: string;\n /**\n * True when DELETING the key from webpieces.config.json is the entire edit — nothing in THIS file\n * replaces it, so `PRUNE_UNKNOWN_COMMAND` may strip it mechanically. `whole-repo-build-guard` is the\n * worked example: its switch moved OUT of the repo config into `~/.webpieces/config.json`, so the\n * value has nowhere to go here.\n *\n * False for a rename or an in-file move: deleting those would DISCARD a value the reader still needs,\n * so they keep their migration instruction and the pruner leaves them alone. Required, not defaulted —\n * a defaulted `false` would let a future deletion-only retirement silently opt out of the mechanical\n * cure and land back in \"the reader decides while every Bash call is blocked\".\n */\n prunable: boolean;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(scope: string, key: string, movedTo: string, instruction: string, label: string, prunable: boolean) {\n this.scope = scope;\n this.key = key;\n this.movedTo = movedTo;\n this.instruction = instruction;\n this.label = label;\n this.prunable = prunable;\n }\n}\n\n/**\n * Every retired key. Keep the newest at the bottom with the release that retired it, so the list reads as a\n * changelog an agent can walk when a config is several versions behind.\n */\nexport const RETIRED_CONFIG_KEYS: readonly RetiredConfigKey[] = [\n // --- Guard renames. Previously applied SILENTLY before validation (a DEPRECATED_RULE_ALIASES table in\n // load-config.ts rewrote the key so no validator ever saw it). That hid the rename from the config file\n // forever: the old name kept working, so no consumer ever updated, and the alias table could never be\n // deleted. Now each is a hard error with the new name.\n //\n // These three RE-POINTED when hookGuards collapsed to one key per policy. Their old destinations\n // (`pr-merge-guard`, `pr-creation-or-push-guard`, `read-stale-guard`) are themselves retired now,\n // and a retirement whose `movedTo` names a dead key teaches the removed API — the same defect one\n // level out. Each therefore names the POLICY key it lands on today, in one hop.\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'pr-merge-cleanup', 'pr-lifecycle-guard',\n 'Rename the key to \"pr-lifecycle-guard\" (merging its value into that entry if you already have ' +\n 'one — the four PR/merge guards share one key now). Its mode and escape hatches carry over.',\n '[pr-merge-cleanup]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'pr-creation-guard', 'pr-lifecycle-guard',\n 'Rename the key to \"pr-lifecycle-guard\" (merging its value into that entry if you already have ' +\n 'one — the four PR/merge guards share one key now). Its mode and escape hatches carry over; the ' +\n 'old \"upsertPrCommand\" field does NOT — that string lives only in commands.guardHints.prCreationOrPush.',\n '[pr-creation-guard]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'main-stale-guard', 'branch-state-guard',\n 'Rename the key to \"branch-state-guard\" (merging its value into that entry if you already have ' +\n 'one — the four branch-state guards share one key now). Its mode and escape hatches carry over. ' +\n 'Note the widened scope: that key arms the Write, Read AND Bash halves of the branch-state ' +\n 'policy, not just the Read block this key used to name.',\n '[main-stale-guard]', false,\n ),\n\n // --- The two flat guard-hint strings, superseded by commands.guardHints so that every guard-facing\n // command string sits in one named sub-object instead of loose beside the gate config.\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'upsertPr', 'commands.guardHints.prCreationOrPush',\n 'Move the value to \"guardHints\": { \"prCreationOrPush\": <value> } inside the same \"commands\" ' +\n 'section, then delete \"upsertPr\".',\n '[commands]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'mergeComplete', 'commands.guardHints.mergeInProgress',\n 'Move the value to \"guardHints\": { \"mergeInProgress\": <value> } inside the same \"commands\" ' +\n 'section, then delete \"mergeComplete\".',\n '[commands]', false,\n ),\n\n // --- The two-list excludePaths object. The split never earned its keep (every consumer set both lists\n // to the same value), and the one case that would need them to differ is served better by a rule's own\n // `excludePaths`. Retired as a SHAPE: `rules` is the entry validateExcludePaths reports for either key.\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'rules', 'excludePaths (one flat array)',\n 'Replace the whole { \"rules\": [...], \"guards\": [...] } object with ONE array holding the union of ' +\n 'both lists, de-duplicated — e.g. \"excludePaths\": [\"repositories/**\"]. A path is governed by ' +\n 'webpieces or it is not, so there is no longer a per-engine split. To exclude a path from one rule ' +\n 'only, use that rule\\'s own \"excludePaths\" inside its config block instead.',\n '[excludePaths]', false,\n ),\n\n // --- whole-repo-build-guard, retired as a REPO-CONFIG key one release after it was added. It shipped\n // as a conventional validated guard (mode ON by default, entry REQUIRED under hookGuards), so every\n // consumer that upgraded hit fault Y — every Bash call blocked — for a feature nobody had opted into.\n // The REQUIRED KEY was the fault, not the default: the failure was at config LOAD, before any command\n // was judged. The switch now lives ONLY in the optional machine-local ~/.webpieces/config.json, where\n // nothing has to be added to be in the default state — and the default is OFF, as every experimental\n // flag's is, so deleting this entry loses nothing.\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'whole-repo-build-guard', '~/.webpieces/config.json → experimental.whole-repo-build-guard',\n 'DELETE this entry from webpieces.config.json — no repo config key controls this guard any more. ' +\n 'The guard is OFF by default for everyone, with no file and no key required. To turn it ON for ' +\n 'YOUR machine only, put {\"experimental\": {\"whole-repo-build-guard\": true}} in ' +\n '~/.webpieces/config.json — that file is optional and is tracked by no repo.',\n '[whole-repo-build-guard]', true,\n ),\n\n // --- THE 9 → 3 COLLAPSE of `hookGuards`: one key per POLICY, not one per implementation CLASS.\n //\n // Eight class-named keys become two policy keys. The CLASSES are untouched — `feature-branch-guard`\n // is still the rule name in every decision-log line and every deny report — so nothing an operator\n // greps for moved. What moved is the SWITCH, because nine keys let a consumer configure HALF a\n // policy: `read-stale-guard: OFF` beside `merged-branch-bash-guard: ON` is \"read the file, yes;\n // `cat` the same file, no\", which nobody chose and the config made reachable.\n //\n // EVERY ONE OF THESE IS `prunable: false`, and that is not bookkeeping. `PRUNABLE_SECTIONS` in\n // ConfigPruner includes `hookGuards`, and the validation banner actively RECOMMENDS running the\n // pruner. A `prunable: true` here would mean the recommended cure silently DELETES four configured\n // guards with no destination named — a config loss, which is worse than a block. False keeps the\n // migration instruction and makes the pruner return null for these keys.\n //\n // 4 → 1 is legal for this table (nothing requires `movedTo` to be unique), but it is NOT legal for\n // a naive 1:1 migrator: see migrateRetiredRuleNames in ai-hook-rules' setup.ts, which unions the\n // four old entries into the destination and then fills any missing required field, rather than\n // renaming the first and deleting the rest.\n ...branchStateRetirements(),\n ...prLifecycleRetirements(),\n\n // --- The per-checklist reviewer agent (issue #938). A checklist used to name its own agent type in\n // `subagent`, which also served as its id; every checklist is now reviewed by the ONE agent type that\n // `commands.pr-gate.reviewerAgentName` names, so the key that is left is only the checklist's NAME.\n // A rename, so `prunable: false` — deleting it would discard the id every verdict file is keyed by.\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'subagent', 'id',\n 'Rename \"subagent\" to \"id\" in EVERY commands.pr-gate.checklists entry, keeping its value (it still ' +\n 'names the checklist and keys review-<id>.json). Checklists no longer choose an agent type: every one ' +\n 'is reviewed by the agent commands.pr-gate.reviewerAgentName names — add ' +\n '\"reviewerAgentName\": \"webpieces-reviewer\" there if it is missing.',\n '[pr-gate.checklists]', false,\n ),\n];\n\n// The four branch-state classes. Split into a helper purely to keep the table above readable; the\n// instruction is per-key because the fields that carry over differ (only feature-branch-guard had\n// `branchNamingConvention`).\n// webpieces-disable no-function-outside-class -- table data for RETIRED_CONFIG_KEYS, beside the array it feeds\nfunction branchStateRetirements(): RetiredConfigKey[] {\n const merged = 'MERGE it into ONE \"branch-state-guard\" entry under \"hookGuards\"';\n const shared =\n `${merged}. All four of feature-branch-guard, read-stale-guard, stale-main-bash-guard and ` +\n 'merged-branch-bash-guard now read that single entry — they are one policy (\"may I work here, ' +\n 'and is what I read current?\") implemented by four classes, and configuring them separately let ' +\n 'you switch on half of it. Keep \"mode\", \"turnOffRuleUntilEpoch\" and \"turnOffRuleWhileOnBranch\"; ' +\n 'the ONE \"hangTimeoutMinutes\" survives (there was only ever one refresher and one cache, so at ' +\n 'most one of the four values could ever take effect); \"branchNamingConvention\" survives from ' +\n 'feature-branch-guard. If the four disagreed on \"mode\", pick the one you meant — \"ON\" arms the ' +\n 'whole policy including the Read block. Then DELETE this key.';\n return [\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'feature-branch-guard', 'branch-state-guard', shared, '[feature-branch-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'read-stale-guard', 'branch-state-guard', shared, '[read-stale-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'stale-main-bash-guard', 'branch-state-guard', shared, '[stale-main-bash-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'merged-branch-bash-guard', 'branch-state-guard', shared, '[merged-branch-bash-guard]', false),\n ];\n}\n\n// The four PR-lifecycle classes.\n// webpieces-disable no-function-outside-class -- table data for RETIRED_CONFIG_KEYS, beside the array it feeds\nfunction prLifecycleRetirements(): RetiredConfigKey[] {\n const shared =\n 'MERGE it into ONE \"pr-lifecycle-guard\" entry under \"hookGuards\". All four of ' +\n 'pr-creation-or-push-guard, merge-in-progress-guard, pr-merge-guard and ' +\n 'redirect-how-to-merge-main now read that single entry — they are one policy (\"do PRs and ' +\n 'merges go through the gated flow?\"). Keep \"mode\", \"turnOffRuleUntilEpoch\" and ' +\n '\"turnOffRuleWhileOnBranch\". Know what \"mode\": \"OFF\" now means: it releases the ' +\n 'unvalidated-merge gate as well as the PR/push blocks, because merge-in-progress-guard is under ' +\n 'this key too. The per-guard \"upsertPrCommand\" / \"mergeCompleteCommand\" fields are gone — those ' +\n 'strings live ONLY in commands.guardHints.prCreationOrPush / .mergeInProgress. Then DELETE this key.';\n return [\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'pr-creation-or-push-guard', 'pr-lifecycle-guard', shared, '[pr-creation-or-push-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'merge-in-progress-guard', 'pr-lifecycle-guard', shared, '[merge-in-progress-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'pr-merge-guard', 'pr-lifecycle-guard', shared, '[pr-merge-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'redirect-how-to-merge-main', 'pr-lifecycle-guard', shared, '[redirect-how-to-merge-main]', false),\n ];\n}\n\n/**\n * The shared message. Leads with the retirement, then the destination, then the edit — an agent reading this\n * should not have to infer anything.\n *\n * RETIRED_KEY_MARKER is the phrase the banner classifier keys off to call this error DEFINITIVE (no\n * install can revive a key this table names), so the marker is IMPORTED rather than re-typed here.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredKeyError(entry: RetiredConfigKey): string {\n const destination = entry.movedTo === ''\n ? 'It was removed with no replacement.'\n : `It moved to \"${entry.movedTo}\".`;\n // Deletion-only retirements get the mechanical cure named right here, so the reader never has to\n // decide whether removing a key is safe while the guard is denying every Bash call.\n const mechanical = entry.prunable\n ? ` Deleting it is the WHOLE fix — \\`${PRUNE_UNKNOWN_COMMAND}\\` does it for you.`\n : '';\n return `${entry.label} \"${entry.key}\" ${RETIRED_KEY_MARKER}. ${destination} ${entry.instruction}${mechanical}`;\n}\n\n/**\n * The retired entry for a rule/guard NAME, or null. Callers must consult this BEFORE reporting a name as an\n * unknown rule: the generic unknown-rule message can only say \"delete it\", while this table knows WHERE the\n * setting went — a rename that must carry its value over, or a move to `~/.webpieces/config.json`. The\n * destination is the whole product, and a bare \"delete it\" would throw it away.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredRuleFor(ruleName: string): RetiredConfigKey | null {\n return RETIRED_CONFIG_KEYS.find(e => e.scope === RETIRED_SCOPE_RULE && e.key === ruleName) ?? null;\n}\n\n/**\n * Whether `key` is retired within `label`'s section. Unknown-key rejection consults this so a retired key\n * gets its migration message instead of a generic \"unknown key, delete it\" — the migration instruction is\n * the entire reason this table exists.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function isRetiredKey(key: string, label: string): boolean {\n return RETIRED_CONFIG_KEYS.some(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key === key);\n}\n\n/**\n * The retired entry for `key` within `label`'s section. For a retirement that is a SHAPE rather than a\n * single key (the excludePaths object), the validator looks the entry up directly so one table row remains\n * the single source of the message.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredEntry(key: string, label: string): RetiredConfigKey | null {\n return RETIRED_CONFIG_KEYS.find(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key === key) ?? null;\n}\n\n/** Errors for every retired plain key present in `section`. `label` scopes the lookup to that section. */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON; only key PRESENCE is read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredKeyErrorsIn(section: Record<string, unknown>, label: string): string[] {\n return RETIRED_CONFIG_KEYS\n .filter(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key in section)\n .map(e => retiredKeyError(e));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"retired-config-keys.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/retired-config-keys.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Retired webpieces.config.json keys — the ONE place in the codebase where a dead config key may be named.\n *\n * ## THE POLICY: webpieces.config.json is never released backwards-compatible\n *\n * When a config key moves, is renamed, or is deleted, the loader REJECTS the old shape with an error that\n * names the destination. It does NOT quietly accept both. There is no fallback, no alias table applied\n * before validation, no \"still accepted for back-compat until every consumer migrates\".\n *\n * This is safe *here* in a way it is not for a normal library, and the reason is the consumer: every reader\n * of this file is a coding agent. The config is validated on startup, the agent is handed the exact edit,\n * and it applies it in one pass — so the upgrade is seamless without shipping a compatibility layer. That\n * makes a hard failure strictly cheaper than duality: two accepted shapes means two code paths, two sets of\n * defaults and two sets of error messages to keep honest forever, and the stale shape then lives in\n * consumer configs indefinitely because nothing ever forces the edit.\n *\n * A hard rejection here is always self-recoverable, which is what makes it safe to do:\n * - editing webpieces.config.json is ALWAYS permitted, even while the config is invalid (see the banner\n * in config-file.ts and the guard matrix — a Write/Edit targeting this file is an unconditional PASS),\n * - and `PRUNE_UNKNOWN_COMMAND` is an L0 CURE, so the mechanical cleanup of keys no validator knows runs\n * from inside the block too.\n * So rejecting an old shape can never wedge a repo. \"It would deadlock the consumer\" is not a reason to add\n * a fallback; it is not true.\n *\n * NOT `pnpm install`. This paragraph used to offer it as the escape hatch that \"fixes the far more common\n * cause of a validation failure: an installed validator lagging the config by a release\". It is permitted\n * (installer bypass), but it is not the cure for anything you can read here: the shim's version-drift guard\n * compares the pin against the installed version and denies every tool call BEFORE the validator runs, so a\n * validation error on screen is proof that package.json and node_modules already agree. The banner from this\n * same package now says \"Do NOT run `pnpm install` — it cannot help\", and a docstring teaching the opposite\n * is how that advice leaked back out.\n *\n * ## Adding an entry\n *\n * When you retire a key, add it here and DELETE its read path — do not leave a `??` fallback behind. The\n * `instruction` is the whole product: it is read by an agent that will act on it verbatim, so give the\n * mechanical edit (\"rename X to Y\", \"move A into B\"), not a description of the change. Saying where a thing\n * moved to is exactly the point of this table; it is what makes the fallback unnecessary.\n *\n * retired-config-keys.spec.ts asserts every entry below actually FAILS the load. That spec is the guard: a\n * future fallback that silently swallows a retired key turns it red.\n */\n\nimport { RETIRED_KEY_MARKER } from './config-error-banner';\nimport { PRUNE_UNKNOWN_COMMAND } from './constants';\n\n// A retired key is matched one of two ways, because config keys live at two very different levels.\n// RULE — a rule/guard NAME, which may sit under either `rules` or `hookGuards`, so it is matched by bare\n// name rather than by a fixed path.\n// KEY — a plain key inside a known section, matched by name within that section.\nexport const RETIRED_SCOPE_RULE = 'rule';\nexport const RETIRED_SCOPE_KEY = 'key';\n\n/** One retired config key and the mechanical edit that replaces it. Data-only (per CLAUDE.md). */\nexport class RetiredConfigKey {\n scope: string;\n // The key exactly as it appears in webpieces.config.json.\n key: string;\n // Where the value goes now. Empty string when the key is deleted outright with no replacement.\n movedTo: string;\n // The imperative fix, written for the agent that will apply it.\n instruction: string;\n // Bracketed label leading the error, matching the `[rule-name]` / `[section]` convention in this package.\n label: string;\n /**\n * True when DELETING the key from webpieces.config.json is the entire edit — nothing in THIS file\n * replaces it, so `PRUNE_UNKNOWN_COMMAND` may strip it mechanically. `whole-repo-build-guard` is the\n * worked example: its switch moved OUT of the repo config into `~/.webpieces/config.json`, so the\n * value has nowhere to go here.\n *\n * False for a rename or an in-file move: deleting those would DISCARD a value the reader still needs,\n * so they keep their migration instruction and the pruner leaves them alone. Required, not defaulted —\n * a defaulted `false` would let a future deletion-only retirement silently opt out of the mechanical\n * cure and land back in \"the reader decides while every Bash call is blocked\".\n */\n prunable: boolean;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(scope: string, key: string, movedTo: string, instruction: string, label: string, prunable: boolean) {\n this.scope = scope;\n this.key = key;\n this.movedTo = movedTo;\n this.instruction = instruction;\n this.label = label;\n this.prunable = prunable;\n }\n}\n\n/**\n * Every retired key. Keep the newest at the bottom with the release that retired it, so the list reads as a\n * changelog an agent can walk when a config is several versions behind.\n */\nexport const RETIRED_CONFIG_KEYS: readonly RetiredConfigKey[] = [\n // --- Guard renames. Previously applied SILENTLY before validation (a DEPRECATED_RULE_ALIASES table in\n // load-config.ts rewrote the key so no validator ever saw it). That hid the rename from the config file\n // forever: the old name kept working, so no consumer ever updated, and the alias table could never be\n // deleted. Now each is a hard error with the new name.\n //\n // These three RE-POINTED when hookGuards collapsed to one key per policy. Their old destinations\n // (`pr-merge-guard`, `pr-creation-or-push-guard`, `read-stale-guard`) are themselves retired now,\n // and a retirement whose `movedTo` names a dead key teaches the removed API — the same defect one\n // level out. Each therefore names the POLICY key it lands on today, in one hop.\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'pr-merge-cleanup', 'pr-lifecycle-guard',\n 'Rename the key to \"pr-lifecycle-guard\" (merging its value into that entry if you already have ' +\n 'one — the four PR/merge guards share one key now). Its mode and escape hatches carry over.',\n '[pr-merge-cleanup]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'pr-creation-guard', 'pr-lifecycle-guard',\n 'Rename the key to \"pr-lifecycle-guard\" (merging its value into that entry if you already have ' +\n 'one — the four PR/merge guards share one key now). Its mode and escape hatches carry over; the ' +\n 'old \"upsertPrCommand\" field does NOT — that string lives only in commands.guardHints.prCreationOrPush.',\n '[pr-creation-guard]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'main-stale-guard', 'branch-state-guard',\n 'Rename the key to \"branch-state-guard\" (merging its value into that entry if you already have ' +\n 'one — the four branch-state guards share one key now). Its mode and escape hatches carry over. ' +\n 'Note the widened scope: that key arms the Write, Read AND Bash halves of the branch-state ' +\n 'policy, not just the Read block this key used to name.',\n '[main-stale-guard]', false,\n ),\n\n // --- The two flat guard-hint strings, superseded by commands.guardHints so that every guard-facing\n // command string sits in one named sub-object instead of loose beside the gate config.\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'upsertPr', 'commands.guardHints.prCreationOrPush',\n 'Move the value to \"guardHints\": { \"prCreationOrPush\": <value> } inside the same \"commands\" ' +\n 'section, then delete \"upsertPr\".',\n '[commands]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'mergeComplete', 'commands.guardHints.mergeInProgress',\n 'Move the value to \"guardHints\": { \"mergeInProgress\": <value> } inside the same \"commands\" ' +\n 'section, then delete \"mergeComplete\".',\n '[commands]', false,\n ),\n\n // --- The two-list excludePaths object. The split never earned its keep (every consumer set both lists\n // to the same value), and the one case that would need them to differ is served better by a rule's own\n // `excludePaths`. Retired as a SHAPE: `rules` is the entry validateExcludePaths reports for either key.\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'rules', 'excludePaths (one flat array)',\n 'Replace the whole { \"rules\": [...], \"guards\": [...] } object with ONE array holding the union of ' +\n 'both lists, de-duplicated — e.g. \"excludePaths\": [\"repositories/**\"]. A path is governed by ' +\n 'webpieces or it is not, so there is no longer a per-engine split. To exclude a path from one rule ' +\n 'only, use that rule\\'s own \"excludePaths\" inside its config block instead.',\n '[excludePaths]', false,\n ),\n\n // --- whole-repo-build-guard, retired as a REPO-CONFIG key one release after it was added. It shipped\n // as a conventional validated guard (mode ON by default, entry REQUIRED under hookGuards), so every\n // consumer that upgraded hit fault Y — every Bash call blocked — for a feature nobody had opted into.\n // The REQUIRED KEY was the fault, not the default: the failure was at config LOAD, before any command\n // was judged. The switch now lives ONLY in the optional machine-local ~/.webpieces/config.json, where\n // nothing has to be added to be in the default state — and the default is OFF, as every experimental\n // flag's is, so deleting this entry loses nothing.\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'whole-repo-build-guard', '~/.webpieces/config.json → experimental.whole-repo-build-guard',\n 'DELETE this entry from webpieces.config.json — no repo config key controls this guard any more. ' +\n 'The guard is OFF by default for everyone, with no file and no key required. To turn it ON for ' +\n 'YOUR machine only, put {\"experimental\": {\"whole-repo-build-guard\": true}} in ' +\n '~/.webpieces/config.json — that file is optional and is tracked by no repo.',\n '[whole-repo-build-guard]', true,\n ),\n\n // --- THE 9 → 3 COLLAPSE of `hookGuards`: one key per POLICY, not one per implementation CLASS.\n //\n // Eight class-named keys become two policy keys. The CLASSES are untouched — `feature-branch-guard`\n // is still the rule name in every decision-log line and every deny report — so nothing an operator\n // greps for moved. What moved is the SWITCH, because nine keys let a consumer configure HALF a\n // policy: `read-stale-guard: OFF` beside `merged-branch-bash-guard: ON` is \"read the file, yes;\n // `cat` the same file, no\", which nobody chose and the config made reachable.\n //\n // EVERY ONE OF THESE IS `prunable: false`, and that is not bookkeeping. `PRUNABLE_SECTIONS` in\n // ConfigPruner includes `hookGuards`, and the validation banner actively RECOMMENDS running the\n // pruner. A `prunable: true` here would mean the recommended cure silently DELETES four configured\n // guards with no destination named — a config loss, which is worse than a block. False keeps the\n // migration instruction and makes the pruner return null for these keys.\n //\n // 4 → 1 is legal for this table (nothing requires `movedTo` to be unique), but it is NOT legal for\n // a naive 1:1 migrator: see migrateRetiredRuleNames in ai-hook-rules' setup.ts, which unions the\n // four old entries into the destination and then fills any missing required field, rather than\n // renaming the first and deleting the rest.\n ...branchStateRetirements(),\n ...prLifecycleRetirements(),\n\n // --- The per-checklist reviewer agent (issue #938). A checklist used to name its own agent type in\n // `subagent`, which also served as its id; every checklist is now reviewed by the ONE agent type that\n // webpieces-reviewer (or an overrideReviewerAgent name) is, so the key that is left is only the checklist's NAME.\n // A rename, so `prunable: false` — deleting it would discard the id every verdict file is keyed by.\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'subagent', 'id',\n 'Rename \"subagent\" to \"id\" in EVERY commands.pr-gate.checklists entry, keeping its value (it still ' +\n 'names the checklist and keys review-<id>.json). Checklists no longer choose an agent type: every one ' +\n 'is reviewed by the webpieces-reviewer agent (or your own, via \"overrideReviewerAgent\": true + ' +\n '\"reviewerAgentName\" in commands.pr-gate).',\n '[pr-gate.checklists]', false,\n ),\n];\n\n// The four branch-state classes. Split into a helper purely to keep the table above readable; the\n// instruction is per-key because the fields that carry over differ (only feature-branch-guard had\n// `branchNamingConvention`).\n// webpieces-disable no-function-outside-class -- table data for RETIRED_CONFIG_KEYS, beside the array it feeds\nfunction branchStateRetirements(): RetiredConfigKey[] {\n const merged = 'MERGE it into ONE \"branch-state-guard\" entry under \"hookGuards\"';\n const shared =\n `${merged}. All four of feature-branch-guard, read-stale-guard, stale-main-bash-guard and ` +\n 'merged-branch-bash-guard now read that single entry — they are one policy (\"may I work here, ' +\n 'and is what I read current?\") implemented by four classes, and configuring them separately let ' +\n 'you switch on half of it. Keep \"mode\", \"turnOffRuleUntilEpoch\" and \"turnOffRuleWhileOnBranch\"; ' +\n 'the ONE \"hangTimeoutMinutes\" survives (there was only ever one refresher and one cache, so at ' +\n 'most one of the four values could ever take effect); \"branchNamingConvention\" survives from ' +\n 'feature-branch-guard. If the four disagreed on \"mode\", pick the one you meant — \"ON\" arms the ' +\n 'whole policy including the Read block. Then DELETE this key.';\n return [\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'feature-branch-guard', 'branch-state-guard', shared, '[feature-branch-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'read-stale-guard', 'branch-state-guard', shared, '[read-stale-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'stale-main-bash-guard', 'branch-state-guard', shared, '[stale-main-bash-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'merged-branch-bash-guard', 'branch-state-guard', shared, '[merged-branch-bash-guard]', false),\n ];\n}\n\n// The four PR-lifecycle classes.\n// webpieces-disable no-function-outside-class -- table data for RETIRED_CONFIG_KEYS, beside the array it feeds\nfunction prLifecycleRetirements(): RetiredConfigKey[] {\n const shared =\n 'MERGE it into ONE \"pr-lifecycle-guard\" entry under \"hookGuards\". All four of ' +\n 'pr-creation-or-push-guard, merge-in-progress-guard, pr-merge-guard and ' +\n 'redirect-how-to-merge-main now read that single entry — they are one policy (\"do PRs and ' +\n 'merges go through the gated flow?\"). Keep \"mode\", \"turnOffRuleUntilEpoch\" and ' +\n '\"turnOffRuleWhileOnBranch\". Know what \"mode\": \"OFF\" now means: it releases the ' +\n 'unvalidated-merge gate as well as the PR/push blocks, because merge-in-progress-guard is under ' +\n 'this key too. The per-guard \"upsertPrCommand\" / \"mergeCompleteCommand\" fields are gone — those ' +\n 'strings live ONLY in commands.guardHints.prCreationOrPush / .mergeInProgress. Then DELETE this key.';\n return [\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'pr-creation-or-push-guard', 'pr-lifecycle-guard', shared, '[pr-creation-or-push-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'merge-in-progress-guard', 'pr-lifecycle-guard', shared, '[merge-in-progress-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'pr-merge-guard', 'pr-lifecycle-guard', shared, '[pr-merge-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'redirect-how-to-merge-main', 'pr-lifecycle-guard', shared, '[redirect-how-to-merge-main]', false),\n ];\n}\n\n/**\n * The shared message. Leads with the retirement, then the destination, then the edit — an agent reading this\n * should not have to infer anything.\n *\n * RETIRED_KEY_MARKER is the phrase the banner classifier keys off to call this error DEFINITIVE (no\n * install can revive a key this table names), so the marker is IMPORTED rather than re-typed here.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredKeyError(entry: RetiredConfigKey): string {\n const destination = entry.movedTo === ''\n ? 'It was removed with no replacement.'\n : `It moved to \"${entry.movedTo}\".`;\n // Deletion-only retirements get the mechanical cure named right here, so the reader never has to\n // decide whether removing a key is safe while the guard is denying every Bash call.\n const mechanical = entry.prunable\n ? ` Deleting it is the WHOLE fix — \\`${PRUNE_UNKNOWN_COMMAND}\\` does it for you.`\n : '';\n return `${entry.label} \"${entry.key}\" ${RETIRED_KEY_MARKER}. ${destination} ${entry.instruction}${mechanical}`;\n}\n\n/**\n * The retired entry for a rule/guard NAME, or null. Callers must consult this BEFORE reporting a name as an\n * unknown rule: the generic unknown-rule message can only say \"delete it\", while this table knows WHERE the\n * setting went — a rename that must carry its value over, or a move to `~/.webpieces/config.json`. The\n * destination is the whole product, and a bare \"delete it\" would throw it away.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredRuleFor(ruleName: string): RetiredConfigKey | null {\n return RETIRED_CONFIG_KEYS.find(e => e.scope === RETIRED_SCOPE_RULE && e.key === ruleName) ?? null;\n}\n\n/**\n * Whether `key` is retired within `label`'s section. Unknown-key rejection consults this so a retired key\n * gets its migration message instead of a generic \"unknown key, delete it\" — the migration instruction is\n * the entire reason this table exists.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function isRetiredKey(key: string, label: string): boolean {\n return RETIRED_CONFIG_KEYS.some(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key === key);\n}\n\n/**\n * The retired entry for `key` within `label`'s section. For a retirement that is a SHAPE rather than a\n * single key (the excludePaths object), the validator looks the entry up directly so one table row remains\n * the single source of the message.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredEntry(key: string, label: string): RetiredConfigKey | null {\n return RETIRED_CONFIG_KEYS.find(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key === key) ?? null;\n}\n\n/** Errors for every retired plain key present in `section`. `label` scopes the lookup to that section. */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON; only key PRESENCE is read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredKeyErrorsIn(section: Record<string, unknown>, label: string): string[] {\n return RETIRED_CONFIG_KEYS\n .filter(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key in section)\n .map(e => retiredKeyError(e));\n}\n"]}
|
|
@@ -75,7 +75,7 @@ export declare const ALL_DIFF_ONE_READ_LINES = 1500;
|
|
|
75
75
|
* re-derivation of the dependency graph. It did not over-review; it was under-supplied. Everything below is
|
|
76
76
|
* chosen to delete a specific one of those calls.
|
|
77
77
|
*
|
|
78
|
-
* It is GENERATED per run, and the reviewer agent (`.claude/agents/<
|
|
78
|
+
* It is GENERATED per run, and the reviewer agent (`.claude/agents/<name>.md`, by default the
|
|
79
79
|
* webpieces-owned `webpieces-reviewer.md`) is checklist-agnostic and points here, because content a human
|
|
80
80
|
* maintains goes stale and a reviewer follows the stale copy. The verdict
|
|
81
81
|
* schema in particular comes from {@link ReviewJsonService.verdictSchemaFor}.
|
|
@@ -51,7 +51,7 @@ exports.BriefedFile = BriefedFile;
|
|
|
51
51
|
* remove. Data-only.
|
|
52
52
|
*/
|
|
53
53
|
class ReviewerBriefing {
|
|
54
|
-
agentName; // the agent type the subagent is spawned as (
|
|
54
|
+
agentName; // the agent type the subagent is spawned as (webpieces-reviewer, or the override)
|
|
55
55
|
docPath; // the checklist's guidance doc ('' when the checklist has none)
|
|
56
56
|
repoRoot;
|
|
57
57
|
diffDir; // '' when nothing was materialized
|
|
@@ -132,7 +132,7 @@ exports.ALL_DIFF_ONE_READ_LINES = 1500;
|
|
|
132
132
|
* re-derivation of the dependency graph. It did not over-review; it was under-supplied. Everything below is
|
|
133
133
|
* chosen to delete a specific one of those calls.
|
|
134
134
|
*
|
|
135
|
-
* It is GENERATED per run, and the reviewer agent (`.claude/agents/<
|
|
135
|
+
* It is GENERATED per run, and the reviewer agent (`.claude/agents/<name>.md`, by default the
|
|
136
136
|
* webpieces-owned `webpieces-reviewer.md`) is checklist-agnostic and points here, because content a human
|
|
137
137
|
* maintains goes stale and a reviewer follows the stale copy. The verdict
|
|
138
138
|
* schema in particular comes from {@link ReviewJsonService.verdictSchemaFor}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviewer-instructions.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/reviewer-instructions.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { ReviewJsonService } from './review-json';\n\n/** One pre-resolved place a reviewer would otherwise have to go hunting for. Data-only (per CLAUDE.md). */\nexport class ContextEntry {\n label: string;\n path: string; // ABSOLUTE, or '' when it could not be resolved\n note: string; // '(not installed)' / '(configured but missing)' — never silently dropped\n\n constructor(label: string, entryPath: string, note = '') {\n this.label = label;\n this.path = entryPath;\n this.note = note;\n }\n}\n\n/**\n * One reviewer's matched file, the extracted diff for it, AND the absolute path of the file itself.\n *\n * `sourcePath` exists because of a measured 0/4: four reviewers on one PR read the diff and not one opened a\n * single full source file. The instructions handed them an absolute path per diff and only a parent DIRECTORY\n * per source, so reading the diff was a paste and reading the source was \"join this dir to that filename\n * yourself\". Identical affordance, or the cheaper one wins every time. Data-only.\n */\nexport class BriefedFile {\n file: string; // repo-relative source path\n diffPath: string; // ABSOLUTE path of the extracted .diff\n sourcePath: string; // ABSOLUTE path of the file itself; '' for a deleted file, which has no after-state\n status: string; // 'M' | 'A' | 'D' | 'U' (untracked) | 'X' (excluded by config)\n diffBytes: number;\n truncated: boolean;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(file: string, diffPath: string, sourcePath = '', status = 'M', diffBytes = 0, truncated = false) {\n this.file = file;\n this.diffPath = diffPath;\n this.sourcePath = sourcePath;\n this.status = status;\n this.diffBytes = diffBytes;\n this.truncated = truncated;\n }\n}\n\n/**\n * Everything ONE reviewer needs, with every path already resolved to an ABSOLUTE one.\n *\n * Absolute is not fussiness: a subagent's working directory is not guaranteed to be the repo root, and a\n * relative path that fails to resolve turns into a search, which is the cost this whole class exists to\n * remove. Data-only.\n */\nexport class ReviewerBriefing {\n agentName: string; // the agent type the subagent is spawned as (commands.pr-gate.reviewerAgentName)\n docPath: string; // the checklist's guidance doc ('' when the checklist has none)\n repoRoot: string;\n diffDir: string; // '' when nothing was materialized\n allDiffPath: string;\n manifestPath: string;\n myFiles: BriefedFile[]; // ONLY this reviewer's matched files\n matchedPatterns: string[]; // [] ⇒ patternless: the whole diff is in scope\n sourceDirs: string[]; // deduped ABSOLUTE parent dirs of myFiles\n contextEntries: ContextEntry[];\n verdictPath: string; // ABSOLUTE review-<id>.json\n checklistId: string;\n fileDiffCommand: string;\n dirty: boolean;\n // Facts ABOUT the manifest, surfaced inline so a reviewer learns the diff is complete without opening it.\n changedFileCount: number;\n truncatedCount: number;\n excludedCount: number;\n allDiffLines: number;\n allDiffBytes: number;\n // The three shas, by the names DiffManifest already uses. Printing two bare shas told a reviewer nothing\n // about WHAT the diff is taken against; C beside A makes \"main has moved since the fork\" self-evident.\n hashForkPoint: string;\n hashFeatureHead: string;\n hashMainHead: string;\n ownAgentFileInDiff: string; // ABSOLUTE path when this diff edits the reviewer agent's own file; '' otherwise\n // From the checklist's config `required`. Carried so the stage-② report can group the spawn blocks\n // (must run) apart from the ones the human is merely OFFERED, without a second lookup that could\n // disagree with the scan. The reviewer's OWN instructions do not mention it: a reviewer that has been\n // spawned reviews the same way either way, and telling an optional one it was optional invites it to\n // grade itself softer.\n required: boolean;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(agentName: string, checklistId: string, repoRoot: string) {\n this.agentName = agentName;\n this.checklistId = checklistId;\n this.repoRoot = repoRoot;\n this.docPath = '';\n this.diffDir = '';\n this.allDiffPath = '';\n this.manifestPath = '';\n this.myFiles = [];\n this.matchedPatterns = [];\n this.sourceDirs = [];\n this.contextEntries = [];\n this.verdictPath = '';\n this.fileDiffCommand = '';\n this.dirty = false;\n this.changedFileCount = 0;\n this.truncatedCount = 0;\n this.excludedCount = 0;\n this.allDiffLines = 0;\n this.allDiffBytes = 0;\n this.hashForkPoint = '';\n this.hashFeatureHead = '';\n this.hashMainHead = '';\n this.ownAgentFileInDiff = '';\n // Fails CLOSED, like RequiredChecklist.required: a briefing built without the flag being copied is\n // treated as a blocking reviewer, never as a skippable one.\n this.required = true;\n }\n}\n\n/**\n * The Read tool truncates at roughly this many lines, silently. Any path this file prints alongside a bigger\n * line count has to say so, or a reviewer reviews a fraction of a change and reports on all of it.\n */\nexport const READ_TRUNCATION_LINES = 2000;\n\n/** Comfortably under {@link READ_TRUNCATION_LINES} — the size at which ALL.diff really is one clean Read. */\nexport const ALL_DIFF_ONE_READ_LINES = 1500;\n\n/**\n * Renders the per-reviewer instructions file that `wp-review-upsert-pr` writes to\n * `.webpieces/pr-review/<feature>/instructions/<checklistId>.instructions.md` — ONE PER CHECKLIST, so a\n * subagent covering several checklists (`commands.pr-gate.reviewerAgents`) is simply handed several files.\n *\n * WHY this file exists, measured rather than assumed: a reviewer subagent on consumer-monorepo2 spent **14 of its\n * 26 tool calls** rediscovering context the tooling already had — three separate greps into\n * `node_modules/@webpieces` to locate a scanner, a hunt through terraform for queue names, and a\n * re-derivation of the dependency graph. It did not over-review; it was under-supplied. Everything below is\n * chosen to delete a specific one of those calls.\n *\n * It is GENERATED per run, and the reviewer agent (`.claude/agents/<reviewerAgentName>.md`, by default the\n * webpieces-owned `webpieces-reviewer.md`) is checklist-agnostic and points here, because content a human\n * maintains goes stale and a reviewer follows the stale copy. The verdict\n * schema in particular comes from {@link ReviewJsonService.verdictSchemaFor}.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewerInstructionsService {\n constructor(private readonly reviewJsonService: ReviewJsonService) {}\n\n /** The dir holding this branch's generated instructions, beside its review.json. */\n instructionsDirFor(repoRoot: string, featureName: string): string {\n return path.join(this.reviewJsonService.prDirFor(repoRoot, featureName), 'instructions');\n }\n\n /** The file name for one checklist's generated instructions. */\n fileNameFor(checklistId: string): string {\n return `${checklistId}.instructions.md`;\n }\n\n /** Absolute path of one checklist's generated instructions file. */\n pathFor(repoRoot: string, featureName: string, checklistId: string): string {\n return path.join(this.instructionsDirFor(repoRoot, featureName), this.fileNameFor(checklistId));\n }\n\n render(briefing: ReviewerBriefing): string {\n return [\n ...this.identity(briefing),\n ...this.checklistSection(briefing),\n ...this.diffSection(briefing),\n ...this.sourceSection(briefing),\n ...this.contextSection(briefing),\n ...this.verdictSection(briefing),\n ...this.budgetSection(),\n ].join('\\n') + '\\n';\n }\n\n private identity(b: ReviewerBriefing): string[] {\n return [\n `# Checklist \\`${b.checklistId}\\` — reviewed by a \\`${b.agentName}\\` subagent`,\n '',\n 'Review this diff against THIS checklist only — not as a general code reviewer — and only over the',\n 'files in scope below. If you were handed several of these files, review each checklist separately',\n 'and write each one its own verdict. Never write a verdict for a checklist you were not handed.',\n '',\n `Your agent definition is \\`${this.agentFileFor(b)}\\`. If your harness did not load it for you`,\n '(Codex spawns a generic subagent), read it before anything else — it is the one canonical',\n 'definition of how a reviewer works here.',\n '',\n '\"You may not review your own authorship\" means: you did not write this diff, so review it — but if',\n 'the diff CHANGES YOU, say so in `output` rather than pretending the conflict is not there.',\n ...this.selfEditWarning(b),\n '',\n '_Generated per run by `wp-review-upsert-pr`. Everything below is already resolved for you; the',\n 'paths are absolute because your working directory is not guaranteed to be the repo root._',\n '',\n ];\n }\n\n /** The absolute path of the reviewer agent's committed definition (`.claude/agents/<agentName>.md`). */\n agentFileFor(b: ReviewerBriefing): string {\n return path.join(b.repoRoot, '.claude', 'agents', `${b.agentName}.md`);\n }\n\n /**\n * Every PR that edits the review gate hits this: on the measured run, 5 of 8 changed files were the\n * reviewer agent files themselves, two reviewers reviewed their OWN definition, and neither flagged it —\n * neither had any guidance to. Detected by the tooling rather than left to the reviewer to notice.\n */\n private selfEditWarning(b: ReviewerBriefing): string[] {\n if (b.ownAgentFileInDiff === '') return [];\n return [\n '',\n `⚠️ THIS DIFF MODIFIES YOUR OWN AGENT FILE: \\`${b.ownAgentFileInDiff}\\``,\n 'You are reviewing a change to your own definition. Review it anyway — and state that fact in',\n '`output`, so a human reading your verdict knows the reviewer and the reviewed were the same thing.',\n ];\n }\n\n private checklistSection(b: ReviewerBriefing): string[] {\n if (b.docPath === '') {\n return ['## Your checklist', '', 'This checklist has no guidance doc — judge the diff on your own standards.', ''];\n }\n return ['## Your checklist', '', `Read this FIRST: \\`${b.docPath}\\``, ''];\n }\n\n /**\n * The change itself. The MANIFEST leads, and the per-file table with it; `ALL.diff` is demoted to one\n * option among several, recommended only where it is actually the right read.\n *\n * That ordering is the fix for a measured 4/4 failure. `ALL.diff` used to be bolded, first, and framed as\n * \"everything\", with the manifest one unbolded line below it called a \"path map\" — which reads like a\n * lookup aid, not something to open. All four reviewers on that PR read `ALL.diff`, none opened the\n * manifest, and none could therefore have established that the combined view was complete. Reviewers did\n * exactly what the emphasis told them to; so the emphasis moved.\n */\n private diffSection(b: ReviewerBriefing): string[] {\n const lines = ['## The change you are reviewing', ''];\n if (b.dirty) {\n lines.push('> ⚠️ This diff INCLUDES uncommitted and untracked work, so it is not what a');\n lines.push('> commit-to-commit range would show. Judge it anyway — it is what your checklist matched.');\n lines.push('');\n }\n lines.push(...this.manifestLines(b), ...this.basisLines(b), ...this.myFilesTable(b), ...this.allDiffLines(b));\n if (b.fileDiffCommand !== '') {\n lines.push(`Reproduce any single file: \\`${b.fileDiffCommand}\\``);\n lines.push('');\n }\n lines.push('Path matching is COARSE. Judge the change, not the path — if a matched file turns out to be');\n lines.push('irrelevant to your checklist, say so in one sentence and move on.');\n lines.push('');\n return lines;\n }\n\n /**\n * The manifest, named as the authority AND with its own headline facts inlined. Inlining them is the\n * point: a reviewer that never opens the manifest still learns whether anything was truncated or\n * excluded, which is the one thing it could not otherwise have known it was missing.\n */\n private manifestLines(b: ReviewerBriefing): string[] {\n if (b.manifestPath === '') return [];\n const warn = b.truncatedCount + b.excludedCount > 0 ? ' ⚠️ NOT everything is materialized — see the table.' : '';\n return [\n `**Path map — AUTHORITATIVE. Read this first:** \\`${b.manifestPath}\\``,\n ` ${b.changedFileCount} file(s) · ${b.truncatedCount} truncated · ${b.excludedCount} excluded${warn}`,\n ' Per file it carries: `status` (M/A/D/U/X), `bytes`, `file`/`fileAbs`, `diffFile`/`diffAbs`, and the',\n ' three shas below. It is the only place that records what was truncated or left out.',\n '',\n ];\n }\n\n /**\n * What the diff is taken AGAINST. Two bare shas in a `git diff` command said nothing about this, so a\n * reviewer could not tell a fork-point diff from a diff against main's current tip — and therefore could\n * not tell that anything merged to main since the fork is simply absent from what it is judging.\n * Rendering C beside A makes \"main has moved\" self-evident exactly when it has.\n */\n private basisLines(b: ReviewerBriefing): string[] {\n if (b.hashForkPoint === '') return [];\n const sameAsFork = b.hashMainHead === b.hashForkPoint;\n const cNote = b.hashMainHead === '' ? '(unresolved)'\n : sameAsFork ? `${b.hashMainHead} ← same as A, so main has not moved since the fork`\n : `${b.hashMainHead} ← main HAS MOVED since the fork`;\n return [\n 'This diff is **fork-point → feature-head**, NOT a diff against main\\'s current tip:',\n '```',\n `fork point (A) ${b.hashForkPoint}`,\n `feature head (B) ${b.hashFeatureHead}`,\n `main head (C) ${cNote}`,\n '```',\n 'Anything merged to main after A is NOT in this diff.',\n '',\n ];\n }\n\n /**\n * Source and diff get IDENTICAL affordance — an absolute path each, in adjacent columns. The previous\n * table gave an absolute path for the diff and nothing for the source, and the source went unread 4/4.\n */\n private myFilesTable(b: ReviewerBriefing): string[] {\n if (b.myFiles.length === 0) return ['_No files matched your patterns._', ''];\n const why = b.matchedPatterns.length === 0\n ? `**In scope: ALL ${b.myFiles.length} changed file(s)** — your checklist has no \\`patterns\\`, so the whole diff is yours.`\n : `**In scope: ${b.myFiles.length} file(s)** matching ${b.matchedPatterns.map((p: string): string => `\\`${p}\\``).join(', ')}:`;\n const rows = b.myFiles.map((f: BriefedFile): string => this.fileRow(f));\n return [why, '', '| file | status | its diff | full source |', '|---|---|---|---|', ...rows, ''];\n }\n\n /**\n * One row. A truncated or excluded diff is flagged HERE, in the row, not left as a field inside a file the\n * reviewer has to think to open — and an oversized diff states the line count that makes a single Read\n * come back silently incomplete.\n */\n private fileRow(f: BriefedFile): string {\n const flags: string[] = [];\n if (f.truncated) flags.push('⚠️ diff TRUNCATED — read the source');\n if (f.status === 'X') flags.push('⚠️ EXCLUDED, not materialized — read the source');\n const status = [`\\`${f.status}\\``, ...flags].join(' ');\n const source = f.sourcePath === '' ? '_deleted — no file on disk_' : `\\`${f.sourcePath}\\``;\n return `| \\`${f.file}\\` | ${status} | \\`${f.diffPath}\\` (${f.diffBytes} bytes) | ${source} |`;\n }\n\n /**\n * `ALL.diff`, recommended ONLY where it is the right read. It is kept — for a patternless reviewer on a\n * small diff it is genuinely one Read instead of N, which is why all four used it — but the blanket\n * \"everything on this branch\" line was wrong in two different ways at once: it invited a pattern-scoped\n * reviewer to spend its budget on files it was explicitly not asked about, and it pointed a reviewer on a\n * large PR at a file that cannot survive one Read.\n */\n private allDiffLines(b: ReviewerBriefing): string[] {\n if (b.allDiffPath === '') return [];\n const size = b.allDiffLines === 0 ? '' : ` — ${b.allDiffLines} lines / ${b.allDiffBytes} bytes`;\n if (b.matchedPatterns.length > 0) {\n return [`The whole branch${size}, for CROSS-FILE CONTEXT if you need it: \\`${b.allDiffPath}\\``,\n 'Your own files above are what you are reviewing; the rest of the branch is someone else\\'s checklist.', ''];\n }\n if (b.allDiffLines > ALL_DIFF_ONE_READ_LINES) {\n return [`⚠️ The combined diff is ${b.allDiffLines} lines and will NOT survive a single Read (the Read tool`,\n `truncates at ~${READ_TRUNCATION_LINES} lines, silently). Read the per-file diffs above instead, or page it:`,\n `\\`${b.allDiffPath}\\``, ''];\n }\n return [`Whole-branch diff${size} — convenient at this size, but the per-file table above is authoritative:`,\n `\\`${b.allDiffPath}\\``, ''];\n }\n\n /**\n * The rule that used to say \"when you need to\", and lost 4/4 to the more emphatic budget section 40 lines\n * later. \"When you need to\" delegates the judgment to a reviewer that, by construction, does not yet know\n * what it is missing. It has no opt-out now.\n */\n private sourceSection(b: ReviewerBriefing): string[] {\n const lines = ['## Read the full source — every file, every time', '', `Repo root: \\`${b.repoRoot}\\``, ''];\n lines.push('**READ THE FULL SOURCE OF EVERY FILE IN YOUR TABLE.** Not \"if you need to\" — every time.');\n lines.push('A hunk shows what changed; only the whole file shows what it changed INTO, and a change that');\n lines.push('looks fine in isolation can still be wrong in place.');\n lines.push('');\n lines.push('The `full source` column above is the path. For files marked `A` (added) you have already done');\n lines.push('this: an add-diff IS the complete file, byte for byte, so its diff and its source are the same');\n lines.push('text. For `M` (modified) and `D` (deleted) files the diff is a fragment — open the file.');\n lines.push('');\n if (b.sourceDirs.length > 0) {\n lines.push('The neighbouring code lives under:');\n for (const dir of b.sourceDirs) lines.push(`- \\`${dir}\\``);\n lines.push('');\n }\n return lines;\n }\n\n /**\n * The section that deletes the `node_modules` greps. Entries come from config, because the tooling\n * cannot guess that a repo keeps its queue names in terraform — but it CAN resolve where a package\n * installed to, and it must never drop an entry silently: a missing path is stated as missing.\n */\n private contextSection(b: ReviewerBriefing): string[] {\n // Emitting NOTHING made the section this class exists for look like a feature that does not exist.\n // One line naming the config keys costs nothing and is the only hint a repo will ever get.\n if (b.contextEntries.length === 0) {\n return ['## Pre-resolved context', '',\n '_None configured for this repo. If you find yourself hunting for the same path on every review,',\n 'it belongs in `pr-gate.reviewContext` / `pr-gate.reviewContextPackages` in `webpieces.config.json`._',\n ''];\n }\n const lines = ['## Pre-resolved context (do not go hunting for these)', ''];\n for (const entry of b.contextEntries) {\n const suffix = entry.note === '' ? '' : ` ${entry.note}`;\n lines.push(`- **${entry.label}** — \\`${entry.path === '' ? entry.note : entry.path}\\`${entry.path === '' ? '' : suffix}`);\n }\n lines.push('');\n return lines;\n }\n\n /**\n * The verdict shape, GENERATED — never restated in a hand-maintained file. `id` is pre-filled with this\n * reviewer's own checklist id so there is nothing to substitute and nothing to get wrong.\n */\n private verdictSection(b: ReviewerBriefing): string[] {\n return [\n '## Write your verdict',\n '',\n `Write EXACTLY this shape, to EXACTLY this file: \\`${b.verdictPath}\\``,\n '',\n '```',\n this.reviewJsonService.verdictSchemaFor(b.checklistId, '', ''),\n '```',\n '',\n ];\n }\n\n /**\n * The anti-hunting rule, with the changed files carved out explicitly.\n *\n * It used to read as \"stay inside what you were given\" and, being the LAST thing in the file, it beat\n * the source-reading instruction 4 times out of 4. It never distinguished HUNTING — greps into\n * `node_modules`, re-deriving the dependency graph — from OPENING A FILE IT WAS EXPLICITLY HANDED. The\n * distinction is now stated, because where two instructions conflict the later and more emphatic one wins.\n */\n private budgetSection(): string[] {\n return [\n '## Budget',\n '',\n 'Do NOT search `node_modules`, do NOT re-derive the dependency graph, and do NOT hunt the repo for',\n 'infrastructure config. That is hunting, and it is what wastes a review budget — those paths are',\n 'either given above or were not configured.',\n '',\n 'Reading the full text of a file that is IN your table is NOT hunting — it is the review. Budget for',\n 'it: read the diff to see what changed, and the source to judge whether it is right in place.',\n '',\n 'If something you genuinely need is missing, name it in `output` and give your verdict on what you',\n 'could actually see.',\n '',\n 'Open your diff files before writing a verdict. `wp-finish-upsert-pr` reads your own transcript and',\n 'reports a verdict written without opening the diff. It also records the path of that transcript,',\n 'beside what you were offered and what you read, in `provenance.json` next to your verdict file —',\n 'so the review is auditable after the fact. You do not write that file; the tooling does.',\n ];\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reviewer-instructions.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/reviewer-instructions.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { ReviewJsonService } from './review-json';\n\n/** One pre-resolved place a reviewer would otherwise have to go hunting for. Data-only (per CLAUDE.md). */\nexport class ContextEntry {\n label: string;\n path: string; // ABSOLUTE, or '' when it could not be resolved\n note: string; // '(not installed)' / '(configured but missing)' — never silently dropped\n\n constructor(label: string, entryPath: string, note = '') {\n this.label = label;\n this.path = entryPath;\n this.note = note;\n }\n}\n\n/**\n * One reviewer's matched file, the extracted diff for it, AND the absolute path of the file itself.\n *\n * `sourcePath` exists because of a measured 0/4: four reviewers on one PR read the diff and not one opened a\n * single full source file. The instructions handed them an absolute path per diff and only a parent DIRECTORY\n * per source, so reading the diff was a paste and reading the source was \"join this dir to that filename\n * yourself\". Identical affordance, or the cheaper one wins every time. Data-only.\n */\nexport class BriefedFile {\n file: string; // repo-relative source path\n diffPath: string; // ABSOLUTE path of the extracted .diff\n sourcePath: string; // ABSOLUTE path of the file itself; '' for a deleted file, which has no after-state\n status: string; // 'M' | 'A' | 'D' | 'U' (untracked) | 'X' (excluded by config)\n diffBytes: number;\n truncated: boolean;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(file: string, diffPath: string, sourcePath = '', status = 'M', diffBytes = 0, truncated = false) {\n this.file = file;\n this.diffPath = diffPath;\n this.sourcePath = sourcePath;\n this.status = status;\n this.diffBytes = diffBytes;\n this.truncated = truncated;\n }\n}\n\n/**\n * Everything ONE reviewer needs, with every path already resolved to an ABSOLUTE one.\n *\n * Absolute is not fussiness: a subagent's working directory is not guaranteed to be the repo root, and a\n * relative path that fails to resolve turns into a search, which is the cost this whole class exists to\n * remove. Data-only.\n */\nexport class ReviewerBriefing {\n agentName: string; // the agent type the subagent is spawned as (webpieces-reviewer, or the override)\n docPath: string; // the checklist's guidance doc ('' when the checklist has none)\n repoRoot: string;\n diffDir: string; // '' when nothing was materialized\n allDiffPath: string;\n manifestPath: string;\n myFiles: BriefedFile[]; // ONLY this reviewer's matched files\n matchedPatterns: string[]; // [] ⇒ patternless: the whole diff is in scope\n sourceDirs: string[]; // deduped ABSOLUTE parent dirs of myFiles\n contextEntries: ContextEntry[];\n verdictPath: string; // ABSOLUTE review-<id>.json\n checklistId: string;\n fileDiffCommand: string;\n dirty: boolean;\n // Facts ABOUT the manifest, surfaced inline so a reviewer learns the diff is complete without opening it.\n changedFileCount: number;\n truncatedCount: number;\n excludedCount: number;\n allDiffLines: number;\n allDiffBytes: number;\n // The three shas, by the names DiffManifest already uses. Printing two bare shas told a reviewer nothing\n // about WHAT the diff is taken against; C beside A makes \"main has moved since the fork\" self-evident.\n hashForkPoint: string;\n hashFeatureHead: string;\n hashMainHead: string;\n ownAgentFileInDiff: string; // ABSOLUTE path when this diff edits the reviewer agent's own file; '' otherwise\n // From the checklist's config `required`. Carried so the stage-② report can group the spawn blocks\n // (must run) apart from the ones the human is merely OFFERED, without a second lookup that could\n // disagree with the scan. The reviewer's OWN instructions do not mention it: a reviewer that has been\n // spawned reviews the same way either way, and telling an optional one it was optional invites it to\n // grade itself softer.\n required: boolean;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(agentName: string, checklistId: string, repoRoot: string) {\n this.agentName = agentName;\n this.checklistId = checklistId;\n this.repoRoot = repoRoot;\n this.docPath = '';\n this.diffDir = '';\n this.allDiffPath = '';\n this.manifestPath = '';\n this.myFiles = [];\n this.matchedPatterns = [];\n this.sourceDirs = [];\n this.contextEntries = [];\n this.verdictPath = '';\n this.fileDiffCommand = '';\n this.dirty = false;\n this.changedFileCount = 0;\n this.truncatedCount = 0;\n this.excludedCount = 0;\n this.allDiffLines = 0;\n this.allDiffBytes = 0;\n this.hashForkPoint = '';\n this.hashFeatureHead = '';\n this.hashMainHead = '';\n this.ownAgentFileInDiff = '';\n // Fails CLOSED, like RequiredChecklist.required: a briefing built without the flag being copied is\n // treated as a blocking reviewer, never as a skippable one.\n this.required = true;\n }\n}\n\n/**\n * The Read tool truncates at roughly this many lines, silently. Any path this file prints alongside a bigger\n * line count has to say so, or a reviewer reviews a fraction of a change and reports on all of it.\n */\nexport const READ_TRUNCATION_LINES = 2000;\n\n/** Comfortably under {@link READ_TRUNCATION_LINES} — the size at which ALL.diff really is one clean Read. */\nexport const ALL_DIFF_ONE_READ_LINES = 1500;\n\n/**\n * Renders the per-reviewer instructions file that `wp-review-upsert-pr` writes to\n * `.webpieces/pr-review/<feature>/instructions/<checklistId>.instructions.md` — ONE PER CHECKLIST, so a\n * subagent covering several checklists (`commands.pr-gate.reviewerAgents`) is simply handed several files.\n *\n * WHY this file exists, measured rather than assumed: a reviewer subagent on consumer-monorepo2 spent **14 of its\n * 26 tool calls** rediscovering context the tooling already had — three separate greps into\n * `node_modules/@webpieces` to locate a scanner, a hunt through terraform for queue names, and a\n * re-derivation of the dependency graph. It did not over-review; it was under-supplied. Everything below is\n * chosen to delete a specific one of those calls.\n *\n * It is GENERATED per run, and the reviewer agent (`.claude/agents/<name>.md`, by default the\n * webpieces-owned `webpieces-reviewer.md`) is checklist-agnostic and points here, because content a human\n * maintains goes stale and a reviewer follows the stale copy. The verdict\n * schema in particular comes from {@link ReviewJsonService.verdictSchemaFor}.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ReviewerInstructionsService {\n constructor(private readonly reviewJsonService: ReviewJsonService) {}\n\n /** The dir holding this branch's generated instructions, beside its review.json. */\n instructionsDirFor(repoRoot: string, featureName: string): string {\n return path.join(this.reviewJsonService.prDirFor(repoRoot, featureName), 'instructions');\n }\n\n /** The file name for one checklist's generated instructions. */\n fileNameFor(checklistId: string): string {\n return `${checklistId}.instructions.md`;\n }\n\n /** Absolute path of one checklist's generated instructions file. */\n pathFor(repoRoot: string, featureName: string, checklistId: string): string {\n return path.join(this.instructionsDirFor(repoRoot, featureName), this.fileNameFor(checklistId));\n }\n\n render(briefing: ReviewerBriefing): string {\n return [\n ...this.identity(briefing),\n ...this.checklistSection(briefing),\n ...this.diffSection(briefing),\n ...this.sourceSection(briefing),\n ...this.contextSection(briefing),\n ...this.verdictSection(briefing),\n ...this.budgetSection(),\n ].join('\\n') + '\\n';\n }\n\n private identity(b: ReviewerBriefing): string[] {\n return [\n `# Checklist \\`${b.checklistId}\\` — reviewed by a \\`${b.agentName}\\` subagent`,\n '',\n 'Review this diff against THIS checklist only — not as a general code reviewer — and only over the',\n 'files in scope below. If you were handed several of these files, review each checklist separately',\n 'and write each one its own verdict. Never write a verdict for a checklist you were not handed.',\n '',\n `Your agent definition is \\`${this.agentFileFor(b)}\\`. If your harness did not load it for you`,\n '(Codex spawns a generic subagent), read it before anything else — it is the one canonical',\n 'definition of how a reviewer works here.',\n '',\n '\"You may not review your own authorship\" means: you did not write this diff, so review it — but if',\n 'the diff CHANGES YOU, say so in `output` rather than pretending the conflict is not there.',\n ...this.selfEditWarning(b),\n '',\n '_Generated per run by `wp-review-upsert-pr`. Everything below is already resolved for you; the',\n 'paths are absolute because your working directory is not guaranteed to be the repo root._',\n '',\n ];\n }\n\n /** The absolute path of the reviewer agent's committed definition (`.claude/agents/<agentName>.md`). */\n agentFileFor(b: ReviewerBriefing): string {\n return path.join(b.repoRoot, '.claude', 'agents', `${b.agentName}.md`);\n }\n\n /**\n * Every PR that edits the review gate hits this: on the measured run, 5 of 8 changed files were the\n * reviewer agent files themselves, two reviewers reviewed their OWN definition, and neither flagged it —\n * neither had any guidance to. Detected by the tooling rather than left to the reviewer to notice.\n */\n private selfEditWarning(b: ReviewerBriefing): string[] {\n if (b.ownAgentFileInDiff === '') return [];\n return [\n '',\n `⚠️ THIS DIFF MODIFIES YOUR OWN AGENT FILE: \\`${b.ownAgentFileInDiff}\\``,\n 'You are reviewing a change to your own definition. Review it anyway — and state that fact in',\n '`output`, so a human reading your verdict knows the reviewer and the reviewed were the same thing.',\n ];\n }\n\n private checklistSection(b: ReviewerBriefing): string[] {\n if (b.docPath === '') {\n return ['## Your checklist', '', 'This checklist has no guidance doc — judge the diff on your own standards.', ''];\n }\n return ['## Your checklist', '', `Read this FIRST: \\`${b.docPath}\\``, ''];\n }\n\n /**\n * The change itself. The MANIFEST leads, and the per-file table with it; `ALL.diff` is demoted to one\n * option among several, recommended only where it is actually the right read.\n *\n * That ordering is the fix for a measured 4/4 failure. `ALL.diff` used to be bolded, first, and framed as\n * \"everything\", with the manifest one unbolded line below it called a \"path map\" — which reads like a\n * lookup aid, not something to open. All four reviewers on that PR read `ALL.diff`, none opened the\n * manifest, and none could therefore have established that the combined view was complete. Reviewers did\n * exactly what the emphasis told them to; so the emphasis moved.\n */\n private diffSection(b: ReviewerBriefing): string[] {\n const lines = ['## The change you are reviewing', ''];\n if (b.dirty) {\n lines.push('> ⚠️ This diff INCLUDES uncommitted and untracked work, so it is not what a');\n lines.push('> commit-to-commit range would show. Judge it anyway — it is what your checklist matched.');\n lines.push('');\n }\n lines.push(...this.manifestLines(b), ...this.basisLines(b), ...this.myFilesTable(b), ...this.allDiffLines(b));\n if (b.fileDiffCommand !== '') {\n lines.push(`Reproduce any single file: \\`${b.fileDiffCommand}\\``);\n lines.push('');\n }\n lines.push('Path matching is COARSE. Judge the change, not the path — if a matched file turns out to be');\n lines.push('irrelevant to your checklist, say so in one sentence and move on.');\n lines.push('');\n return lines;\n }\n\n /**\n * The manifest, named as the authority AND with its own headline facts inlined. Inlining them is the\n * point: a reviewer that never opens the manifest still learns whether anything was truncated or\n * excluded, which is the one thing it could not otherwise have known it was missing.\n */\n private manifestLines(b: ReviewerBriefing): string[] {\n if (b.manifestPath === '') return [];\n const warn = b.truncatedCount + b.excludedCount > 0 ? ' ⚠️ NOT everything is materialized — see the table.' : '';\n return [\n `**Path map — AUTHORITATIVE. Read this first:** \\`${b.manifestPath}\\``,\n ` ${b.changedFileCount} file(s) · ${b.truncatedCount} truncated · ${b.excludedCount} excluded${warn}`,\n ' Per file it carries: `status` (M/A/D/U/X), `bytes`, `file`/`fileAbs`, `diffFile`/`diffAbs`, and the',\n ' three shas below. It is the only place that records what was truncated or left out.',\n '',\n ];\n }\n\n /**\n * What the diff is taken AGAINST. Two bare shas in a `git diff` command said nothing about this, so a\n * reviewer could not tell a fork-point diff from a diff against main's current tip — and therefore could\n * not tell that anything merged to main since the fork is simply absent from what it is judging.\n * Rendering C beside A makes \"main has moved\" self-evident exactly when it has.\n */\n private basisLines(b: ReviewerBriefing): string[] {\n if (b.hashForkPoint === '') return [];\n const sameAsFork = b.hashMainHead === b.hashForkPoint;\n const cNote = b.hashMainHead === '' ? '(unresolved)'\n : sameAsFork ? `${b.hashMainHead} ← same as A, so main has not moved since the fork`\n : `${b.hashMainHead} ← main HAS MOVED since the fork`;\n return [\n 'This diff is **fork-point → feature-head**, NOT a diff against main\\'s current tip:',\n '```',\n `fork point (A) ${b.hashForkPoint}`,\n `feature head (B) ${b.hashFeatureHead}`,\n `main head (C) ${cNote}`,\n '```',\n 'Anything merged to main after A is NOT in this diff.',\n '',\n ];\n }\n\n /**\n * Source and diff get IDENTICAL affordance — an absolute path each, in adjacent columns. The previous\n * table gave an absolute path for the diff and nothing for the source, and the source went unread 4/4.\n */\n private myFilesTable(b: ReviewerBriefing): string[] {\n if (b.myFiles.length === 0) return ['_No files matched your patterns._', ''];\n const why = b.matchedPatterns.length === 0\n ? `**In scope: ALL ${b.myFiles.length} changed file(s)** — your checklist has no \\`patterns\\`, so the whole diff is yours.`\n : `**In scope: ${b.myFiles.length} file(s)** matching ${b.matchedPatterns.map((p: string): string => `\\`${p}\\``).join(', ')}:`;\n const rows = b.myFiles.map((f: BriefedFile): string => this.fileRow(f));\n return [why, '', '| file | status | its diff | full source |', '|---|---|---|---|', ...rows, ''];\n }\n\n /**\n * One row. A truncated or excluded diff is flagged HERE, in the row, not left as a field inside a file the\n * reviewer has to think to open — and an oversized diff states the line count that makes a single Read\n * come back silently incomplete.\n */\n private fileRow(f: BriefedFile): string {\n const flags: string[] = [];\n if (f.truncated) flags.push('⚠️ diff TRUNCATED — read the source');\n if (f.status === 'X') flags.push('⚠️ EXCLUDED, not materialized — read the source');\n const status = [`\\`${f.status}\\``, ...flags].join(' ');\n const source = f.sourcePath === '' ? '_deleted — no file on disk_' : `\\`${f.sourcePath}\\``;\n return `| \\`${f.file}\\` | ${status} | \\`${f.diffPath}\\` (${f.diffBytes} bytes) | ${source} |`;\n }\n\n /**\n * `ALL.diff`, recommended ONLY where it is the right read. It is kept — for a patternless reviewer on a\n * small diff it is genuinely one Read instead of N, which is why all four used it — but the blanket\n * \"everything on this branch\" line was wrong in two different ways at once: it invited a pattern-scoped\n * reviewer to spend its budget on files it was explicitly not asked about, and it pointed a reviewer on a\n * large PR at a file that cannot survive one Read.\n */\n private allDiffLines(b: ReviewerBriefing): string[] {\n if (b.allDiffPath === '') return [];\n const size = b.allDiffLines === 0 ? '' : ` — ${b.allDiffLines} lines / ${b.allDiffBytes} bytes`;\n if (b.matchedPatterns.length > 0) {\n return [`The whole branch${size}, for CROSS-FILE CONTEXT if you need it: \\`${b.allDiffPath}\\``,\n 'Your own files above are what you are reviewing; the rest of the branch is someone else\\'s checklist.', ''];\n }\n if (b.allDiffLines > ALL_DIFF_ONE_READ_LINES) {\n return [`⚠️ The combined diff is ${b.allDiffLines} lines and will NOT survive a single Read (the Read tool`,\n `truncates at ~${READ_TRUNCATION_LINES} lines, silently). Read the per-file diffs above instead, or page it:`,\n `\\`${b.allDiffPath}\\``, ''];\n }\n return [`Whole-branch diff${size} — convenient at this size, but the per-file table above is authoritative:`,\n `\\`${b.allDiffPath}\\``, ''];\n }\n\n /**\n * The rule that used to say \"when you need to\", and lost 4/4 to the more emphatic budget section 40 lines\n * later. \"When you need to\" delegates the judgment to a reviewer that, by construction, does not yet know\n * what it is missing. It has no opt-out now.\n */\n private sourceSection(b: ReviewerBriefing): string[] {\n const lines = ['## Read the full source — every file, every time', '', `Repo root: \\`${b.repoRoot}\\``, ''];\n lines.push('**READ THE FULL SOURCE OF EVERY FILE IN YOUR TABLE.** Not \"if you need to\" — every time.');\n lines.push('A hunk shows what changed; only the whole file shows what it changed INTO, and a change that');\n lines.push('looks fine in isolation can still be wrong in place.');\n lines.push('');\n lines.push('The `full source` column above is the path. For files marked `A` (added) you have already done');\n lines.push('this: an add-diff IS the complete file, byte for byte, so its diff and its source are the same');\n lines.push('text. For `M` (modified) and `D` (deleted) files the diff is a fragment — open the file.');\n lines.push('');\n if (b.sourceDirs.length > 0) {\n lines.push('The neighbouring code lives under:');\n for (const dir of b.sourceDirs) lines.push(`- \\`${dir}\\``);\n lines.push('');\n }\n return lines;\n }\n\n /**\n * The section that deletes the `node_modules` greps. Entries come from config, because the tooling\n * cannot guess that a repo keeps its queue names in terraform — but it CAN resolve where a package\n * installed to, and it must never drop an entry silently: a missing path is stated as missing.\n */\n private contextSection(b: ReviewerBriefing): string[] {\n // Emitting NOTHING made the section this class exists for look like a feature that does not exist.\n // One line naming the config keys costs nothing and is the only hint a repo will ever get.\n if (b.contextEntries.length === 0) {\n return ['## Pre-resolved context', '',\n '_None configured for this repo. If you find yourself hunting for the same path on every review,',\n 'it belongs in `pr-gate.reviewContext` / `pr-gate.reviewContextPackages` in `webpieces.config.json`._',\n ''];\n }\n const lines = ['## Pre-resolved context (do not go hunting for these)', ''];\n for (const entry of b.contextEntries) {\n const suffix = entry.note === '' ? '' : ` ${entry.note}`;\n lines.push(`- **${entry.label}** — \\`${entry.path === '' ? entry.note : entry.path}\\`${entry.path === '' ? '' : suffix}`);\n }\n lines.push('');\n return lines;\n }\n\n /**\n * The verdict shape, GENERATED — never restated in a hand-maintained file. `id` is pre-filled with this\n * reviewer's own checklist id so there is nothing to substitute and nothing to get wrong.\n */\n private verdictSection(b: ReviewerBriefing): string[] {\n return [\n '## Write your verdict',\n '',\n `Write EXACTLY this shape, to EXACTLY this file: \\`${b.verdictPath}\\``,\n '',\n '```',\n this.reviewJsonService.verdictSchemaFor(b.checklistId, '', ''),\n '```',\n '',\n ];\n }\n\n /**\n * The anti-hunting rule, with the changed files carved out explicitly.\n *\n * It used to read as \"stay inside what you were given\" and, being the LAST thing in the file, it beat\n * the source-reading instruction 4 times out of 4. It never distinguished HUNTING — greps into\n * `node_modules`, re-deriving the dependency graph — from OPENING A FILE IT WAS EXPLICITLY HANDED. The\n * distinction is now stated, because where two instructions conflict the later and more emphatic one wins.\n */\n private budgetSection(): string[] {\n return [\n '## Budget',\n '',\n 'Do NOT search `node_modules`, do NOT re-derive the dependency graph, and do NOT hunt the repo for',\n 'infrastructure config. That is hunting, and it is what wastes a review budget — those paths are',\n 'either given above or were not configured.',\n '',\n 'Reading the full text of a file that is IN your table is NOT hunting — it is the review. Budget for',\n 'it: read the diff to see what changed, and the source to judge whether it is right in place.',\n '',\n 'If something you genuinely need is missing, name it in `output` and give your verdict on what you',\n 'could actually see.',\n '',\n 'Open your diff files before writing a verdict. `wp-finish-upsert-pr` reads your own transcript and',\n 'reports a verdict written without opening the diff. It also records the path of that transcript,',\n 'beside what you were offered and what you read, in `provenance.json` next to your verdict file —',\n 'so the review is auditable after the fact. You do not write that file; the tooling does.',\n ];\n }\n}\n"]}
|
|
@@ -127,7 +127,7 @@ export declare class ExpectedReviewer {
|
|
|
127
127
|
/**
|
|
128
128
|
* Verifies — from the Claude Code harness's OWN artifacts, never from anything the model asserts — that
|
|
129
129
|
* a subagent of a given `agentType` actually ran during the current session on the current branch. Used
|
|
130
|
-
* to enforce that every checklist was reviewed by a
|
|
130
|
+
* to enforce that every checklist was reviewed by a subagent of the repo reviewer-agent type: that an INDEPENDENT
|
|
131
131
|
* reviewer looked, rather than the coding agent self-certifying.
|
|
132
132
|
*
|
|
133
133
|
* The harness writes, beside each subagent transcript:
|
|
@@ -180,7 +180,7 @@ exports.ExpectedReviewer = ExpectedReviewer;
|
|
|
180
180
|
/**
|
|
181
181
|
* Verifies — from the Claude Code harness's OWN artifacts, never from anything the model asserts — that
|
|
182
182
|
* a subagent of a given `agentType` actually ran during the current session on the current branch. Used
|
|
183
|
-
* to enforce that every checklist was reviewed by a
|
|
183
|
+
* to enforce that every checklist was reviewed by a subagent of the repo reviewer-agent type: that an INDEPENDENT
|
|
184
184
|
* reviewer looked, rather than the coding agent self-certifying.
|
|
185
185
|
*
|
|
186
186
|
* The harness writes, beside each subagent transcript:
|