@webpieces/pr-gate 0.4.597 → 0.4.599
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/pr-gate",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.599",
|
|
4
4
|
"description": "Gated PR system: 3-point squash-merge, merge validation gate, and red/yellow/green PR dashboard. Standalone scripts, no Nx dependency required.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"directory": "packages/tooling/pr-gate"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@webpieces/rules-config": "0.4.
|
|
18
|
+
"@webpieces/rules-config": "0.4.599",
|
|
19
19
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
20
20
|
"inversify": "7.10.4",
|
|
21
21
|
"reflect-metadata": "0.2.2"
|
|
@@ -44,8 +44,13 @@ export declare class ProvenanceEnforcer {
|
|
|
44
44
|
*/
|
|
45
45
|
private gatherEvidence;
|
|
46
46
|
/**
|
|
47
|
-
* Write this round's audit record: `.webpieces/pr-review/<
|
|
48
|
-
* to the transcript of the subagent that produced it.
|
|
47
|
+
* Write this round's audit record: `.webpieces/pr-review/<featureSlug>/provenance.json`, linking each
|
|
48
|
+
* verdict to the transcript of the subagent that produced it.
|
|
49
|
+
*
|
|
50
|
+
* `<featureSlug>`, not `<branch>`: the dir is named by `AiBranchName.getFeatureName()`, which
|
|
51
|
+
* dash-sanitizes the branch. Calling it `<branch>` is what led a bug reporter to conclude the gate
|
|
52
|
+
* must be comparing a dash-form name against a slash-form git branch — see
|
|
53
|
+
* ProvenanceWriteRequest.featureSlug, whose field carried the same mislabel.
|
|
49
54
|
*
|
|
50
55
|
* A SEPARATE file rather than a field inside review.json / review-<id>.json, for two reasons. The
|
|
51
56
|
* reviewer cannot supply this itself — a subagent's environment exposes the PARENT session id and no
|
|
@@ -107,8 +107,13 @@ let ProvenanceEnforcer = class ProvenanceEnforcer {
|
|
|
107
107
|
return this.provenance.evidenceFor(new rules_config_1.EvidenceRequest(branch, result.agentIds, diffDir, docPaths));
|
|
108
108
|
}
|
|
109
109
|
/**
|
|
110
|
-
* Write this round's audit record: `.webpieces/pr-review/<
|
|
111
|
-
* to the transcript of the subagent that produced it.
|
|
110
|
+
* Write this round's audit record: `.webpieces/pr-review/<featureSlug>/provenance.json`, linking each
|
|
111
|
+
* verdict to the transcript of the subagent that produced it.
|
|
112
|
+
*
|
|
113
|
+
* `<featureSlug>`, not `<branch>`: the dir is named by `AiBranchName.getFeatureName()`, which
|
|
114
|
+
* dash-sanitizes the branch. Calling it `<branch>` is what led a bug reporter to conclude the gate
|
|
115
|
+
* must be comparing a dash-form name against a slash-form git branch — see
|
|
116
|
+
* ProvenanceWriteRequest.featureSlug, whose field carried the same mislabel.
|
|
112
117
|
*
|
|
113
118
|
* A SEPARATE file rather than a field inside review.json / review-<id>.json, for two reasons. The
|
|
114
119
|
* reviewer cannot supply this itself — a subagent's environment exposes the PARENT session id and no
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provenance-enforcer.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/provenance-enforcer.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as path from 'path';\nimport {\n prDirFor, reviewJsonPath, RequiredChecklist, PrGateConfig, ReviewJsonService,\n SubagentProvenanceService, PROVENANCE_OK, PROVENANCE_MISSING, PROVENANCE_SKIPPED,\n ProvenanceResult, ReviewerEvidence, EvidenceRequest,\n ReviewProvenanceService, ProvenanceWriteRequest, ReviewerTranscript, ReviewerPaths, OfferedContext,\n ReviewerInstructionsService, InformAiError,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from './git-readAiBranchName';\n\n/**\n * The provenance outcome: whether each reviewer was VERIFIED to have run (the integrity check, which\n * blocks) plus what each one actually read (the quality signal, which is published). Data-only.\n */\nexport class ProvenanceReport {\n verified: boolean;\n evidence: ReviewerEvidence[];\n\n constructor(verified: boolean, evidence: ReviewerEvidence[]) {\n this.verified = verified;\n this.evidence = evidence;\n }\n}\n\n/**\n * Proves each checklist verdict came from its OWN independently-run reviewer subagent, and records what\n * each one read.\n *\n * Split out of FinishUpsertPrCommand when that file crossed the 700-line cap, and it is the right seam\n * twice over: this is a self-contained integrity check with its own spec\n * (`finish-provenance-record.spec.ts`), and that spec previously had to reach through\n * `command as unknown as { enforceProvenance(...) }` to get at it. Now `enforce` is simply public.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is drawn in the DI design and injected by type.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ProvenanceEnforcer {\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n private readonly aiBranchName: AiBranchName,\n private readonly provenance: SubagentProvenanceService,\n private readonly provenanceRecord: ReviewProvenanceService,\n private readonly reviewerInstructions: ReviewerInstructionsService,\n private readonly reviewJsonService: ReviewJsonService,\n ) {}\n\n /**\n * Archive this round's provenance.json alongside the archived review.json, so an archived review keeps\n * the transcript links belonging to the round that produced it — a record overwritten by the NEXT\n * round audits nothing.\n *\n * Here rather than in the command because this class owns the record; the command only knows WHEN to\n * archive, not what the record is.\n */\n archiveRecord(prDir: string): void {\n this.provenanceRecord.archive(prDir);\n }\n\n // HEAD sha for the audit record. Failure-tolerant ('' rather than a throw): a missing sha degrades\n // the audit trail, and refusing a PR over it would be the wrong trade — see writeProvenanceRecord.\n private gitOut(args: string[]): string {\n const result = spawnSync('git', args, { encoding: 'utf8' });\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n }\n\n // Enforce that EACH matched checklist was reviewed by its OWN named subagent, as a DISTINCT run —\n // the coding agent may not self-certify, and one reviewer may not stand in for several. A verified set\n // passes silently; no session id warns but passes; any missing reviewer throws so the PR does not open.\n // eslint-disable-next-line @typescript-eslint/max-params\n enforce(required: readonly RequiredChecklist[], branch: string, repoRoot: string, config: PrGateConfig): ProvenanceReport {\n const errors: string[] = [];\n const report = new ProvenanceReport(true, []); // no reviewers to verify ⇒ vacuously verified\n const subagents = required.map((r: RequiredChecklist): string => r.subagent.trim()).filter((s: string): boolean => s !== '');\n // verifyDistinct short-circuits to OK on an empty set, so this runs unconditionally: a repo with no\n // checklists still gets a provenance record naming the session and the main agent's own transcript.\n const result = this.provenance.verifyDistinct(subagents, branch);\n report.verified = result.status === PROVENANCE_OK;\n if (result.status === PROVENANCE_MISSING) {\n errors.push(result.detail);\n } else if (result.status === PROVENANCE_SKIPPED) {\n process.stderr.write(`⚠️ ${result.detail}\\n`);\n }\n report.evidence = this.gatherEvidence(repoRoot, required, result, branch);\n errors.push(...this.evidenceErrors(report.evidence, config));\n // BEFORE the throw below, deliberately. A refused round is the one most worth auditing, and a record\n // that only ever appeared on success could not answer what the reviewers did the time it was refused.\n this.writeProvenanceRecord(repoRoot, required, result, report.evidence);\n if (errors.length > 0) {\n throw new InformAiError(\n `${errors.length} checklist(s) require an independent reviewer subagent that did not run — fix, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\nSpawn the named reviewer subagent to review the checklist on THIS branch, then re-run.`,\n );\n }\n return report;\n }\n\n /**\n * What each credited reviewer actually read. Purely observational here — {@link evidenceErrors} decides\n * whether any of it blocks, and by default none of it does.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private gatherEvidence(repoRoot: string, required: readonly RequiredChecklist[], result: ProvenanceResult, branch: string): ReviewerEvidence[] {\n const docPaths: Record<string, string> = {};\n for (const req of required) {\n if (req.subagent.trim() !== '') docPaths[req.subagent] = req.doc.trim() === '' ? '' : path.resolve(repoRoot, req.doc);\n }\n const diffDir = path.join(prDirFor(repoRoot, this.aiBranchName.getFeatureName()), 'diff');\n return this.provenance.evidenceFor(new EvidenceRequest(branch, result.agentIds, diffDir, docPaths));\n }\n\n /**\n * Write this round's audit record: `.webpieces/pr-review/<branch>/provenance.json`, linking each verdict\n * to the transcript of the subagent that produced it.\n *\n * A SEPARATE file rather than a field inside review.json / review-<id>.json, for two reasons. The\n * reviewer cannot supply this itself — a subagent's environment exposes the PARENT session id and no\n * agent id, so a self-reported transcript link would be invented — and keeping the AI-authored files\n * byte-untouched means nothing in the record can be mistaken for something a reviewer claimed about\n * itself. Every path here is derived by the tooling from the harness's own artifacts.\n *\n * Never fatal: an unwritable record is a lost audit trail, not a reason to refuse a PR.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private writeProvenanceRecord(repoRoot: string, required: readonly RequiredChecklist[], result: ProvenanceResult, evidence: readonly ReviewerEvidence[]): void {\n const featureName = this.aiBranchName.getFeatureName();\n const prDir = prDirFor(repoRoot, featureName);\n const request = new ProvenanceWriteRequest(prDir, featureName, this.gitOut(['rev-parse', 'HEAD']), result.status);\n request.offered = new OfferedContext(\n path.join(prDir, 'diff'), this.reviewerInstructions.instructionsDirFor(repoRoot, featureName));\n request.reviewers = evidence.map((e: ReviewerEvidence): ReviewerTranscript =>\n new ReviewerTranscript(e, this.reviewerPathsFor(repoRoot, featureName, required, e.agentType)));\n const written = this.provenanceRecord.write(request);\n if (written !== '') process.stdout.write(` transcript provenance → ${written}\\n`);\n }\n\n // Where ONE reviewer's verdict, instructions and checklist doc live. Keyed by agentType, which IS the\n // checklist id: ChecklistDefinition sets `id = subagent`, so the two never diverge.\n // eslint-disable-next-line @typescript-eslint/max-params\n private reviewerPathsFor(repoRoot: string, featureName: string, required: readonly RequiredChecklist[], agentType: string): ReviewerPaths {\n const req = required.find((r: RequiredChecklist): boolean => r.subagent.trim() === agentType);\n const doc = req !== undefined && req.doc.trim() !== '' ? path.resolve(repoRoot, req.doc) : '';\n return new ReviewerPaths(\n this.reviewJsonService.checklistResultPath(reviewJsonPath(repoRoot, featureName), req?.id ?? agentType),\n this.reviewerInstructions.pathFor(repoRoot, featureName, agentType),\n doc,\n );\n }\n\n /**\n * WARN (default) or REFUSE (opt-in) on a reviewer that wrote a verdict without opening the diff.\n *\n * Default-warn because the signal is derived from undocumented Claude Code transcript internals: if the\n * format shifts, a blocking check wedges every PR in every consumer repo with no self-service recovery.\n * `requireDiffEvidence` lets a repo that has watched the warning promote it deliberately.\n */\n private evidenceErrors(evidence: readonly ReviewerEvidence[], config: PrGateConfig): string[] {\n const blind = evidence.filter((e: ReviewerEvidence): boolean => !e.readDiff);\n if (blind.length === 0) return [];\n const names = blind.map((e: ReviewerEvidence): string => e.agentType).join(', ');\n if (!config.requireDiffEvidence) {\n process.stderr.write(\n `\\n⚠️ ${blind.length} reviewer(s) wrote a verdict with no record of opening the extracted diff: ${names}\\n` +\n ' Published on the PR as a note. Not blocking — set pr-gate.requireDiffEvidence:true to make it one.\\n');\n return [];\n }\n return [`these reviewers wrote a verdict without opening the diff (pr-gate.requireDiffEvidence is on): ${names}`];\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"provenance-enforcer.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/provenance-enforcer.ts"],"names":[],"mappings":";;;;AAAA,iDAA0C;AAC1C,mDAA6B;AAC7B,0DAMiC;AACjC,yCAA2D;AAC3D,iEAAsD;AAEtD;;;GAGG;AACH,MAAa,gBAAgB;IACzB,QAAQ,CAAU;IAClB,QAAQ,CAAqB;IAE7B,YAAY,QAAiB,EAAE,QAA4B;QACvD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;CACJ;AARD,4CAQC;AAED;;;;;;;;;;GAUG;AAEI,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAGN;IACA;IACA;IACA;IACA;IANrB,yDAAyD;IACzD,YACqB,YAA0B,EAC1B,UAAqC,EACrC,gBAAyC,EACzC,oBAAiD,EACjD,iBAAoC;QAJpC,iBAAY,GAAZ,YAAY,CAAc;QAC1B,eAAU,GAAV,UAAU,CAA2B;QACrC,qBAAgB,GAAhB,gBAAgB,CAAyB;QACzC,yBAAoB,GAApB,oBAAoB,CAA6B;QACjD,sBAAiB,GAAjB,iBAAiB,CAAmB;IACtD,CAAC;IAEJ;;;;;;;OAOG;IACH,aAAa,CAAC,KAAa;QACvB,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC;IAED,mGAAmG;IACnG,mGAAmG;IAC3F,MAAM,CAAC,IAAc;QACzB,MAAM,MAAM,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5D,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,CAAC;IAED,kGAAkG;IAClG,uGAAuG;IACvG,wGAAwG;IACxG,yDAAyD;IACzD,OAAO,CAAC,QAAsC,EAAE,MAAc,EAAE,QAAgB,EAAE,MAAoB;QAClG,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAI,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,8CAA8C;QAC7F,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAoB,EAAU,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAS,EAAW,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7H,oGAAoG;QACpG,oGAAoG;QACpG,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACjE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,KAAK,4BAAa,CAAC;QAClD,IAAI,MAAM,CAAC,MAAM,KAAK,iCAAkB,EAAE,CAAC;YACvC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,KAAK,iCAAkB,EAAE,CAAC;YAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;QACnD,CAAC;QACD,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAC1E,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;QAC7D,qGAAqG;QACrG,sGAAsG;QACtG,IAAI,CAAC,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACxE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,4BAAa,CACnB,GAAG,MAAM,CAAC,MAAM,0HAA0H;gBAC1I,MAAM,CAAC,GAAG,CAAC,CAAC,CAAS,EAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;gBACxD,4FAA4F,CAC/F,CAAC;QACN,CAAC;QACD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED;;;OAGG;IACH,yDAAyD;IACjD,cAAc,CAAC,QAAgB,EAAE,QAAsC,EAAE,MAAwB,EAAE,MAAc;QACrH,MAAM,QAAQ,GAA2B,EAAE,CAAC;QAC5C,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;YACzB,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE;gBAAE,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1H,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAA,uBAAQ,EAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;QAC1F,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,8BAAe,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxG,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,yDAAyD;IACjD,qBAAqB,CAAC,QAAgB,EAAE,QAAsC,EAAE,MAAwB,EAAE,QAAqC;QACnJ,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,IAAA,uBAAQ,EAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,qCAAsB,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAClH,OAAO,CAAC,OAAO,GAAG,IAAI,6BAAc,CAChC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,oBAAoB,CAAC,kBAAkB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;QACnG,OAAO,CAAC,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAmB,EAAsB,EAAE,CACzE,IAAI,iCAAkB,CAAC,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACpG,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACrD,IAAI,OAAO,KAAK,EAAE;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,OAAO,IAAI,CAAC,CAAC;IACxF,CAAC;IAED,sGAAsG;IACtG,oFAAoF;IACpF,yDAAyD;IACjD,gBAAgB,CAAC,QAAgB,EAAE,WAAmB,EAAE,QAAsC,EAAE,SAAiB;QACrH,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAoB,EAAW,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,SAAS,CAAC,CAAC;QAC9F,MAAM,GAAG,GAAG,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9F,OAAO,IAAI,4BAAa,CACpB,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,IAAA,6BAAc,EAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,SAAS,CAAC,EACvG,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,QAAQ,EAAE,WAAW,EAAE,SAAS,CAAC,EACnE,GAAG,CACN,CAAC;IACN,CAAC;IAED;;;;;;OAMG;IACK,cAAc,CAAC,QAAqC,EAAE,MAAoB;QAC9E,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAmB,EAAW,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC7E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAmB,EAAU,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjF,IAAI,CAAC,MAAM,CAAC,mBAAmB,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,SAAS,KAAK,CAAC,MAAM,8EAA8E,KAAK,IAAI;gBAC5G,yGAAyG,CAAC,CAAC;YAC/G,OAAO,EAAE,CAAC;QACd,CAAC;QACD,OAAO,CAAC,iGAAiG,KAAK,EAAE,CAAC,CAAC;IACtH,CAAC;CACJ,CAAA;AAzIY,gDAAkB;6BAAlB,kBAAkB;IAD9B,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAIF,mCAAY;QACd,wCAAyB;QACnB,sCAAuB;QACnB,0CAA2B;QAC9B,gCAAiB;GAPhD,kBAAkB,CAyI9B","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as path from 'path';\nimport {\n prDirFor, reviewJsonPath, RequiredChecklist, PrGateConfig, ReviewJsonService,\n SubagentProvenanceService, PROVENANCE_OK, PROVENANCE_MISSING, PROVENANCE_SKIPPED,\n ProvenanceResult, ReviewerEvidence, EvidenceRequest,\n ReviewProvenanceService, ProvenanceWriteRequest, ReviewerTranscript, ReviewerPaths, OfferedContext,\n ReviewerInstructionsService, InformAiError,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { AiBranchName } from './git-readAiBranchName';\n\n/**\n * The provenance outcome: whether each reviewer was VERIFIED to have run (the integrity check, which\n * blocks) plus what each one actually read (the quality signal, which is published). Data-only.\n */\nexport class ProvenanceReport {\n verified: boolean;\n evidence: ReviewerEvidence[];\n\n constructor(verified: boolean, evidence: ReviewerEvidence[]) {\n this.verified = verified;\n this.evidence = evidence;\n }\n}\n\n/**\n * Proves each checklist verdict came from its OWN independently-run reviewer subagent, and records what\n * each one read.\n *\n * Split out of FinishUpsertPrCommand when that file crossed the 700-line cap, and it is the right seam\n * twice over: this is a self-contained integrity check with its own spec\n * (`finish-provenance-record.spec.ts`), and that spec previously had to reach through\n * `command as unknown as { enforceProvenance(...) }` to get at it. Now `enforce` is simply public.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is drawn in the DI design and injected by type.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ProvenanceEnforcer {\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n private readonly aiBranchName: AiBranchName,\n private readonly provenance: SubagentProvenanceService,\n private readonly provenanceRecord: ReviewProvenanceService,\n private readonly reviewerInstructions: ReviewerInstructionsService,\n private readonly reviewJsonService: ReviewJsonService,\n ) {}\n\n /**\n * Archive this round's provenance.json alongside the archived review.json, so an archived review keeps\n * the transcript links belonging to the round that produced it — a record overwritten by the NEXT\n * round audits nothing.\n *\n * Here rather than in the command because this class owns the record; the command only knows WHEN to\n * archive, not what the record is.\n */\n archiveRecord(prDir: string): void {\n this.provenanceRecord.archive(prDir);\n }\n\n // HEAD sha for the audit record. Failure-tolerant ('' rather than a throw): a missing sha degrades\n // the audit trail, and refusing a PR over it would be the wrong trade — see writeProvenanceRecord.\n private gitOut(args: string[]): string {\n const result = spawnSync('git', args, { encoding: 'utf8' });\n return result.status === 0 ? (result.stdout ?? '').trim() : '';\n }\n\n // Enforce that EACH matched checklist was reviewed by its OWN named subagent, as a DISTINCT run —\n // the coding agent may not self-certify, and one reviewer may not stand in for several. A verified set\n // passes silently; no session id warns but passes; any missing reviewer throws so the PR does not open.\n // eslint-disable-next-line @typescript-eslint/max-params\n enforce(required: readonly RequiredChecklist[], branch: string, repoRoot: string, config: PrGateConfig): ProvenanceReport {\n const errors: string[] = [];\n const report = new ProvenanceReport(true, []); // no reviewers to verify ⇒ vacuously verified\n const subagents = required.map((r: RequiredChecklist): string => r.subagent.trim()).filter((s: string): boolean => s !== '');\n // verifyDistinct short-circuits to OK on an empty set, so this runs unconditionally: a repo with no\n // checklists still gets a provenance record naming the session and the main agent's own transcript.\n const result = this.provenance.verifyDistinct(subagents, branch);\n report.verified = result.status === PROVENANCE_OK;\n if (result.status === PROVENANCE_MISSING) {\n errors.push(result.detail);\n } else if (result.status === PROVENANCE_SKIPPED) {\n process.stderr.write(`⚠️ ${result.detail}\\n`);\n }\n report.evidence = this.gatherEvidence(repoRoot, required, result, branch);\n errors.push(...this.evidenceErrors(report.evidence, config));\n // BEFORE the throw below, deliberately. A refused round is the one most worth auditing, and a record\n // that only ever appeared on success could not answer what the reviewers did the time it was refused.\n this.writeProvenanceRecord(repoRoot, required, result, report.evidence);\n if (errors.length > 0) {\n throw new InformAiError(\n `${errors.length} checklist(s) require an independent reviewer subagent that did not run — fix, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\nSpawn the named reviewer subagent to review the checklist on THIS branch, then re-run.`,\n );\n }\n return report;\n }\n\n /**\n * What each credited reviewer actually read. Purely observational here — {@link evidenceErrors} decides\n * whether any of it blocks, and by default none of it does.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private gatherEvidence(repoRoot: string, required: readonly RequiredChecklist[], result: ProvenanceResult, branch: string): ReviewerEvidence[] {\n const docPaths: Record<string, string> = {};\n for (const req of required) {\n if (req.subagent.trim() !== '') docPaths[req.subagent] = req.doc.trim() === '' ? '' : path.resolve(repoRoot, req.doc);\n }\n const diffDir = path.join(prDirFor(repoRoot, this.aiBranchName.getFeatureName()), 'diff');\n return this.provenance.evidenceFor(new EvidenceRequest(branch, result.agentIds, diffDir, docPaths));\n }\n\n /**\n * Write this round's audit record: `.webpieces/pr-review/<featureSlug>/provenance.json`, linking each\n * verdict to the transcript of the subagent that produced it.\n *\n * `<featureSlug>`, not `<branch>`: the dir is named by `AiBranchName.getFeatureName()`, which\n * dash-sanitizes the branch. Calling it `<branch>` is what led a bug reporter to conclude the gate\n * must be comparing a dash-form name against a slash-form git branch — see\n * ProvenanceWriteRequest.featureSlug, whose field carried the same mislabel.\n *\n * A SEPARATE file rather than a field inside review.json / review-<id>.json, for two reasons. The\n * reviewer cannot supply this itself — a subagent's environment exposes the PARENT session id and no\n * agent id, so a self-reported transcript link would be invented — and keeping the AI-authored files\n * byte-untouched means nothing in the record can be mistaken for something a reviewer claimed about\n * itself. Every path here is derived by the tooling from the harness's own artifacts.\n *\n * Never fatal: an unwritable record is a lost audit trail, not a reason to refuse a PR.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private writeProvenanceRecord(repoRoot: string, required: readonly RequiredChecklist[], result: ProvenanceResult, evidence: readonly ReviewerEvidence[]): void {\n const featureName = this.aiBranchName.getFeatureName();\n const prDir = prDirFor(repoRoot, featureName);\n const request = new ProvenanceWriteRequest(prDir, featureName, this.gitOut(['rev-parse', 'HEAD']), result.status);\n request.offered = new OfferedContext(\n path.join(prDir, 'diff'), this.reviewerInstructions.instructionsDirFor(repoRoot, featureName));\n request.reviewers = evidence.map((e: ReviewerEvidence): ReviewerTranscript =>\n new ReviewerTranscript(e, this.reviewerPathsFor(repoRoot, featureName, required, e.agentType)));\n const written = this.provenanceRecord.write(request);\n if (written !== '') process.stdout.write(` transcript provenance → ${written}\\n`);\n }\n\n // Where ONE reviewer's verdict, instructions and checklist doc live. Keyed by agentType, which IS the\n // checklist id: ChecklistDefinition sets `id = subagent`, so the two never diverge.\n // eslint-disable-next-line @typescript-eslint/max-params\n private reviewerPathsFor(repoRoot: string, featureName: string, required: readonly RequiredChecklist[], agentType: string): ReviewerPaths {\n const req = required.find((r: RequiredChecklist): boolean => r.subagent.trim() === agentType);\n const doc = req !== undefined && req.doc.trim() !== '' ? path.resolve(repoRoot, req.doc) : '';\n return new ReviewerPaths(\n this.reviewJsonService.checklistResultPath(reviewJsonPath(repoRoot, featureName), req?.id ?? agentType),\n this.reviewerInstructions.pathFor(repoRoot, featureName, agentType),\n doc,\n );\n }\n\n /**\n * WARN (default) or REFUSE (opt-in) on a reviewer that wrote a verdict without opening the diff.\n *\n * Default-warn because the signal is derived from undocumented Claude Code transcript internals: if the\n * format shifts, a blocking check wedges every PR in every consumer repo with no self-service recovery.\n * `requireDiffEvidence` lets a repo that has watched the warning promote it deliberately.\n */\n private evidenceErrors(evidence: readonly ReviewerEvidence[], config: PrGateConfig): string[] {\n const blind = evidence.filter((e: ReviewerEvidence): boolean => !e.readDiff);\n if (blind.length === 0) return [];\n const names = blind.map((e: ReviewerEvidence): string => e.agentType).join(', ');\n if (!config.requireDiffEvidence) {\n process.stderr.write(\n `\\n⚠️ ${blind.length} reviewer(s) wrote a verdict with no record of opening the extracted diff: ${names}\\n` +\n ' Published on the PR as a note. Not blocking — set pr-gate.requireDiffEvidence:true to make it one.\\n');\n return [];\n }\n return [`these reviewers wrote a verdict without opening the diff (pr-gate.requireDiffEvidence is on): ${names}`];\n }\n}\n"]}
|