@webpieces/ai-hook-rules 0.3.189 → 0.3.191
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 +2 -2
- package/src/bin/setup.js +20 -4
- package/src/bin/setup.js.map +1 -1
- package/src/core/load-config.d.ts +1 -1
- package/src/core/load-config.js +1 -2
- package/src/core/load-config.js.map +1 -1
- package/src/core/rules/branch-creation-guard.d.ts +5 -2
- package/src/core/rules/branch-creation-guard.js +45 -8
- package/src/core/rules/branch-creation-guard.js.map +1 -1
- package/src/core/rules/merge-in-progress-guard.d.ts +1 -1
- package/src/core/rules/merge-in-progress-guard.js +5 -5
- package/src/core/rules/merge-in-progress-guard.js.map +1 -1
- package/src/core/rules/pr-creation-guard.js +9 -8
- package/src/core/rules/pr-creation-guard.js.map +1 -1
- package/src/core/runner.js +8 -8
- package/src/core/runner.js.map +1 -1
- package/templates/webpieces.config.seed.json +17 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/ai-hook-rules",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.191",
|
|
4
4
|
"description": "Pluggable write-time validation framework for AI coding agents (@webpieces/ai-hook-rules). Claude Code PreToolUse + openclaw before_tool_call adapters share one rule engine.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"directory": "packages/tooling/ai-hook-rules"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@webpieces/rules-config": "0.3.
|
|
35
|
+
"@webpieces/rules-config": "0.3.191"
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
package/src/bin/setup.js
CHANGED
|
@@ -7,6 +7,15 @@ const path = tslib_1.__importStar(require("path"));
|
|
|
7
7
|
const index_1 = require("../core/rules/index");
|
|
8
8
|
const HOOK_COMMAND = './node_modules/.bin/wp-ai-hook';
|
|
9
9
|
const CONFIG_FILENAME = 'webpieces.config.json';
|
|
10
|
+
// ignoreModifiedUntilEpoch is required on every rule (0 = active). A freshly seeded rule is OFF.
|
|
11
|
+
function seedRule() {
|
|
12
|
+
return { mode: 'OFF', ignoreModifiedUntilEpoch: 0 };
|
|
13
|
+
}
|
|
14
|
+
// pr-gate is a required top-level block; seed it OFF (opt-out) with a ready-to-use buildCommand so
|
|
15
|
+
// flipping it ON is a one-line edit.
|
|
16
|
+
function seedPrGate() {
|
|
17
|
+
return { mode: 'OFF', buildCommand: 'pnpm nx affected --target=ci --base=origin/main', gates: [] };
|
|
18
|
+
}
|
|
10
19
|
function settingsAlreadyHasHook(settingsPath) {
|
|
11
20
|
if (!fs.existsSync(settingsPath))
|
|
12
21
|
return false;
|
|
@@ -49,9 +58,9 @@ function seedConfig(projectRoot) {
|
|
|
49
58
|
}
|
|
50
59
|
const rules = {};
|
|
51
60
|
for (const name of index_1.builtInRuleNames) {
|
|
52
|
-
rules[name] =
|
|
61
|
+
rules[name] = seedRule();
|
|
53
62
|
}
|
|
54
|
-
const config = { rules, rulesDir: [] };
|
|
63
|
+
const config = { rules, rulesDir: [], 'pr-gate': seedPrGate() };
|
|
55
64
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 4) + '\n');
|
|
56
65
|
console.log(` [ai-hook-rules] Created ${CONFIG_FILENAME} with all rules set to OFF.`);
|
|
57
66
|
console.log(' Review and enable the rules you want by changing "mode" to "ON" or "MODIFIED_CODE" etc.');
|
|
@@ -67,15 +76,22 @@ function syncConfig(projectRoot) {
|
|
|
67
76
|
const added = [];
|
|
68
77
|
for (const name of index_1.builtInRuleNames) {
|
|
69
78
|
if (!Object.prototype.hasOwnProperty.call(config.rules, name)) {
|
|
70
|
-
config.rules[name] =
|
|
79
|
+
config.rules[name] = seedRule();
|
|
71
80
|
added.push(name);
|
|
72
81
|
}
|
|
73
82
|
}
|
|
74
|
-
|
|
83
|
+
let addedPrGate = false;
|
|
84
|
+
if (!Object.prototype.hasOwnProperty.call(config, 'pr-gate')) {
|
|
85
|
+
config['pr-gate'] = seedPrGate();
|
|
86
|
+
addedPrGate = true;
|
|
87
|
+
}
|
|
88
|
+
if (added.length === 0 && !addedPrGate) {
|
|
75
89
|
console.log(` [ai-hook-rules] ${CONFIG_FILENAME} is already up to date — no new rules to add.`);
|
|
76
90
|
return;
|
|
77
91
|
}
|
|
78
92
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 4) + '\n');
|
|
93
|
+
if (addedPrGate)
|
|
94
|
+
console.log(` [ai-hook-rules] Added the required "pr-gate" block (mode OFF) to ${CONFIG_FILENAME}.`);
|
|
79
95
|
console.log(` [ai-hook-rules] Added ${added.length} new rule(s) to ${CONFIG_FILENAME} (set to OFF):`);
|
|
80
96
|
for (const name of added) {
|
|
81
97
|
console.log(` - ${name}`);
|
package/src/bin/setup.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/setup.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/setup.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { builtInRuleNames } from '../core/rules/index';\n\nconst HOOK_COMMAND = './node_modules/.bin/wp-ai-hook';\nconst CONFIG_FILENAME = 'webpieces.config.json';\n\ninterface HookEntry {\n matcher: string;\n hooks: Array<{ type: string; command: string }>;\n}\n\ninterface ClaudeSettings {\n hooks?: {\n PreToolUse?: HookEntry[];\n };\n // webpieces-disable no-any-unknown -- opaque settings bag allows arbitrary key access\n [key: string]: unknown;\n}\n\ninterface RulesConfig {\n rules: Record<string, object>;\n rulesDir?: string[];\n 'pr-gate'?: object;\n}\n\n// ignoreModifiedUntilEpoch is required on every rule (0 = active). A freshly seeded rule is OFF.\nfunction seedRule(): object {\n return { mode: 'OFF', ignoreModifiedUntilEpoch: 0 };\n}\n\n// pr-gate is a required top-level block; seed it OFF (opt-out) with a ready-to-use buildCommand so\n// flipping it ON is a one-line edit.\nfunction seedPrGate(): object {\n return { mode: 'OFF', buildCommand: 'pnpm nx affected --target=ci --base=origin/main', gates: [] };\n}\n\nfunction settingsAlreadyHasHook(settingsPath: string): boolean {\n if (!fs.existsSync(settingsPath)) return false;\n const content = fs.readFileSync(settingsPath, 'utf8');\n return content.includes('wp-ai-hook');\n}\n\nfunction wireSettings(projectRoot: string): void {\n const claudeDir = path.join(projectRoot, '.claude');\n if (!fs.existsSync(claudeDir)) {\n console.log(' [ai-hook-rules] No .claude/ directory found — add the hook manually:');\n console.log(' In .claude/settings.json under hooks.PreToolUse, add:');\n console.log(` { \"matcher\": \"Write|Edit|MultiEdit|Bash\", \"hooks\": [{ \"type\": \"command\", \"command\": \"${HOOK_COMMAND}\" }] }`);\n return;\n }\n\n const settingsPath = path.join(claudeDir, 'settings.json');\n\n if (settingsAlreadyHasHook(settingsPath)) {\n console.log(' [ai-hook-rules] .claude/settings.json already has the hook — skipping.');\n return;\n }\n\n let settings: ClaudeSettings = {};\n if (fs.existsSync(settingsPath)) {\n settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')) as ClaudeSettings;\n }\n\n if (!settings.hooks) settings.hooks = {};\n if (!Array.isArray(settings.hooks.PreToolUse)) settings.hooks.PreToolUse = [];\n\n settings.hooks.PreToolUse.push({\n matcher: 'Write|Edit|MultiEdit|Bash',\n hooks: [{ type: 'command', command: HOOK_COMMAND }],\n });\n\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 4) + '\\n');\n console.log(` [ai-hook-rules] Wired ${HOOK_COMMAND} into .claude/settings.json`);\n}\n\nfunction seedConfig(projectRoot: string): void {\n const configPath = path.join(projectRoot, CONFIG_FILENAME);\n if (fs.existsSync(configPath)) {\n console.log(` [ai-hook-rules] ${CONFIG_FILENAME} already exists — run with --sync to add missing rules.`);\n return;\n }\n\n const rules: Record<string, object> = {};\n for (const name of builtInRuleNames) {\n rules[name] = seedRule();\n }\n\n const config: RulesConfig = { rules, rulesDir: [], 'pr-gate': seedPrGate() };\n fs.writeFileSync(configPath, JSON.stringify(config, null, 4) + '\\n');\n console.log(` [ai-hook-rules] Created ${CONFIG_FILENAME} with all rules set to OFF.`);\n console.log(' Review and enable the rules you want by changing \"mode\" to \"ON\" or \"MODIFIED_CODE\" etc.');\n}\n\nfunction syncConfig(projectRoot: string): void {\n const configPath = path.join(projectRoot, CONFIG_FILENAME);\n\n let config: RulesConfig = { rules: {}, rulesDir: [] };\n if (fs.existsSync(configPath)) {\n config = JSON.parse(fs.readFileSync(configPath, 'utf8')) as RulesConfig;\n }\n if (!config.rules) config.rules = {};\n\n const added: string[] = [];\n for (const name of builtInRuleNames) {\n if (!Object.prototype.hasOwnProperty.call(config.rules, name)) {\n config.rules[name] = seedRule();\n added.push(name);\n }\n }\n\n let addedPrGate = false;\n if (!Object.prototype.hasOwnProperty.call(config, 'pr-gate')) {\n config['pr-gate'] = seedPrGate();\n addedPrGate = true;\n }\n\n if (added.length === 0 && !addedPrGate) {\n console.log(` [ai-hook-rules] ${CONFIG_FILENAME} is already up to date — no new rules to add.`);\n return;\n }\n\n fs.writeFileSync(configPath, JSON.stringify(config, null, 4) + '\\n');\n if (addedPrGate) console.log(` [ai-hook-rules] Added the required \"pr-gate\" block (mode OFF) to ${CONFIG_FILENAME}.`);\n console.log(` [ai-hook-rules] Added ${added.length} new rule(s) to ${CONFIG_FILENAME} (set to OFF):`);\n for (const name of added) {\n console.log(` - ${name}`);\n }\n console.log(' Review each new rule and set \"mode\" to ON/MODIFIED_CODE/etc. as desired.');\n}\n\nexport function main(): void {\n const args = process.argv.slice(2);\n const isSync = args.includes('--sync');\n\n const projectRoot = process.cwd();\n\n if (isSync) {\n syncConfig(projectRoot);\n } else {\n seedConfig(projectRoot);\n console.log('');\n console.log(' To install the global Claude Code hook (one-time, per machine):');\n console.log(' ./node_modules/.bin/wp-setup-global-ai-hooks');\n }\n}\n\nif (require.main === module) {\n main();\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { findConfigFile, CONFIG_FILENAME } from '@webpieces/rules-config';
|
package/src/core/load-config.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CONFIG_FILENAME = exports.findConfigFile =
|
|
3
|
+
exports.CONFIG_FILENAME = exports.findConfigFile = void 0;
|
|
4
4
|
// Re-export the shared workspace config loader so ai-hooks and the Nx
|
|
5
5
|
// validate-code executor use the same webpieces.config.json.
|
|
6
6
|
var rules_config_1 = require("@webpieces/rules-config");
|
|
7
|
-
Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return rules_config_1.loadConfig; } });
|
|
8
7
|
Object.defineProperty(exports, "findConfigFile", { enumerable: true, get: function () { return rules_config_1.findConfigFile; } });
|
|
9
8
|
Object.defineProperty(exports, "CONFIG_FILENAME", { enumerable: true, get: function () { return rules_config_1.CONFIG_FILENAME; } });
|
|
10
9
|
//# sourceMappingURL=load-config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-config.ts"],"names":[],"mappings":";;;AAAA,sEAAsE;AACtE,6DAA6D;AAC7D,
|
|
1
|
+
{"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-config.ts"],"names":[],"mappings":";;;AAAA,sEAAsE;AACtE,6DAA6D;AAC7D,wDAA0E;AAAjE,8GAAA,cAAc,OAAA;AAAE,+GAAA,eAAe,OAAA","sourcesContent":["// Re-export the shared workspace config loader so ai-hooks and the Nx\n// validate-code executor use the same webpieces.config.json.\nexport { findConfigFile, CONFIG_FILENAME } from '@webpieces/rules-config';\n"]}
|
|
@@ -3,10 +3,13 @@ import type { BashContext, Violation } from '../types';
|
|
|
3
3
|
import { BashRuleBase } from '../rule-base';
|
|
4
4
|
export declare class BranchCreationGuardRule extends BashRuleBase<BranchCreationGuardConfig> {
|
|
5
5
|
constructor(config: BranchCreationGuardConfig);
|
|
6
|
-
readonly description = "Block new-branch creation when main is stale, or when
|
|
6
|
+
readonly description = "Block new-branch creation when main is stale, or when branching off a non-main branch.";
|
|
7
7
|
readonly defaultOptions: {
|
|
8
8
|
subBranchNaming: string;
|
|
9
|
+
branchFormat: string;
|
|
9
10
|
};
|
|
10
|
-
|
|
11
|
+
private get branchFormat();
|
|
12
|
+
private get subBranchNaming();
|
|
13
|
+
get fixHint(): readonly string[];
|
|
11
14
|
check(ctx: BashContext): readonly Violation[];
|
|
12
15
|
}
|
|
@@ -4,10 +4,11 @@ exports.BranchCreationGuardRule = void 0;
|
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const types_1 = require("../types");
|
|
6
6
|
const rule_base_1 = require("../rule-base");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
// Defaults used when the rule has no explicit value in webpieces.config.json.
|
|
8
|
+
// branchFormat is a human sentence telling the AI how to name a branch created off main; it is
|
|
9
|
+
// intentionally NOT the sub-branch convention (sub-branches are a separate, human-approved path).
|
|
10
|
+
const DEFAULT_BRANCH_FORMAT = 'Name it {whoami}/<short-feature-description> — lowercase, no version numbers, no sub/ prefix (e.g. dean/upgrade-webpieces)';
|
|
11
|
+
const DEFAULT_SUB_BRANCH_NAMING = 'feature/<ticket>/<short-description>';
|
|
11
12
|
const BRANCH_PATTERNS = [
|
|
12
13
|
/git\s+checkout\s+-[bB]\s+([^\s]+)/,
|
|
13
14
|
/git\s+switch\s+-[cC]\s+([^\s]+)/,
|
|
@@ -42,9 +43,34 @@ function checkMainIsUpToDate(ctx, requestedName) {
|
|
|
42
43
|
}
|
|
43
44
|
class BranchCreationGuardRule extends rule_base_1.BashRuleBase {
|
|
44
45
|
constructor(config) { super(config, 'branch-creation-guard'); }
|
|
45
|
-
description = 'Block new-branch creation when main is stale, or when
|
|
46
|
-
defaultOptions = {
|
|
47
|
-
|
|
46
|
+
description = 'Block new-branch creation when main is stale, or when branching off a non-main branch.';
|
|
47
|
+
defaultOptions = {
|
|
48
|
+
subBranchNaming: DEFAULT_SUB_BRANCH_NAMING,
|
|
49
|
+
branchFormat: DEFAULT_BRANCH_FORMAT,
|
|
50
|
+
};
|
|
51
|
+
get branchFormat() {
|
|
52
|
+
return this.config.branchFormat ?? DEFAULT_BRANCH_FORMAT;
|
|
53
|
+
}
|
|
54
|
+
get subBranchNaming() {
|
|
55
|
+
return this.config.subBranchNaming ?? DEFAULT_SUB_BRANCH_NAMING;
|
|
56
|
+
}
|
|
57
|
+
// Mode-aware fix hints. Branches off main follow branchFormat — never the sub-branch
|
|
58
|
+
// convention. The sub-branch affordance only appears under mode 'ON'; 'ON_NO_SUBBRANCHES'
|
|
59
|
+
// hard-blocks it and points instead at the ignoreModifiedUntilEpoch escape hatch.
|
|
60
|
+
get fixHint() {
|
|
61
|
+
const hints = [
|
|
62
|
+
"Run 'git checkout main && git pull origin main', then create your branch FROM main",
|
|
63
|
+
`Name a branch off main per branch-creation-guard.branchFormat: ${this.branchFormat}`,
|
|
64
|
+
];
|
|
65
|
+
if (this.config.mode === 'ON_NO_SUBBRANCHES') {
|
|
66
|
+
hints.push('Sub-branches (branching off another feature branch) are disabled. To temporarily allow one, set ' +
|
|
67
|
+
"branch-creation-guard.ignoreModifiedUntilEpoch to a future epoch in webpieces.config.json");
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
hints.push(`If you truly need a stacked sub-branch (requires human approval), name it per branch-creation-guard.subBranchNaming: ${this.subBranchNaming}`);
|
|
71
|
+
}
|
|
72
|
+
return hints;
|
|
73
|
+
}
|
|
48
74
|
check(ctx) {
|
|
49
75
|
const requestedName = extractBranchName(ctx.command);
|
|
50
76
|
if (!requestedName)
|
|
@@ -56,7 +82,18 @@ class BranchCreationGuardRule extends rule_base_1.BashRuleBase {
|
|
|
56
82
|
if (currentBranch === 'main') {
|
|
57
83
|
return checkMainIsUpToDate(ctx, requestedName);
|
|
58
84
|
}
|
|
59
|
-
|
|
85
|
+
// Not on main: creating this branch would stack it on a feature branch (a sub-branch).
|
|
86
|
+
if (this.config.mode === 'ON_NO_SUBBRANCHES') {
|
|
87
|
+
return [new types_1.Violation(1, truncate(ctx.command), `You are on '${currentBranch}', not main. You need to run ` +
|
|
88
|
+
`git checkout main && git pull && git checkout -b ${requestedName} ` +
|
|
89
|
+
`instead of branching from this branch!!! ${this.branchFormat}. ` +
|
|
90
|
+
`You can temporarily turn this off if you truly need a sub-branch by setting ` +
|
|
91
|
+
`branch-creation-guard.ignoreModifiedUntilEpoch (a future epoch) in webpieces.config.json.`)];
|
|
92
|
+
}
|
|
93
|
+
return [new types_1.Violation(1, truncate(ctx.command), `You are on '${currentBranch}', not main. Branches must be created from main: ` +
|
|
94
|
+
`git checkout main && git pull && git checkout -b ${requestedName}. ${this.branchFormat}. ` +
|
|
95
|
+
`If you truly need a stacked sub-branch (requires human approval), name it per ` +
|
|
96
|
+
`branch-creation-guard.subBranchNaming ('${this.subBranchNaming}').`)];
|
|
60
97
|
}
|
|
61
98
|
}
|
|
62
99
|
exports.BranchCreationGuardRule = BranchCreationGuardRule;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/branch-creation-guard.ts"],"names":[],"mappings":";;;AAAA,iDAAyC;AAKzC,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAM,
|
|
1
|
+
{"version":3,"file":"branch-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/branch-creation-guard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\n\nimport { BranchCreationGuardConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\n\n// Defaults used when the rule has no explicit value in webpieces.config.json.\n// branchFormat is a human sentence telling the AI how to name a branch created off main; it is\n// intentionally NOT the sub-branch convention (sub-branches are a separate, human-approved path).\nconst DEFAULT_BRANCH_FORMAT =\n 'Name it {whoami}/<short-feature-description> — lowercase, no version numbers, no sub/ prefix (e.g. dean/upgrade-webpieces)';\nconst DEFAULT_SUB_BRANCH_NAMING = 'feature/<ticket>/<short-description>';\n\nconst BRANCH_PATTERNS: RegExp[] = [\n /git\\s+checkout\\s+-[bB]\\s+([^\\s]+)/,\n /git\\s+switch\\s+-[cC]\\s+([^\\s]+)/,\n /git\\s+branch\\s+(?!-[dDmMrRla])([^\\s-][^\\s]*)/,\n];\n\nfunction extractBranchName(command: string): string | null {\n for (const pattern of BRANCH_PATTERNS) {\n const m = pattern.exec(command);\n if (m) return m[1];\n }\n return null;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nfunction checkMainIsUpToDate(ctx: BashContext, requestedName: string): readonly Violation[] {\n execSync('git fetch origin main --quiet', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n });\n const countStr = execSync('git rev-list HEAD..origin/main --count', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n const count = parseInt(countStr, 10);\n if (count > 0) {\n return [new V(\n 1,\n truncate(ctx.command),\n `Local main is ${count} commit(s) behind origin/main. Run 'git pull origin main' first, then retry creating branch '${requestedName}'.`,\n )];\n }\n return [];\n}\n\nexport class BranchCreationGuardRule extends BashRuleBase<BranchCreationGuardConfig> {\n constructor(config: BranchCreationGuardConfig) { super(config, 'branch-creation-guard'); }\n\n readonly description = 'Block new-branch creation when main is stale, or when branching off a non-main branch.';\n override readonly defaultOptions = {\n subBranchNaming: DEFAULT_SUB_BRANCH_NAMING,\n branchFormat: DEFAULT_BRANCH_FORMAT,\n };\n\n private get branchFormat(): string {\n return this.config.branchFormat ?? DEFAULT_BRANCH_FORMAT;\n }\n\n private get subBranchNaming(): string {\n return this.config.subBranchNaming ?? DEFAULT_SUB_BRANCH_NAMING;\n }\n\n // Mode-aware fix hints. Branches off main follow branchFormat — never the sub-branch\n // convention. The sub-branch affordance only appears under mode 'ON'; 'ON_NO_SUBBRANCHES'\n // hard-blocks it and points instead at the ignoreModifiedUntilEpoch escape hatch.\n get fixHint(): readonly string[] {\n const hints = [\n \"Run 'git checkout main && git pull origin main', then create your branch FROM main\",\n `Name a branch off main per branch-creation-guard.branchFormat: ${this.branchFormat}`,\n ];\n if (this.config.mode === 'ON_NO_SUBBRANCHES') {\n hints.push(\n 'Sub-branches (branching off another feature branch) are disabled. To temporarily allow one, set ' +\n \"branch-creation-guard.ignoreModifiedUntilEpoch to a future epoch in webpieces.config.json\",\n );\n } else {\n hints.push(\n `If you truly need a stacked sub-branch (requires human approval), name it per branch-creation-guard.subBranchNaming: ${this.subBranchNaming}`,\n );\n }\n return hints;\n }\n\n check(ctx: BashContext): readonly Violation[] {\n const requestedName = extractBranchName(ctx.command);\n if (!requestedName) return [];\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n if (currentBranch === 'main') {\n return checkMainIsUpToDate(ctx, requestedName);\n }\n\n // Not on main: creating this branch would stack it on a feature branch (a sub-branch).\n if (this.config.mode === 'ON_NO_SUBBRANCHES') {\n return [new V(\n 1,\n truncate(ctx.command),\n `You are on '${currentBranch}', not main. You need to run ` +\n `git checkout main && git pull && git checkout -b ${requestedName} ` +\n `instead of branching from this branch!!! ${this.branchFormat}. ` +\n `You can temporarily turn this off if you truly need a sub-branch by setting ` +\n `branch-creation-guard.ignoreModifiedUntilEpoch (a future epoch) in webpieces.config.json.`,\n )];\n }\n\n return [new V(\n 1,\n truncate(ctx.command),\n `You are on '${currentBranch}', not main. Branches must be created from main: ` +\n `git checkout main && git pull && git checkout -b ${requestedName}. ${this.branchFormat}. ` +\n `If you truly need a stacked sub-branch (requires human approval), name it per ` +\n `branch-creation-guard.subBranchNaming ('${this.subBranchNaming}').`,\n )];\n }\n}\n"]}
|
|
@@ -3,7 +3,7 @@ import type { BashContext, Violation } from '../types';
|
|
|
3
3
|
import { BashRuleBase } from '../rule-base';
|
|
4
4
|
export declare class MergeInProgressGuardRule extends BashRuleBase<MergeInProgressGuardConfig> {
|
|
5
5
|
constructor(config: MergeInProgressGuardConfig);
|
|
6
|
-
readonly description = "Block commit/push/merge/PR while a 3-point merge marker is unvalidated, forcing pnpm wp-
|
|
6
|
+
readonly description = "Block commit/push/merge/PR while a 3-point merge marker is unvalidated, forcing pnpm wp-finish-upsert-pr.";
|
|
7
7
|
readonly fixHint: readonly string[];
|
|
8
8
|
check(ctx: BashContext): readonly Violation[];
|
|
9
9
|
}
|
|
@@ -10,9 +10,9 @@ const rule_base_1 = require("../rule-base");
|
|
|
10
10
|
const FIX_HINT = [
|
|
11
11
|
'A 3-point merge is in progress and not yet validated.',
|
|
12
12
|
'Resolve the remaining conflicts in the working tree, then run:',
|
|
13
|
-
' pnpm wp-
|
|
14
|
-
'That scans for leftover conflict markers and runs the build; only when green does it',
|
|
15
|
-
'
|
|
13
|
+
' pnpm wp-finish-upsert-pr',
|
|
14
|
+
'That scans for leftover conflict markers and runs the build; only when green does it commit,',
|
|
15
|
+
'unblock commit/push/PR, render the dashboard, and create/update the PR.',
|
|
16
16
|
];
|
|
17
17
|
// Returns the path of the first UNVALIDATED merge marker found, or null. We detect validation
|
|
18
18
|
// by a raw substring (no JSON.parse) so a malformed marker can never crash the guard.
|
|
@@ -46,7 +46,7 @@ function truncate(s) {
|
|
|
46
46
|
}
|
|
47
47
|
class MergeInProgressGuardRule extends rule_base_1.BashRuleBase {
|
|
48
48
|
constructor(config) { super(config, 'merge-in-progress-guard'); }
|
|
49
|
-
description = 'Block commit/push/merge/PR while a 3-point merge marker is unvalidated, forcing pnpm wp-
|
|
49
|
+
description = 'Block commit/push/merge/PR while a 3-point merge marker is unvalidated, forcing pnpm wp-finish-upsert-pr.';
|
|
50
50
|
fixHint = FIX_HINT;
|
|
51
51
|
check(ctx) {
|
|
52
52
|
if (!isBlockedDuringMerge(ctx.command))
|
|
@@ -57,7 +57,7 @@ class MergeInProgressGuardRule extends rule_base_1.BashRuleBase {
|
|
|
57
57
|
return [new types_1.Violation(1, truncate(ctx.command), [
|
|
58
58
|
'A merge is in progress and not yet validated — this command is blocked.',
|
|
59
59
|
`Marker: ${marker}`,
|
|
60
|
-
'Finish resolving conflicts, then run: pnpm wp-
|
|
60
|
+
'Finish resolving conflicts, then run: pnpm wp-finish-upsert-pr',
|
|
61
61
|
].join('\n'))];
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-in-progress-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/merge-in-progress-guard.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAE7B,0DAAkI;AAGlI,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAM,QAAQ,GAAsB;IAChC,uDAAuD;IACvD,gEAAgE;IAChE,
|
|
1
|
+
{"version":3,"file":"merge-in-progress-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/merge-in-progress-guard.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAE7B,0DAAkI;AAGlI,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAM,QAAQ,GAAsB;IAChC,uDAAuD;IACvD,gEAAgE;IAChE,4BAA4B;IAC5B,8FAA8F;IAC9F,yEAAyE;CAC5E,CAAC;AAEF,8FAA8F;AAC9F,sFAAsF;AACtF,SAAS,oBAAoB,CAAC,aAAqB;IAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,gCAAiB,CAAC,CAAC;IAC3D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,+BAAgB,CAAC;YAAE,SAAS;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,qCAAsB,CAAC,CAAC;QAChE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,SAAS;QACrC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,MAAM,CAAC;IAC3D,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,kEAAkE;AAClE,SAAS,oBAAoB,CAAC,GAAW;IACrC,OAAO,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC;WAC5B,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC;WAC1B,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC;WAC3B,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC;WAC5B,mCAAmC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,MAAa,wBAAyB,SAAQ,wBAAwC;IAClF,YAAY,MAAkC,IAAI,KAAK,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC;IAEpF,WAAW,GAAG,2GAA2G,CAAC;IAC1H,OAAO,GAAG,QAAQ,CAAC;IAE5B,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAClD,MAAM,MAAM,GAAG,oBAAoB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;gBACI,yEAAyE;gBACzE,WAAW,MAAM,EAAE;gBACnB,iEAAiE;aACpE,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ;AApBD,4DAoBC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { WEBPIECES_TMP_DIR, MERGE_DIR_PREFIX, MERGE_IN_PROGRESS_FILE, MergeInProgressGuardConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\n\nconst FIX_HINT: readonly string[] = [\n 'A 3-point merge is in progress and not yet validated.',\n 'Resolve the remaining conflicts in the working tree, then run:',\n ' pnpm wp-finish-upsert-pr',\n 'That scans for leftover conflict markers and runs the build; only when green does it commit,',\n 'unblock commit/push/PR, render the dashboard, and create/update the PR.',\n];\n\n// Returns the path of the first UNVALIDATED merge marker found, or null. We detect validation\n// by a raw substring (no JSON.parse) so a malformed marker can never crash the guard.\nfunction findUnvalidatedMerge(workspaceRoot: string): string | null {\n const tmpDir = path.join(workspaceRoot, WEBPIECES_TMP_DIR);\n if (!fs.existsSync(tmpDir)) return null;\n for (const entry of fs.readdirSync(tmpDir)) {\n if (!entry.startsWith(MERGE_DIR_PREFIX)) continue;\n const marker = path.join(tmpDir, entry, MERGE_IN_PROGRESS_FILE);\n if (!fs.existsSync(marker)) continue;\n const raw = fs.readFileSync(marker, 'utf8');\n if (!/\"validated\"\\s*:\\s*true/.test(raw)) return marker;\n }\n return null;\n}\n\n// Operations that would let an agent route around the merge gate.\nfunction isBlockedDuringMerge(cmd: string): boolean {\n return /\\bgit\\s+commit\\b/.test(cmd)\n || /\\bgit\\s+push\\b/.test(cmd)\n || /\\bgit\\s+merge\\b/.test(cmd)\n || /\\bgit\\s+rebase\\b/.test(cmd)\n || /\\bgh\\s+pr\\s+(create|edit|merge)\\b/.test(cmd);\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport class MergeInProgressGuardRule extends BashRuleBase<MergeInProgressGuardConfig> {\n constructor(config: MergeInProgressGuardConfig) { super(config, 'merge-in-progress-guard'); }\n\n readonly description = 'Block commit/push/merge/PR while a 3-point merge marker is unvalidated, forcing pnpm wp-finish-upsert-pr.';\n readonly fixHint = FIX_HINT;\n\n check(ctx: BashContext): readonly Violation[] {\n if (!isBlockedDuringMerge(ctx.command)) return [];\n const marker = findUnvalidatedMerge(ctx.workspaceRoot);\n if (!marker) return [];\n return [new V(\n 1,\n truncate(ctx.command),\n [\n 'A merge is in progress and not yet validated — this command is blocked.',\n `Marker: ${marker}`,\n 'Finish resolving conflicts, then run: pnpm wp-finish-upsert-pr',\n ].join('\\n'),\n )];\n }\n}\n"]}
|
|
@@ -3,19 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.PrCreationGuardRule = void 0;
|
|
4
4
|
const types_1 = require("../types");
|
|
5
5
|
const rule_base_1 = require("../rule-base");
|
|
6
|
-
const DEFAULT_UPSERT_PR_COMMAND = 'pnpm wp-upsert-pr';
|
|
6
|
+
const DEFAULT_UPSERT_PR_COMMAND = 'pnpm wp-start-upsert-pr';
|
|
7
7
|
function fixHintFor(upsertPrCommand) {
|
|
8
8
|
return [
|
|
9
|
-
'Direct PR creation is blocked. Create or update a PR ONLY via the gated
|
|
9
|
+
'Direct PR creation is blocked. Create or update a PR ONLY via the gated flow:',
|
|
10
10
|
` ${upsertPrCommand}`,
|
|
11
|
-
'It updates the branch from main (3-point merge)
|
|
12
|
-
'
|
|
13
|
-
'
|
|
11
|
+
'It updates the branch from main (3-point merge) and runs the real build (nx affected), then',
|
|
12
|
+
'instructs you to write review.json and run `pnpm wp-finish-upsert-pr`, which assembles the',
|
|
13
|
+
'dashboard and creates/updates the PR itself. A failing build = no PR.',
|
|
14
|
+
'There is nothing to paste or attest to; the commands do the work.',
|
|
14
15
|
];
|
|
15
16
|
}
|
|
16
|
-
// Detect every way an agent could open/update a PR directly, so the ONLY path left is
|
|
17
|
-
//
|
|
18
|
-
// never sees). Read-only `gh pr list` / `gh api .../pulls` GET are intentionally allowed.
|
|
17
|
+
// Detect every way an agent could open/update a PR directly, so the ONLY path left is the gated
|
|
18
|
+
// flow (wp-start-upsert-pr → wp-finish-upsert-pr, whose internal `gh pr create` runs as a child
|
|
19
|
+
// process the hook never sees). Read-only `gh pr list` / `gh api .../pulls` GET are intentionally allowed.
|
|
19
20
|
function isDirectPrCreation(cmd) {
|
|
20
21
|
if (/\bgh\s+pr\s+(create|edit)\b/.test(cmd))
|
|
21
22
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-creation-guard.ts"],"names":[],"mappings":";;;AAGA,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAM,yBAAyB,GAAG,
|
|
1
|
+
{"version":3,"file":"pr-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-creation-guard.ts"],"names":[],"mappings":";;;AAGA,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAM,yBAAyB,GAAG,yBAAyB,CAAC;AAE5D,SAAS,UAAU,CAAC,eAAuB;IACvC,OAAO;QACH,+EAA+E;QAC/E,KAAK,eAAe,EAAE;QACtB,6FAA6F;QAC7F,4FAA4F;QAC5F,uEAAuE;QACvE,mEAAmE;KACtE,CAAC;AACN,CAAC;AAED,gGAAgG;AAChG,gGAAgG;AAChG,2GAA2G;AAC3G,SAAS,kBAAkB,CAAC,GAAW;IACnC,IAAI,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,UAAU,GAAG,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3D,IAAI,UAAU,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC9I,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,SAAS,GAAG,+CAA+C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5E,IAAI,SAAS,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC1G,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,MAAa,mBAAoB,SAAQ,wBAAmC;IACvD,eAAe,CAAS;IAEzC,YAAY,MAA6B;QACrC,KAAK,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;QACnC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,eAAe,IAAI,yBAAyB,CAAC;IAC/E,CAAC;IAEQ,WAAW,GAAG,2GAA2G,CAAC;IACnI,IAAI,OAAO,KAAwB,OAAO,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAE7E,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAChD,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;gBACI,uCAAuC;gBACvC,6EAA6E;gBAC7E,KAAK,IAAI,CAAC,eAAe,EAAE;aAC9B,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ;AAvBD,kDAuBC","sourcesContent":["import { PrCreationGuardConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\n\nconst DEFAULT_UPSERT_PR_COMMAND = 'pnpm wp-start-upsert-pr';\n\nfunction fixHintFor(upsertPrCommand: string): readonly string[] {\n return [\n 'Direct PR creation is blocked. Create or update a PR ONLY via the gated flow:',\n ` ${upsertPrCommand}`,\n 'It updates the branch from main (3-point merge) and runs the real build (nx affected), then',\n 'instructs you to write review.json and run `pnpm wp-finish-upsert-pr`, which assembles the',\n 'dashboard and creates/updates the PR itself. A failing build = no PR.',\n 'There is nothing to paste or attest to; the commands do the work.',\n ];\n}\n\n// Detect every way an agent could open/update a PR directly, so the ONLY path left is the gated\n// flow (wp-start-upsert-pr → wp-finish-upsert-pr, whose internal `gh pr create` runs as a child\n// process the hook never sees). Read-only `gh pr list` / `gh api .../pulls` GET are intentionally allowed.\nfunction isDirectPrCreation(cmd: string): boolean {\n if (/\\bgh\\s+pr\\s+(create|edit)\\b/.test(cmd)) return true;\n\n const ghApiPulls = /\\bgh\\s+api\\b[^\\n]*\\/pulls\\b/.test(cmd);\n if (ghApiPulls && (/--method\\s+POST/i.test(cmd) || /-X\\s+POST/i.test(cmd) || /\\s-f\\b/.test(cmd) || /\\s-F\\b/.test(cmd) || /--field\\b/.test(cmd))) {\n return true;\n }\n\n const curlPulls = /\\bcurl\\b[^\\n]*api\\.github\\.com[^\\n]*\\/pulls\\b/.test(cmd);\n if (curlPulls && (/-X\\s*POST/i.test(cmd) || /--request\\s+POST/i.test(cmd) || /(\\s-d\\b|--data\\b)/.test(cmd))) {\n return true;\n }\n return false;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport class PrCreationGuardRule extends BashRuleBase<PrCreationGuardConfig> {\n private readonly upsertPrCommand: string;\n\n constructor(config: PrCreationGuardConfig) {\n super(config, 'pr-creation-guard');\n this.upsertPrCommand = config.upsertPrCommand ?? DEFAULT_UPSERT_PR_COMMAND;\n }\n\n readonly description = 'Block direct PR creation/edit (gh pr / gh api / curl) so PRs go only through the gated upsert-pr command.';\n get fixHint(): readonly string[] { return fixHintFor(this.upsertPrCommand); }\n\n check(ctx: BashContext): readonly Violation[] {\n if (!isDirectPrCreation(ctx.command)) return [];\n return [new V(\n 1,\n truncate(ctx.command),\n [\n 'Direct PR creation/update is blocked.',\n 'Use the gated command instead — it runs the build and builds the dashboard:',\n ` ${this.upsertPrCommand}`,\n ].join('\\n'),\n )];\n }\n}\n"]}
|
package/src/core/runner.js
CHANGED
|
@@ -17,18 +17,18 @@ function run(toolKind, input, cwd) {
|
|
|
17
17
|
return runInternal(toolKind, input, cwd);
|
|
18
18
|
}
|
|
19
19
|
function runInternal(toolKind, input, cwd) {
|
|
20
|
-
const loaded = (0, rules_config_1.
|
|
21
|
-
if (
|
|
20
|
+
const loaded = (0, rules_config_1.loadAndValidate)(cwd);
|
|
21
|
+
if (loaded.configPath === null)
|
|
22
22
|
return new types_1.BlockedResult(CONFIG_MISSING_REPORT);
|
|
23
23
|
const workspaceRoot = path.dirname(loaded.configPath);
|
|
24
24
|
// Always allow edits to webpieces.config.json — it's the fix target when out of sync
|
|
25
25
|
if (path.resolve(input.filePath) === path.resolve(loaded.configPath)) {
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
|
-
const rules = (0, load_rules_1.loadRules)(loaded.
|
|
28
|
+
const rules = (0, load_rules_1.loadRules)(loaded.rulesConfig, workspaceRoot);
|
|
29
29
|
if (rules.length === 0)
|
|
30
30
|
return null;
|
|
31
|
-
const outOfSync = checkConfigSync(rules, loaded.
|
|
31
|
+
const outOfSync = checkConfigSync(rules, loaded.rulesConfig);
|
|
32
32
|
if (outOfSync)
|
|
33
33
|
return outOfSync;
|
|
34
34
|
const contexts = (0, build_context_1.buildContexts)(toolKind, input, workspaceRoot);
|
|
@@ -45,14 +45,14 @@ function runBash(command, cwd) {
|
|
|
45
45
|
return runBashInternal(command, cwd);
|
|
46
46
|
}
|
|
47
47
|
function runBashInternal(command, cwd) {
|
|
48
|
-
const loaded = (0, rules_config_1.
|
|
49
|
-
if (
|
|
48
|
+
const loaded = (0, rules_config_1.loadAndValidate)(cwd);
|
|
49
|
+
if (loaded.configPath === null)
|
|
50
50
|
return new types_1.BlockedResult(CONFIG_MISSING_REPORT);
|
|
51
51
|
const workspaceRoot = path.dirname(loaded.configPath);
|
|
52
|
-
const rules = (0, load_rules_1.loadRules)(loaded.
|
|
52
|
+
const rules = (0, load_rules_1.loadRules)(loaded.rulesConfig, workspaceRoot);
|
|
53
53
|
if (rules.length === 0)
|
|
54
54
|
return null;
|
|
55
|
-
const outOfSync = checkConfigSync(rules, loaded.
|
|
55
|
+
const outOfSync = checkConfigSync(rules, loaded.rulesConfig);
|
|
56
56
|
if (outOfSync)
|
|
57
57
|
return outOfSync;
|
|
58
58
|
const ctx = (0, build_context_1.buildBashContext)(command, workspaceRoot);
|
package/src/core/runner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/runner.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\n\nimport { loadWebpiecesRulesConfig, WebpiecesRulesConfig } from '@webpieces/rules-config';\n\nimport { buildContexts, buildBashContext } from './build-context';\nimport { loadRules, globMatches } from './load-rules';\nimport { toError } from './to-error';\nimport { formatReport } from './report';\nimport {\n ToolKind, NormalizedToolInput, BlockedResult,\n Rule, Violation, RuleGroup,\n EditContext, FileContext, BashContext,\n} from './types';\n\nconst CONFIG_MISSING_REPORT =\n 'webpieces.config.json not found.\\n' +\n 'Tell the human: run `./node_modules/.bin/wp-setup-ai-hooks` to initialize the project configuration.\\n' +\n 'Do not proceed until the human has done this.';\n\nexport function run(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n cwd: string,\n): BlockedResult | null {\n return runInternal(toolKind, input, cwd);\n}\n\nfunction runInternal(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n cwd: string,\n): BlockedResult | null {\n const loaded = loadWebpiecesRulesConfig(cwd);\n if (!loaded) return new BlockedResult(CONFIG_MISSING_REPORT);\n\n const workspaceRoot = path.dirname(loaded.configPath);\n\n // Always allow edits to webpieces.config.json — it's the fix target when out of sync\n if (path.resolve(input.filePath) === path.resolve(loaded.configPath)) {\n return null;\n }\n\n const rules = loadRules(loaded.config, workspaceRoot);\n if (rules.length === 0) return null;\n\n const outOfSync = checkConfigSync(rules, loaded.config);\n if (outOfSync) return outOfSync;\n\n const contexts = buildContexts(toolKind, input, workspaceRoot);\n const relativePath = path.relative(workspaceRoot, input.filePath);\n\n const editGroups = runEditRules(rules, contexts.editContexts);\n const fileGroups = runFileRules(rules, contexts.fileContext);\n const allGroups = [...editGroups, ...fileGroups];\n\n if (allGroups.length === 0) return null;\n\n const report = formatReport(relativePath, allGroups);\n return new BlockedResult(report);\n}\n\nexport function runBash(command: string, cwd: string): BlockedResult | null {\n return runBashInternal(command, cwd);\n}\n\nfunction runBashInternal(command: string, cwd: string): BlockedResult | null {\n const loaded = loadWebpiecesRulesConfig(cwd);\n if (!loaded) return new BlockedResult(CONFIG_MISSING_REPORT);\n\n const workspaceRoot = path.dirname(loaded.configPath);\n const rules = loadRules(loaded.config, workspaceRoot);\n if (rules.length === 0) return null;\n\n const outOfSync = checkConfigSync(rules, loaded.config);\n if (outOfSync) return outOfSync;\n\n const ctx = buildBashContext(command, workspaceRoot);\n const groups = runBashRules(rules, ctx);\n if (groups.length === 0) return null;\n\n const report = formatReport('<bash>', groups);\n return new BlockedResult(report);\n}\n\n// The set of rule names explicitly present in webpieces.config.json (every key except rulesDir).\nfunction configuredRuleNames(config: WebpiecesRulesConfig): ReadonlySet<string> {\n return new Set(Object.keys(config).filter((k: string) => k !== 'rulesDir'));\n}\n\nfunction checkConfigSync(rules: readonly Rule[], config: WebpiecesRulesConfig): BlockedResult | null {\n const configured = configuredRuleNames(config);\n const unconfiguredRules = rules.filter((r: Rule) => !configured.has(r.name));\n if (unconfiguredRules.length === 0) return null;\n\n const lines = [\n 'webpieces.config.json is out of sync — new built-in rules are present that have no entry in webpieces.config.json.',\n '',\n 'Tell the human: the following rules need to be configured. Ask for each one:',\n ' - Should this rule be ON, OFF, MODIFIED_CODE, or MODIFIED_FILES?',\n ' - What values do you want for the options listed below?',\n 'Then update webpieces.config.json and retry.',\n '',\n 'Do NOT proceed until webpieces.config.json has an entry for every rule below.',\n '',\n ];\n\n for (const rule of unconfiguredRules) {\n lines.push(`--- ${rule.name} ---`);\n lines.push(`Description: ${rule.description}`);\n const opts = rule.defaultOptions;\n const optKeys = Object.keys(opts);\n if (optKeys.length > 0) {\n lines.push(`Available options (suggested defaults shown):`);\n for (const key of optKeys) {\n lines.push(` ${key}: ${JSON.stringify(opts[key])}`);\n }\n } else {\n lines.push('Available options: none beyond mode');\n }\n lines.push(`Example entry for webpieces.config.json:`);\n lines.push(` \"${rule.name}\": { \"mode\": \"ON\" }`);\n lines.push('');\n }\n\n return new BlockedResult(lines.join('\\n'));\n}\n\n// N-legs pattern: each rule runs independently; crash → visible violation so AI sees it, not silent []\nfunction runRuleCheck(rule: Rule, ctx: EditContext | FileContext | BashContext): readonly Violation[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return rule.check(ctx);\n } catch (err: unknown) {\n const error = toError(err);\n return [new Violation(0, '', `Rule '${rule.name}' crashed: ${error.message}`)];\n }\n}\n\nfunction ruleMatchesFile(rule: Rule, relativePath: string): boolean {\n for (const pattern of rule.files) {\n if (globMatches(pattern, relativePath)) return true;\n }\n return false;\n}\n\nfunction runBashRules(rules: readonly Rule[], bashContext: BashContext): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'bash') continue;\n if (!rule.shouldRun()) continue;\n const vs = runRuleCheck(rule, bashContext);\n if (vs.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], [...vs],\n ));\n }\n }\n return groups;\n}\n\nfunction runEditRules(rules: readonly Rule[], editContexts: readonly EditContext[]): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'edit') continue;\n if (!rule.shouldRun()) continue;\n const allViolations: Violation[] = [];\n for (const ctx of editContexts) {\n if (!ruleMatchesFile(rule, ctx.relativePath)) continue;\n const vs = runRuleCheck(rule, ctx);\n for (const v of vs) {\n const copy = new Violation(v.line, v.snippet, v.message);\n copy.editIndex = ctx.editIndex;\n copy.editCount = ctx.editCount;\n allViolations.push(copy);\n }\n }\n if (allViolations.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], allViolations,\n ));\n }\n }\n return groups;\n}\n\nfunction runFileRules(rules: readonly Rule[], fileContext: FileContext): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'file') continue;\n if (!rule.shouldRun()) continue;\n if (!ruleMatchesFile(rule, fileContext.relativePath)) continue;\n const vs = runRuleCheck(rule, fileContext);\n if (vs.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], [...vs],\n ));\n }\n }\n return groups;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/runner.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\n\nimport { loadAndValidate, WebpiecesRulesConfig } from '@webpieces/rules-config';\n\nimport { buildContexts, buildBashContext } from './build-context';\nimport { loadRules, globMatches } from './load-rules';\nimport { toError } from './to-error';\nimport { formatReport } from './report';\nimport {\n ToolKind, NormalizedToolInput, BlockedResult,\n Rule, Violation, RuleGroup,\n EditContext, FileContext, BashContext,\n} from './types';\n\nconst CONFIG_MISSING_REPORT =\n 'webpieces.config.json not found.\\n' +\n 'Tell the human: run `./node_modules/.bin/wp-setup-ai-hooks` to initialize the project configuration.\\n' +\n 'Do not proceed until the human has done this.';\n\nexport function run(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n cwd: string,\n): BlockedResult | null {\n return runInternal(toolKind, input, cwd);\n}\n\nfunction runInternal(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n cwd: string,\n): BlockedResult | null {\n const loaded = loadAndValidate(cwd);\n if (loaded.configPath === null) return new BlockedResult(CONFIG_MISSING_REPORT);\n\n const workspaceRoot = path.dirname(loaded.configPath);\n\n // Always allow edits to webpieces.config.json — it's the fix target when out of sync\n if (path.resolve(input.filePath) === path.resolve(loaded.configPath)) {\n return null;\n }\n\n const rules = loadRules(loaded.rulesConfig, workspaceRoot);\n if (rules.length === 0) return null;\n\n const outOfSync = checkConfigSync(rules, loaded.rulesConfig);\n if (outOfSync) return outOfSync;\n\n const contexts = buildContexts(toolKind, input, workspaceRoot);\n const relativePath = path.relative(workspaceRoot, input.filePath);\n\n const editGroups = runEditRules(rules, contexts.editContexts);\n const fileGroups = runFileRules(rules, contexts.fileContext);\n const allGroups = [...editGroups, ...fileGroups];\n\n if (allGroups.length === 0) return null;\n\n const report = formatReport(relativePath, allGroups);\n return new BlockedResult(report);\n}\n\nexport function runBash(command: string, cwd: string): BlockedResult | null {\n return runBashInternal(command, cwd);\n}\n\nfunction runBashInternal(command: string, cwd: string): BlockedResult | null {\n const loaded = loadAndValidate(cwd);\n if (loaded.configPath === null) return new BlockedResult(CONFIG_MISSING_REPORT);\n\n const workspaceRoot = path.dirname(loaded.configPath);\n const rules = loadRules(loaded.rulesConfig, workspaceRoot);\n if (rules.length === 0) return null;\n\n const outOfSync = checkConfigSync(rules, loaded.rulesConfig);\n if (outOfSync) return outOfSync;\n\n const ctx = buildBashContext(command, workspaceRoot);\n const groups = runBashRules(rules, ctx);\n if (groups.length === 0) return null;\n\n const report = formatReport('<bash>', groups);\n return new BlockedResult(report);\n}\n\n// The set of rule names explicitly present in webpieces.config.json (every key except rulesDir).\nfunction configuredRuleNames(config: WebpiecesRulesConfig): ReadonlySet<string> {\n return new Set(Object.keys(config).filter((k: string) => k !== 'rulesDir'));\n}\n\nfunction checkConfigSync(rules: readonly Rule[], config: WebpiecesRulesConfig): BlockedResult | null {\n const configured = configuredRuleNames(config);\n const unconfiguredRules = rules.filter((r: Rule) => !configured.has(r.name));\n if (unconfiguredRules.length === 0) return null;\n\n const lines = [\n 'webpieces.config.json is out of sync — new built-in rules are present that have no entry in webpieces.config.json.',\n '',\n 'Tell the human: the following rules need to be configured. Ask for each one:',\n ' - Should this rule be ON, OFF, MODIFIED_CODE, or MODIFIED_FILES?',\n ' - What values do you want for the options listed below?',\n 'Then update webpieces.config.json and retry.',\n '',\n 'Do NOT proceed until webpieces.config.json has an entry for every rule below.',\n '',\n ];\n\n for (const rule of unconfiguredRules) {\n lines.push(`--- ${rule.name} ---`);\n lines.push(`Description: ${rule.description}`);\n const opts = rule.defaultOptions;\n const optKeys = Object.keys(opts);\n if (optKeys.length > 0) {\n lines.push(`Available options (suggested defaults shown):`);\n for (const key of optKeys) {\n lines.push(` ${key}: ${JSON.stringify(opts[key])}`);\n }\n } else {\n lines.push('Available options: none beyond mode');\n }\n lines.push(`Example entry for webpieces.config.json:`);\n lines.push(` \"${rule.name}\": { \"mode\": \"ON\" }`);\n lines.push('');\n }\n\n return new BlockedResult(lines.join('\\n'));\n}\n\n// N-legs pattern: each rule runs independently; crash → visible violation so AI sees it, not silent []\nfunction runRuleCheck(rule: Rule, ctx: EditContext | FileContext | BashContext): readonly Violation[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return rule.check(ctx);\n } catch (err: unknown) {\n const error = toError(err);\n return [new Violation(0, '', `Rule '${rule.name}' crashed: ${error.message}`)];\n }\n}\n\nfunction ruleMatchesFile(rule: Rule, relativePath: string): boolean {\n for (const pattern of rule.files) {\n if (globMatches(pattern, relativePath)) return true;\n }\n return false;\n}\n\nfunction runBashRules(rules: readonly Rule[], bashContext: BashContext): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'bash') continue;\n if (!rule.shouldRun()) continue;\n const vs = runRuleCheck(rule, bashContext);\n if (vs.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], [...vs],\n ));\n }\n }\n return groups;\n}\n\nfunction runEditRules(rules: readonly Rule[], editContexts: readonly EditContext[]): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'edit') continue;\n if (!rule.shouldRun()) continue;\n const allViolations: Violation[] = [];\n for (const ctx of editContexts) {\n if (!ruleMatchesFile(rule, ctx.relativePath)) continue;\n const vs = runRuleCheck(rule, ctx);\n for (const v of vs) {\n const copy = new Violation(v.line, v.snippet, v.message);\n copy.editIndex = ctx.editIndex;\n copy.editCount = ctx.editCount;\n allViolations.push(copy);\n }\n }\n if (allViolations.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], allViolations,\n ));\n }\n }\n return groups;\n}\n\nfunction runFileRules(rules: readonly Rule[], fileContext: FileContext): readonly RuleGroup[] {\n const groups: RuleGroup[] = [];\n for (const rule of rules) {\n if (rule.scope !== 'file') continue;\n if (!rule.shouldRun()) continue;\n if (!ruleMatchesFile(rule, fileContext.relativePath)) continue;\n const vs = runRuleCheck(rule, fileContext);\n if (vs.length > 0) {\n groups.push(new RuleGroup(\n rule.name, rule.description, [...rule.fixHint], [...vs],\n ));\n }\n }\n return groups;\n}\n"]}
|
|
@@ -12,5 +12,21 @@
|
|
|
12
12
|
"no-unmanaged-exceptions": {},
|
|
13
13
|
"nx-wiring": { "mode": "ON" }
|
|
14
14
|
},
|
|
15
|
-
"rulesDir": []
|
|
15
|
+
"rulesDir": [],
|
|
16
|
+
"pr-gate": {
|
|
17
|
+
"mode": "ON",
|
|
18
|
+
"buildCommandWhy": "--base is the FORK POINT via $(git merge-base origin/main HEAD), NOT origin/main: basing on origin/main marks projects from other people's already-merged PRs as 'affected' (your branch still has their pre-merge versions), wasting rebuilds. The fork point scopes affected to only your branch's work. The $(...) resolves because the gate runs with shell:true.",
|
|
19
|
+
"buildCommand": "pnpm nx affected --target=ci --base=$(git merge-base origin/main HEAD)",
|
|
20
|
+
"gatesWhy": "color = the dashboard color shown WHEN a gate's patterns match a changed file (green is implicit when nothing matches; color is purely visual and never blocks the PR — only the build gate can). disabled:true gates are kept-in-file examples (JSON has no comments) you flip to false and tune for your project.",
|
|
21
|
+
"gates": [
|
|
22
|
+
{ "name": "API Changed", "patterns": ["libraries/apis/**", "**/*Api.ts"], "color": "yellow" },
|
|
23
|
+
{ "name": "Config Files Changed", "patterns": ["**/package.json", "**/tsconfig*.json", "nx.json", "**/*.config.*"], "color": "yellow" },
|
|
24
|
+
{ "name": "Dependency Graph Changed", "patterns": ["architecture/dependencies.json"], "color": "yellow" },
|
|
25
|
+
{ "name": "Claude / Rules Changed", "patterns": ["**/CLAUDE.md", "**/claude.*.md", ".claude/**", "webpieces.config.json"], "color": "yellow" },
|
|
26
|
+
{ "name": "Package.json Changed", "patterns": ["**/package.json"], "color": "yellow", "disabled": true },
|
|
27
|
+
{ "name": "Authentication Changed", "patterns": ["**/authentication.ts"], "color": "yellow", "disabled": true },
|
|
28
|
+
{ "name": "DB Schema Changed", "patterns": ["**/schema.prisma"], "color": "red", "disabled": true },
|
|
29
|
+
{ "name": "Migration Files", "patterns": ["**/migrations/**"], "color": "red", "disabled": true }
|
|
30
|
+
]
|
|
31
|
+
}
|
|
16
32
|
}
|