@webpieces/ai-hook-rules 0.4.843 → 0.4.845
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/README.md +3 -1
- package/package.json +10 -5
- package/src/adapters/hook-app-fixtures.js +5 -2
- package/src/adapters/hook-app-fixtures.js.map +1 -1
- package/src/configs/rule-configs.d.ts +23 -0
- package/src/configs/rule-configs.js +38 -0
- package/src/configs/rule-configs.js.map +1 -0
- package/src/core/load-rules.d.ts +1 -1
- package/src/core/load-rules.js.map +1 -1
- package/src/core/rules/catch-error-pattern.d.ts +3 -3
- package/src/core/rules/catch-error-pattern.js +1 -1
- package/src/core/rules/catch-error-pattern.js.map +1 -1
- package/src/core/rules/max-file-lines.d.ts +3 -3
- package/src/core/rules/max-file-lines.js.map +1 -1
- package/src/core/rules/no-any-unknown.d.ts +3 -3
- package/src/core/rules/no-any-unknown.js +1 -2
- package/src/core/rules/no-any-unknown.js.map +1 -1
- package/src/core/rules/no-custom-css.d.ts +3 -3
- package/src/core/rules/no-custom-css.js +1 -1
- package/src/core/rules/no-custom-css.js.map +1 -1
- package/src/core/rules/no-destructure.d.ts +3 -3
- package/src/core/rules/no-destructure.js +1 -1
- package/src/core/rules/no-destructure.js.map +1 -1
- package/src/core/rules/no-implicit-any.d.ts +3 -3
- package/src/core/rules/no-implicit-any.js +1 -2
- package/src/core/rules/no-implicit-any.js.map +1 -1
- package/src/core/rules/no-js-files.d.ts +1 -1
- package/src/core/rules/no-js-files.js.map +1 -1
- package/src/core/rules/no-process-exit-outside-main.d.ts +3 -3
- package/src/core/rules/no-process-exit-outside-main.js +1 -1
- package/src/core/rules/no-process-exit-outside-main.js.map +1 -1
- package/src/core/rules/no-symbol-di-tokens.d.ts +3 -3
- package/src/core/rules/no-symbol-di-tokens.js +1 -1
- package/src/core/rules/no-symbol-di-tokens.js.map +1 -1
- package/src/core/rules/no-unmanaged-exceptions.d.ts +3 -3
- package/src/core/rules/no-unmanaged-exceptions.js +1 -1
- package/src/core/rules/no-unmanaged-exceptions.js.map +1 -1
- package/src/core/rules/require-return-type.d.ts +3 -3
- package/src/core/rules/require-return-type.js +1 -2
- package/src/core/rules/require-return-type.js.map +1 -1
- package/src/core/rules/throw-cause-required.d.ts +1 -1
- package/src/core/rules/throw-cause-required.js +1 -1
- package/src/core/rules/throw-cause-required.js.map +1 -1
- package/src/core/rules/validate-ts-in-src.d.ts +1 -1
- package/src/core/rules/validate-ts-in-src.js.map +1 -1
- package/src/core/source-contribution-config.d.ts +9 -0
- package/src/core/source-contribution-config.js +14 -0
- package/src/core/source-contribution-config.js.map +1 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +9 -2
- package/src/index.js.map +1 -1
- package/src/rule-pack.d.ts +2 -0
- package/src/rule-pack.js +27 -0
- package/src/rule-pack.js.map +1 -0
- package/src/rule-settings.d.ts +5 -0
- package/src/rule-settings.js +40 -0
- package/src/rule-settings.js.map +1 -0
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ Source edit validation for AI coding agents, contributed independently to the sh
|
|
|
15
15
|
- Git and worktree primitives (repo-workflow-core).
|
|
16
16
|
- Config validation (rules-config), committed-diff validation (code-rules), and aggregate packaging (nx-webpieces-rules).
|
|
17
17
|
|
|
18
|
-
The public source and guard hooks keep their existing binary names. Import shared rule contracts from `@webpieces/hook-runtime`,
|
|
18
|
+
The public source and guard hooks keep their existing binary names. Import shared rule contracts from `@webpieces/hook-runtime`, pure schema/value contracts from `@webpieces/rules-sdk` and concrete configuration types from their owner package, and errors from `@webpieces/tooling-common`. Workflow APIs previously imported from `ai-hook-rules` now belong to `agent-workflow-rules`. OpenClaw composition is provided by the umbrella package.
|
|
19
19
|
|
|
20
20
|
The committed dispatcher is upgraded from the installed published release; this extraction does not regenerate it from unreleased source.
|
|
21
|
+
|
|
22
|
+
This pack owns its concrete policy schemas, mode enums, optional tuning, and reviewed seeds. Workflow rule/field retirements and keyless safeguard metadata are published by the workflow owner.
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/ai-hook-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.845",
|
|
4
4
|
"description": "Independent source edit rules and source hook for AI coding agents.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js",
|
|
9
9
|
"./package.json": "./package.json",
|
|
10
|
-
"./claude-code-rules": "./src/adapters/rules-hook.js"
|
|
10
|
+
"./claude-code-rules": "./src/adapters/rules-hook.js",
|
|
11
|
+
"./rule-pack": "./src/rule-pack.js"
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
13
14
|
"src/**/*",
|
|
@@ -21,15 +22,19 @@
|
|
|
21
22
|
"directory": "packages/tooling/ai-hook-rules"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
24
|
-
"@webpieces/hook-runtime": "0.4.
|
|
25
|
-
"@webpieces/rules-config": "0.4.
|
|
25
|
+
"@webpieces/hook-runtime": "0.4.845",
|
|
26
|
+
"@webpieces/rules-config": "0.4.845",
|
|
26
27
|
"inversify": "7.10.4",
|
|
27
28
|
"reflect-metadata": "0.2.2",
|
|
28
|
-
"@webpieces/tooling-common": "0.4.
|
|
29
|
+
"@webpieces/tooling-common": "0.4.845",
|
|
30
|
+
"@webpieces/rules-sdk": "0.4.845"
|
|
29
31
|
},
|
|
30
32
|
"publishConfig": {
|
|
31
33
|
"access": "public"
|
|
32
34
|
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@webpieces/tooling-testkit": "0.4.845"
|
|
37
|
+
},
|
|
33
38
|
"types": "./src/index.d.ts",
|
|
34
39
|
"bin": {
|
|
35
40
|
"wp-ai-rules-hook": "./src/adapters/rules-hook.js"
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GoldenRepoBuilder = exports.GOLDEN_FIXTURES = exports.REPO_TOKEN = exports.PreparedFixture = exports.GoldenFixture = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
+
const rule_pack_1 = require("../rule-pack");
|
|
6
|
+
const tooling_testkit_1 = require("@webpieces/tooling-testkit");
|
|
7
|
+
const tooling_testkit_2 = require("@webpieces/tooling-testkit");
|
|
5
8
|
const child_process_1 = require("child_process");
|
|
6
9
|
const fs = tslib_1.__importStar(require("fs"));
|
|
7
10
|
const path = tslib_1.__importStar(require("path"));
|
|
8
|
-
const rules_config_1 = require("@webpieces/rules-config");
|
|
9
11
|
/**
|
|
10
12
|
* THE WIRE BYTES the golden tests drive, and the throwaway repo they are judged against.
|
|
11
13
|
*
|
|
@@ -127,7 +129,7 @@ class GoldenRepoBuilder {
|
|
|
127
129
|
this.configJson = fs.readFileSync(path.join(__dirname, '__goldens__', 'fixture-webpieces.config.json'), 'utf8');
|
|
128
130
|
}
|
|
129
131
|
build(fixture) {
|
|
130
|
-
const root =
|
|
132
|
+
const root = tooling_testkit_2.specTempDirs.makeReal('wp-hook-golden-');
|
|
131
133
|
const repo = path.join(root, 'repo');
|
|
132
134
|
fs.mkdirSync(repo);
|
|
133
135
|
this.git(repo, 'init -q -b main');
|
|
@@ -136,6 +138,7 @@ class GoldenRepoBuilder {
|
|
|
136
138
|
this.git(repo, 'config user.email t@t.co');
|
|
137
139
|
this.git(repo, 'config user.name tester');
|
|
138
140
|
fs.writeFileSync(path.join(repo, 'webpieces.config.json'), this.repoConfig(fixture));
|
|
141
|
+
tooling_testkit_1.policyFixture.declareIn(repo, tooling_testkit_1.policyFixture.manifests().map(pack => pack.packageName === rule_pack_1.rulePackManifest.packageName ? rule_pack_1.rulePackManifest : pack));
|
|
139
142
|
fs.writeFileSync(path.join(repo, 'f.txt'), 'hello\n');
|
|
140
143
|
fs.mkdirSync(path.join(repo, 'scripts'));
|
|
141
144
|
fs.writeFileSync(path.join(repo, 'scripts', 'ok.ts'), 'const a = 1;\n');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook-app-fixtures.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/hook-app-fixtures.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { HookMode } from '@webpieces/hook-runtime';\nimport { specTempDirs } from '@webpieces/rules-config';\n\n/**\n * THE WIRE BYTES the golden tests drive, and the throwaway repo they are judged against.\n *\n * Everything here exists so a reader can see EXACTLY what a harness sends: the payloads are built from\n * the measured envelope key sets (see the fixture docs on GoldenFixture below), not from anything the\n * hook itself produces, and the repo is a real `git init` with a frozen webpieces.config.json rather\n * than whatever tree the suite happens to be running in. A golden computed against the live repo would\n * change verdict with the branch you are standing on.\n *\n * This is NOT a spec file, deliberately: `tsconfig.lib.json` excludes `*.spec.ts`, so a payload builder\n * living in one would never be type-checked by the build.\n */\n\n/** How one PreToolUse call is presented to the hook. Data class per CLAUDE.md rule 1. */\nexport class GoldenFixture {\n /** Stable key into `__goldens__/hook-app-goldens.json`. */\n readonly name: string;\n /** Which hook binary's mode — `guards` or `rules`. */\n readonly mode: HookMode;\n /**\n * The raw stdin bytes. A STRING, never an object, because malformed stdin is one of the fixtures\n * and a shape that cannot express \"not JSON\" would quietly drop the case that matters most.\n */\n readonly stdin: string;\n /** True ⇒ the fixture repo also gets a `rulesDir` whose one module throws when required. */\n readonly crashingRulesDir: boolean;\n\n constructor(name: string, mode: HookMode, stdin: string, crashingRulesDir: boolean = false) {\n this.name = name;\n this.mode = mode;\n this.stdin = stdin;\n this.crashingRulesDir = crashingRulesDir;\n }\n}\n\n/** A built fixture repo: where it lives, and the stdin bytes with `<REPO>` resolved into it. */\nexport class PreparedFixture {\n readonly repo: string;\n readonly root: string;\n readonly stdin: string;\n\n constructor(repo: string, root: string, stdin: string) {\n this.repo = repo;\n this.root = root;\n this.stdin = stdin;\n }\n}\n\n/**\n * The placeholder that stands for the fixture repo's absolute path, in BOTH directions: payloads are\n * written with it and it is substituted in before the run; golden bytes are compared with the real\n * path substituted back out. Guard reports legitimately name absolute paths (the git-workflow doc\n * pointer, the blocked file), and a golden that hard-coded one machine's `/var/folders/...` would be\n * a golden nobody else could run.\n */\nexport const REPO_TOKEN = '<REPO>';\n\nconst CLAUDE_ENVELOPE = { hook_event_name: 'PreToolUse', session_id: 'sess-golden', transcript_path: '/dev/null', cwd: REPO_TOKEN };\n\n/**\n * The Codex additions, MEASURED from codex-cli 0.151.0: it uses Claude's key names and merely ADDS\n * `model`, `turn_id`, `tool_use_id` and `permission_mode`. `turn_id` is the one discriminator (see\n * detect-ai.ts). `agent_id` empty ⇒ the coordinator, populated ⇒ a subagent — identical semantics in\n * both harnesses.\n */\nconst CODEX_ENVELOPE = { ...CLAUDE_ENVELOPE, model: 'gpt-5-codex', turn_id: 'turn-golden', tool_use_id: 'call_1', permission_mode: 'default', agent_type: 'default', agent_id: '' };\n\n// Codex's edit tool. MEASURED: the tool is named `apply_patch`, it carries `tool_input.command` (not\n// file_path), hunk headers are a bare `@@`, and ONE patch may carry many files with mixed operations.\nconst PATCH_ADD_JS = '*** Begin Patch\\n*** Add File: src/foo.js\\n+var x = 1;\\n*** End Patch\\n';\nconst PATCH_ADD_TS = '*** Begin Patch\\n*** Add File: scripts/added.ts\\n+export const b = 2;\\n*** End Patch\\n';\n\n/**\n * Serializes ONE PreToolUse envelope to the bytes a harness would put on stdin. A class rather than a\n * module-scope function because that is the repo rule, and the one instance below is built at module\n * load so the fixture table can stay a plain literal list.\n */\nclass WirePayload {\n // webpieces-disable no-any-unknown -- these objects ARE the wire envelope; JSON.stringify of a plain object is the payload under test, and naming a type for it would assert a shape the fixtures exist to state literally\n write(envelope: Record<string, unknown>, toolName: string, toolInput: Record<string, unknown>): string {\n return JSON.stringify({ ...envelope, tool_name: toolName, tool_input: toolInput });\n }\n}\n\nconst WIRE = new WirePayload();\n\n/**\n * ONE fixture per row of the coverage the composed pipeline had none of before: for BOTH harnesses a\n * Bash deny (the ANSI-red systemMessage), a file-tool deny (NO systemMessage), an allow, a read-only\n * tool, malformed stdin, and a fail-closed crash.\n */\nexport const GOLDEN_FIXTURES: readonly GoldenFixture[] = [\n // ── Claude Code ────────────────────────────────────────────────────────────────────────────────\n // A Bash deny. `git merge` is blocked on every branch, so this verdict does not depend on repo\n // state — and a Bash deny is the ONE case that carries the red `systemMessage`.\n // The read-only tool: log-and-allow, and the only guard that can deny it is a stale `main`.\n // Write / Edit / MultiEdit denies — all three must emit NO systemMessage.\n new GoldenFixture('claude/write-deny', 'rules', WIRE.write(CLAUDE_ENVELOPE, 'Write', { file_path: `${REPO_TOKEN}/src/foo.js`, content: 'var x = 1;\\n' })),\n new GoldenFixture('claude/edit-deny', 'rules', WIRE.write(CLAUDE_ENVELOPE, 'Edit', { file_path: `${REPO_TOKEN}/scripts/ok.ts`, old_string: 'const a = 1;', new_string: 'const { a } = b;' })),\n new GoldenFixture('claude/multiedit-deny', 'rules', WIRE.write(CLAUDE_ENVELOPE, 'MultiEdit', { file_path: `${REPO_TOKEN}/scripts/ok.ts`, edits: [{ old_string: 'const a = 1;', new_string: 'const { a } = b;' }] })),\n new GoldenFixture('claude/write-allow', 'rules', WIRE.write(CLAUDE_ENVELOPE, 'Write', { file_path: `${REPO_TOKEN}/scripts/ok.ts`, content: 'export const a = 1;\\n' })),\n new GoldenFixture('claude/crash', 'rules', WIRE.write(CLAUDE_ENVELOPE, 'Write', { file_path: `${REPO_TOKEN}/scripts/ok.ts`, content: 'export const a = 1;\\n' }), true),\n\n // ── Codex ──────────────────────────────────────────────────────────────────────────────────────\n // Codex has no Read tool: a read arrives as `Bash` running a pager, which read parity turns into a\n // read-scoped verdict ON TOP of the bash guards.\n new GoldenFixture('codex/apply-patch-deny', 'rules', WIRE.write(CODEX_ENVELOPE, 'apply_patch', { command: PATCH_ADD_JS })),\n new GoldenFixture('codex/apply-patch-allow', 'rules', WIRE.write(CODEX_ENVELOPE, 'apply_patch', { command: PATCH_ADD_TS })),\n new GoldenFixture('codex/crash', 'rules', WIRE.write(CODEX_ENVELOPE, 'apply_patch', { command: PATCH_ADD_TS }), true),\n];\n\n// A rules module that blows up the moment it is required — the shortest honest way to reach the hook's\n// fail-closed boundary from OUTSIDE the hook, i.e. without stubbing anything the pipeline owns.\nconst CRASHING_RULE_MODULE = \"throw new Error('boom from a custom rule module');\\n\";\n\n/**\n * Builds ONE throwaway git repo per fixture and returns it with the payload's `<REPO>` resolved.\n *\n * A FRESH repo per fixture, not one shared: the hook writes `.webpieces/` state (the decision log, the\n * main-sync cache) as it runs, so a shared tree would let fixture N's leftovers decide fixture N+1's\n * verdict — an order-dependent suite, which is the one kind of golden test worse than none.\n *\n * `realpathSync` matters on macOS: `os.tmpdir()` hands back `/var/folders/...` which resolves to\n * `/private/var/...`, and the L1 location guard compares the payload's cwd against the resolved repo\n * root. Without it every fixture denies with \"run git from the repo root\" instead of the verdict under\n * test.\n */\nexport class GoldenRepoBuilder {\n private readonly configJson: string;\n\n constructor() {\n this.configJson = fs.readFileSync(path.join(__dirname, '__goldens__', 'fixture-webpieces.config.json'), 'utf8');\n }\n\n build(fixture: GoldenFixture): PreparedFixture {\n const root = specTempDirs.makeReal('wp-hook-golden-');\n const repo = path.join(root, 'repo');\n fs.mkdirSync(repo);\n this.git(repo, 'init -q -b main');\n // The developer's own commit hooks would otherwise fire inside this throwaway repo.\n this.git(repo, 'config core.hooksPath /dev/null');\n this.git(repo, 'config user.email t@t.co');\n this.git(repo, 'config user.name tester');\n fs.writeFileSync(path.join(repo, 'webpieces.config.json'), this.repoConfig(fixture));\n fs.writeFileSync(path.join(repo, 'f.txt'), 'hello\\n');\n fs.mkdirSync(path.join(repo, 'scripts'));\n fs.writeFileSync(path.join(repo, 'scripts', 'ok.ts'), 'const a = 1;\\n');\n if (fixture.crashingRulesDir) {\n fs.mkdirSync(path.join(repo, 'wprules'));\n fs.writeFileSync(path.join(repo, 'wprules', 'crash.js'), CRASHING_RULE_MODULE);\n }\n this.git(repo, 'add -A');\n this.git(repo, 'commit -qm init');\n // A local origin/main, so `origin/main..<branch>` resolves exactly as it does in a real clone.\n this.git(repo, 'update-ref refs/remotes/origin/main HEAD');\n // A feature branch, because that is where an agent actually works.\n this.git(repo, 'checkout -q -b dean/fixture');\n return new PreparedFixture(repo, root, fixture.stdin.split(REPO_TOKEN).join(repo));\n }\n\n private repoConfig(fixture: GoldenFixture): string {\n if (!fixture.crashingRulesDir) return this.configJson;\n // webpieces-disable no-any-unknown -- the frozen fixture config is data on disk; re-parsing it into a named type here would be a second declaration of a file whose whole point is being literal\n const parsed = JSON.parse(this.configJson) as Record<string, unknown>;\n parsed['rulesDir'] = ['wprules'];\n return JSON.stringify(parsed, null, 4);\n }\n\n private git(repo: string, args: string): void {\n execSync(`git ${args}`, { cwd: repo, encoding: 'utf8' });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"hook-app-fixtures.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/hook-app-fixtures.ts"],"names":[],"mappings":"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","sourcesContent":["import { rulePackManifest } from '../rule-pack';\nimport { policyFixture } from '@webpieces/tooling-testkit';\nimport { specTempDirs } from '@webpieces/tooling-testkit';\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { HookMode } from '@webpieces/hook-runtime';\n\n\n/**\n * THE WIRE BYTES the golden tests drive, and the throwaway repo they are judged against.\n *\n * Everything here exists so a reader can see EXACTLY what a harness sends: the payloads are built from\n * the measured envelope key sets (see the fixture docs on GoldenFixture below), not from anything the\n * hook itself produces, and the repo is a real `git init` with a frozen webpieces.config.json rather\n * than whatever tree the suite happens to be running in. A golden computed against the live repo would\n * change verdict with the branch you are standing on.\n *\n * This is NOT a spec file, deliberately: `tsconfig.lib.json` excludes `*.spec.ts`, so a payload builder\n * living in one would never be type-checked by the build.\n */\n\n/** How one PreToolUse call is presented to the hook. Data class per CLAUDE.md rule 1. */\nexport class GoldenFixture {\n /** Stable key into `__goldens__/hook-app-goldens.json`. */\n readonly name: string;\n /** Which hook binary's mode — `guards` or `rules`. */\n readonly mode: HookMode;\n /**\n * The raw stdin bytes. A STRING, never an object, because malformed stdin is one of the fixtures\n * and a shape that cannot express \"not JSON\" would quietly drop the case that matters most.\n */\n readonly stdin: string;\n /** True ⇒ the fixture repo also gets a `rulesDir` whose one module throws when required. */\n readonly crashingRulesDir: boolean;\n\n constructor(name: string, mode: HookMode, stdin: string, crashingRulesDir: boolean = false) {\n this.name = name;\n this.mode = mode;\n this.stdin = stdin;\n this.crashingRulesDir = crashingRulesDir;\n }\n}\n\n/** A built fixture repo: where it lives, and the stdin bytes with `<REPO>` resolved into it. */\nexport class PreparedFixture {\n readonly repo: string;\n readonly root: string;\n readonly stdin: string;\n\n constructor(repo: string, root: string, stdin: string) {\n this.repo = repo;\n this.root = root;\n this.stdin = stdin;\n }\n}\n\n/**\n * The placeholder that stands for the fixture repo's absolute path, in BOTH directions: payloads are\n * written with it and it is substituted in before the run; golden bytes are compared with the real\n * path substituted back out. Guard reports legitimately name absolute paths (the git-workflow doc\n * pointer, the blocked file), and a golden that hard-coded one machine's `/var/folders/...` would be\n * a golden nobody else could run.\n */\nexport const REPO_TOKEN = '<REPO>';\n\nconst CLAUDE_ENVELOPE = { hook_event_name: 'PreToolUse', session_id: 'sess-golden', transcript_path: '/dev/null', cwd: REPO_TOKEN };\n\n/**\n * The Codex additions, MEASURED from codex-cli 0.151.0: it uses Claude's key names and merely ADDS\n * `model`, `turn_id`, `tool_use_id` and `permission_mode`. `turn_id` is the one discriminator (see\n * detect-ai.ts). `agent_id` empty ⇒ the coordinator, populated ⇒ a subagent — identical semantics in\n * both harnesses.\n */\nconst CODEX_ENVELOPE = { ...CLAUDE_ENVELOPE, model: 'gpt-5-codex', turn_id: 'turn-golden', tool_use_id: 'call_1', permission_mode: 'default', agent_type: 'default', agent_id: '' };\n\n// Codex's edit tool. MEASURED: the tool is named `apply_patch`, it carries `tool_input.command` (not\n// file_path), hunk headers are a bare `@@`, and ONE patch may carry many files with mixed operations.\nconst PATCH_ADD_JS = '*** Begin Patch\\n*** Add File: src/foo.js\\n+var x = 1;\\n*** End Patch\\n';\nconst PATCH_ADD_TS = '*** Begin Patch\\n*** Add File: scripts/added.ts\\n+export const b = 2;\\n*** End Patch\\n';\n\n/**\n * Serializes ONE PreToolUse envelope to the bytes a harness would put on stdin. A class rather than a\n * module-scope function because that is the repo rule, and the one instance below is built at module\n * load so the fixture table can stay a plain literal list.\n */\nclass WirePayload {\n // webpieces-disable no-any-unknown -- these objects ARE the wire envelope; JSON.stringify of a plain object is the payload under test, and naming a type for it would assert a shape the fixtures exist to state literally\n write(envelope: Record<string, unknown>, toolName: string, toolInput: Record<string, unknown>): string {\n return JSON.stringify({ ...envelope, tool_name: toolName, tool_input: toolInput });\n }\n}\n\nconst WIRE = new WirePayload();\n\n/**\n * ONE fixture per row of the coverage the composed pipeline had none of before: for BOTH harnesses a\n * Bash deny (the ANSI-red systemMessage), a file-tool deny (NO systemMessage), an allow, a read-only\n * tool, malformed stdin, and a fail-closed crash.\n */\nexport const GOLDEN_FIXTURES: readonly GoldenFixture[] = [\n // ── Claude Code ────────────────────────────────────────────────────────────────────────────────\n // A Bash deny. `git merge` is blocked on every branch, so this verdict does not depend on repo\n // state — and a Bash deny is the ONE case that carries the red `systemMessage`.\n // The read-only tool: log-and-allow, and the only guard that can deny it is a stale `main`.\n // Write / Edit / MultiEdit denies — all three must emit NO systemMessage.\n new GoldenFixture('claude/write-deny', 'rules', WIRE.write(CLAUDE_ENVELOPE, 'Write', { file_path: `${REPO_TOKEN}/src/foo.js`, content: 'var x = 1;\\n' })),\n new GoldenFixture('claude/edit-deny', 'rules', WIRE.write(CLAUDE_ENVELOPE, 'Edit', { file_path: `${REPO_TOKEN}/scripts/ok.ts`, old_string: 'const a = 1;', new_string: 'const { a } = b;' })),\n new GoldenFixture('claude/multiedit-deny', 'rules', WIRE.write(CLAUDE_ENVELOPE, 'MultiEdit', { file_path: `${REPO_TOKEN}/scripts/ok.ts`, edits: [{ old_string: 'const a = 1;', new_string: 'const { a } = b;' }] })),\n new GoldenFixture('claude/write-allow', 'rules', WIRE.write(CLAUDE_ENVELOPE, 'Write', { file_path: `${REPO_TOKEN}/scripts/ok.ts`, content: 'export const a = 1;\\n' })),\n new GoldenFixture('claude/crash', 'rules', WIRE.write(CLAUDE_ENVELOPE, 'Write', { file_path: `${REPO_TOKEN}/scripts/ok.ts`, content: 'export const a = 1;\\n' }), true),\n\n // ── Codex ──────────────────────────────────────────────────────────────────────────────────────\n // Codex has no Read tool: a read arrives as `Bash` running a pager, which read parity turns into a\n // read-scoped verdict ON TOP of the bash guards.\n new GoldenFixture('codex/apply-patch-deny', 'rules', WIRE.write(CODEX_ENVELOPE, 'apply_patch', { command: PATCH_ADD_JS })),\n new GoldenFixture('codex/apply-patch-allow', 'rules', WIRE.write(CODEX_ENVELOPE, 'apply_patch', { command: PATCH_ADD_TS })),\n new GoldenFixture('codex/crash', 'rules', WIRE.write(CODEX_ENVELOPE, 'apply_patch', { command: PATCH_ADD_TS }), true),\n];\n\n// A rules module that blows up the moment it is required — the shortest honest way to reach the hook's\n// fail-closed boundary from OUTSIDE the hook, i.e. without stubbing anything the pipeline owns.\nconst CRASHING_RULE_MODULE = \"throw new Error('boom from a custom rule module');\\n\";\n\n/**\n * Builds ONE throwaway git repo per fixture and returns it with the payload's `<REPO>` resolved.\n *\n * A FRESH repo per fixture, not one shared: the hook writes `.webpieces/` state (the decision log, the\n * main-sync cache) as it runs, so a shared tree would let fixture N's leftovers decide fixture N+1's\n * verdict — an order-dependent suite, which is the one kind of golden test worse than none.\n *\n * `realpathSync` matters on macOS: `os.tmpdir()` hands back `/var/folders/...` which resolves to\n * `/private/var/...`, and the L1 location guard compares the payload's cwd against the resolved repo\n * root. Without it every fixture denies with \"run git from the repo root\" instead of the verdict under\n * test.\n */\nexport class GoldenRepoBuilder {\n private readonly configJson: string;\n\n constructor() {\n this.configJson = fs.readFileSync(path.join(__dirname, '__goldens__', 'fixture-webpieces.config.json'), 'utf8');\n }\n\n build(fixture: GoldenFixture): PreparedFixture {\n const root = specTempDirs.makeReal('wp-hook-golden-');\n const repo = path.join(root, 'repo');\n fs.mkdirSync(repo);\n this.git(repo, 'init -q -b main');\n // The developer's own commit hooks would otherwise fire inside this throwaway repo.\n this.git(repo, 'config core.hooksPath /dev/null');\n this.git(repo, 'config user.email t@t.co');\n this.git(repo, 'config user.name tester');\n fs.writeFileSync(path.join(repo, 'webpieces.config.json'), this.repoConfig(fixture));\n policyFixture.declareIn(repo, policyFixture.manifests().map(pack => pack.packageName === rulePackManifest.packageName ? rulePackManifest : pack));\n fs.writeFileSync(path.join(repo, 'f.txt'), 'hello\\n');\n fs.mkdirSync(path.join(repo, 'scripts'));\n fs.writeFileSync(path.join(repo, 'scripts', 'ok.ts'), 'const a = 1;\\n');\n if (fixture.crashingRulesDir) {\n fs.mkdirSync(path.join(repo, 'wprules'));\n fs.writeFileSync(path.join(repo, 'wprules', 'crash.js'), CRASHING_RULE_MODULE);\n }\n this.git(repo, 'add -A');\n this.git(repo, 'commit -qm init');\n // A local origin/main, so `origin/main..<branch>` resolves exactly as it does in a real clone.\n this.git(repo, 'update-ref refs/remotes/origin/main HEAD');\n // A feature branch, because that is where an agent actually works.\n this.git(repo, 'checkout -q -b dean/fixture');\n return new PreparedFixture(repo, root, fixture.stdin.split(REPO_TOKEN).join(repo));\n }\n\n private repoConfig(fixture: GoldenFixture): string {\n if (!fixture.crashingRulesDir) return this.configJson;\n // webpieces-disable no-any-unknown -- the frozen fixture config is data on disk; re-parsing it into a named type here would be a second declaration of a file whose whole point is being literal\n const parsed = JSON.parse(this.configJson) as Record<string, unknown>;\n parsed['rulesDir'] = ['wprules'];\n return JSON.stringify(parsed, null, 4);\n }\n\n private git(repo: string, args: string): void {\n execSync(`git ${args}`, { cwd: repo, encoding: 'utf8' });\n }\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FileLimitMode } from "@webpieces/rules-sdk";
|
|
2
|
+
import { BaseRuleConfig } from "@webpieces/rules-sdk";
|
|
3
|
+
import { SchemaShape } from "@webpieces/rules-sdk";
|
|
4
|
+
export declare const THROW_CAUSE_MODES: readonly ["OFF", "NEW_AND_MODIFIED_CODE"];
|
|
5
|
+
export type ThrowCauseMode = typeof THROW_CAUSE_MODES[number];
|
|
6
|
+
export declare const VALIDATE_TS_MODES: readonly ["OFF", "NEW_AND_MODIFIED_FILES"];
|
|
7
|
+
export type ValidateTsMode = typeof VALIDATE_TS_MODES[number];
|
|
8
|
+
export declare class ThrowCauseRequiredConfig extends BaseRuleConfig {
|
|
9
|
+
mode?: ThrowCauseMode;
|
|
10
|
+
disableAllowed?: boolean;
|
|
11
|
+
static readonly SCHEMA: SchemaShape<ThrowCauseRequiredConfig>;
|
|
12
|
+
}
|
|
13
|
+
export declare class NoJsFilesConfig extends BaseRuleConfig {
|
|
14
|
+
mode?: FileLimitMode;
|
|
15
|
+
allowedPaths?: string[];
|
|
16
|
+
static readonly SCHEMA: SchemaShape<NoJsFilesConfig>;
|
|
17
|
+
}
|
|
18
|
+
export declare class ValidateTsInSrcConfig extends BaseRuleConfig {
|
|
19
|
+
mode?: ValidateTsMode;
|
|
20
|
+
allowedRootFiles?: string[];
|
|
21
|
+
excludePaths?: string[];
|
|
22
|
+
static readonly SCHEMA: SchemaShape<ValidateTsInSrcConfig>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ValidateTsInSrcConfig = exports.NoJsFilesConfig = exports.ThrowCauseRequiredConfig = exports.VALIDATE_TS_MODES = exports.THROW_CAUSE_MODES = void 0;
|
|
4
|
+
const rules_sdk_1 = require("@webpieces/rules-sdk");
|
|
5
|
+
const rules_sdk_2 = require("@webpieces/rules-sdk");
|
|
6
|
+
const rules_sdk_3 = require("@webpieces/rules-sdk");
|
|
7
|
+
exports.THROW_CAUSE_MODES = ['OFF', 'NEW_AND_MODIFIED_CODE'];
|
|
8
|
+
exports.VALIDATE_TS_MODES = ['OFF', 'NEW_AND_MODIFIED_FILES'];
|
|
9
|
+
class ThrowCauseRequiredConfig extends rules_sdk_2.BaseRuleConfig {
|
|
10
|
+
disableAllowed;
|
|
11
|
+
static SCHEMA = {
|
|
12
|
+
mode: new rules_sdk_3.FieldDef('string', exports.THROW_CAUSE_MODES),
|
|
13
|
+
disableAllowed: rules_sdk_3.FieldDef.optional('boolean'),
|
|
14
|
+
...rules_sdk_2.BASE_RULE_SCHEMA,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
exports.ThrowCauseRequiredConfig = ThrowCauseRequiredConfig;
|
|
18
|
+
class NoJsFilesConfig extends rules_sdk_2.BaseRuleConfig {
|
|
19
|
+
allowedPaths;
|
|
20
|
+
static SCHEMA = {
|
|
21
|
+
mode: new rules_sdk_3.FieldDef('string', rules_sdk_1.FILE_LIMIT_MODES),
|
|
22
|
+
allowedPaths: rules_sdk_3.FieldDef.optional('string[]'),
|
|
23
|
+
...rules_sdk_2.BASE_RULE_SCHEMA,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
exports.NoJsFilesConfig = NoJsFilesConfig;
|
|
27
|
+
class ValidateTsInSrcConfig extends rules_sdk_2.BaseRuleConfig {
|
|
28
|
+
allowedRootFiles;
|
|
29
|
+
excludePaths;
|
|
30
|
+
static SCHEMA = {
|
|
31
|
+
mode: new rules_sdk_3.FieldDef('string', exports.VALIDATE_TS_MODES),
|
|
32
|
+
allowedRootFiles: rules_sdk_3.FieldDef.optional('string[]'),
|
|
33
|
+
excludePaths: rules_sdk_3.FieldDef.optional('string[]'),
|
|
34
|
+
...rules_sdk_2.BASE_RULE_SCHEMA,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
exports.ValidateTsInSrcConfig = ValidateTsInSrcConfig;
|
|
38
|
+
//# sourceMappingURL=rule-configs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-configs.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/configs/rule-configs.ts"],"names":[],"mappings":";;;AAAA,oDAAuE;AACvE,oDAAwE;AACxE,oDAA6D;AAChD,QAAA,iBAAiB,GAAG,CAAC,KAAK,EAAE,uBAAuB,CAAU,CAAC;AAI9D,QAAA,iBAAiB,GAAG,CAAC,KAAK,EAAE,wBAAwB,CAAU,CAAC;AAI5E,MAAa,wBAAyB,SAAQ,0BAAc;IAExD,cAAc,CAAW;IAEzB,MAAM,CAAU,MAAM,GAA0C;QAC5D,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,yBAAiB,CAAC;QAC/C,cAAc,EAAE,oBAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC5C,GAAG,4BAAgB;KACtB,CAAC;;AARN,4DASC;AAED,MAAa,eAAgB,SAAQ,0BAAc;IAI/C,YAAY,CAAY;IAExB,MAAM,CAAU,MAAM,GAAiC;QACnD,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,4BAAgB,CAAC;QAC9C,YAAY,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC3C,GAAG,4BAAgB;KACtB,CAAC;;AAVN,0CAWC;AAED,MAAa,qBAAsB,SAAQ,0BAAc;IAErD,gBAAgB,CAAY;IAC5B,YAAY,CAAY;IAExB,MAAM,CAAU,MAAM,GAAuC;QACzD,IAAI,EAAE,IAAI,oBAAQ,CAAC,QAAQ,EAAE,yBAAiB,CAAC;QAC/C,gBAAgB,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC/C,YAAY,EAAE,oBAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC3C,GAAG,4BAAgB;KACtB,CAAC;;AAVN,sDAWC","sourcesContent":["import { FileLimitMode, FILE_LIMIT_MODES } from \"@webpieces/rules-sdk\";\nimport { BaseRuleConfig, BASE_RULE_SCHEMA } from \"@webpieces/rules-sdk\";\nimport { FieldDef, SchemaShape } from \"@webpieces/rules-sdk\";\nexport const THROW_CAUSE_MODES = ['OFF', 'NEW_AND_MODIFIED_CODE'] as const;\n\nexport type ThrowCauseMode = typeof THROW_CAUSE_MODES[number];\n\nexport const VALIDATE_TS_MODES = ['OFF', 'NEW_AND_MODIFIED_FILES'] as const;\n\nexport type ValidateTsMode = typeof VALIDATE_TS_MODES[number];\n\nexport class ThrowCauseRequiredConfig extends BaseRuleConfig {\n declare mode?: ThrowCauseMode;\n disableAllowed?: boolean;\n\n static readonly SCHEMA: SchemaShape<ThrowCauseRequiredConfig> = {\n mode: new FieldDef('string', THROW_CAUSE_MODES),\n disableAllowed: FieldDef.optional('boolean'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class NoJsFilesConfig extends BaseRuleConfig {\n // File-tier: NEW_AND_MODIFIED_FILES (active) intercepts a .js/.jsx Write — the file being\n // written is inherently a new/modified file, so it's already diff-scoped in practice.\n declare mode?: FileLimitMode;\n allowedPaths?: string[];\n\n static readonly SCHEMA: SchemaShape<NoJsFilesConfig> = {\n mode: new FieldDef('string', FILE_LIMIT_MODES),\n allowedPaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n\nexport class ValidateTsInSrcConfig extends BaseRuleConfig {\n declare mode?: ValidateTsMode;\n allowedRootFiles?: string[];\n excludePaths?: string[];\n\n static readonly SCHEMA: SchemaShape<ValidateTsInSrcConfig> = {\n mode: new FieldDef('string', VALIDATE_TS_MODES),\n allowedRootFiles: FieldDef.optional('string[]'),\n excludePaths: FieldDef.optional('string[]'),\n ...BASE_RULE_SCHEMA,\n };\n}\n"]}
|
package/src/core/load-rules.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WebpiecesRulesConfig, MatchRuleConfig } from
|
|
1
|
+
import { WebpiecesRulesConfig, MatchRuleConfig } from "@webpieces/rules-config";
|
|
2
2
|
import type { Rule } from '@webpieces/hook-runtime';
|
|
3
3
|
export declare function loadRules(config: WebpiecesRulesConfig, workspaceRoot: string): readonly Rule[];
|
|
4
4
|
export declare function loadMatchRules(matchRules: readonly MatchRuleConfig[]): Rule[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":";;AAmEA,8BAOC;AAKD,wCAEC;AAID,4CAaC;AAGD,0CAUC;;AA/GD,+CAAyB;AACzB,mDAA6B;AAK7B,8DAA0D;AAC1D,iEAA6D;AAC7D,0DAA0D;AAC1D,0DAA4D;AAC5D,yCAAkD;AAClD,2DAA0D;AAC1D,6DAA4D;AAC5D,2DAA0D;AAC1D,mEAAiE;AACjE,2DAA2D;AAC3D,qEAAoE;AACpE,6EAA4E;AAC5E,qEAAoE;AACpE,uEAAsE;AACtE,qEAAmE;AACnE,yDAAwD;AACxD,uFAAoF;AACpF,qDAAoD;AACpD,mDAA+C;AAE/C,MAAM,eAAe,GAAsB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC9F,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAevD,MAAM,iBAAiB,GAAgC;IACnD,gBAAgB,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,iCAAgB,CAAC,CAAuB,CAAC,CAAC;IACxF,iBAAiB,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,mCAAiB,CAAC,CAAwB,CAAC,CAAC;IAC3F,gBAAgB,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,iCAAgB,CAAC,CAAuB,CAAC,CAAC;IACxF,oBAAoB,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,wCAAmB,CAAC,CAA0B,CAAC,CAAC;IAClG,gBAAgB,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,kCAAiB,CAAC,CAAwB,CAAC,CAAC;IAC1F,qBAAqB,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,2CAAqB,CAAC,CAA4B,CAAC,CAAC;IACvG,yBAAyB,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,mDAAyB,CAAC,CAAgC,CAAC,CAAC;IACnH,qBAAqB,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,2CAAqB,CAAC,CAA4B,CAAC,CAAC;IACvG,sBAAsB,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,6CAAsB,CAAC,CAA6B,CAAC,CAAC;IAC1G,qBAAqB,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,0CAAoB,CAAC,CAA2B,CAAC,CAAC;IACrG,eAAe,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,+BAAe,CAAC,CAAsB,CAAC,CAAC;IACrF,8BAA8B,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,2DAA4B,CAAC,CAAmC,CAAC,CAAC;IAC9H,aAAa,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,2BAAa,CAAC,CAAoB,CAAC,CAAC;CAElF,CAAC;AAEF,6FAA6F;AAC7F,qFAAqF;AACrF,SAAS,WAAW,CAAC,MAA4B;IAC7C,kFAAkF;IAClF,OAAO,MAA+D,CAAC;AAC3E,CAAC;AAED,iOAAiO;AACjO,SAAgB,SAAS,CACrB,MAA4B,EAC5B,aAAqB;IAErB,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACtD,OAAO,CAAC,GAAG,QAAQ,EAAE,GAAG,MAAM,CAAC,CAAC;AACpC,CAAC;AAED,sGAAsG;AACtG,2FAA2F;AAC3F,qFAAqF;AACrF,SAAgB,cAAc,CAAC,UAAsC;IACjE,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAkB,EAAE,EAAE,CAAC,IAAI,sBAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,oGAAoG;AACpG,qHAAqH;AACrH,SAAgB,gBAAgB,CAAC,MAA4B;IACzD,MAAM,GAAG,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC,MAAM,KAAK,GAAW,EAAE,CAAC;IACzB,KAAK,MAAM,SAAS,IAAI,yBAAiB,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2CAA2C,SAAS,IAAI,CAAC,CAAC;YAC/E,SAAS;QACb,CAAC;QACD,MAAM,UAAU,GAAG,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,8BAAe,EAAE,CAAC;QAC3D,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,iIAAiI;AACjI,SAAgB,eAAe,CAAC,MAA4B,EAAE,aAAqB;IAC/E,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;IACnC,yFAAyF;IACzF,MAAM,GAAG,GAAG,MAA4D,CAAC;IACzE,MAAM,KAAK,GAAW,EAAE,CAAC;IACzB,KAAK,MAAM,KAAK,IAAI,oBAAoB,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC;QAC5D,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACxC,KAAK,CAAC,IAAI,CAAC,IAAI,gCAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,oBAAoB,CAAC,SAA4B,EAAE,aAAqB;IAC7E,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC1E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,MAAM,IAAI,CAAC,CAAC;YACnE,SAAS;QACb,CAAC;QACD,IAAI,OAAiB,CAAC;QACtB,8DAA8D;QAC9D,IAAI,CAAC;YACD,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9E,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,MAAM,IAAI,8BAAa,CAAC,uCAAuC,MAAM,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAChG,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YACtC,8DAA8D;YAC9D,IAAI,CAAC;gBACD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC1B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;gBACrC,IAAI,YAAY,CAAC,SAAS,CAAC;oBAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACzD,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;gBAC3B,MAAM,IAAI,8BAAa,CAAC,4BAA4B,IAAI,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACnF,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,8FAA8F;AAC9F,SAAS,YAAY,CAAC,IAAa;IAC/B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;QACrE,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,gFAAgF;IAChF,MAAM,GAAG,GAAG,IAA+B,CAAC;IAC5C,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;QAClC,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;YACzE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,IAAI,6BAA6B,KAAK,IAAI,CAAC,CAAC;YACrF,OAAO,KAAK,CAAC;QACjB,CAAC;IACL,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAW,CAAC,EAAE,CAAC;QAC5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,GAAG,CAAC,MAAM,CAAC,wBAAwB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;QACtG,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,GAAG,CAAC,MAAM,CAAC,4BAA4B,CAAC,CAAC;QAClF,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,UAAU,EAAE,CAAC;QACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,GAAG,CAAC,MAAM,CAAC,8BAA8B,CAAC,CAAC;QACpF,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { BaseRuleConfig, RuleOptions, WebpiecesRulesConfig, NoAnyUnknownConfig, NoImplicitAnyConfig, MaxFileLinesConfig, ValidateTsInSrcConfig, NoDestructureConfig, RequireReturnTypeConfig, NoUnmanagedExceptionsConfig, CatchErrorPatternConfig, ThrowCauseRequiredConfig, NoSymbolDiTokensConfig, NoCustomCssConfig, NoProcessExitOutsideMainConfig, NoJsFilesConfig, MatchRuleConfig } from '@webpieces/rules-config';\n\nimport type { Rule, PlainRule } from '@webpieces/hook-runtime';\nimport { InformAiError } from '@webpieces/tooling-common';\nimport { toError } from '@webpieces/tooling-common/to-error';\nimport { EmptyRuleConfig } from '@webpieces/hook-runtime';\nimport { CustomRuleAdapter } from '@webpieces/hook-runtime';\nimport { builtInConfigKeys } from './rules/index';\nimport { NoAnyUnknownRule } from './rules/no-any-unknown';\nimport { NoImplicitAnyRule } from './rules/no-implicit-any';\nimport { MaxFileLinesRule } from './rules/max-file-lines';\nimport { ValidateTsInSrcRule } from './rules/validate-ts-in-src';\nimport { NoDestructureRule } from './rules/no-destructure';\nimport { RequireReturnTypeRule } from './rules/require-return-type';\nimport { NoUnmanagedExceptionsRule } from './rules/no-unmanaged-exceptions';\nimport { CatchErrorPatternRule } from './rules/catch-error-pattern';\nimport { ThrowCauseRequiredRule } from './rules/throw-cause-required';\nimport { NoSymbolDiTokensRule } from './rules/no-symbol-di-tokens';\nimport { NoCustomCssRule } from './rules/no-custom-css';\nimport { NoProcessExitOutsideMainRule } from './rules/no-process-exit-outside-main';\nimport { NoJsFilesRule } from './rules/no-js-files';\nimport { MatchRule } from './rules/match-rule';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\n/**\n * ONE CONFIG KEY → N RULES.\n *\n * Each built-in rule is constructed from its typed *Config (the entry in webpieces.config.json). The\n * config arrives as a plain object structurally typed as the *Config class, so the `as` narrows the\n * shared BaseRuleConfig param back to the concrete config the rule consumes.\n *\n * This registry contains source-rule factories only. Each accepts BaseRuleConfig and returns an\n * array of source rule instances for its config key. Workflow policies and command hints are owned\n * by agent-workflow-rules and are not inputs to this loader.\n */\ntype RuleFactory = (config: BaseRuleConfig) => readonly Rule[];\n\nconst BUILT_IN_RULE_MAP: Record<string, RuleFactory> = {\n 'no-any-unknown': (c: BaseRuleConfig) => [new NoAnyUnknownRule(c as NoAnyUnknownConfig)],\n 'no-implicit-any': (c: BaseRuleConfig) => [new NoImplicitAnyRule(c as NoImplicitAnyConfig)],\n 'max-file-lines': (c: BaseRuleConfig) => [new MaxFileLinesRule(c as MaxFileLinesConfig)],\n 'validate-ts-in-src': (c: BaseRuleConfig) => [new ValidateTsInSrcRule(c as ValidateTsInSrcConfig)],\n 'no-destructure': (c: BaseRuleConfig) => [new NoDestructureRule(c as NoDestructureConfig)],\n 'require-return-type': (c: BaseRuleConfig) => [new RequireReturnTypeRule(c as RequireReturnTypeConfig)],\n 'no-unmanaged-exceptions': (c: BaseRuleConfig) => [new NoUnmanagedExceptionsRule(c as NoUnmanagedExceptionsConfig)],\n 'catch-error-pattern': (c: BaseRuleConfig) => [new CatchErrorPatternRule(c as CatchErrorPatternConfig)],\n 'throw-cause-required': (c: BaseRuleConfig) => [new ThrowCauseRequiredRule(c as ThrowCauseRequiredConfig)],\n 'no-symbol-di-tokens': (c: BaseRuleConfig) => [new NoSymbolDiTokensRule(c as NoSymbolDiTokensConfig)],\n 'no-custom-css': (c: BaseRuleConfig) => [new NoCustomCssRule(c as NoCustomCssConfig)],\n 'no-process-exit-outside-main': (c: BaseRuleConfig) => [new NoProcessExitOutsideMainRule(c as NoProcessExitOutsideMainConfig)],\n 'no-js-files': (c: BaseRuleConfig) => [new NoJsFilesRule(c as NoJsFilesConfig)],\n\n};\n\n// Index the typed config by rule name. Each value is the rule's *Config (a plain object from\n// JSON), or undefined when the rule has no entry yet (the sync check reports those).\nfunction asConfigMap(config: WebpiecesRulesConfig): Record<string, BaseRuleConfig | undefined> {\n // webpieces-disable no-any-unknown -- index the typed config by dynamic rule name\n return config as unknown as Record<string, BaseRuleConfig | undefined>;\n}\n\n// webpieces-disable no-function-outside-class -- the module's entry point, beside loadMatchRules/loadKeylessBashRules; this whole loader is module-scope functions and a lone class for one of them would break the file's shape\nexport function loadRules(\n config: WebpiecesRulesConfig,\n workspaceRoot: string,\n): readonly Rule[] {\n const builtIns = loadBuiltInRules(config);\n const custom = loadCustomRules(config, workspaceRoot);\n return [...builtIns, ...custom];\n}\n\n// One MatchRule per entry of the `match-rules` array. Kept separate from loadRules (built-ins/custom)\n// because match-rules live in their own validated section — they must NOT flow through the\n// config-sync check, which compares rule names against the `rules`/`hookGuards` map.\nexport function loadMatchRules(matchRules: readonly MatchRuleConfig[]): Rule[] {\n return matchRules.map((c: MatchRuleConfig) => new MatchRule(c));\n}\n\n// Iterates CONFIG KEYS, not rule names — one entry can yield several rules (see BUILT_IN_RULE_MAP).\n// webpieces-disable no-function-outside-class -- the body of loadRules above, in the same module of loader functions\nexport function loadBuiltInRules(config: WebpiecesRulesConfig): Rule[] {\n const map = asConfigMap(config);\n const rules: Rule[] = [];\n for (const configKey of builtInConfigKeys) {\n const factory = BUILT_IN_RULE_MAP[configKey];\n if (!factory) {\n process.stderr.write(`[ai-hooks] unknown built-in config key: ${configKey}\\n`);\n continue;\n }\n const ruleConfig = map[configKey] ?? new EmptyRuleConfig();\n rules.push(...factory(ruleConfig));\n }\n return rules;\n}\n\n// webpieces-disable no-function-outside-class -- existing source registry helper contributed to both source and umbrella runners\nexport function loadCustomRules(config: WebpiecesRulesConfig, workspaceRoot: string): Rule[] {\n const dirs = config.rulesDir ?? [];\n // webpieces-disable no-any-unknown -- index the typed config by dynamic custom-rule name\n const map = config as unknown as Record<string, RuleOptions | undefined>;\n const rules: Rule[] = [];\n for (const plain of loadCustomPlainRules(dirs, workspaceRoot)) {\n const rawConfig = map[plain.name] ?? {};\n rules.push(new CustomRuleAdapter(plain, rawConfig));\n }\n return rules;\n}\n\nfunction loadCustomPlainRules(rulesDirs: readonly string[], workspaceRoot: string): PlainRule[] {\n const modules: PlainRule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e: string) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot read custom rules directory '${absDir}'`, { cause: error });\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n const candidate = mod.default || mod;\n if (validateRule(candidate)) modules.push(candidate);\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot load custom rule '${full}'`, { cause: error });\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is PlainRule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import { SourceContributionConfig } from \"./source-contribution-config\";\nimport { BaseRuleConfig } from '@webpieces/rules-sdk';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { RuleOptions, WebpiecesRulesConfig, MatchRuleConfig } from \"@webpieces/rules-config\";\nimport { ValidateTsInSrcConfig, ThrowCauseRequiredConfig, NoJsFilesConfig } from \"../configs/rule-configs\";\n\nimport type { Rule, PlainRule } from '@webpieces/hook-runtime';\nimport { InformAiError } from '@webpieces/tooling-common';\nimport { toError } from '@webpieces/tooling-common/to-error';\nimport { EmptyRuleConfig } from '@webpieces/hook-runtime';\nimport { CustomRuleAdapter } from '@webpieces/hook-runtime';\nimport { builtInConfigKeys } from './rules/index';\nimport { NoAnyUnknownRule } from './rules/no-any-unknown';\nimport { NoImplicitAnyRule } from './rules/no-implicit-any';\nimport { MaxFileLinesRule } from './rules/max-file-lines';\nimport { ValidateTsInSrcRule } from './rules/validate-ts-in-src';\nimport { NoDestructureRule } from './rules/no-destructure';\nimport { RequireReturnTypeRule } from './rules/require-return-type';\nimport { NoUnmanagedExceptionsRule } from './rules/no-unmanaged-exceptions';\nimport { CatchErrorPatternRule } from './rules/catch-error-pattern';\nimport { ThrowCauseRequiredRule } from './rules/throw-cause-required';\nimport { NoSymbolDiTokensRule } from './rules/no-symbol-di-tokens';\nimport { NoCustomCssRule } from './rules/no-custom-css';\nimport { NoProcessExitOutsideMainRule } from './rules/no-process-exit-outside-main';\nimport { NoJsFilesRule } from './rules/no-js-files';\nimport { MatchRule } from './rules/match-rule';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\n/**\n * ONE CONFIG KEY → N RULES.\n *\n * Each built-in rule is constructed from its typed *Config (the entry in webpieces.config.json). The\n * config arrives as a plain object structurally typed as the *Config class, so the `as` narrows the\n * shared BaseRuleConfig param back to the concrete config the rule consumes.\n *\n * This registry contains source-rule factories only. Each accepts BaseRuleConfig and returns an\n * array of source rule instances for its config key. Workflow policies and command hints are owned\n * by agent-workflow-rules and are not inputs to this loader.\n */\ntype RuleFactory = (config: BaseRuleConfig) => readonly Rule[];\n\nconst BUILT_IN_RULE_MAP: Record<string, RuleFactory> = {\n 'no-any-unknown': (c: BaseRuleConfig) => [new NoAnyUnknownRule(c as SourceContributionConfig)],\n 'no-implicit-any': (c: BaseRuleConfig) => [new NoImplicitAnyRule(c as SourceContributionConfig)],\n 'max-file-lines': (c: BaseRuleConfig) => [new MaxFileLinesRule(c as SourceContributionConfig)],\n 'validate-ts-in-src': (c: BaseRuleConfig) => [new ValidateTsInSrcRule(c as ValidateTsInSrcConfig)],\n 'no-destructure': (c: BaseRuleConfig) => [new NoDestructureRule(c as SourceContributionConfig)],\n 'require-return-type': (c: BaseRuleConfig) => [new RequireReturnTypeRule(c as SourceContributionConfig)],\n 'no-unmanaged-exceptions': (c: BaseRuleConfig) => [new NoUnmanagedExceptionsRule(c as SourceContributionConfig)],\n 'catch-error-pattern': (c: BaseRuleConfig) => [new CatchErrorPatternRule(c as SourceContributionConfig)],\n 'throw-cause-required': (c: BaseRuleConfig) => [new ThrowCauseRequiredRule(c as ThrowCauseRequiredConfig)],\n 'no-symbol-di-tokens': (c: BaseRuleConfig) => [new NoSymbolDiTokensRule(c as SourceContributionConfig)],\n 'no-custom-css': (c: BaseRuleConfig) => [new NoCustomCssRule(c as SourceContributionConfig)],\n 'no-process-exit-outside-main': (c: BaseRuleConfig) => [new NoProcessExitOutsideMainRule(c as SourceContributionConfig)],\n 'no-js-files': (c: BaseRuleConfig) => [new NoJsFilesRule(c as NoJsFilesConfig)],\n\n};\n\n// Index the typed config by rule name. Each value is the rule's *Config (a plain object from\n// JSON), or undefined when the rule has no entry yet (the sync check reports those).\nfunction asConfigMap(config: WebpiecesRulesConfig): Record<string, BaseRuleConfig | undefined> {\n // webpieces-disable no-any-unknown -- index the typed config by dynamic rule name\n return config as unknown as Record<string, BaseRuleConfig | undefined>;\n}\n\n// webpieces-disable no-function-outside-class -- the module's entry point, beside loadMatchRules/loadKeylessBashRules; this whole loader is module-scope functions and a lone class for one of them would break the file's shape\nexport function loadRules(\n config: WebpiecesRulesConfig,\n workspaceRoot: string,\n): readonly Rule[] {\n const builtIns = loadBuiltInRules(config);\n const custom = loadCustomRules(config, workspaceRoot);\n return [...builtIns, ...custom];\n}\n\n// One MatchRule per entry of the `match-rules` array. Kept separate from loadRules (built-ins/custom)\n// because match-rules live in their own validated section — they must NOT flow through the\n// config-sync check, which compares rule names against the `rules`/`hookGuards` map.\nexport function loadMatchRules(matchRules: readonly MatchRuleConfig[]): Rule[] {\n return matchRules.map((c: MatchRuleConfig) => new MatchRule(c));\n}\n\n// Iterates CONFIG KEYS, not rule names — one entry can yield several rules (see BUILT_IN_RULE_MAP).\n// webpieces-disable no-function-outside-class -- the body of loadRules above, in the same module of loader functions\nexport function loadBuiltInRules(config: WebpiecesRulesConfig): Rule[] {\n const map = asConfigMap(config);\n const rules: Rule[] = [];\n for (const configKey of builtInConfigKeys) {\n const factory = BUILT_IN_RULE_MAP[configKey];\n if (!factory) {\n process.stderr.write(`[ai-hooks] unknown built-in config key: ${configKey}\\n`);\n continue;\n }\n const ruleConfig = map[configKey] ?? new EmptyRuleConfig();\n rules.push(...factory(ruleConfig));\n }\n return rules;\n}\n\n// webpieces-disable no-function-outside-class -- existing source registry helper contributed to both source and umbrella runners\nexport function loadCustomRules(config: WebpiecesRulesConfig, workspaceRoot: string): Rule[] {\n const dirs = config.rulesDir ?? [];\n // webpieces-disable no-any-unknown -- index the typed config by dynamic custom-rule name\n const map = config as unknown as Record<string, RuleOptions | undefined>;\n const rules: Rule[] = [];\n for (const plain of loadCustomPlainRules(dirs, workspaceRoot)) {\n const rawConfig = map[plain.name] ?? {};\n rules.push(new CustomRuleAdapter(plain, rawConfig));\n }\n return rules;\n}\n\nfunction loadCustomPlainRules(rulesDirs: readonly string[], workspaceRoot: string): PlainRule[] {\n const modules: PlainRule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e: string) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot read custom rules directory '${absDir}'`, { cause: error });\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n const candidate = mod.default || mod;\n if (validateRule(candidate)) modules.push(candidate);\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot load custom rule '${full}'`, { cause: error });\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is PlainRule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SourceContributionConfig } from "../source-contribution-config";
|
|
2
2
|
import type { EditContext, Violation } from '@webpieces/hook-runtime';
|
|
3
3
|
import { EditRuleBase } from '@webpieces/hook-runtime';
|
|
4
4
|
import { FixHint } from '@webpieces/hook-runtime';
|
|
5
|
-
export declare class CatchErrorPatternRule extends EditRuleBase<
|
|
6
|
-
constructor(config:
|
|
5
|
+
export declare class CatchErrorPatternRule extends EditRuleBase<SourceContributionConfig> {
|
|
6
|
+
constructor(config: SourceContributionConfig);
|
|
7
7
|
readonly description = "Catch blocks must use: catch (err: unknown) { const error = toError(err); }";
|
|
8
8
|
readonly files: string[];
|
|
9
9
|
get fixHint(): FixHint;
|
|
@@ -45,7 +45,7 @@ class CatchErrorPatternRule extends hook_runtime_2.EditRuleBase {
|
|
|
45
45
|
if (!catchMatch)
|
|
46
46
|
continue;
|
|
47
47
|
const lineNum = i + 1;
|
|
48
|
-
if (disableAllowed && ctx.isLineDisabled(lineNum,
|
|
48
|
+
if (disableAllowed && ctx.isLineDisabled(lineNum, "catch-error-pattern"))
|
|
49
49
|
continue;
|
|
50
50
|
const actualParam = catchMatch[1];
|
|
51
51
|
const typeAnnotation = catchMatch[2];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catch-error-pattern.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/catch-error-pattern.ts"],"names":[],"mappings":"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","sourcesContent":["import { CatchErrorPatternConfig, RULE_NAMES, Option } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '@webpieces/hook-runtime';\nimport { Violation as V } from '@webpieces/hook-runtime';\nimport { EditRuleBase } from '@webpieces/hook-runtime';\nimport { FixHint, DisableEscape } from '@webpieces/hook-runtime';\nimport { writeTemplateIfMissing } from '@webpieces/rules-config';\n\n/**\n * Matches a catch clause opening: } catch (paramName: typeAnnotation) {\n * Captures: group 1 = param name, group 2 = type annotation (if present)\n */\nconst CATCH_PATTERN = /\\bcatch\\s*\\(\\s*(\\w+)(?:\\s*:\\s*(\\w+))?\\s*\\)/;\n\n/**\n * Matches the required toError first statement (with or without comment-out).\n * Group 1 = variable name, group 2 = param passed to toError\n *\n * The optional `//` prefix is what makes Fix Option 2 — \"to explicitly ignore the error, write\n * `//const error = toError(err);`\" — a real escape. It is only reachable when the pattern is tested\n * against the RAW source line; see findToErrorStatement() for why both line arrays are needed.\n */\nconst TO_ERROR_PATTERN = /^\\s*(?:\\/\\/\\s*)?const\\s+(\\w+)\\s*=\\s*toError\\(\\s*(\\w+)\\s*\\)\\s*;?\\s*$/;\n\n/**\n * What `stripTsNoise` leaves behind where a `//` comment was: the two slashes, then blanks to the end of\n * the line. Trimming a stripped comment line therefore yields `//`, not `''`. See findToErrorStatement().\n */\nconst COMMENT_REMNANT = /\\/\\/\\s*$/;\n\ninterface ToErrorMatch {\n varName: string;\n paramName: string;\n lineIndex: number;\n}\n\nexport class CatchErrorPatternRule extends EditRuleBase<CatchErrorPatternConfig> {\n constructor(config: CatchErrorPatternConfig) { super(config, 'catch-error-pattern', 'catch-error-pattern'); }\n\n readonly description = 'Catch blocks must use: catch (err: unknown) { const error = toError(err); }'; // webpieces-disable catch-error-pattern -- example text in a description string\n override readonly files = ['**/*.ts', '**/*.tsx'];\n get fixHint(): FixHint {\n return new FixHint(\n 'Catch block does not follow the toError(err) pattern.',\n 'Name the catch parameter err (err2/err3 when nested), then pick one:',\n [\n new Option('Add as the first statement in the catch block: const error = toError(err);', true),\n new Option('To explicitly ignore the error: //const error = toError(err);'),\n ],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable catch-error-pattern -- <reason>'), // webpieces-disable catch-error-pattern -- example text in a hint string\n );\n }\n\n check(ctx: EditContext): readonly Violation[] {\n const disableAllowed = this.config.disableAllowed ?? true;\n const violations: V[] = [];\n const lines = ctx.strippedLines;\n\n for (let i = 0; i < lines.length; i += 1) {\n const stripped = lines[i];\n const catchMatch = CATCH_PATTERN.exec(stripped);\n if (!catchMatch) continue;\n\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, RULE_NAMES.CATCH_ERROR_PATTERN)) continue;\n\n const actualParam = catchMatch[1];\n const typeAnnotation = catchMatch[2];\n\n // Determine expected names from suffix on the actual param (err, err2, err3...)\n const suffixMatch = actualParam.match(/^err(\\d*)$/);\n const suffix = suffixMatch ? suffixMatch[1] : '';\n const expectedParam = 'err' + suffix;\n const expectedVar = 'error' + suffix;\n\n // Check parameter name\n if (actualParam !== expectedParam) {\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Catch parameter must be named \"${expectedParam}\" (or \"err2\", \"err3\" for nested catches), got \"${actualParam}\"`,\n ));\n }\n\n // Check type annotation is unknown\n if (typeAnnotation !== 'unknown') {\n const msg = typeAnnotation\n ? `Catch parameter must be typed as \"unknown\": catch (${expectedParam}: unknown), got \"${typeAnnotation}\"`\n : `Catch parameter must be typed as \"unknown\": catch (${expectedParam}: unknown)`;\n violations.push(new V(lineNum, ctx.lines[i].trim(), msg));\n }\n\n // Find next non-blank line after the catch opening to check for toError\n const toErrorResult = this.findToErrorStatement(ctx, i + 1);\n if (toErrorResult === 'not-found') {\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Catch block must call toError(${actualParam}) as first statement: const ${expectedVar} = toError(${actualParam}); or //const ${expectedVar} = toError(${actualParam});`,\n ));\n } else if (toErrorResult !== 'end-of-content') {\n // Validate variable name and param match\n if (toErrorResult.varName !== expectedVar) {\n const toErrorLineNum = toErrorResult.lineIndex + 1;\n violations.push(new V(\n toErrorLineNum,\n ctx.lines[toErrorResult.lineIndex].trim(),\n `Error variable must be named \"${expectedVar}\", got \"${toErrorResult.varName}\"`,\n ));\n }\n if (toErrorResult.paramName !== actualParam) {\n const toErrorLineNum = toErrorResult.lineIndex + 1;\n violations.push(new V(\n toErrorLineNum,\n ctx.lines[toErrorResult.lineIndex].trim(),\n `toError() must be called with \"${actualParam}\", got \"${toErrorResult.paramName}\"`,\n ));\n }\n }\n }\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n }\n\n /**\n * The first statement of the catch block, judged against BOTH line arrays — and that pairing is the\n * whole fix for a bug that made this rule refuse the exact cure it prescribes.\n *\n * The rule FINDS catch clauses in `ctx.strippedLines`, which is right: a `catch (e) {` inside a\n * comment is not a catch clause. But it used to also LOOK FOR the toError statement there, and\n * stripping deletes `//const error = toError(err);` down to an empty line — so Fix Option 2, the\n * documented way to say \"this error is deliberately ignored\", was reported as \"no toError statement\"\n * every single time. TO_ERROR_PATTERN's optional `//` prefix could never match, because nothing\n * carrying a `//` ever reached it. 34 catches in this repo already use that form.\n *\n * So: the RAW line decides whether the commented form is present, and the STRIPPED line decides what\n * counts as \"the first statement\" (a blank line, a `{`, or an unrelated comment is skipped past).\n * Raw is tested first because it is the only array in which the comment form survives; a live\n * statement with a trailing comment (`const error = toError(err); // why`) fails the raw test on the\n * `$` anchor and is then matched on its stripped form, which is exactly the intent.\n *\n * COMMENT_REMNANT is the other half of that, and it is a property of the stripper: `stripTsNoise`\n * KEEPS the `//` marker and blanks only what follows it, so a stripped comment line trims to `//`\n * rather than to the empty string. Without normalizing that away, `//` is neither blank nor a `{`,\n * so it was taken for the first statement — which is the mechanical reason a commented-out toError\n * reported \"no toError statement\", and the reason a trailing `// why` on a LIVE toError reported the\n * same. One replace fixes both, in the one place the question is asked.\n */\n private findToErrorStatement(ctx: EditContext, startIndex: number): ToErrorMatch | 'not-found' | 'end-of-content' {\n const stripped = ctx.strippedLines;\n for (let j = startIndex; j < stripped.length; j += 1) {\n const rawMatch = TO_ERROR_PATTERN.exec((ctx.lines[j] ?? '').trim());\n if (rawMatch) return { varName: rawMatch[1], paramName: rawMatch[2], lineIndex: j };\n\n const line = stripped[j].replace(COMMENT_REMNANT, '').trim();\n // Blank, an opening brace, or a comment that stripping emptied — none of these is the first\n // statement, so keep looking.\n if (line === '' || line === '{') continue;\n\n const match = TO_ERROR_PATTERN.exec(line);\n if (match) return { varName: match[1], paramName: match[2], lineIndex: j };\n // First real statement is not a toError call\n return 'not-found';\n }\n // Ran off the end of the edit content — can't validate further\n return 'end-of-content';\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"catch-error-pattern.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/catch-error-pattern.ts"],"names":[],"mappings":"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","sourcesContent":["import { SourceContributionConfig } from \"../source-contribution-config\";\nimport { Option } from \"@webpieces/rules-config\";\n\nimport type { EditContext, Violation } from '@webpieces/hook-runtime';\nimport { Violation as V } from '@webpieces/hook-runtime';\nimport { EditRuleBase } from '@webpieces/hook-runtime';\nimport { FixHint, DisableEscape } from '@webpieces/hook-runtime';\nimport { writeTemplateIfMissing } from '@webpieces/rules-config';\n\n/**\n * Matches a catch clause opening: } catch (paramName: typeAnnotation) {\n * Captures: group 1 = param name, group 2 = type annotation (if present)\n */\nconst CATCH_PATTERN = /\\bcatch\\s*\\(\\s*(\\w+)(?:\\s*:\\s*(\\w+))?\\s*\\)/;\n\n/**\n * Matches the required toError first statement (with or without comment-out).\n * Group 1 = variable name, group 2 = param passed to toError\n *\n * The optional `//` prefix is what makes Fix Option 2 — \"to explicitly ignore the error, write\n * `//const error = toError(err);`\" — a real escape. It is only reachable when the pattern is tested\n * against the RAW source line; see findToErrorStatement() for why both line arrays are needed.\n */\nconst TO_ERROR_PATTERN = /^\\s*(?:\\/\\/\\s*)?const\\s+(\\w+)\\s*=\\s*toError\\(\\s*(\\w+)\\s*\\)\\s*;?\\s*$/;\n\n/**\n * What `stripTsNoise` leaves behind where a `//` comment was: the two slashes, then blanks to the end of\n * the line. Trimming a stripped comment line therefore yields `//`, not `''`. See findToErrorStatement().\n */\nconst COMMENT_REMNANT = /\\/\\/\\s*$/;\n\ninterface ToErrorMatch {\n varName: string;\n paramName: string;\n lineIndex: number;\n}\n\nexport class CatchErrorPatternRule extends EditRuleBase<SourceContributionConfig> {\n constructor(config: SourceContributionConfig) { super(config, 'catch-error-pattern', 'catch-error-pattern'); }\n\n readonly description = 'Catch blocks must use: catch (err: unknown) { const error = toError(err); }'; // webpieces-disable catch-error-pattern -- example text in a description string\n override readonly files = ['**/*.ts', '**/*.tsx'];\n get fixHint(): FixHint {\n return new FixHint(\n 'Catch block does not follow the toError(err) pattern.',\n 'Name the catch parameter err (err2/err3 when nested), then pick one:',\n [\n new Option('Add as the first statement in the catch block: const error = toError(err);', true),\n new Option('To explicitly ignore the error: //const error = toError(err);'),\n ],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable catch-error-pattern -- <reason>'), // webpieces-disable catch-error-pattern -- example text in a hint string\n );\n }\n\n check(ctx: EditContext): readonly Violation[] {\n const disableAllowed = this.config.disableAllowed ?? true;\n const violations: V[] = [];\n const lines = ctx.strippedLines;\n\n for (let i = 0; i < lines.length; i += 1) {\n const stripped = lines[i];\n const catchMatch = CATCH_PATTERN.exec(stripped);\n if (!catchMatch) continue;\n\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, \"catch-error-pattern\")) continue;\n\n const actualParam = catchMatch[1];\n const typeAnnotation = catchMatch[2];\n\n // Determine expected names from suffix on the actual param (err, err2, err3...)\n const suffixMatch = actualParam.match(/^err(\\d*)$/);\n const suffix = suffixMatch ? suffixMatch[1] : '';\n const expectedParam = 'err' + suffix;\n const expectedVar = 'error' + suffix;\n\n // Check parameter name\n if (actualParam !== expectedParam) {\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Catch parameter must be named \"${expectedParam}\" (or \"err2\", \"err3\" for nested catches), got \"${actualParam}\"`,\n ));\n }\n\n // Check type annotation is unknown\n if (typeAnnotation !== 'unknown') {\n const msg = typeAnnotation\n ? `Catch parameter must be typed as \"unknown\": catch (${expectedParam}: unknown), got \"${typeAnnotation}\"`\n : `Catch parameter must be typed as \"unknown\": catch (${expectedParam}: unknown)`;\n violations.push(new V(lineNum, ctx.lines[i].trim(), msg));\n }\n\n // Find next non-blank line after the catch opening to check for toError\n const toErrorResult = this.findToErrorStatement(ctx, i + 1);\n if (toErrorResult === 'not-found') {\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Catch block must call toError(${actualParam}) as first statement: const ${expectedVar} = toError(${actualParam}); or //const ${expectedVar} = toError(${actualParam});`,\n ));\n } else if (toErrorResult !== 'end-of-content') {\n // Validate variable name and param match\n if (toErrorResult.varName !== expectedVar) {\n const toErrorLineNum = toErrorResult.lineIndex + 1;\n violations.push(new V(\n toErrorLineNum,\n ctx.lines[toErrorResult.lineIndex].trim(),\n `Error variable must be named \"${expectedVar}\", got \"${toErrorResult.varName}\"`,\n ));\n }\n if (toErrorResult.paramName !== actualParam) {\n const toErrorLineNum = toErrorResult.lineIndex + 1;\n violations.push(new V(\n toErrorLineNum,\n ctx.lines[toErrorResult.lineIndex].trim(),\n `toError() must be called with \"${actualParam}\", got \"${toErrorResult.paramName}\"`,\n ));\n }\n }\n }\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n }\n\n /**\n * The first statement of the catch block, judged against BOTH line arrays — and that pairing is the\n * whole fix for a bug that made this rule refuse the exact cure it prescribes.\n *\n * The rule FINDS catch clauses in `ctx.strippedLines`, which is right: a `catch (e) {` inside a\n * comment is not a catch clause. But it used to also LOOK FOR the toError statement there, and\n * stripping deletes `//const error = toError(err);` down to an empty line — so Fix Option 2, the\n * documented way to say \"this error is deliberately ignored\", was reported as \"no toError statement\"\n * every single time. TO_ERROR_PATTERN's optional `//` prefix could never match, because nothing\n * carrying a `//` ever reached it. 34 catches in this repo already use that form.\n *\n * So: the RAW line decides whether the commented form is present, and the STRIPPED line decides what\n * counts as \"the first statement\" (a blank line, a `{`, or an unrelated comment is skipped past).\n * Raw is tested first because it is the only array in which the comment form survives; a live\n * statement with a trailing comment (`const error = toError(err); // why`) fails the raw test on the\n * `$` anchor and is then matched on its stripped form, which is exactly the intent.\n *\n * COMMENT_REMNANT is the other half of that, and it is a property of the stripper: `stripTsNoise`\n * KEEPS the `//` marker and blanks only what follows it, so a stripped comment line trims to `//`\n * rather than to the empty string. Without normalizing that away, `//` is neither blank nor a `{`,\n * so it was taken for the first statement — which is the mechanical reason a commented-out toError\n * reported \"no toError statement\", and the reason a trailing `// why` on a LIVE toError reported the\n * same. One replace fixes both, in the one place the question is asked.\n */\n private findToErrorStatement(ctx: EditContext, startIndex: number): ToErrorMatch | 'not-found' | 'end-of-content' {\n const stripped = ctx.strippedLines;\n for (let j = startIndex; j < stripped.length; j += 1) {\n const rawMatch = TO_ERROR_PATTERN.exec((ctx.lines[j] ?? '').trim());\n if (rawMatch) return { varName: rawMatch[1], paramName: rawMatch[2], lineIndex: j };\n\n const line = stripped[j].replace(COMMENT_REMNANT, '').trim();\n // Blank, an opening brace, or a comment that stripping emptied — none of these is the first\n // statement, so keep looking.\n if (line === '' || line === '{') continue;\n\n const match = TO_ERROR_PATTERN.exec(line);\n if (match) return { varName: match[1], paramName: match[2], lineIndex: j };\n // First real statement is not a toError call\n return 'not-found';\n }\n // Ran off the end of the edit content — can't validate further\n return 'end-of-content';\n }\n}\n"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SourceContributionConfig } from "../source-contribution-config";
|
|
2
2
|
import type { FileContext, Violation } from '@webpieces/hook-runtime';
|
|
3
3
|
import { FileRuleBase } from '@webpieces/hook-runtime';
|
|
4
4
|
import { FixHint } from '@webpieces/hook-runtime';
|
|
5
|
-
export declare class MaxFileLinesRule extends FileRuleBase<
|
|
6
|
-
constructor(config:
|
|
5
|
+
export declare class MaxFileLinesRule extends FileRuleBase<SourceContributionConfig> {
|
|
6
|
+
constructor(config: SourceContributionConfig);
|
|
7
7
|
readonly description = "Cap file length at a configured line limit.";
|
|
8
8
|
readonly files: string[];
|
|
9
9
|
readonly defaultOptions: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"max-file-lines.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/max-file-lines.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"max-file-lines.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/max-file-lines.ts"],"names":[],"mappings":";;;AACA,0DAA+H;AAG/H,0DAAyD;AACzD,0DAAuD;AACvD,0DAAiE;AAEjE,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B,MAAM,aAAa,GAAG,uBAAuB,CAAC;AAE9C,MAAa,gBAAiB,SAAQ,2BAAsC;IACxE,YAAY,MAAgC,IAAI,KAAK,CAAC,MAAM,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAE3F,WAAW,GAAG,6CAA6C,CAAC;IACnD,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAChC,cAAc,GAAG,EAAE,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IAC9E,IAAI,OAAO;QACP,OAAO,IAAI,sBAAO,CACd,wCAAwC,EACxC,WAAW,EACX;YACI,IAAI,qBAAM,CAAC,+GAA+G,EAAE,IAAI,CAAC;YACjI,IAAI,qBAAM,CAAC,2QAA2Q,CAAC;SAC1R,EACD,IAAI,4BAAa,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,EAAE,0FAA0F,CAAC,CACpJ,CAAC;IACN,CAAC;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,aAAa,CAAC;QACjD,uFAAuF;QACvF,IAAI,IAAA,6BAAc,EAAC,GAAG,CAAC,YAAY,EAAE,CAAC,GAAG,mCAAoB,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC;YAAE,OAAO,EAAE,CAAC;QAChH,IAAI,GAAG,CAAC,kBAAkB,IAAI,KAAK;YAAE,OAAO,EAAE,CAAC;QAC/C,IAAA,qCAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,IAAI,6BAAc,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACzF,OAAO,CAAC,IAAI,wBAAC,CACT,CAAC,EACD,cAAc,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,SAAS,EACrD,gBAAgB,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,yBAAyB,MAAM,CAAC,KAAK,CAAC,qBAAqB,OAAO,yCAAyC,CAC5J,CAAC,CAAC;IACP,CAAC;CACJ;AA/BD,4CA+BC","sourcesContent":["import { SourceContributionConfig } from \"../source-contribution-config\";\nimport { writeTemplateIfMissing, RepoRootFinder, isPathExcluded, GENERATED_CODE_PATHS, Option } from \"@webpieces/rules-config\";\n\nimport type { FileContext, Violation } from '@webpieces/hook-runtime';\nimport { Violation as V } from '@webpieces/hook-runtime';\nimport { FileRuleBase } from '@webpieces/hook-runtime';\nimport { FixHint, DisableEscape } from '@webpieces/hook-runtime';\n\nconst DEFAULT_LIMIT = 900;\nconst INSTRUCT_FILE = 'webpieces.filesize.md';\n\nexport class MaxFileLinesRule extends FileRuleBase<SourceContributionConfig> {\n constructor(config: SourceContributionConfig) { super(config, 'max-file-lines', 'max-file-lines'); }\n\n readonly description = 'Cap file length at a configured line limit.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n override readonly defaultOptions = { limit: DEFAULT_LIMIT, allowedPaths: [] };\n get fixHint(): FixHint {\n return new FixHint(\n 'File exceeds the max-file-lines limit.',\n 'Pick one:',\n [\n new Option('Refactor to reduce the file size — READ the instruct-ai doc at the absolute path on the violation line above.', true),\n new Option('If this file is MACHINE-GENERATED and its size is not yours to control, add its tree to max-file-lines.allowedPaths in webpieces.config.json (the generated trees in GENERATED_CODE_PATHS are already exempt with no config at all). Never do this for hand-written code.'),\n ],\n new DisableEscape(this.config.disableAllowed ?? true, '// eslint-disable-next-line @webpieces/max-file-lines (also suppresses the eslint rule)'),\n );\n }\n\n check(ctx: FileContext): readonly Violation[] {\n const limit = this.config.limit ?? DEFAULT_LIMIT;\n // Machine-generated trees are exempt unconditionally; allowedPaths ADDS to that floor.\n if (isPathExcluded(ctx.relativePath, [...GENERATED_CODE_PATHS, ...(this.config.allowedPaths ?? [])])) return [];\n if (ctx.projectedFileLines <= limit) return [];\n writeTemplateIfMissing(ctx.workspaceRoot, INSTRUCT_FILE);\n const docPath = new RepoRootFinder().instructAiDocPath(ctx.workspaceRoot, INSTRUCT_FILE);\n return [new V(\n 1,\n `(projected ${String(ctx.projectedFileLines)} lines)`,\n `File will be ${String(ctx.projectedFileLines)} lines, exceeding the ${String(limit)}-line limit. READ ${docPath} for detailed refactoring instructions.`,\n )];\n }\n}\n"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SourceContributionConfig } from "../source-contribution-config";
|
|
2
2
|
import type { EditContext, Violation } from '@webpieces/hook-runtime';
|
|
3
3
|
import { EditRuleBase } from '@webpieces/hook-runtime';
|
|
4
4
|
import { FixHint } from '@webpieces/hook-runtime';
|
|
5
|
-
export declare class NoAnyUnknownRule extends EditRuleBase<
|
|
6
|
-
constructor(config:
|
|
5
|
+
export declare class NoAnyUnknownRule extends EditRuleBase<SourceContributionConfig> {
|
|
6
|
+
constructor(config: SourceContributionConfig);
|
|
7
7
|
readonly description = "Disallow the `any` keyword. Use concrete types or interfaces.";
|
|
8
8
|
readonly files: string[];
|
|
9
9
|
get fixHint(): FixHint;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.NoAnyUnknownRule = void 0;
|
|
4
|
-
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
4
|
const hook_runtime_1 = require("@webpieces/hook-runtime");
|
|
6
5
|
const hook_runtime_2 = require("@webpieces/hook-runtime");
|
|
7
6
|
const hook_runtime_3 = require("@webpieces/hook-runtime");
|
|
@@ -22,7 +21,7 @@ class NoAnyUnknownRule extends hook_runtime_2.EditRuleBase {
|
|
|
22
21
|
if (!ANY_PATTERN.test(stripped))
|
|
23
22
|
continue;
|
|
24
23
|
const lineNum = i + 1;
|
|
25
|
-
if (disableAllowed && ctx.isLineDisabled(lineNum,
|
|
24
|
+
if (disableAllowed && ctx.isLineDisabled(lineNum, "no-any-unknown"))
|
|
26
25
|
continue;
|
|
27
26
|
violations.push(new hook_runtime_1.Violation(lineNum, ctx.lines[i].trim()));
|
|
28
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-any-unknown.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-any-unknown.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"no-any-unknown.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-any-unknown.ts"],"names":[],"mappings":";;;AAIA,0DAAyD;AACzD,0DAAuD;AACvD,0DAAiE;AAEjE,+DAA+D;AAC/D,MAAM,WAAW,GACb,4HAA4H,CAAC,CAAC,gEAAgE;AAElM,MAAa,gBAAiB,SAAQ,2BAAsC;IACxE,YAAY,MAAgC,IAAI,KAAK,CAAC,MAAM,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAE3F,WAAW,GAAG,+DAA+D,CAAC;IACrE,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAClD,IAAI,OAAO;QACP,OAAO,IAAI,sBAAO,CACd,gCAAgC,EAChC,wGAAwG,EACxG,EAAE,EACF,IAAI,4BAAa,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,EAAE,0DAA0D,CAAC,CACpH,CAAC;IACN,CAAC;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,CAAC;QAC1D,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,cAAc,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBAAE,SAAS;YAC9E,UAAU,CAAC,IAAI,CAAC,IAAI,wBAAC,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AA1BD,4CA0BC","sourcesContent":["import { SourceContributionConfig } from \"../source-contribution-config\";\n\n\nimport type { EditContext, Violation } from '@webpieces/hook-runtime';\nimport { Violation as V } from '@webpieces/hook-runtime';\nimport { EditRuleBase } from '@webpieces/hook-runtime';\nimport { FixHint, DisableEscape } from '@webpieces/hook-runtime';\n\n// This regex literal matches the token as text, not a TS type.\nconst ANY_PATTERN =\n /(?::\\s*any\\b|\\bas\\s+any\\b|<any>|any\\[\\]|Array<any>|Promise<any>|Map<[^,<>]+,\\s*any\\s*>|Record<[^,<>]+,\\s*any\\s*>|Set<any>)/; // webpieces-disable no-any-unknown -- regex literal, not a type\n\nexport class NoAnyUnknownRule extends EditRuleBase<SourceContributionConfig> {\n constructor(config: SourceContributionConfig) { super(config, 'no-any-unknown', 'no-any-unknown'); }\n\n readonly description = 'Disallow the `any` keyword. Use concrete types or interfaces.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n get fixHint(): FixHint {\n return new FixHint(\n '`any` erases type information.',\n 'Use a concrete type: interface MyData { ... } or class MyData { ... } (or `unknown` with type guards).',\n [],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-any-unknown -- <one-line reason>'),\n );\n }\n\n check(ctx: EditContext): readonly Violation[] {\n const disableAllowed = this.config.disableAllowed ?? true;\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!ANY_PATTERN.test(stripped)) continue;\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, \"no-any-unknown\")) continue;\n violations.push(new V(lineNum, ctx.lines[i].trim()));\n }\n return violations;\n }\n}\n"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SourceContributionConfig } from "../source-contribution-config";
|
|
2
2
|
import type { EditContext, Violation } from '@webpieces/hook-runtime';
|
|
3
3
|
import { EditRuleBase } from '@webpieces/hook-runtime';
|
|
4
4
|
import { FixHint } from '@webpieces/hook-runtime';
|
|
5
|
-
export declare class NoCustomCssRule extends EditRuleBase<
|
|
5
|
+
export declare class NoCustomCssRule extends EditRuleBase<SourceContributionConfig> {
|
|
6
6
|
private readonly pathScope;
|
|
7
|
-
constructor(config:
|
|
7
|
+
constructor(config: SourceContributionConfig);
|
|
8
8
|
readonly description = "Ban hand-written CSS in Angular (styles/styleUrls in @Component, inline style=, [style.x], [ngStyle]) \u2014 style with Tailwind utility classes.";
|
|
9
9
|
readonly files: string[];
|
|
10
10
|
readonly defaultOptions: {
|
|
@@ -43,7 +43,7 @@ class NoCustomCssRule extends hook_runtime_2.EditRuleBase {
|
|
|
43
43
|
if (!detail)
|
|
44
44
|
continue;
|
|
45
45
|
const lineNum = i + 1;
|
|
46
|
-
if (disableAllowed && ctx.isLineDisabled(lineNum,
|
|
46
|
+
if (disableAllowed && ctx.isLineDisabled(lineNum, "no-custom-css"))
|
|
47
47
|
continue;
|
|
48
48
|
violations.push(new hook_runtime_1.Violation(lineNum, `${detail}: ${ctx.lines[i]?.trim() ?? ''}`));
|
|
49
49
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-custom-css.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-custom-css.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"no-custom-css.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-custom-css.ts"],"names":[],"mappings":"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","sourcesContent":["import { SourceContributionConfig } from \"../source-contribution-config\";\nimport { NoCustomCssScope, Option } from \"@webpieces/rules-config\";\n\nimport type { EditContext, Violation } from '@webpieces/hook-runtime';\nimport { Violation as V } from '@webpieces/hook-runtime';\nimport { EditRuleBase } from '@webpieces/hook-runtime';\nimport { FixHint, DisableEscape } from '@webpieces/hook-runtime';\n\n// Edit-time (regex/line) counterpart to the CI code-rule validate-no-custom-css. The hook has no TS\n// AST, so the `.ts` side matches the @Component style props by shape; the `.html` side matches inline\n// style attributes/bindings. Both are diff-precise via the hook's per-line edit context.\nconst RE_STYLE_URLS = /(^|\\s)styleUrls?\\s*:/; // styleUrls: / styleUrl:\nconst RE_STYLES_ARRAY = /(^|\\s)styles\\s*:\\s*\\[/; // styles: [ ... ] (component inline CSS)\nconst RE_INLINE_STYLE = /(^|\\s)style\\s*=\\s*[\"']/; // static style=\"…\"\nconst RE_NG_STYLE = /\\[?ngStyle\\]?\\s*=/; // [ngStyle]=\"…\"\nconst RE_STYLE_BINDING = /\\[style(?:\\.[\\w.-]+)?\\]/; // [style] / [style.width]\n\nexport class NoCustomCssRule extends EditRuleBase<SourceContributionConfig> {\n // The SAME exemption the CI validator narrows its changed-file set with. Shared, because a second copy\n // of this predicate is how `allowGlobs` came to be honoured here and ignored there.\n private readonly pathScope: NoCustomCssScope;\n\n constructor(config: SourceContributionConfig) {\n super(config, 'no-custom-css', 'no-custom-css');\n this.pathScope = new NoCustomCssScope(config);\n }\n\n readonly description =\n 'Ban hand-written CSS in Angular (styles/styleUrls in @Component, inline style=, [style.x], [ngStyle]) — style with Tailwind utility classes.';\n override readonly files = ['**/*.ts', '**/*.tsx', '**/*.html'];\n override readonly defaultOptions = { allowGlobs: [] };\n\n get fixHint(): FixHint {\n return new FixHint(\n 'Custom CSS bypasses Tailwind-first styling.',\n 'Pick one:',\n [\n new Option('Delete the CSS and use Tailwind utility classes (flex, grid, gap-4, text-red-600).', true),\n new Option('Need a specific value? Use an arbitrary-value class: w-[240px], bg-[#fffde7], grid-cols-[2fr_2fr_48px].'),\n new Option('Dynamic on/off? Prefer [class.x]=\"cond\" over a style binding.'),\n new Option('Whole tree not yours to restyle (a vendored kit, a generated artifact)? Add a glob to no-custom-css.allowGlobs in webpieces.config.json — it exempts the path in the editor and in CI alike.'),\n ],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-custom-css -- <reason>'),\n );\n }\n\n check(ctx: EditContext): readonly Violation[] {\n if (this.pathScope.isExempt(ctx.relativePath)) return [];\n\n const isHtml = ctx.relativePath.endsWith('.html');\n const disableAllowed = this.config.disableAllowed ?? true;\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const detail = this.detailForLine(ctx.strippedLines[i] ?? '', isHtml);\n if (!detail) continue;\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, \"no-custom-css\")) continue;\n violations.push(new V(lineNum, `${detail}: ${ctx.lines[i]?.trim() ?? ''}`));\n }\n return violations;\n }\n\n /** The banned-pattern label for a line, or '' when clean. Branches on the file being a template. */\n private detailForLine(line: string, isHtml: boolean): string {\n if (isHtml) {\n if (RE_NG_STYLE.test(line)) return 'inline `[ngStyle]`';\n if (RE_STYLE_BINDING.test(line)) return 'inline `[style.x]` binding';\n if (RE_INLINE_STYLE.test(line)) return 'inline `style=` attribute';\n return '';\n }\n if (RE_STYLE_URLS.test(line)) return '`styleUrls`/`styleUrl` in @Component';\n if (RE_STYLES_ARRAY.test(line)) return '`styles` block in @Component';\n return '';\n }\n}\n"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SourceContributionConfig } from "../source-contribution-config";
|
|
2
2
|
import type { EditContext, Violation } from '@webpieces/hook-runtime';
|
|
3
3
|
import { EditRuleBase } from '@webpieces/hook-runtime';
|
|
4
4
|
import { FixHint } from '@webpieces/hook-runtime';
|
|
5
|
-
export declare class NoDestructureRule extends EditRuleBase<
|
|
6
|
-
constructor(config:
|
|
5
|
+
export declare class NoDestructureRule extends EditRuleBase<SourceContributionConfig> {
|
|
6
|
+
constructor(config: SourceContributionConfig);
|
|
7
7
|
readonly description = "Disallow destructuring patterns. Assign the whole result and pass it around or access properties explicitly.";
|
|
8
8
|
readonly files: string[];
|
|
9
9
|
readonly defaultOptions: {
|
|
@@ -28,7 +28,7 @@ class NoDestructureRule extends hook_runtime_2.EditRuleBase {
|
|
|
28
28
|
if (!VARIABLE_DESTRUCTURE.test(stripped))
|
|
29
29
|
continue;
|
|
30
30
|
const lineNum = i + 1;
|
|
31
|
-
if (disableAllowed && ctx.isLineDisabled(lineNum,
|
|
31
|
+
if (disableAllowed && ctx.isLineDisabled(lineNum, "no-destructure"))
|
|
32
32
|
continue;
|
|
33
33
|
violations.push(new hook_runtime_1.Violation(lineNum, ctx.lines[i].trim()));
|
|
34
34
|
}
|