@webpieces/ai-hook-rules 0.3.185 → 0.3.186
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -4
- package/src/core/custom-rule-adapter.d.ts +21 -0
- package/src/core/custom-rule-adapter.js +48 -0
- package/src/core/custom-rule-adapter.js.map +1 -0
- package/src/core/load-rules.d.ts +3 -2
- package/src/core/load-rules.js +71 -50
- package/src/core/load-rules.js.map +1 -1
- package/src/core/rule-base.d.ts +38 -0
- package/src/core/rule-base.js +36 -0
- package/src/core/rule-base.js.map +1 -0
- package/src/core/rules/branch-creation-guard.d.ts +12 -3
- package/src/core/rules/branch-creation-guard.js +37 -37
- package/src/core/rules/branch-creation-guard.js.map +1 -1
- package/src/core/rules/catch-error-pattern.d.ts +10 -3
- package/src/core/rules/catch-error-pattern.js +26 -26
- package/src/core/rules/catch-error-pattern.js.map +1 -1
- package/src/core/rules/max-file-lines.d.ts +13 -3
- package/src/core/rules/max-file-lines.js +14 -15
- package/src/core/rules/max-file-lines.js.map +1 -1
- package/src/core/rules/merge-in-progress-guard.d.ts +9 -3
- package/src/core/rules/merge-in-progress-guard.js +12 -13
- package/src/core/rules/merge-in-progress-guard.js.map +1 -1
- package/src/core/rules/no-any-unknown.d.ts +10 -3
- package/src/core/rules/no-any-unknown.js +14 -13
- package/src/core/rules/no-any-unknown.js.map +1 -1
- package/src/core/rules/no-destructure.d.ts +13 -3
- package/src/core/rules/no-destructure.js +13 -12
- package/src/core/rules/no-destructure.js.map +1 -1
- package/src/core/rules/no-direct-main-update.d.ts +9 -3
- package/src/core/rules/no-direct-main-update.js +12 -13
- package/src/core/rules/no-direct-main-update.js.map +1 -1
- package/src/core/rules/no-edit-on-main.d.ts +13 -3
- package/src/core/rules/no-edit-on-main.js +14 -14
- package/src/core/rules/no-edit-on-main.js.map +1 -1
- package/src/core/rules/no-implicit-any.d.ts +10 -3
- package/src/core/rules/no-implicit-any.js +12 -12
- package/src/core/rules/no-implicit-any.js.map +1 -1
- package/src/core/rules/no-js-files.d.ts +13 -3
- package/src/core/rules/no-js-files.js +13 -16
- package/src/core/rules/no-js-files.js.map +1 -1
- package/src/core/rules/no-shell-substitution.d.ts +9 -3
- package/src/core/rules/no-shell-substitution.js +22 -23
- package/src/core/rules/no-shell-substitution.js.map +1 -1
- package/src/core/rules/no-symbol-di-tokens.d.ts +13 -3
- package/src/core/rules/no-symbol-di-tokens.js +14 -13
- package/src/core/rules/no-symbol-di-tokens.js.map +1 -1
- package/src/core/rules/no-unmanaged-exceptions.d.ts +10 -3
- package/src/core/rules/no-unmanaged-exceptions.js +17 -17
- package/src/core/rules/no-unmanaged-exceptions.js.map +1 -1
- package/src/core/rules/pr-creation-guard.d.ts +9 -3
- package/src/core/rules/pr-creation-guard.js +12 -13
- package/src/core/rules/pr-creation-guard.js.map +1 -1
- package/src/core/rules/pr-merge-cleanup.d.ts +9 -3
- package/src/core/rules/pr-merge-cleanup.js +13 -14
- package/src/core/rules/pr-merge-cleanup.js.map +1 -1
- package/src/core/rules/require-return-type.d.ts +10 -3
- package/src/core/rules/require-return-type.js +23 -23
- package/src/core/rules/require-return-type.js.map +1 -1
- package/src/core/rules/throw-cause-required.d.ts +10 -3
- package/src/core/rules/throw-cause-required.js +12 -12
- package/src/core/rules/throw-cause-required.js.map +1 -1
- package/src/core/rules/validate-ts-in-src.d.ts +14 -3
- package/src/core/rules/validate-ts-in-src.js +15 -18
- package/src/core/rules/validate-ts-in-src.js.map +1 -1
- package/src/core/runner.js +38 -67
- package/src/core/runner.js.map +1 -1
- package/src/core/types.d.ts +14 -13
- package/src/core/types.js.map +1 -1
- package/src/index.d.ts +21 -1
- package/src/index.js +47 -1
- package/src/index.js.map +1 -1
- package/src/core/configs/default.d.ts +0 -2
- package/src/core/configs/default.js +0 -22
- package/src/core/configs/default.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-edit-on-main.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-edit-on-main.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-edit-on-main.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-edit-on-main.ts"],"names":[],"mappings":";;;AAAA,iDAAyC;AAKzC,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAAsC;AAEtC,MAAa,gBAAiB,SAAQ,wBAAgC;IAClE,YAAY,MAA0B,IAAI,KAAK,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAEpE,WAAW,GAAG,sFAAsF,CAAC;IAC5F,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC;IACjB,cAAc,GAAG;QAC/B,sBAAsB,EAAE,wBAAwB;KACnD,CAAC;IACO,OAAO,GAAG;QACf,oCAAoC;QACpC,QAAQ;QACR,kDAAkD;QAClD,8DAA8D;QAC9D,4GAA4G;KAC/G,CAAC;IAEF,KAAK,CAAC,GAAgB;QAClB,gDAAgD;QAChD,IAAI,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QAEjD,IAAI,aAAqB,CAAC;QAC1B,8DAA8D;QAC9D,IAAI,CAAC;YACD,aAAa,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE;gBACxD,GAAG,EAAE,GAAG,CAAC,aAAa;gBACtB,QAAQ,EAAE,MAAM;gBAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;aAClC,CAAC,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,KAAK,KAAK,CAAC;YACX,OAAO,EAAE,CAAC;QACd,CAAC;QAED,IAAI,aAAa,KAAK,MAAM;YAAE,OAAO,EAAE,CAAC;QAExC,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,sBAAsB,IAAI,wBAAwB,CAAC;QAElF,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,GAAG,CAAC,YAAY,EAChB;gBACI,oCAAoC;gBACpC,yGAAyG;gBACzG,0DAA0D,UAAU,EAAE;gBACtE,2BAA2B,GAAG,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC;aACxE,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ;AAjDD,4CAiDC","sourcesContent":["import { execSync } from 'child_process';\n\nimport { NoEditOnMainConfig } from '@webpieces/rules-config';\n\nimport type { FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { FileRuleBase } from '../rule-base';\nimport { toError } from '../to-error';\n\nexport class NoEditOnMainRule extends FileRuleBase<NoEditOnMainConfig> {\n constructor(config: NoEditOnMainConfig) { super(config, 'no-edit-on-main'); }\n\n readonly description = 'Block file edits when on the main branch — all work must happen on a feature branch.';\n override readonly files = ['**/*'];\n override readonly defaultOptions = {\n branchNamingConvention: '{whoami}/{featurename}',\n };\n readonly fixHint = [\n 'You should not be working on main.',\n 'Steps:',\n ' 1. git pull origin main ← get latest commits',\n ' 2. git checkout -b <branch-name> ← create feature branch',\n 'Branch naming convention is defined in webpieces.config.json under no-edit-on-main.branchNamingConvention.',\n ];\n\n check(ctx: FileContext): readonly Violation[] {\n // Only apply to files inside the workspace root\n if (ctx.relativePath.startsWith('..')) return [];\n\n let currentBranch: string;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n\n if (currentBranch !== 'main') return [];\n\n const convention = this.config.branchNamingConvention ?? '{whoami}/{featurename}';\n\n return [new V(\n 1,\n ctx.relativePath,\n [\n 'You should not be working on main.',\n 'Do a `git pull origin main` to get latest, then create a feature branch based on the naming convention.',\n `Branch naming convention (from webpieces.config.json): ${convention}`,\n 'Example: git checkout -b ' + convention.replace(/<[^>]+>/g, 'value'),\n ].join('\\n'),\n )];\n }\n}\n"]}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { NoImplicitAnyConfig } from '@webpieces/rules-config';
|
|
2
|
+
import type { EditContext, Violation } from '../types';
|
|
3
|
+
import { EditRuleBase } from '../rule-base';
|
|
4
|
+
export declare class NoImplicitAnyRule extends EditRuleBase<NoImplicitAnyConfig> {
|
|
5
|
+
constructor(config: NoImplicitAnyConfig);
|
|
6
|
+
readonly description = "Disallow function parameters without explicit type annotations (implicit-any).";
|
|
7
|
+
readonly files: string[];
|
|
8
|
+
readonly fixHint: string[];
|
|
9
|
+
check(ctx: EditContext): readonly Violation[];
|
|
10
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
3
|
+
exports.NoImplicitAnyRule = void 0;
|
|
4
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const rule_base_1 = require("../rule-base");
|
|
5
7
|
const ARROW_PARAMS_RE = /\(([^()]*)\)\s*=>/g;
|
|
6
8
|
const FN_DECL_PARAMS_RE = /\bfunction\s*[\w$]*\s*\(([^()]*)\)/g;
|
|
7
9
|
function firstUntypedParam(paramsStr) {
|
|
@@ -41,16 +43,14 @@ function findOffender(line) {
|
|
|
41
43
|
}
|
|
42
44
|
return null;
|
|
43
45
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
description
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
defaultOptions: {},
|
|
50
|
-
fixHint: [
|
|
46
|
+
class NoImplicitAnyRule extends rule_base_1.EditRuleBase {
|
|
47
|
+
constructor(config) { super(config, 'no-implicit-any'); }
|
|
48
|
+
description = 'Disallow function parameters without explicit type annotations (implicit-any).';
|
|
49
|
+
files = ['**/*.ts', '**/*.tsx'];
|
|
50
|
+
fixHint = [
|
|
51
51
|
'Add explicit types: (x: string) => ... or function foo(x: number)',
|
|
52
52
|
'// webpieces-disable no-implicit-any -- <one-line reason>',
|
|
53
|
-
]
|
|
53
|
+
];
|
|
54
54
|
check(ctx) {
|
|
55
55
|
const violations = [];
|
|
56
56
|
for (let i = 0; i < ctx.strippedLines.length; i += 1) {
|
|
@@ -64,7 +64,7 @@ const noImplicitAnyRule = {
|
|
|
64
64
|
violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim(), `Parameter "${offender}" has no type annotation. Add an explicit type to avoid implicit-any.`));
|
|
65
65
|
}
|
|
66
66
|
return violations;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
exports.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.NoImplicitAnyRule = NoImplicitAnyRule;
|
|
70
70
|
//# sourceMappingURL=no-implicit-any.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-implicit-any.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-implicit-any.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-implicit-any.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-implicit-any.ts"],"names":[],"mappings":"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","sourcesContent":["import { NoImplicitAnyConfig, RULE_NAMES } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { EditRuleBase } from '../rule-base';\n\nconst ARROW_PARAMS_RE = /\\(([^()]*)\\)\\s*=>/g;\nconst FN_DECL_PARAMS_RE = /\\bfunction\\s*[\\w$]*\\s*\\(([^()]*)\\)/g;\n\nfunction firstUntypedParam(paramsStr: string): string | null {\n if (paramsStr.includes('{') || paramsStr.includes('[')) return null;\n const parts = paramsStr.split(',').map((p: string) => p.trim()).filter((p: string) => p.length > 0);\n for (const part of parts) {\n if (part.startsWith('...')) continue;\n if (part.includes(':')) continue;\n if (part.includes('=')) continue;\n if (part === 'this') continue;\n if (/^[a-zA-Z_$][\\w$]*$/.test(part)) return part;\n }\n return null;\n}\n\nfunction findOffender(line: string): string | null {\n ARROW_PARAMS_RE.lastIndex = 0;\n let m: RegExpExecArray | null = ARROW_PARAMS_RE.exec(line);\n while (m !== null) {\n const bad = firstUntypedParam(m[1]);\n if (bad) return bad;\n m = ARROW_PARAMS_RE.exec(line);\n }\n FN_DECL_PARAMS_RE.lastIndex = 0;\n m = FN_DECL_PARAMS_RE.exec(line);\n while (m !== null) {\n const bad = firstUntypedParam(m[1]);\n if (bad) return bad;\n m = FN_DECL_PARAMS_RE.exec(line);\n }\n return null;\n}\n\nexport class NoImplicitAnyRule extends EditRuleBase<NoImplicitAnyConfig> {\n constructor(config: NoImplicitAnyConfig) { super(config, 'no-implicit-any'); }\n\n readonly description = 'Disallow function parameters without explicit type annotations (implicit-any).';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n readonly fixHint = [\n 'Add explicit types: (x: string) => ... or function foo(x: number)',\n '// webpieces-disable no-implicit-any -- <one-line reason>',\n ];\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, RULE_NAMES.NO_IMPLICIT_ANY)) continue;\n const offender = findOffender(stripped);\n if (!offender) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Parameter \"${offender}\" has no type annotation. Add an explicit type to avoid implicit-any.`,\n ));\n }\n return violations;\n }\n}\n"]}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { NoJsFilesConfig } from '@webpieces/rules-config';
|
|
2
|
+
import type { FileContext, Violation } from '../types';
|
|
3
|
+
import { FileRuleBase } from '../rule-base';
|
|
4
|
+
export declare class NoJsFilesRule extends FileRuleBase<NoJsFilesConfig> {
|
|
5
|
+
constructor(config: NoJsFilesConfig);
|
|
6
|
+
readonly description = "Disallow writing new .js/.jsx files. Use .ts/.tsx instead.";
|
|
7
|
+
readonly files: string[];
|
|
8
|
+
readonly defaultOptions: {
|
|
9
|
+
allowedPaths: never[];
|
|
10
|
+
};
|
|
11
|
+
readonly fixHint: string[];
|
|
12
|
+
check(ctx: FileContext): readonly Violation[];
|
|
13
|
+
}
|
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NoJsFilesRule = void 0;
|
|
3
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
4
5
|
const types_1 = require("../types");
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
files
|
|
10
|
-
defaultOptions:
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
fixHint: [
|
|
6
|
+
const rule_base_1 = require("../rule-base");
|
|
7
|
+
class NoJsFilesRule extends rule_base_1.FileRuleBase {
|
|
8
|
+
constructor(config) { super(config, 'no-js-files'); }
|
|
9
|
+
description = 'Disallow writing new .js/.jsx files. Use .ts/.tsx instead.';
|
|
10
|
+
files = ['**/*.js', '**/*.jsx'];
|
|
11
|
+
defaultOptions = { allowedPaths: [] };
|
|
12
|
+
fixHint = [
|
|
14
13
|
'Write a .ts or .tsx file instead of .js/.jsx.',
|
|
15
14
|
'If this path must be .js (e.g. a generated or legacy file), add it to no-js-files.allowedPaths in webpieces.config.json',
|
|
16
|
-
]
|
|
15
|
+
];
|
|
17
16
|
check(ctx) {
|
|
18
17
|
if (ctx.tool !== 'Write')
|
|
19
18
|
return [];
|
|
20
|
-
const allowedPaths =
|
|
21
|
-
? ctx.options['allowedPaths']
|
|
22
|
-
: [];
|
|
19
|
+
const allowedPaths = this.config.allowedPaths ?? [];
|
|
23
20
|
if ((0, rules_config_1.isPathExcluded)(ctx.relativePath, allowedPaths))
|
|
24
21
|
return [];
|
|
25
22
|
return [new types_1.Violation(1, ctx.relativePath, 'Writing .js/.jsx files is not allowed. Use .ts/.tsx instead.')];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
exports.
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.NoJsFilesRule = NoJsFilesRule;
|
|
29
26
|
//# sourceMappingURL=no-js-files.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-js-files.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-js-files.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-js-files.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-js-files.ts"],"names":[],"mappings":";;;AAAA,0DAA0E;AAG1E,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAa,aAAc,SAAQ,wBAA6B;IAC5D,YAAY,MAAuB,IAAI,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAE7D,WAAW,GAAG,4DAA4D,CAAC;IAClE,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAChC,cAAc,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IAC/C,OAAO,GAAG;QACf,+CAA+C;QAC/C,yHAAyH;KAC5H,CAAC;IAEF,KAAK,CAAC,GAAgB;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,EAAE,CAAC;QAEpC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC;QAEpD,IAAI,IAAA,6BAAc,EAAC,GAAG,CAAC,YAAY,EAAE,YAAY,CAAC;YAAE,OAAO,EAAE,CAAC;QAE9D,OAAO,CAAC,IAAI,iBAAC,CAAC,CAAC,EAAE,GAAG,CAAC,YAAY,EAAE,8DAA8D,CAAC,CAAC,CAAC;IACxG,CAAC;CACJ;AApBD,sCAoBC","sourcesContent":["import { NoJsFilesConfig, isPathExcluded } from '@webpieces/rules-config';\n\nimport type { FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { FileRuleBase } from '../rule-base';\n\nexport class NoJsFilesRule extends FileRuleBase<NoJsFilesConfig> {\n constructor(config: NoJsFilesConfig) { super(config, 'no-js-files'); }\n\n readonly description = 'Disallow writing new .js/.jsx files. Use .ts/.tsx instead.';\n override readonly files = ['**/*.js', '**/*.jsx'];\n override readonly defaultOptions = { allowedPaths: [] };\n readonly fixHint = [\n 'Write a .ts or .tsx file instead of .js/.jsx.',\n 'If this path must be .js (e.g. a generated or legacy file), add it to no-js-files.allowedPaths in webpieces.config.json',\n ];\n\n check(ctx: FileContext): readonly Violation[] {\n if (ctx.tool !== 'Write') return [];\n\n const allowedPaths = this.config.allowedPaths ?? [];\n\n if (isPathExcluded(ctx.relativePath, allowedPaths)) return [];\n\n return [new V(1, ctx.relativePath, 'Writing .js/.jsx files is not allowed. Use .ts/.tsx instead.')];\n }\n}\n"]}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { NoShellSubstitutionConfig } from '@webpieces/rules-config';
|
|
2
|
+
import type { BashContext, Violation } from '../types';
|
|
3
|
+
import { BashRuleBase } from '../rule-base';
|
|
4
|
+
export declare class NoShellSubstitutionRule extends BashRuleBase<NoShellSubstitutionConfig> {
|
|
5
|
+
constructor(config: NoShellSubstitutionConfig);
|
|
6
|
+
readonly description = "Reject Bash commands containing shell substitutions ($(...), backticks, $VAR).";
|
|
7
|
+
readonly fixHint: readonly string[];
|
|
8
|
+
check(ctx: BashContext): readonly Violation[];
|
|
9
|
+
}
|
|
@@ -1,33 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NoShellSubstitutionRule = void 0;
|
|
3
4
|
const types_1 = require("../types");
|
|
5
|
+
const rule_base_1 = require("../rule-base");
|
|
4
6
|
const FIX_HINT = [
|
|
5
7
|
'Build a payload file with the Write tool and pass it as stdin: node script.js < /path/to/payload.json',
|
|
6
8
|
'Use the Read, Grep, or Glob tools directly instead of capturing output with $(...)',
|
|
7
9
|
'Write a small script file with the Write tool and execute it directly: bash /path/to/script.sh',
|
|
8
10
|
];
|
|
9
|
-
const noShellSubstitutionRule = {
|
|
10
|
-
name: 'no-shell-substitution',
|
|
11
|
-
description: 'Reject Bash commands containing shell substitutions ($(...), backticks, $VAR).',
|
|
12
|
-
scope: 'bash',
|
|
13
|
-
files: [],
|
|
14
|
-
defaultOptions: {},
|
|
15
|
-
fixHint: FIX_HINT,
|
|
16
|
-
check(ctx) {
|
|
17
|
-
const scanned = stripSingleQuoted(ctx.command);
|
|
18
|
-
const violations = [];
|
|
19
|
-
if (/\$\(/.test(scanned)) {
|
|
20
|
-
violations.push(new types_1.Violation(1, truncate(ctx.command), 'Command contains `$(...)` command substitution.'));
|
|
21
|
-
}
|
|
22
|
-
if (hasUnescapedBacktick(scanned)) {
|
|
23
|
-
violations.push(new types_1.Violation(1, truncate(ctx.command), 'Command contains backtick command substitution.'));
|
|
24
|
-
}
|
|
25
|
-
if (/\$\{[A-Za-z_][A-Za-z0-9_]*\}/.test(scanned) || hasBareVarExpansion(scanned)) {
|
|
26
|
-
violations.push(new types_1.Violation(1, truncate(ctx.command), 'Command contains `$VAR` or `${VAR}` variable expansion.'));
|
|
27
|
-
}
|
|
28
|
-
return violations;
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
11
|
function stripSingleQuoted(cmd) {
|
|
32
12
|
return cmd.replace(/'[^']*'/g, "''");
|
|
33
13
|
}
|
|
@@ -48,5 +28,24 @@ function truncate(s) {
|
|
|
48
28
|
return s;
|
|
49
29
|
return s.slice(0, MAX) + '…';
|
|
50
30
|
}
|
|
51
|
-
|
|
31
|
+
class NoShellSubstitutionRule extends rule_base_1.BashRuleBase {
|
|
32
|
+
constructor(config) { super(config, 'no-shell-substitution'); }
|
|
33
|
+
description = 'Reject Bash commands containing shell substitutions ($(...), backticks, $VAR).';
|
|
34
|
+
fixHint = FIX_HINT;
|
|
35
|
+
check(ctx) {
|
|
36
|
+
const scanned = stripSingleQuoted(ctx.command);
|
|
37
|
+
const violations = [];
|
|
38
|
+
if (/\$\(/.test(scanned)) {
|
|
39
|
+
violations.push(new types_1.Violation(1, truncate(ctx.command), 'Command contains `$(...)` command substitution.'));
|
|
40
|
+
}
|
|
41
|
+
if (hasUnescapedBacktick(scanned)) {
|
|
42
|
+
violations.push(new types_1.Violation(1, truncate(ctx.command), 'Command contains backtick command substitution.'));
|
|
43
|
+
}
|
|
44
|
+
if (/\$\{[A-Za-z_][A-Za-z0-9_]*\}/.test(scanned) || hasBareVarExpansion(scanned)) {
|
|
45
|
+
violations.push(new types_1.Violation(1, truncate(ctx.command), 'Command contains `$VAR` or `${VAR}` variable expansion.'));
|
|
46
|
+
}
|
|
47
|
+
return violations;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.NoShellSubstitutionRule = NoShellSubstitutionRule;
|
|
52
51
|
//# sourceMappingURL=no-shell-substitution.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-shell-substitution.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-shell-substitution.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-shell-substitution.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-shell-substitution.ts"],"names":[],"mappings":";;;AAGA,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAM,QAAQ,GAAsB;IAChC,uGAAuG;IACvG,oFAAoF;IACpF,gGAAgG;CACnG,CAAC;AAEF,SAAS,iBAAiB,CAAC,GAAW;IAClC,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,oBAAoB,CAAC,GAAW;IACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACxE,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAW;IACpC,MAAM,EAAE,GAAG,sCAAsC,CAAC;IAClD,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACjC,CAAC;AAED,MAAa,uBAAwB,SAAQ,wBAAuC;IAChF,YAAY,MAAiC,IAAI,KAAK,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC,CAAC;IAEjF,WAAW,GAAG,gFAAgF,CAAC;IAC/F,OAAO,GAAG,QAAQ,CAAC;IAE5B,KAAK,CAAC,GAAgB;QAClB,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,UAAU,GAAgB,EAAE,CAAC;QAEnC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACvB,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,iDAAiD,CACpD,CAAC,CAAC;QACP,CAAC;QACD,IAAI,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC;YAChC,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,iDAAiD,CACpD,CAAC,CAAC;QACP,CAAC;QACD,IAAI,8BAA8B,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/E,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,yDAAyD,CAC5D,CAAC,CAAC;QACP,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AAjCD,0DAiCC","sourcesContent":["import { NoShellSubstitutionConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\n\nconst FIX_HINT: readonly string[] = [\n 'Build a payload file with the Write tool and pass it as stdin: node script.js < /path/to/payload.json',\n 'Use the Read, Grep, or Glob tools directly instead of capturing output with $(...)',\n 'Write a small script file with the Write tool and execute it directly: bash /path/to/script.sh',\n];\n\nfunction stripSingleQuoted(cmd: string): string {\n return cmd.replace(/'[^']*'/g, \"''\");\n}\n\nfunction hasUnescapedBacktick(cmd: string): boolean {\n for (let i = 0; i < cmd.length; i += 1) {\n if (cmd[i] === '`' && (i === 0 || cmd[i - 1] !== '\\\\')) return true;\n }\n return false;\n}\n\nfunction hasBareVarExpansion(cmd: string): boolean {\n const re = /(^|[^\\\\])\\$([A-Za-z_][A-Za-z0-9_]*)/g;\n return re.test(cmd);\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n if (s.length <= MAX) return s;\n return s.slice(0, MAX) + '…';\n}\n\nexport class NoShellSubstitutionRule extends BashRuleBase<NoShellSubstitutionConfig> {\n constructor(config: NoShellSubstitutionConfig) { super(config, 'no-shell-substitution'); }\n\n readonly description = 'Reject Bash commands containing shell substitutions ($(...), backticks, $VAR).';\n readonly fixHint = FIX_HINT;\n\n check(ctx: BashContext): readonly Violation[] {\n const scanned = stripSingleQuoted(ctx.command);\n const violations: Violation[] = [];\n\n if (/\\$\\(/.test(scanned)) {\n violations.push(new V(\n 1,\n truncate(ctx.command),\n 'Command contains `$(...)` command substitution.',\n ));\n }\n if (hasUnescapedBacktick(scanned)) {\n violations.push(new V(\n 1,\n truncate(ctx.command),\n 'Command contains backtick command substitution.',\n ));\n }\n if (/\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}/.test(scanned) || hasBareVarExpansion(scanned)) {\n violations.push(new V(\n 1,\n truncate(ctx.command),\n 'Command contains `$VAR` or `${VAR}` variable expansion.',\n ));\n }\n return violations;\n }\n}\n"]}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { NoSymbolDiTokensConfig } from '@webpieces/rules-config';
|
|
2
|
+
import type { EditContext, Violation } from '../types';
|
|
3
|
+
import { EditRuleBase } from '../rule-base';
|
|
4
|
+
export declare class NoSymbolDiTokensRule extends EditRuleBase<NoSymbolDiTokensConfig> {
|
|
5
|
+
constructor(config: NoSymbolDiTokensConfig);
|
|
6
|
+
readonly description = "Disallow Symbol() DI tokens outside explicitly configured paths. Use @provideSingleton() + inject-by-type instead.";
|
|
7
|
+
readonly files: string[];
|
|
8
|
+
readonly defaultOptions: {
|
|
9
|
+
allowedPaths: never[];
|
|
10
|
+
};
|
|
11
|
+
readonly fixHint: string[];
|
|
12
|
+
check(ctx: EditContext): readonly Violation[];
|
|
13
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
3
|
+
exports.NoSymbolDiTokensRule = void 0;
|
|
4
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const rule_base_1 = require("../rule-base");
|
|
5
7
|
const SYMBOL_DI_REGEX = /=\s*Symbol(?:\.for)?\(/;
|
|
6
8
|
const TEST_PATHS = [/\.test\.ts$/, /\.spec\.ts$/, /__tests__\//];
|
|
7
9
|
function globToRegex(pattern) {
|
|
@@ -41,20 +43,19 @@ function isAllowedPath(relativePath, allowedPaths) {
|
|
|
41
43
|
return true;
|
|
42
44
|
return allowedPaths.some((pattern) => globToRegex(pattern).test(relativePath));
|
|
43
45
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
description
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
fixHint: [
|
|
46
|
+
class NoSymbolDiTokensRule extends rule_base_1.EditRuleBase {
|
|
47
|
+
constructor(config) { super(config, 'no-symbol-di-tokens'); }
|
|
48
|
+
description = 'Disallow Symbol() DI tokens outside explicitly configured paths. Use @provideSingleton() + inject-by-type instead.';
|
|
49
|
+
files = ['**/*.ts', '**/*.tsx'];
|
|
50
|
+
defaultOptions = { allowedPaths: [] };
|
|
51
|
+
fixHint = [
|
|
51
52
|
'Option 1: Use @provideSingleton() on the class and inject by type — no Symbol needed.',
|
|
52
53
|
'Option 2: Implement an API interface — import the Symbol from the API definition and use @provideSingletonAs(TOKEN).',
|
|
53
54
|
'Option 3: External lib class (DataSource, Anthropic, etc.) — bind<Cls>(Cls).toDynamicValue(...).inSingletonScope() — no Symbol.',
|
|
54
55
|
'Option 4 (last resort): // webpieces-disable no-symbol-di-tokens -- <reason>',
|
|
55
|
-
]
|
|
56
|
+
];
|
|
56
57
|
check(ctx) {
|
|
57
|
-
const allowedPaths =
|
|
58
|
+
const allowedPaths = this.config.allowedPaths ?? [];
|
|
58
59
|
if (isAllowedPath(ctx.relativePath, allowedPaths))
|
|
59
60
|
return [];
|
|
60
61
|
const violations = [];
|
|
@@ -68,7 +69,7 @@ const noSymbolDiTokensRule = {
|
|
|
68
69
|
violations.push(new types_1.Violation(lineNum, ctx.lines[i]?.trim() ?? '', 'Symbol() used as a DI token. Mostly we avoid Symbol if we can — see fix options below.'));
|
|
69
70
|
}
|
|
70
71
|
return violations;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
exports.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.NoSymbolDiTokensRule = NoSymbolDiTokensRule;
|
|
74
75
|
//# sourceMappingURL=no-symbol-di-tokens.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-symbol-di-tokens.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-symbol-di-tokens.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-symbol-di-tokens.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-symbol-di-tokens.ts"],"names":[],"mappings":"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","sourcesContent":["import { NoSymbolDiTokensConfig, RULE_NAMES } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { EditRuleBase } from '../rule-base';\n\nconst SYMBOL_DI_REGEX = /=\\s*Symbol(?:\\.for)?\\(/;\n\nconst TEST_PATHS: RegExp[] = [/\\.test\\.ts$/, /\\.spec\\.ts$/, /__tests__\\//];\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n\nfunction isAllowedPath(relativePath: string, allowedPaths: readonly string[]): boolean {\n if (TEST_PATHS.some((re: RegExp) => re.test(relativePath))) return true;\n return allowedPaths.some((pattern: string) => globToRegex(pattern).test(relativePath));\n}\n\nexport class NoSymbolDiTokensRule extends EditRuleBase<NoSymbolDiTokensConfig> {\n constructor(config: NoSymbolDiTokensConfig) { super(config, 'no-symbol-di-tokens'); }\n\n readonly description = 'Disallow Symbol() DI tokens outside explicitly configured paths. Use @provideSingleton() + inject-by-type instead.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n override readonly defaultOptions = { allowedPaths: [] };\n readonly fixHint = [\n 'Option 1: Use @provideSingleton() on the class and inject by type — no Symbol needed.',\n 'Option 2: Implement an API interface — import the Symbol from the API definition and use @provideSingletonAs(TOKEN).',\n 'Option 3: External lib class (DataSource, Anthropic, etc.) — bind<Cls>(Cls).toDynamicValue(...).inSingletonScope() — no Symbol.',\n 'Option 4 (last resort): // webpieces-disable no-symbol-di-tokens -- <reason>',\n ];\n\n check(ctx: EditContext): readonly Violation[] {\n const allowedPaths = this.config.allowedPaths ?? [];\n if (isAllowedPath(ctx.relativePath, allowedPaths)) return [];\n\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!SYMBOL_DI_REGEX.test(stripped ?? '')) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, RULE_NAMES.NO_SYMBOL_DI_TOKENS)) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i]?.trim() ?? '',\n 'Symbol() used as a DI token. Mostly we avoid Symbol if we can — see fix options below.',\n ));\n }\n return violations;\n }\n}\n"]}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { NoUnmanagedExceptionsConfig } from '@webpieces/rules-config';
|
|
2
|
+
import type { EditContext, Violation } from '../types';
|
|
3
|
+
import { EditRuleBase } from '../rule-base';
|
|
4
|
+
export declare class NoUnmanagedExceptionsRule extends EditRuleBase<NoUnmanagedExceptionsConfig> {
|
|
5
|
+
constructor(config: NoUnmanagedExceptionsConfig);
|
|
6
|
+
readonly description = "try/catch is generally not allowed. Only allowed in chokepoints (filter, globalErrorHandler) or other rare locations.";
|
|
7
|
+
readonly files: string[];
|
|
8
|
+
readonly fixHint: string[];
|
|
9
|
+
check(ctx: EditContext): readonly Violation[];
|
|
10
|
+
}
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
3
|
+
exports.NoUnmanagedExceptionsRule = void 0;
|
|
4
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const rule_base_1 = require("../rule-base");
|
|
5
7
|
const instruct_ai_writer_1 = require("../instruct-ai-writer");
|
|
6
8
|
const TRY_PATTERN = /\btry\s*\{/;
|
|
7
9
|
// Both webpieces-disable and the existing ESLint directive suppress this rule
|
|
8
10
|
const DISABLE_PATTERN = /@webpieces\/no-unmanaged-exceptions|webpieces-disable\s+(?:[\w-]+,\s*)*no-unmanaged-exceptions/;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
function hasPrecedingDisable(lines, idx) {
|
|
12
|
+
if (idx === 0)
|
|
13
|
+
return false;
|
|
14
|
+
const prevLine = lines[idx - 1];
|
|
15
|
+
return DISABLE_PATTERN.test(prevLine);
|
|
16
|
+
}
|
|
17
|
+
class NoUnmanagedExceptionsRule extends rule_base_1.EditRuleBase {
|
|
18
|
+
constructor(config) { super(config, 'no-unmanaged-exceptions'); }
|
|
19
|
+
description = 'try/catch is generally not allowed. Only allowed in chokepoints (filter, globalErrorHandler) or other rare locations.';
|
|
20
|
+
files = ['**/*.ts', '**/*.tsx'];
|
|
21
|
+
fixHint = [
|
|
16
22
|
'Fix Option 1 (preferred): Remove the try/catch — let the exception bubble to the top-level chokepoint (filter, globalErrorHandler) where it is already logged and handled.',
|
|
17
23
|
'Fix Option 2 (ask the human first): If you genuinely believe this IS a chokepoint, STOP and tell the human:',
|
|
18
24
|
' - What exception could be thrown here',
|
|
@@ -21,7 +27,7 @@ const noUnmanagedExceptionsRule = {
|
|
|
21
27
|
' Then ask: "Should I add a disable comment or remove the try/catch?"',
|
|
22
28
|
' Only add // webpieces-disable no-unmanaged-exceptions -- <reason> if the human says yes.',
|
|
23
29
|
'NOTE: If the code is calling an external process (execSync, fs, network), the correct answer is almost always Option 1 — let it throw. Hooks have a top-level runner that reports errors properly.',
|
|
24
|
-
]
|
|
30
|
+
];
|
|
25
31
|
check(ctx) {
|
|
26
32
|
const violations = [];
|
|
27
33
|
for (let i = 0; i < ctx.strippedLines.length; i += 1) {
|
|
@@ -38,13 +44,7 @@ const noUnmanagedExceptionsRule = {
|
|
|
38
44
|
if (violations.length > 0)
|
|
39
45
|
(0, instruct_ai_writer_1.writeTemplateIfMissing)(ctx.workspaceRoot, 'webpieces.exceptions.md');
|
|
40
46
|
return violations;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
function hasPrecedingDisable(lines, idx) {
|
|
44
|
-
if (idx === 0)
|
|
45
|
-
return false;
|
|
46
|
-
const prevLine = lines[idx - 1];
|
|
47
|
-
return DISABLE_PATTERN.test(prevLine);
|
|
47
|
+
}
|
|
48
48
|
}
|
|
49
|
-
exports.
|
|
49
|
+
exports.NoUnmanagedExceptionsRule = NoUnmanagedExceptionsRule;
|
|
50
50
|
//# sourceMappingURL=no-unmanaged-exceptions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-unmanaged-exceptions.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-unmanaged-exceptions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-unmanaged-exceptions.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-unmanaged-exceptions.ts"],"names":[],"mappings":";;;AAAA,0DAAkF;AAGlF,oCAA0C;AAC1C,4CAA4C;AAC5C,8DAA+D;AAE/D,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,8EAA8E;AAC9E,MAAM,eAAe,GAAG,gGAAgG,CAAC;AAEzH,SAAS,mBAAmB,CAAC,KAAwB,EAAE,GAAW;IAC9D,IAAI,GAAG,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5B,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAChC,OAAO,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,MAAa,yBAA0B,SAAQ,wBAAyC;IACpF,YAAY,MAAmC,IAAI,KAAK,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC;IAErF,WAAW,GAAG,uHAAuH,CAAC;IAC7H,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACzC,OAAO,GAAG;QACf,4KAA4K;QAC5K,6GAA6G;QAC7G,yCAAyC;QACzC,2FAA2F;QAC3F,0EAA0E;QAC1E,uEAAuE;QACvE,4FAA4F;QAC5F,oMAAoM;KACvM,CAAC;IAEF,KAAK,CAAC,GAAgB;QAClB,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,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAU,CAAC,uBAAuB,CAAC;gBAAE,SAAS;YAC9E,IAAI,mBAAmB,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;gBAAE,SAAS;YAChD,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,gLAAgL,CACnL,CAAC,CAAC;QACP,CAAC;QACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,IAAA,2CAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;QAChG,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AAjCD,8DAiCC","sourcesContent":["import { NoUnmanagedExceptionsConfig, RULE_NAMES } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { EditRuleBase } from '../rule-base';\nimport { writeTemplateIfMissing } from '../instruct-ai-writer';\n\nconst TRY_PATTERN = /\\btry\\s*\\{/;\n\n// Both webpieces-disable and the existing ESLint directive suppress this rule\nconst DISABLE_PATTERN = /@webpieces\\/no-unmanaged-exceptions|webpieces-disable\\s+(?:[\\w-]+,\\s*)*no-unmanaged-exceptions/;\n\nfunction hasPrecedingDisable(lines: readonly string[], idx: number): boolean {\n if (idx === 0) return false;\n const prevLine = lines[idx - 1];\n return DISABLE_PATTERN.test(prevLine);\n}\n\nexport class NoUnmanagedExceptionsRule extends EditRuleBase<NoUnmanagedExceptionsConfig> {\n constructor(config: NoUnmanagedExceptionsConfig) { super(config, 'no-unmanaged-exceptions'); }\n\n readonly description = 'try/catch is generally not allowed. Only allowed in chokepoints (filter, globalErrorHandler) or other rare locations.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n readonly fixHint = [\n 'Fix Option 1 (preferred): Remove the try/catch — let the exception bubble to the top-level chokepoint (filter, globalErrorHandler) where it is already logged and handled.',\n 'Fix Option 2 (ask the human first): If you genuinely believe this IS a chokepoint, STOP and tell the human:',\n ' - What exception could be thrown here',\n ' - What the current top-level chokepoint is (the try/catch at the top of the call stack)',\n ' - Why throwing to that chokepoint would be wrong in this specific case',\n ' Then ask: \"Should I add a disable comment or remove the try/catch?\"',\n ' Only add // webpieces-disable no-unmanaged-exceptions -- <reason> if the human says yes.',\n 'NOTE: If the code is calling an external process (execSync, fs, network), the correct answer is almost always Option 1 — let it throw. Hooks have a top-level runner that reports errors properly.',\n ];\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!TRY_PATTERN.test(stripped)) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, RULE_NAMES.NO_UNMANAGED_EXCEPTIONS)) continue;\n if (hasPrecedingDisable(ctx.lines, i)) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'try/catch is generally not allowed. READ .webpieces/instruct-ai/webpieces.exceptions.md to understand why. Only chokepoints (filter, globalErrorHandler) may catch exceptions.',\n ));\n }\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n }\n}\n"]}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { PrCreationGuardConfig } from '@webpieces/rules-config';
|
|
2
|
+
import type { BashContext, Violation } from '../types';
|
|
3
|
+
import { BashRuleBase } from '../rule-base';
|
|
4
|
+
export declare class PrCreationGuardRule extends BashRuleBase<PrCreationGuardConfig> {
|
|
5
|
+
constructor(config: PrCreationGuardConfig);
|
|
6
|
+
readonly description = "Block direct PR creation/edit (gh pr / gh api / curl) so PRs go only through pnpm wp-upsert-pr.";
|
|
7
|
+
readonly fixHint: readonly string[];
|
|
8
|
+
check(ctx: BashContext): readonly Violation[];
|
|
9
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PrCreationGuardRule = void 0;
|
|
3
4
|
const types_1 = require("../types");
|
|
5
|
+
const rule_base_1 = require("../rule-base");
|
|
4
6
|
const FIX_HINT = [
|
|
5
7
|
'Direct PR creation is blocked. Create or update a PR ONLY via the gated command:',
|
|
6
8
|
' pnpm wp-upsert-pr',
|
|
@@ -24,13 +26,14 @@ function isDirectPrCreation(cmd) {
|
|
|
24
26
|
}
|
|
25
27
|
return false;
|
|
26
28
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
function truncate(s) {
|
|
30
|
+
const MAX = 120;
|
|
31
|
+
return s.length <= MAX ? s : s.slice(0, MAX) + '…';
|
|
32
|
+
}
|
|
33
|
+
class PrCreationGuardRule extends rule_base_1.BashRuleBase {
|
|
34
|
+
constructor(config) { super(config, 'pr-creation-guard'); }
|
|
35
|
+
description = 'Block direct PR creation/edit (gh pr / gh api / curl) so PRs go only through pnpm wp-upsert-pr.';
|
|
36
|
+
fixHint = FIX_HINT;
|
|
34
37
|
check(ctx) {
|
|
35
38
|
if (!isDirectPrCreation(ctx.command))
|
|
36
39
|
return [];
|
|
@@ -39,11 +42,7 @@ const prCreationGuard = {
|
|
|
39
42
|
'Use the gated command instead — it runs the build and builds the dashboard:',
|
|
40
43
|
' pnpm wp-upsert-pr',
|
|
41
44
|
].join('\n'))];
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
function truncate(s) {
|
|
45
|
-
const MAX = 120;
|
|
46
|
-
return s.length <= MAX ? s : s.slice(0, MAX) + '…';
|
|
45
|
+
}
|
|
47
46
|
}
|
|
48
|
-
exports.
|
|
47
|
+
exports.PrCreationGuardRule = PrCreationGuardRule;
|
|
49
48
|
//# sourceMappingURL=pr-creation-guard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-creation-guard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pr-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-creation-guard.ts"],"names":[],"mappings":";;;AAGA,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAM,QAAQ,GAAsB;IAChC,kFAAkF;IAClF,qBAAqB;IACrB,yFAAyF;IACzF,2FAA2F;IAC3F,oEAAoE;CACvE,CAAC;AAEF,sFAAsF;AACtF,sFAAsF;AACtF,0FAA0F;AAC1F,SAAS,kBAAkB,CAAC,GAAW;IACnC,IAAI,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,UAAU,GAAG,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3D,IAAI,UAAU,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC9I,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,SAAS,GAAG,+CAA+C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5E,IAAI,SAAS,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC1G,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,MAAa,mBAAoB,SAAQ,wBAAmC;IACxE,YAAY,MAA6B,IAAI,KAAK,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAEzE,WAAW,GAAG,iGAAiG,CAAC;IAChH,OAAO,GAAG,QAAQ,CAAC;IAE5B,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAChD,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;gBACI,uCAAuC;gBACvC,6EAA6E;gBAC7E,qBAAqB;aACxB,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ;AAlBD,kDAkBC","sourcesContent":["import { PrCreationGuardConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\n\nconst FIX_HINT: readonly string[] = [\n 'Direct PR creation is blocked. Create or update a PR ONLY via the gated command:',\n ' pnpm wp-upsert-pr',\n 'It updates the branch from main (3-point merge), runs the real build (nx affected), and',\n 'assembles the PR dashboard — then creates/updates the PR itself. A failing build = no PR.',\n 'There is nothing to paste or attest to; the command does the work.',\n];\n\n// Detect every way an agent could open/update a PR directly, so the ONLY path left is\n// `pnpm wp-upsert-pr` (whose internal `gh pr create` runs as a child process the hook\n// never sees). Read-only `gh pr list` / `gh api .../pulls` GET are intentionally allowed.\nfunction isDirectPrCreation(cmd: string): boolean {\n if (/\\bgh\\s+pr\\s+(create|edit)\\b/.test(cmd)) return true;\n\n const ghApiPulls = /\\bgh\\s+api\\b[^\\n]*\\/pulls\\b/.test(cmd);\n if (ghApiPulls && (/--method\\s+POST/i.test(cmd) || /-X\\s+POST/i.test(cmd) || /\\s-f\\b/.test(cmd) || /\\s-F\\b/.test(cmd) || /--field\\b/.test(cmd))) {\n return true;\n }\n\n const curlPulls = /\\bcurl\\b[^\\n]*api\\.github\\.com[^\\n]*\\/pulls\\b/.test(cmd);\n if (curlPulls && (/-X\\s*POST/i.test(cmd) || /--request\\s+POST/i.test(cmd) || /(\\s-d\\b|--data\\b)/.test(cmd))) {\n return true;\n }\n return false;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport class PrCreationGuardRule extends BashRuleBase<PrCreationGuardConfig> {\n constructor(config: PrCreationGuardConfig) { super(config, 'pr-creation-guard'); }\n\n readonly description = 'Block direct PR creation/edit (gh pr / gh api / curl) so PRs go only through pnpm wp-upsert-pr.';\n readonly fixHint = FIX_HINT;\n\n check(ctx: BashContext): readonly Violation[] {\n if (!isDirectPrCreation(ctx.command)) return [];\n return [new V(\n 1,\n truncate(ctx.command),\n [\n 'Direct PR creation/update is blocked.',\n 'Use the gated command instead — it runs the build and builds the dashboard:',\n ' pnpm wp-upsert-pr',\n ].join('\\n'),\n )];\n }\n}\n"]}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { PrMergeCleanupConfig } from '@webpieces/rules-config';
|
|
2
|
+
import type { BashContext, Violation } from '../types';
|
|
3
|
+
import { BashRuleBase } from '../rule-base';
|
|
4
|
+
export declare class PrMergeCleanupRule extends BashRuleBase<PrMergeCleanupConfig> {
|
|
5
|
+
constructor(config: PrMergeCleanupConfig);
|
|
6
|
+
readonly description = "After merging a PR, require switching to main, pulling, and deleting the local branch.";
|
|
7
|
+
readonly fixHint: string[];
|
|
8
|
+
check(ctx: BashContext): readonly Violation[];
|
|
9
|
+
}
|