@webpieces/rules-config 0.4.485 → 0.4.487
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/rules-config",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.487",
|
|
4
4
|
"description": "Shared webpieces.config.json loader. Single source of truth for validation rule configuration consumed by @webpieces/ai-hook-rules, @webpieces/code-rules, and @webpieces/nx-webpieces-rules.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChecklistReviewContext, RequiredChecklist } from './review-json';
|
|
1
|
+
import { ChecklistReviewContext, RequiredChecklist, ReviewJsonService } from './review-json';
|
|
2
2
|
/**
|
|
3
3
|
* Renders the ONE block that tells the coding agent which reviewer subagents it must run and exactly what
|
|
4
4
|
* to tell them. There is a single renderer because three callers need the identical text and any drift
|
|
@@ -15,6 +15,8 @@ import { ChecklistReviewContext, RequiredChecklist } from './review-json';
|
|
|
15
15
|
* `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
|
|
16
16
|
*/
|
|
17
17
|
export declare class ChecklistInstructionsService {
|
|
18
|
+
private readonly reviewJsonService;
|
|
19
|
+
constructor(reviewJsonService: ReviewJsonService);
|
|
18
20
|
/**
|
|
19
21
|
* The full instruction block, or '' when nothing is pending (so a caller can concatenate it blindly).
|
|
20
22
|
* `reviewPath` is the branch's review.json — each verdict file sits beside it as review-<id>.json.
|
|
@@ -31,5 +33,4 @@ export declare class ChecklistInstructionsService {
|
|
|
31
33
|
private scope;
|
|
32
34
|
private verdictFormat;
|
|
33
35
|
private diffLines;
|
|
34
|
-
private verdictPath;
|
|
35
36
|
}
|
|
@@ -4,6 +4,7 @@ exports.ChecklistInstructionsService = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const inversify_1 = require("inversify");
|
|
6
6
|
const checklist_config_1 = require("./checklist-config");
|
|
7
|
+
const review_json_1 = require("./review-json");
|
|
7
8
|
/**
|
|
8
9
|
* Renders the ONE block that tells the coding agent which reviewer subagents it must run and exactly what
|
|
9
10
|
* to tell them. There is a single renderer because three callers need the identical text and any drift
|
|
@@ -20,6 +21,14 @@ const checklist_config_1 = require("./checklist-config");
|
|
|
20
21
|
* `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.
|
|
21
22
|
*/
|
|
22
23
|
let ChecklistInstructionsService = class ChecklistInstructionsService {
|
|
24
|
+
reviewJsonService;
|
|
25
|
+
// Injected so the verdict path comes from the ONE place that knows it. This class used to hand-roll
|
|
26
|
+
// dirname with a regex and a hardcoded '/', a second implementation of ReviewJsonService's
|
|
27
|
+
// checklistResultPath that agreed on POSIX and disagreed on Windows — the same duplicate-logic trap as
|
|
28
|
+
// the two different matched-file truncation caps.
|
|
29
|
+
constructor(reviewJsonService) {
|
|
30
|
+
this.reviewJsonService = reviewJsonService;
|
|
31
|
+
}
|
|
23
32
|
/**
|
|
24
33
|
* The full instruction block, or '' when nothing is pending (so a caller can concatenate it blindly).
|
|
25
34
|
* `reviewPath` is the branch's review.json — each verdict file sits beside it as review-<id>.json.
|
|
@@ -51,7 +60,7 @@ let ChecklistInstructionsService = class ChecklistInstructionsService {
|
|
|
51
60
|
lines.push(` doc to read: ${req.doc}`);
|
|
52
61
|
for (const scopeLine of this.scope(req))
|
|
53
62
|
lines.push(` ${scopeLine}`);
|
|
54
|
-
lines.push(` must write: ${this.
|
|
63
|
+
lines.push(` must write: ${this.reviewJsonService.checklistResultPath(reviewPath, req.id)}`);
|
|
55
64
|
return lines;
|
|
56
65
|
}
|
|
57
66
|
/**
|
|
@@ -85,8 +94,16 @@ let ChecklistInstructionsService = class ChecklistInstructionsService {
|
|
|
85
94
|
// The diff every reviewer judges. Stated once, here, because path matching is deliberately coarse and a
|
|
86
95
|
// reviewer that only sees filenames cannot make the content-level call the checklist is asking for.
|
|
87
96
|
diffLines(context) {
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
// NEVER omit these lines quietly. A reviewer given filenames but no way to read the change cannot make
|
|
98
|
+
// the content-level judgment the checklist asks for, and a silently shorter instruction block is
|
|
99
|
+
// indistinguishable from a complete one — so an unresolvable base is stated as the problem it is.
|
|
100
|
+
if (context.baseSha.trim() === '') {
|
|
101
|
+
return [
|
|
102
|
+
'⚠️ No diff base resolved for this branch, so the exact `git diff` command could not be given.',
|
|
103
|
+
' Tell each reviewer to diff against main itself (`git diff $(git merge-base origin/main HEAD) HEAD -- <file>`)',
|
|
104
|
+
' and note that path matching is COARSE — judge the real change, not the path.',
|
|
105
|
+
];
|
|
106
|
+
}
|
|
90
107
|
const lines = [
|
|
91
108
|
'Also give EACH one the real diff — path matching is COARSE, so judge the change, not the path:',
|
|
92
109
|
` git diff ${context.baseSha} HEAD -- <file>`,
|
|
@@ -96,15 +113,10 @@ let ChecklistInstructionsService = class ChecklistInstructionsService {
|
|
|
96
113
|
}
|
|
97
114
|
return lines;
|
|
98
115
|
}
|
|
99
|
-
// review-<id>.json beside the branch's review.json — one file per checklist so N concurrent reviewer
|
|
100
|
-
// subagents never clobber a shared file.
|
|
101
|
-
verdictPath(reviewPath, checklistId) {
|
|
102
|
-
const dir = reviewPath.replace(/[/\\][^/\\]*$/, '');
|
|
103
|
-
return `${dir}/review-${checklistId}.json`;
|
|
104
|
-
}
|
|
105
116
|
};
|
|
106
117
|
exports.ChecklistInstructionsService = ChecklistInstructionsService;
|
|
107
118
|
exports.ChecklistInstructionsService = ChecklistInstructionsService = tslib_1.__decorate([
|
|
108
|
-
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton)
|
|
119
|
+
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
|
|
120
|
+
tslib_1.__metadata("design:paramtypes", [review_json_1.ReviewJsonService])
|
|
109
121
|
], ChecklistInstructionsService);
|
|
110
122
|
//# sourceMappingURL=checklist-instructions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-instructions.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-instructions.ts"],"names":[],"mappings":";;;;AAAA,yCAA2D;AAC3D,yDAAoD;
|
|
1
|
+
{"version":3,"file":"checklist-instructions.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-instructions.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, bindingScopeValues } from 'inversify';\nimport { formatFileList } from './checklist-config';\nimport { ChecklistReviewContext, RequiredChecklist, ReviewJsonService } from './review-json';\n\n/**\n * Renders the ONE block that tells the coding agent which reviewer subagents it must run and exactly what\n * to tell them. There is a single renderer because three callers need the identical text and any drift\n * between them is a correctness bug, not a cosmetic one:\n *\n * - `wp-checklist` — the AI asks \"what review do I owe on this diff?\"\n * - `wp-finish-upsert-pr` — fails fast, listing ONLY the reviewers that still have not run\n * - `ReviewJsonService` — the same list appended to a review.json validation failure\n *\n * Callers pass ONLY the checklists still needing a verdict. A checklist already reviewed on this branch is\n * never re-listed — re-instructing it invites a redundant second run, and (worse) reads as though the\n * earlier verdict did not count.\n *\n * `@injectable(bindingScopeValues.Singleton)` so it is injected by type and drawn in the DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ChecklistInstructionsService {\n // Injected so the verdict path comes from the ONE place that knows it. This class used to hand-roll\n // dirname with a regex and a hardcoded '/', a second implementation of ReviewJsonService's\n // checklistResultPath that agreed on POSIX and disagreed on Windows — the same duplicate-logic trap as\n // the two different matched-file truncation caps.\n constructor(private readonly reviewJsonService: ReviewJsonService) {}\n\n /**\n * The full instruction block, or '' when nothing is pending (so a caller can concatenate it blindly).\n * `reviewPath` is the branch's review.json — each verdict file sits beside it as review-<id>.json.\n */\n render(pending: readonly RequiredChecklist[], reviewPath: string, context: ChecklistReviewContext): string {\n if (pending.length === 0) return '';\n const lines: string[] = [\n `You MUST run these ${pending.length} reviewer subagent(s) — a SEPARATE one for each. You may NOT review`,\n `your own work, and you may NOT write a reviewer's verdict file on its behalf.`,\n '',\n ];\n for (const req of pending) lines.push(...this.oneReviewer(req, reviewPath));\n lines.push('', ...this.verdictFormat());\n lines.push('', ...this.diffLines(context));\n return lines.join('\\n');\n }\n\n // Just the reviewer NAMES, for a caller that wants a one-line summary rather than the whole block.\n names(pending: readonly RequiredChecklist[]): string {\n return pending.map((r: RequiredChecklist): string => r.subagent).join(', ');\n }\n\n // What ONE subagent must be given: its doc, why it is running + over what, and the file it must write.\n private oneReviewer(req: RequiredChecklist, reviewPath: string): string[] {\n const lines = [` • ${req.subagent}`];\n // The doc is REPO-relative by the time it reaches here (see ChecklistDefinition.doc), so a subagent\n // handed this string can actually open it. Printing the raw config value would not resolve.\n if (req.doc.trim() !== '') lines.push(` doc to read: ${req.doc}`);\n for (const scopeLine of this.scope(req)) lines.push(` ${scopeLine}`);\n lines.push(` must write: ${this.reviewJsonService.checklistResultPath(reviewPath, req.id)}`);\n return lines;\n }\n\n /**\n * WHY this reviewer is running, and over what. NOT every checklist is pattern-matched: one with no\n * `patterns` runs on EVERY PR, and calling its file list \"matched\" implies the list is a narrow,\n * pre-filtered slice of the diff when it is in fact the whole thing. When patterns DID fire, they are\n * named — the reviewer cannot otherwise tell a precise migrations-only glob from a blanket match-all one.\n */\n private scope(req: RequiredChecklist): string[] {\n if (req.matchedPatterns.length === 0) {\n return [\n `in scope: ALWAYS RUNS — this checklist has no patterns, so the WHOLE diff is in scope`,\n ` all ${req.matchedFiles.length} changed file(s): ${formatFileList(req.matchedFiles)}`,\n ];\n }\n const globs = req.matchedPatterns.map((p: string): string => `\"${p}\"`).join(', ');\n return [\n `in scope: ${req.matchedFiles.length} file(s) matched ${globs}`,\n ` ${formatFileList(req.matchedFiles)}`,\n ];\n }\n\n // ONE shared format block for every reviewer, rather than repeating the schema under each name.\n private verdictFormat(): string[] {\n return [\n 'TELL EACH subagent to write that file with EXACTLY this format:',\n ' { \"id\": \"<its own subagent name>\", \"success\": true, \"output\": \"what you checked / found\", \"override\": \"\" }',\n ' success:false + empty \"override\" → REFUSES the PR; the reviewer\\'s \"output\" is printed verbatim',\n ' success:false + non-empty \"override\" → ships anyway as 🟡; the justification is published on the PR',\n ];\n }\n\n // The diff every reviewer judges. Stated once, here, because path matching is deliberately coarse and a\n // reviewer that only sees filenames cannot make the content-level call the checklist is asking for.\n private diffLines(context: ChecklistReviewContext): string[] {\n // NEVER omit these lines quietly. A reviewer given filenames but no way to read the change cannot make\n // the content-level judgment the checklist asks for, and a silently shorter instruction block is\n // indistinguishable from a complete one — so an unresolvable base is stated as the problem it is.\n if (context.baseSha.trim() === '') {\n return [\n '⚠️ No diff base resolved for this branch, so the exact `git diff` command could not be given.',\n ' Tell each reviewer to diff against main itself (`git diff $(git merge-base origin/main HEAD) HEAD -- <file>`)',\n ' and note that path matching is COARSE — judge the real change, not the path.',\n ];\n }\n const lines = [\n 'Also give EACH one the real diff — path matching is COARSE, so judge the change, not the path:',\n ` git diff ${context.baseSha} HEAD -- <file>`,\n ];\n if (context.prContextPath.trim() !== '') {\n lines.push(` full changed-file set + base/head sha: ${context.prContextPath}`);\n }\n return lines;\n }\n\n}\n"]}
|
|
@@ -6,7 +6,10 @@ import { ChecklistDefinition } from './checklist-config';
|
|
|
6
6
|
* old shape gets a hard config error naming the exact edit — which an AI applies in one pass — and that is
|
|
7
7
|
* strictly better than carrying two code paths forever so that nobody has to read an error message.
|
|
8
8
|
*
|
|
9
|
-
* `@injectable
|
|
9
|
+
* Deliberately NOT `@injectable`: config validation runs from module-level functions in validate-config.ts,
|
|
10
|
+
* before any container exists, so this is constructed directly. It previously carried an @injectable
|
|
11
|
+
* decorator plus a comment claiming it was "injected by type and drawn in the DI design" — while nothing
|
|
12
|
+
* injected it and it appeared nowhere in design.json. A false claim the next reader would have trusted.
|
|
10
13
|
*/
|
|
11
14
|
export declare class ChecklistValidator {
|
|
12
15
|
/**
|
|
@@ -4,7 +4,6 @@ exports.ChecklistValidator = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const fs = tslib_1.__importStar(require("fs"));
|
|
6
6
|
const path = tslib_1.__importStar(require("path"));
|
|
7
|
-
const inversify_1 = require("inversify");
|
|
8
7
|
// Where a reviewer subagent's definition must live for Claude Code to be able to spawn it.
|
|
9
8
|
const AGENTS_DIR = path.join('.claude', 'agents');
|
|
10
9
|
// Every checklist error names this, so a reader always knows which file and key to open. There is exactly
|
|
@@ -17,9 +16,12 @@ const SOURCE = 'pr-gate.checklists in webpieces.config.json';
|
|
|
17
16
|
* old shape gets a hard config error naming the exact edit — which an AI applies in one pass — and that is
|
|
18
17
|
* strictly better than carrying two code paths forever so that nobody has to read an error message.
|
|
19
18
|
*
|
|
20
|
-
* `@injectable
|
|
19
|
+
* Deliberately NOT `@injectable`: config validation runs from module-level functions in validate-config.ts,
|
|
20
|
+
* before any container exists, so this is constructed directly. It previously carried an @injectable
|
|
21
|
+
* decorator plus a comment claiming it was "injected by type and drawn in the DI design" — while nothing
|
|
22
|
+
* injected it and it appeared nowhere in design.json. A false claim the next reader would have trusted.
|
|
21
23
|
*/
|
|
22
|
-
|
|
24
|
+
class ChecklistValidator {
|
|
23
25
|
/**
|
|
24
26
|
* Human-readable errors for the configured checklists, or [] when they are valid. Never throws.
|
|
25
27
|
* `defs` are already narrowed by buildPrGateConfig; this checks what only the filesystem can answer.
|
|
@@ -70,9 +72,6 @@ let ChecklistValidator = class ChecklistValidator {
|
|
|
70
72
|
`Create that agent file or fix the name.`,
|
|
71
73
|
];
|
|
72
74
|
}
|
|
73
|
-
}
|
|
75
|
+
}
|
|
74
76
|
exports.ChecklistValidator = ChecklistValidator;
|
|
75
|
-
exports.ChecklistValidator = ChecklistValidator = tslib_1.__decorate([
|
|
76
|
-
(0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton)
|
|
77
|
-
], ChecklistValidator);
|
|
78
77
|
//# sourceMappingURL=checklist-validator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checklist-validator.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-validator.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;
|
|
1
|
+
{"version":3,"file":"checklist-validator.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/checklist-validator.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { ChecklistDefinition } from './checklist-config';\n\n// Where a reviewer subagent's definition must live for Claude Code to be able to spawn it.\nconst AGENTS_DIR = path.join('.claude', 'agents');\n\n// Every checklist error names this, so a reader always knows which file and key to open. There is exactly\n// ONE place checklists can be configured, so there is exactly one label.\nconst SOURCE = 'pr-gate.checklists in webpieces.config.json';\n\n/**\n * Validates the review checklists declared in `pr-gate.checklists`. The array in webpieces.config.json is\n * the ONLY accepted shape: there is deliberately no fallback, no second location, and no back-compat path\n * for the `{ doc }` + `<!-- webpieces:checklists -->` HTML-comment manifest this replaced. A consumer on the\n * old shape gets a hard config error naming the exact edit — which an AI applies in one pass — and that is\n * strictly better than carrying two code paths forever so that nobody has to read an error message.\n *\n * Deliberately NOT `@injectable`: config validation runs from module-level functions in validate-config.ts,\n * before any container exists, so this is constructed directly. It previously carried an @injectable\n * decorator plus a comment claiming it was \"injected by type and drawn in the DI design\" — while nothing\n * injected it and it appeared nowhere in design.json. A false claim the next reader would have trusted.\n */\nexport class ChecklistValidator {\n /**\n * Human-readable errors for the configured checklists, or [] when they are valid. Never throws.\n * `defs` are already narrowed by buildPrGateConfig; this checks what only the filesystem can answer.\n */\n validate(repoRoot: string, defs: readonly ChecklistDefinition[]): string[] {\n const errors: string[] = [];\n const seen = new Set<string>();\n // Only enforce the reviewer-agent file when this repo HAS an agents dir — a non-Claude-Code consumer\n // that drives the gate some other way must not be broken by a check for a directory it never has.\n const agentsDir = path.join(repoRoot, AGENTS_DIR);\n const checkAgents = fs.existsSync(agentsDir);\n defs.forEach((def: ChecklistDefinition, i: number): void => {\n const label = def.subagent !== '' ? `\"${def.subagent}\"` : `checklists[${i}]`;\n errors.push(...this.validateSubagent(def, i, seen, checkAgents, agentsDir));\n if (def.doc !== '' && !fs.existsSync(path.join(repoRoot, def.doc))) {\n errors.push(`[pr-gate] ${SOURCE} ${label}.doc \"${def.doc}\" does not exist (paths are REPO-relative).`);\n }\n });\n return errors;\n }\n\n // `subagent` is the ONE required field and the whole distinct-reviewer guarantee rests on it.\n // eslint-disable-next-line @typescript-eslint/max-params\n private validateSubagent(def: ChecklistDefinition, i: number, seen: Set<string>, checkAgents: boolean, agentsDir: string): string[] {\n const label = def.subagent !== '' ? `\"${def.subagent}\"` : `checklists[${i}]`;\n if (def.subagent.trim() === '') {\n return [`[pr-gate] ${SOURCE} checklists[${i}].subagent must be a non-empty string (the reviewer agent name, matching ${AGENTS_DIR}/<subagent>.md).`];\n }\n if (seen.has(def.subagent)) {\n return [`[pr-gate] ${SOURCE} duplicate subagent \"${def.subagent}\" — each checklist must use a DISTINCT reviewer subagent (that is how independent review is enforced).`];\n }\n seen.add(def.subagent);\n return this.validateSubagentExists(def.subagent, label, checkAgents, agentsDir);\n }\n\n /**\n * The check this validator once lacked: nothing confirmed `subagent` named a real agent. A typo used to\n * validate clean, then get printed to the coding agent as \"spawn this\" — and since wp-finish blocks on\n * `review-<that-typo>.json`, the path of least resistance became writing the reviewer's verdict itself,\n * which is the exact self-certification the distinct-subagent rule exists to prevent. Reject at load.\n */\n private validateSubagentExists(subagent: string, label: string, checkAgents: boolean, agentsDir: string): string[] {\n if (!checkAgents) return [];\n if (fs.existsSync(path.join(agentsDir, `${subagent}.md`))) return [];\n return [\n `[pr-gate] ${SOURCE} ${label}.subagent names no reviewer — ${AGENTS_DIR}/${subagent}.md does not exist, ` +\n `so nothing can spawn it and wp-finish-upsert-pr would block forever on a review-${subagent}.json that no reviewer can write. ` +\n `Create that agent file or fix the name.`,\n ];\n }\n}\n"]}
|