@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/ai-hook-rules",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.186",
|
|
4
4
|
"description": "Pluggable write-time validation framework for AI coding agents (@webpieces/ai-hook-rules). Claude Code PreToolUse + openclaw before_tool_call adapters share one rule engine.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
".": "./src/index.js",
|
|
16
16
|
"./package.json": "./package.json",
|
|
17
17
|
"./claude-code": "./src/adapters/claude-code-hook.js",
|
|
18
|
-
"./openclaw-plugin": "./src/adapters/openclaw-plugin.js"
|
|
19
|
-
"./default": "./src/core/configs/default.js"
|
|
18
|
+
"./openclaw-plugin": "./src/adapters/openclaw-plugin.js"
|
|
20
19
|
},
|
|
21
20
|
"files": [
|
|
22
21
|
"src/**/*",
|
|
@@ -33,7 +32,7 @@
|
|
|
33
32
|
"directory": "packages/tooling/ai-hook-rules"
|
|
34
33
|
},
|
|
35
34
|
"dependencies": {
|
|
36
|
-
"@webpieces/rules-config": "0.3.
|
|
35
|
+
"@webpieces/rules-config": "0.3.186"
|
|
37
36
|
},
|
|
38
37
|
"publishConfig": {
|
|
39
38
|
"access": "public"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AbstractRule, BaseRuleConfig, RuleOptions } from '@webpieces/rules-config';
|
|
2
|
+
import type { PlainRule, Rule, RuleScope, EditContext, FileContext, BashContext, Violation } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a custom rule loaded from a `rulesDir` (a plain object) so it satisfies the same
|
|
5
|
+
* runtime `Rule` contract as the built-in rule classes: it gains `shouldRun()` (driven by the
|
|
6
|
+
* rule's config entry in webpieces.config.json) from AbstractRule, and it seeds `ctx.options`
|
|
7
|
+
* with the merged option bag before delegating to the plain rule's `check()` — preserving the
|
|
8
|
+
* exact behavior custom rules had under the old options-based runner.
|
|
9
|
+
*/
|
|
10
|
+
export declare class CustomRuleAdapter extends AbstractRule<BaseRuleConfig> implements Rule {
|
|
11
|
+
readonly scope: RuleScope;
|
|
12
|
+
readonly files: readonly string[];
|
|
13
|
+
readonly description: string;
|
|
14
|
+
readonly fixHint: readonly string[];
|
|
15
|
+
readonly defaultOptions: RuleOptions;
|
|
16
|
+
private readonly impl;
|
|
17
|
+
private readonly rawConfig;
|
|
18
|
+
constructor(impl: PlainRule, rawConfig: RuleOptions);
|
|
19
|
+
private buildOptions;
|
|
20
|
+
check(ctx: EditContext | FileContext | BashContext): readonly Violation[];
|
|
21
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CustomRuleAdapter = void 0;
|
|
4
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a custom rule loaded from a `rulesDir` (a plain object) so it satisfies the same
|
|
7
|
+
* runtime `Rule` contract as the built-in rule classes: it gains `shouldRun()` (driven by the
|
|
8
|
+
* rule's config entry in webpieces.config.json) from AbstractRule, and it seeds `ctx.options`
|
|
9
|
+
* with the merged option bag before delegating to the plain rule's `check()` — preserving the
|
|
10
|
+
* exact behavior custom rules had under the old options-based runner.
|
|
11
|
+
*/
|
|
12
|
+
class CustomRuleAdapter extends rules_config_1.AbstractRule {
|
|
13
|
+
scope;
|
|
14
|
+
files;
|
|
15
|
+
description;
|
|
16
|
+
fixHint;
|
|
17
|
+
defaultOptions;
|
|
18
|
+
impl;
|
|
19
|
+
rawConfig;
|
|
20
|
+
constructor(impl, rawConfig) {
|
|
21
|
+
super(rawConfig, impl.name);
|
|
22
|
+
this.impl = impl;
|
|
23
|
+
this.scope = impl.scope;
|
|
24
|
+
this.files = impl.files;
|
|
25
|
+
this.description = impl.description;
|
|
26
|
+
this.fixHint = impl.fixHint;
|
|
27
|
+
this.defaultOptions = impl.defaultOptions;
|
|
28
|
+
this.rawConfig = rawConfig;
|
|
29
|
+
}
|
|
30
|
+
buildOptions() {
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const key of Object.keys(this.defaultOptions))
|
|
33
|
+
out[key] = this.defaultOptions[key];
|
|
34
|
+
for (const key of Object.keys(this.rawConfig)) {
|
|
35
|
+
// 'mode' is the framework-level on/off switch, not a rule option.
|
|
36
|
+
if (key === 'mode')
|
|
37
|
+
continue;
|
|
38
|
+
out[key] = this.rawConfig[key];
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
check(ctx) {
|
|
43
|
+
ctx.options = this.buildOptions();
|
|
44
|
+
return this.impl.check(ctx);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.CustomRuleAdapter = CustomRuleAdapter;
|
|
48
|
+
//# sourceMappingURL=custom-rule-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-rule-adapter.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/custom-rule-adapter.ts"],"names":[],"mappings":";;;AAAA,0DAAoF;AAOpF;;;;;;GAMG;AACH,MAAa,iBAAkB,SAAQ,2BAA4B;IACtD,KAAK,CAAY;IACjB,KAAK,CAAoB;IACzB,WAAW,CAAS;IACpB,OAAO,CAAoB;IAC3B,cAAc,CAAc;IACpB,IAAI,CAAY;IAChB,SAAS,CAAc;IAExC,YAAY,IAAe,EAAE,SAAsB;QAC/C,KAAK,CAAC,SAA2B,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;QACpC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC;QAC1C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/B,CAAC;IAEO,YAAY;QAChB,MAAM,GAAG,GAAgB,EAAE,CAAC;QAC5B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QACxF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5C,kEAAkE;YAClE,IAAI,GAAG,KAAK,MAAM;gBAAE,SAAS;YAC7B,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED,KAAK,CAAC,GAA4C;QAC9C,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;CACJ;AAnCD,8CAmCC","sourcesContent":["import { AbstractRule, BaseRuleConfig, RuleOptions } from '@webpieces/rules-config';\n\nimport type {\n PlainRule, Rule, RuleScope,\n EditContext, FileContext, BashContext, Violation,\n} from './types';\n\n/**\n * Wraps a custom rule loaded from a `rulesDir` (a plain object) so it satisfies the same\n * runtime `Rule` contract as the built-in rule classes: it gains `shouldRun()` (driven by the\n * rule's config entry in webpieces.config.json) from AbstractRule, and it seeds `ctx.options`\n * with the merged option bag before delegating to the plain rule's `check()` — preserving the\n * exact behavior custom rules had under the old options-based runner.\n */\nexport class CustomRuleAdapter extends AbstractRule<BaseRuleConfig> implements Rule {\n readonly scope: RuleScope;\n readonly files: readonly string[];\n readonly description: string;\n readonly fixHint: readonly string[];\n readonly defaultOptions: RuleOptions;\n private readonly impl: PlainRule;\n private readonly rawConfig: RuleOptions;\n\n constructor(impl: PlainRule, rawConfig: RuleOptions) {\n super(rawConfig as BaseRuleConfig, impl.name);\n this.impl = impl;\n this.scope = impl.scope;\n this.files = impl.files;\n this.description = impl.description;\n this.fixHint = impl.fixHint;\n this.defaultOptions = impl.defaultOptions;\n this.rawConfig = rawConfig;\n }\n\n private buildOptions(): RuleOptions {\n const out: RuleOptions = {};\n for (const key of Object.keys(this.defaultOptions)) out[key] = this.defaultOptions[key];\n for (const key of Object.keys(this.rawConfig)) {\n // 'mode' is the framework-level on/off switch, not a rule option.\n if (key === 'mode') continue;\n out[key] = this.rawConfig[key];\n }\n return out;\n }\n\n check(ctx: EditContext | FileContext | BashContext): readonly Violation[] {\n ctx.options = this.buildOptions();\n return this.impl.check(ctx);\n }\n}\n"]}
|
package/src/core/load-rules.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { WebpiecesRulesConfig } from '@webpieces/rules-config';
|
|
2
|
+
import type { Rule } from './types';
|
|
3
|
+
export declare function loadRules(config: WebpiecesRulesConfig, workspaceRoot: string): readonly Rule[];
|
|
3
4
|
export declare function globMatches(pattern: string, filePath: string): boolean;
|
package/src/core/load-rules.js
CHANGED
|
@@ -7,67 +7,86 @@ const fs = tslib_1.__importStar(require("fs"));
|
|
|
7
7
|
const path = tslib_1.__importStar(require("path"));
|
|
8
8
|
const types_1 = require("./types");
|
|
9
9
|
const to_error_1 = require("./to-error");
|
|
10
|
+
const rule_base_1 = require("./rule-base");
|
|
11
|
+
const custom_rule_adapter_1 = require("./custom-rule-adapter");
|
|
10
12
|
const index_1 = require("./rules/index");
|
|
11
|
-
const no_any_unknown_1 =
|
|
12
|
-
const no_implicit_any_1 =
|
|
13
|
-
const max_file_lines_1 =
|
|
14
|
-
const validate_ts_in_src_1 =
|
|
15
|
-
const no_destructure_1 =
|
|
16
|
-
const require_return_type_1 =
|
|
17
|
-
const no_unmanaged_exceptions_1 =
|
|
18
|
-
const catch_error_pattern_1 =
|
|
19
|
-
const throw_cause_required_1 =
|
|
20
|
-
const no_shell_substitution_1 =
|
|
21
|
-
const no_symbol_di_tokens_1 =
|
|
22
|
-
const branch_creation_guard_1 =
|
|
23
|
-
const pr_creation_guard_1 =
|
|
24
|
-
const merge_in_progress_guard_1 =
|
|
25
|
-
const pr_merge_cleanup_1 =
|
|
26
|
-
const no_direct_main_update_1 =
|
|
27
|
-
const no_js_files_1 =
|
|
28
|
-
const no_edit_on_main_1 =
|
|
13
|
+
const no_any_unknown_1 = require("./rules/no-any-unknown");
|
|
14
|
+
const no_implicit_any_1 = require("./rules/no-implicit-any");
|
|
15
|
+
const max_file_lines_1 = require("./rules/max-file-lines");
|
|
16
|
+
const validate_ts_in_src_1 = require("./rules/validate-ts-in-src");
|
|
17
|
+
const no_destructure_1 = require("./rules/no-destructure");
|
|
18
|
+
const require_return_type_1 = require("./rules/require-return-type");
|
|
19
|
+
const no_unmanaged_exceptions_1 = require("./rules/no-unmanaged-exceptions");
|
|
20
|
+
const catch_error_pattern_1 = require("./rules/catch-error-pattern");
|
|
21
|
+
const throw_cause_required_1 = require("./rules/throw-cause-required");
|
|
22
|
+
const no_shell_substitution_1 = require("./rules/no-shell-substitution");
|
|
23
|
+
const no_symbol_di_tokens_1 = require("./rules/no-symbol-di-tokens");
|
|
24
|
+
const branch_creation_guard_1 = require("./rules/branch-creation-guard");
|
|
25
|
+
const pr_creation_guard_1 = require("./rules/pr-creation-guard");
|
|
26
|
+
const merge_in_progress_guard_1 = require("./rules/merge-in-progress-guard");
|
|
27
|
+
const pr_merge_cleanup_1 = require("./rules/pr-merge-cleanup");
|
|
28
|
+
const no_direct_main_update_1 = require("./rules/no-direct-main-update");
|
|
29
|
+
const no_js_files_1 = require("./rules/no-js-files");
|
|
30
|
+
const no_edit_on_main_1 = require("./rules/no-edit-on-main");
|
|
29
31
|
const REQUIRED_FIELDS = ['name', 'description', 'scope', 'files', 'check'];
|
|
30
32
|
const VALID_SCOPES = new Set(['edit', 'file', 'bash']);
|
|
31
33
|
const BUILT_IN_RULE_MAP = {
|
|
32
|
-
'no-any-unknown': no_any_unknown_1.
|
|
33
|
-
'no-implicit-any': no_implicit_any_1.
|
|
34
|
-
'max-file-lines': max_file_lines_1.
|
|
35
|
-
'validate-ts-in-src': validate_ts_in_src_1.
|
|
36
|
-
'no-destructure': no_destructure_1.
|
|
37
|
-
'require-return-type': require_return_type_1.
|
|
38
|
-
'no-unmanaged-exceptions': no_unmanaged_exceptions_1.
|
|
39
|
-
'catch-error-pattern': catch_error_pattern_1.
|
|
40
|
-
'throw-cause-required': throw_cause_required_1.
|
|
41
|
-
'no-shell-substitution': no_shell_substitution_1.
|
|
42
|
-
'no-symbol-di-tokens': no_symbol_di_tokens_1.
|
|
43
|
-
'branch-creation-guard': branch_creation_guard_1.
|
|
44
|
-
'pr-creation-guard': pr_creation_guard_1.
|
|
45
|
-
'merge-in-progress-guard': merge_in_progress_guard_1.
|
|
46
|
-
'pr-merge-cleanup': pr_merge_cleanup_1.
|
|
47
|
-
'no-direct-main-update': no_direct_main_update_1.
|
|
48
|
-
'no-js-files': no_js_files_1.
|
|
49
|
-
'no-edit-on-main': no_edit_on_main_1.
|
|
34
|
+
'no-any-unknown': (c) => new no_any_unknown_1.NoAnyUnknownRule(c),
|
|
35
|
+
'no-implicit-any': (c) => new no_implicit_any_1.NoImplicitAnyRule(c),
|
|
36
|
+
'max-file-lines': (c) => new max_file_lines_1.MaxFileLinesRule(c),
|
|
37
|
+
'validate-ts-in-src': (c) => new validate_ts_in_src_1.ValidateTsInSrcRule(c),
|
|
38
|
+
'no-destructure': (c) => new no_destructure_1.NoDestructureRule(c),
|
|
39
|
+
'require-return-type': (c) => new require_return_type_1.RequireReturnTypeRule(c),
|
|
40
|
+
'no-unmanaged-exceptions': (c) => new no_unmanaged_exceptions_1.NoUnmanagedExceptionsRule(c),
|
|
41
|
+
'catch-error-pattern': (c) => new catch_error_pattern_1.CatchErrorPatternRule(c),
|
|
42
|
+
'throw-cause-required': (c) => new throw_cause_required_1.ThrowCauseRequiredRule(c),
|
|
43
|
+
'no-shell-substitution': (c) => new no_shell_substitution_1.NoShellSubstitutionRule(c),
|
|
44
|
+
'no-symbol-di-tokens': (c) => new no_symbol_di_tokens_1.NoSymbolDiTokensRule(c),
|
|
45
|
+
'branch-creation-guard': (c) => new branch_creation_guard_1.BranchCreationGuardRule(c),
|
|
46
|
+
'pr-creation-guard': (c) => new pr_creation_guard_1.PrCreationGuardRule(c),
|
|
47
|
+
'merge-in-progress-guard': (c) => new merge_in_progress_guard_1.MergeInProgressGuardRule(c),
|
|
48
|
+
'pr-merge-cleanup': (c) => new pr_merge_cleanup_1.PrMergeCleanupRule(c),
|
|
49
|
+
'no-direct-main-update': (c) => new no_direct_main_update_1.NoDirectMainUpdateRule(c),
|
|
50
|
+
'no-js-files': (c) => new no_js_files_1.NoJsFilesRule(c),
|
|
51
|
+
'no-edit-on-main': (c) => new no_edit_on_main_1.NoEditOnMainRule(c),
|
|
50
52
|
};
|
|
53
|
+
// Index the typed config by rule name. Each value is the rule's *Config (a plain object from
|
|
54
|
+
// JSON), or undefined when the rule has no entry yet (the sync check reports those).
|
|
55
|
+
function asConfigMap(config) {
|
|
56
|
+
// webpieces-disable no-any-unknown -- index the typed config by dynamic rule name
|
|
57
|
+
return config;
|
|
58
|
+
}
|
|
51
59
|
function loadRules(config, workspaceRoot) {
|
|
52
|
-
const builtIns = loadBuiltInRules();
|
|
53
|
-
const custom = loadCustomRules(config
|
|
54
|
-
|
|
55
|
-
return all.filter((rule) => validateRule(rule));
|
|
60
|
+
const builtIns = loadBuiltInRules(config);
|
|
61
|
+
const custom = loadCustomRules(config, workspaceRoot);
|
|
62
|
+
return [...builtIns, ...custom];
|
|
56
63
|
}
|
|
57
|
-
function loadBuiltInRules() {
|
|
58
|
-
const
|
|
64
|
+
function loadBuiltInRules(config) {
|
|
65
|
+
const map = asConfigMap(config);
|
|
66
|
+
const rules = [];
|
|
59
67
|
for (const name of index_1.builtInRuleNames) {
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
62
|
-
modules.push(mod);
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
68
|
+
const factory = BUILT_IN_RULE_MAP[name];
|
|
69
|
+
if (!factory) {
|
|
65
70
|
process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\n`);
|
|
71
|
+
continue;
|
|
66
72
|
}
|
|
73
|
+
const ruleConfig = map[name] ?? new rule_base_1.EmptyRuleConfig();
|
|
74
|
+
rules.push(factory(ruleConfig));
|
|
67
75
|
}
|
|
68
|
-
return
|
|
76
|
+
return rules;
|
|
77
|
+
}
|
|
78
|
+
function loadCustomRules(config, workspaceRoot) {
|
|
79
|
+
const dirs = config.rulesDir ?? [];
|
|
80
|
+
// webpieces-disable no-any-unknown -- index the typed config by dynamic custom-rule name
|
|
81
|
+
const map = config;
|
|
82
|
+
const rules = [];
|
|
83
|
+
for (const plain of loadCustomPlainRules(dirs, workspaceRoot)) {
|
|
84
|
+
const rawConfig = map[plain.name] ?? {};
|
|
85
|
+
rules.push(new custom_rule_adapter_1.CustomRuleAdapter(plain, rawConfig));
|
|
86
|
+
}
|
|
87
|
+
return rules;
|
|
69
88
|
}
|
|
70
|
-
function
|
|
89
|
+
function loadCustomPlainRules(rulesDirs, workspaceRoot) {
|
|
71
90
|
const modules = [];
|
|
72
91
|
for (const dir of rulesDirs) {
|
|
73
92
|
const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);
|
|
@@ -89,7 +108,9 @@ function loadCustomRules(rulesDirs, workspaceRoot) {
|
|
|
89
108
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
90
109
|
try {
|
|
91
110
|
const mod = require(full);
|
|
92
|
-
|
|
111
|
+
const candidate = mod.default || mod;
|
|
112
|
+
if (validateRule(candidate))
|
|
113
|
+
modules.push(candidate);
|
|
93
114
|
}
|
|
94
115
|
catch (err) {
|
|
95
116
|
const error = (0, to_error_1.toError)(err);
|
|
@@ -1 +1 @@
|
|
|
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 * as fs from 'fs';\nimport * as path from 'path';\n\nimport type { Rule, ResolvedConfig } from './types';\nimport { InformAiError } from './types';\nimport { toError } from './to-error';\nimport { builtInRuleNames } from './rules/index';\nimport noAnyUnknown from './rules/no-any-unknown';\nimport noImplicitAny from './rules/no-implicit-any';\nimport maxFileLines from './rules/max-file-lines';\nimport validateTsInSrc from './rules/validate-ts-in-src';\nimport noDestructure from './rules/no-destructure';\nimport requireReturnType from './rules/require-return-type';\nimport noUnmanagedExceptions from './rules/no-unmanaged-exceptions';\nimport catchErrorPattern from './rules/catch-error-pattern';\nimport throwCauseRequired from './rules/throw-cause-required';\nimport noShellSubstitution from './rules/no-shell-substitution';\nimport noSymbolDiTokens from './rules/no-symbol-di-tokens';\nimport branchCreationGuard from './rules/branch-creation-guard';\nimport prCreationGuard from './rules/pr-creation-guard';\nimport mergeInProgressGuard from './rules/merge-in-progress-guard';\nimport prMergeCleanup from './rules/pr-merge-cleanup';\nimport noDirectMainUpdate from './rules/no-direct-main-update';\nimport noJsFiles from './rules/no-js-files';\nimport noEditOnMain from './rules/no-edit-on-main';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\nconst BUILT_IN_RULE_MAP: Record<string, Rule> = {\n 'no-any-unknown': noAnyUnknown as Rule,\n 'no-implicit-any': noImplicitAny as Rule,\n 'max-file-lines': maxFileLines as Rule,\n 'validate-ts-in-src': validateTsInSrc as Rule,\n 'no-destructure': noDestructure as Rule,\n 'require-return-type': requireReturnType as Rule,\n 'no-unmanaged-exceptions': noUnmanagedExceptions as Rule,\n 'catch-error-pattern': catchErrorPattern as Rule,\n 'throw-cause-required': throwCauseRequired as Rule,\n 'no-shell-substitution': noShellSubstitution as Rule,\n 'no-symbol-di-tokens': noSymbolDiTokens as Rule,\n 'branch-creation-guard': branchCreationGuard as Rule,\n 'pr-creation-guard': prCreationGuard as Rule,\n 'merge-in-progress-guard': mergeInProgressGuard as Rule,\n 'pr-merge-cleanup': prMergeCleanup as Rule,\n 'no-direct-main-update': noDirectMainUpdate as Rule,\n 'no-js-files': noJsFiles as Rule,\n 'no-edit-on-main': noEditOnMain as Rule,\n};\n\nexport function loadRules(config: ResolvedConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules();\n const custom = loadCustomRules(config.rulesDir, workspaceRoot);\n const all: Rule[] = [...builtIns, ...custom];\n return all.filter((rule: Rule) => validateRule(rule));\n}\n\nfunction loadBuiltInRules(): Rule[] {\n const modules: Rule[] = [];\n for (const name of builtInRuleNames) {\n const mod = BUILT_IN_RULE_MAP[name];\n if (mod) {\n modules.push(mod);\n } else {\n process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\\n`);\n }\n }\n return modules;\n}\n\nfunction loadCustomRules(rulesDirs: readonly string[], workspaceRoot: string): Rule[] {\n const modules: Rule[] = [];\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) => 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 modules.push(mod.default || mod);\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 Rule {\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\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\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"]}
|
|
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 * as fs from 'fs';\nimport * as path from 'path';\n\nimport {\n BaseRuleConfig, RuleOptions, WebpiecesRulesConfig,\n NoAnyUnknownConfig, NoImplicitAnyConfig, MaxFileLinesConfig, ValidateTsInSrcConfig,\n NoDestructureConfig, RequireReturnTypeConfig, NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig, ThrowCauseRequiredConfig, NoShellSubstitutionConfig,\n NoSymbolDiTokensConfig, BranchCreationGuardConfig, PrCreationGuardConfig,\n MergeInProgressGuardConfig, PrMergeCleanupConfig, NoDirectMainUpdateConfig,\n NoJsFilesConfig, NoEditOnMainConfig,\n} from '@webpieces/rules-config';\n\nimport type { Rule, PlainRule } from './types';\nimport { InformAiError } from './types';\nimport { toError } from './to-error';\nimport { EmptyRuleConfig } from './rule-base';\nimport { CustomRuleAdapter } from './custom-rule-adapter';\nimport { builtInRuleNames } 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 { NoShellSubstitutionRule } from './rules/no-shell-substitution';\nimport { NoSymbolDiTokensRule } from './rules/no-symbol-di-tokens';\nimport { BranchCreationGuardRule } from './rules/branch-creation-guard';\nimport { PrCreationGuardRule } from './rules/pr-creation-guard';\nimport { MergeInProgressGuardRule } from './rules/merge-in-progress-guard';\nimport { PrMergeCleanupRule } from './rules/pr-merge-cleanup';\nimport { NoDirectMainUpdateRule } from './rules/no-direct-main-update';\nimport { NoJsFilesRule } from './rules/no-js-files';\nimport { NoEditOnMainRule } from './rules/no-edit-on-main';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\n// Each built-in rule is constructed from its typed *Config (the entry in webpieces.config.json).\n// The config arrives as a plain object structurally typed as the *Config class, so the `as`\n// narrows the shared BaseRuleConfig param back to the concrete config the rule consumes.\ntype RuleFactory = (config: BaseRuleConfig) => 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-shell-substitution': (c: BaseRuleConfig) => new NoShellSubstitutionRule(c as NoShellSubstitutionConfig),\n 'no-symbol-di-tokens': (c: BaseRuleConfig) => new NoSymbolDiTokensRule(c as NoSymbolDiTokensConfig),\n 'branch-creation-guard': (c: BaseRuleConfig) => new BranchCreationGuardRule(c as BranchCreationGuardConfig),\n 'pr-creation-guard': (c: BaseRuleConfig) => new PrCreationGuardRule(c as PrCreationGuardConfig),\n 'merge-in-progress-guard': (c: BaseRuleConfig) => new MergeInProgressGuardRule(c as MergeInProgressGuardConfig),\n 'pr-merge-cleanup': (c: BaseRuleConfig) => new PrMergeCleanupRule(c as PrMergeCleanupConfig),\n 'no-direct-main-update': (c: BaseRuleConfig) => new NoDirectMainUpdateRule(c as NoDirectMainUpdateConfig),\n 'no-js-files': (c: BaseRuleConfig) => new NoJsFilesRule(c as NoJsFilesConfig),\n 'no-edit-on-main': (c: BaseRuleConfig) => new NoEditOnMainRule(c as NoEditOnMainConfig),\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\nexport function loadRules(config: WebpiecesRulesConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules(config);\n const custom = loadCustomRules(config, workspaceRoot);\n return [...builtIns, ...custom];\n}\n\nfunction loadBuiltInRules(config: WebpiecesRulesConfig): Rule[] {\n const map = asConfigMap(config);\n const rules: Rule[] = [];\n for (const name of builtInRuleNames) {\n const factory = BUILT_IN_RULE_MAP[name];\n if (!factory) {\n process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\\n`);\n continue;\n }\n const ruleConfig = map[name] ?? new EmptyRuleConfig();\n rules.push(factory(ruleConfig));\n }\n return rules;\n}\n\nfunction 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\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\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"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { AbstractRule, BaseRuleConfig, RuleOptions } from '@webpieces/rules-config';
|
|
2
|
+
import type { EditContext, BashContext, FileContext, Violation, Rule } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* A concrete, instantiable BaseRuleConfig used as a fallback when a built-in rule has no
|
|
5
|
+
* entry in webpieces.config.json yet. It carries no values, so the rule's `description`,
|
|
6
|
+
* `fixHint`, and `defaultOptions` (which are config-independent) are still readable for the
|
|
7
|
+
* out-of-sync report — the rule never actually runs in that state (the sync check blocks first).
|
|
8
|
+
*/
|
|
9
|
+
export declare class EmptyRuleConfig extends BaseRuleConfig {
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Scope-specific base for edit rules. Extends the shared AbstractRule (which owns `name`,
|
|
13
|
+
* the typed `config`, and `shouldRun()`), and adds the ai-hook execution surface.
|
|
14
|
+
*/
|
|
15
|
+
export declare abstract class EditRuleBase<C extends BaseRuleConfig> extends AbstractRule<C> implements Rule {
|
|
16
|
+
readonly scope: "edit";
|
|
17
|
+
readonly files: readonly string[];
|
|
18
|
+
readonly defaultOptions: RuleOptions;
|
|
19
|
+
abstract readonly description: string;
|
|
20
|
+
abstract readonly fixHint: readonly string[];
|
|
21
|
+
abstract check(ctx: EditContext): readonly Violation[];
|
|
22
|
+
}
|
|
23
|
+
export declare abstract class FileRuleBase<C extends BaseRuleConfig> extends AbstractRule<C> implements Rule {
|
|
24
|
+
readonly scope: "file";
|
|
25
|
+
readonly files: readonly string[];
|
|
26
|
+
readonly defaultOptions: RuleOptions;
|
|
27
|
+
abstract readonly description: string;
|
|
28
|
+
abstract readonly fixHint: readonly string[];
|
|
29
|
+
abstract check(ctx: FileContext): readonly Violation[];
|
|
30
|
+
}
|
|
31
|
+
export declare abstract class BashRuleBase<C extends BaseRuleConfig> extends AbstractRule<C> implements Rule {
|
|
32
|
+
readonly scope: "bash";
|
|
33
|
+
readonly files: readonly string[];
|
|
34
|
+
readonly defaultOptions: RuleOptions;
|
|
35
|
+
abstract readonly description: string;
|
|
36
|
+
abstract readonly fixHint: readonly string[];
|
|
37
|
+
abstract check(ctx: BashContext): readonly Violation[];
|
|
38
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BashRuleBase = exports.FileRuleBase = exports.EditRuleBase = exports.EmptyRuleConfig = void 0;
|
|
4
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
|
+
/**
|
|
6
|
+
* A concrete, instantiable BaseRuleConfig used as a fallback when a built-in rule has no
|
|
7
|
+
* entry in webpieces.config.json yet. It carries no values, so the rule's `description`,
|
|
8
|
+
* `fixHint`, and `defaultOptions` (which are config-independent) are still readable for the
|
|
9
|
+
* out-of-sync report — the rule never actually runs in that state (the sync check blocks first).
|
|
10
|
+
*/
|
|
11
|
+
class EmptyRuleConfig extends rules_config_1.BaseRuleConfig {
|
|
12
|
+
}
|
|
13
|
+
exports.EmptyRuleConfig = EmptyRuleConfig;
|
|
14
|
+
/**
|
|
15
|
+
* Scope-specific base for edit rules. Extends the shared AbstractRule (which owns `name`,
|
|
16
|
+
* the typed `config`, and `shouldRun()`), and adds the ai-hook execution surface.
|
|
17
|
+
*/
|
|
18
|
+
class EditRuleBase extends rules_config_1.AbstractRule {
|
|
19
|
+
scope = 'edit';
|
|
20
|
+
files = [];
|
|
21
|
+
defaultOptions = {};
|
|
22
|
+
}
|
|
23
|
+
exports.EditRuleBase = EditRuleBase;
|
|
24
|
+
class FileRuleBase extends rules_config_1.AbstractRule {
|
|
25
|
+
scope = 'file';
|
|
26
|
+
files = [];
|
|
27
|
+
defaultOptions = {};
|
|
28
|
+
}
|
|
29
|
+
exports.FileRuleBase = FileRuleBase;
|
|
30
|
+
class BashRuleBase extends rules_config_1.AbstractRule {
|
|
31
|
+
scope = 'bash';
|
|
32
|
+
files = [];
|
|
33
|
+
defaultOptions = {};
|
|
34
|
+
}
|
|
35
|
+
exports.BashRuleBase = BashRuleBase;
|
|
36
|
+
//# sourceMappingURL=rule-base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-base.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/rule-base.ts"],"names":[],"mappings":";;;AAAA,0DAAoF;AAIpF;;;;;GAKG;AACH,MAAa,eAAgB,SAAQ,6BAAc;CAAG;AAAtD,0CAAsD;AAEtD;;;GAGG;AACH,MAAsB,YAAuC,SAAQ,2BAAe;IACvE,KAAK,GAAG,MAAe,CAAC;IACxB,KAAK,GAAsB,EAAE,CAAC;IAC9B,cAAc,GAAgB,EAAE,CAAC;CAK7C;AARD,oCAQC;AAED,MAAsB,YAAuC,SAAQ,2BAAe;IACvE,KAAK,GAAG,MAAe,CAAC;IACxB,KAAK,GAAsB,EAAE,CAAC;IAC9B,cAAc,GAAgB,EAAE,CAAC;CAK7C;AARD,oCAQC;AAED,MAAsB,YAAuC,SAAQ,2BAAe;IACvE,KAAK,GAAG,MAAe,CAAC;IACxB,KAAK,GAAsB,EAAE,CAAC;IAC9B,cAAc,GAAgB,EAAE,CAAC;CAK7C;AARD,oCAQC","sourcesContent":["import { AbstractRule, BaseRuleConfig, RuleOptions } from '@webpieces/rules-config';\n\nimport type { EditContext, BashContext, FileContext, Violation, Rule } from './types';\n\n/**\n * A concrete, instantiable BaseRuleConfig used as a fallback when a built-in rule has no\n * entry in webpieces.config.json yet. It carries no values, so the rule's `description`,\n * `fixHint`, and `defaultOptions` (which are config-independent) are still readable for the\n * out-of-sync report — the rule never actually runs in that state (the sync check blocks first).\n */\nexport class EmptyRuleConfig extends BaseRuleConfig {}\n\n/**\n * Scope-specific base for edit rules. Extends the shared AbstractRule (which owns `name`,\n * the typed `config`, and `shouldRun()`), and adds the ai-hook execution surface.\n */\nexport abstract class EditRuleBase<C extends BaseRuleConfig> extends AbstractRule<C> implements Rule {\n readonly scope = 'edit' as const;\n readonly files: readonly string[] = [];\n readonly defaultOptions: RuleOptions = {};\n abstract readonly description: string;\n abstract readonly fixHint: readonly string[];\n\n abstract check(ctx: EditContext): readonly Violation[];\n}\n\nexport abstract class FileRuleBase<C extends BaseRuleConfig> extends AbstractRule<C> implements Rule {\n readonly scope = 'file' as const;\n readonly files: readonly string[] = [];\n readonly defaultOptions: RuleOptions = {};\n abstract readonly description: string;\n abstract readonly fixHint: readonly string[];\n\n abstract check(ctx: FileContext): readonly Violation[];\n}\n\nexport abstract class BashRuleBase<C extends BaseRuleConfig> extends AbstractRule<C> implements Rule {\n readonly scope = 'bash' as const;\n readonly files: readonly string[] = [];\n readonly defaultOptions: RuleOptions = {};\n abstract readonly description: string;\n abstract readonly fixHint: readonly string[];\n\n abstract check(ctx: BashContext): readonly Violation[];\n}\n"]}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { BranchCreationGuardConfig } from '@webpieces/rules-config';
|
|
2
|
+
import type { BashContext, Violation } from '../types';
|
|
3
|
+
import { BashRuleBase } from '../rule-base';
|
|
4
|
+
export declare class BranchCreationGuardRule extends BashRuleBase<BranchCreationGuardConfig> {
|
|
5
|
+
constructor(config: BranchCreationGuardConfig);
|
|
6
|
+
readonly description = "Block new-branch creation when main is stale, or when not on main.";
|
|
7
|
+
readonly defaultOptions: {
|
|
8
|
+
subBranchNaming: string;
|
|
9
|
+
};
|
|
10
|
+
readonly fixHint: readonly string[];
|
|
11
|
+
check(ctx: BashContext): readonly Violation[];
|
|
12
|
+
}
|
|
@@ -1,47 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BranchCreationGuardRule = void 0;
|
|
3
4
|
const child_process_1 = require("child_process");
|
|
4
5
|
const types_1 = require("../types");
|
|
6
|
+
const rule_base_1 = require("../rule-base");
|
|
5
7
|
const FIX_HINT = [
|
|
6
8
|
"Run 'git checkout main && git pull origin main', then create your branch from main",
|
|
7
9
|
"If you truly need a sub-branch (requires human approval), name it using the convention in webpieces.config.json 'branch-creation-guard.subBranchNaming'",
|
|
8
10
|
];
|
|
9
|
-
const branchCreationGuard = {
|
|
10
|
-
name: 'branch-creation-guard',
|
|
11
|
-
description: 'Block new-branch creation when main is stale, or when not on main.',
|
|
12
|
-
scope: 'bash',
|
|
13
|
-
files: [],
|
|
14
|
-
defaultOptions: { subBranchNaming: 'feature/<ticket>/<short-description>' },
|
|
15
|
-
fixHint: FIX_HINT,
|
|
16
|
-
check(ctx) {
|
|
17
|
-
const requestedName = extractBranchName(ctx.command);
|
|
18
|
-
if (!requestedName)
|
|
19
|
-
return [];
|
|
20
|
-
const currentBranch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
|
|
21
|
-
cwd: ctx.workspaceRoot,
|
|
22
|
-
encoding: 'utf8',
|
|
23
|
-
}).trim();
|
|
24
|
-
if (currentBranch === 'main') {
|
|
25
|
-
return checkMainIsUpToDate(ctx, requestedName);
|
|
26
|
-
}
|
|
27
|
-
return [new types_1.Violation(1, truncate(ctx.command), `You are on '${currentBranch}', not main. Branches must be created from main.`)];
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
function checkMainIsUpToDate(ctx, requestedName) {
|
|
31
|
-
(0, child_process_1.execSync)('git fetch origin main --quiet', {
|
|
32
|
-
cwd: ctx.workspaceRoot,
|
|
33
|
-
encoding: 'utf8',
|
|
34
|
-
});
|
|
35
|
-
const countStr = (0, child_process_1.execSync)('git rev-list HEAD..origin/main --count', {
|
|
36
|
-
cwd: ctx.workspaceRoot,
|
|
37
|
-
encoding: 'utf8',
|
|
38
|
-
}).trim();
|
|
39
|
-
const count = parseInt(countStr, 10);
|
|
40
|
-
if (count > 0) {
|
|
41
|
-
return [new types_1.Violation(1, truncate(ctx.command), `Local main is ${count} commit(s) behind origin/main. Run 'git pull origin main' first, then retry creating branch '${requestedName}'.`)];
|
|
42
|
-
}
|
|
43
|
-
return [];
|
|
44
|
-
}
|
|
45
11
|
const BRANCH_PATTERNS = [
|
|
46
12
|
/git\s+checkout\s+-[bB]\s+([^\s]+)/,
|
|
47
13
|
/git\s+switch\s+-[cC]\s+([^\s]+)/,
|
|
@@ -59,5 +25,39 @@ function truncate(s) {
|
|
|
59
25
|
const MAX = 120;
|
|
60
26
|
return s.length <= MAX ? s : s.slice(0, MAX) + '…';
|
|
61
27
|
}
|
|
62
|
-
|
|
28
|
+
function checkMainIsUpToDate(ctx, requestedName) {
|
|
29
|
+
(0, child_process_1.execSync)('git fetch origin main --quiet', {
|
|
30
|
+
cwd: ctx.workspaceRoot,
|
|
31
|
+
encoding: 'utf8',
|
|
32
|
+
});
|
|
33
|
+
const countStr = (0, child_process_1.execSync)('git rev-list HEAD..origin/main --count', {
|
|
34
|
+
cwd: ctx.workspaceRoot,
|
|
35
|
+
encoding: 'utf8',
|
|
36
|
+
}).trim();
|
|
37
|
+
const count = parseInt(countStr, 10);
|
|
38
|
+
if (count > 0) {
|
|
39
|
+
return [new types_1.Violation(1, truncate(ctx.command), `Local main is ${count} commit(s) behind origin/main. Run 'git pull origin main' first, then retry creating branch '${requestedName}'.`)];
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
class BranchCreationGuardRule extends rule_base_1.BashRuleBase {
|
|
44
|
+
constructor(config) { super(config, 'branch-creation-guard'); }
|
|
45
|
+
description = 'Block new-branch creation when main is stale, or when not on main.';
|
|
46
|
+
defaultOptions = { subBranchNaming: 'feature/<ticket>/<short-description>' };
|
|
47
|
+
fixHint = FIX_HINT;
|
|
48
|
+
check(ctx) {
|
|
49
|
+
const requestedName = extractBranchName(ctx.command);
|
|
50
|
+
if (!requestedName)
|
|
51
|
+
return [];
|
|
52
|
+
const currentBranch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
|
|
53
|
+
cwd: ctx.workspaceRoot,
|
|
54
|
+
encoding: 'utf8',
|
|
55
|
+
}).trim();
|
|
56
|
+
if (currentBranch === 'main') {
|
|
57
|
+
return checkMainIsUpToDate(ctx, requestedName);
|
|
58
|
+
}
|
|
59
|
+
return [new types_1.Violation(1, truncate(ctx.command), `You are on '${currentBranch}', not main. Branches must be created from main.`)];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.BranchCreationGuardRule = BranchCreationGuardRule;
|
|
63
63
|
//# sourceMappingURL=branch-creation-guard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/branch-creation-guard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"branch-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/branch-creation-guard.ts"],"names":[],"mappings":";;;AAAA,iDAAyC;AAKzC,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAM,QAAQ,GAAsB;IAChC,oFAAoF;IACpF,yJAAyJ;CAC5J,CAAC;AAEF,MAAM,eAAe,GAAa;IAC9B,mCAAmC;IACnC,iCAAiC;IACjC,8CAA8C;CACjD,CAAC;AAEF,SAAS,iBAAiB,CAAC,OAAe;IACtC,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,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,SAAS,mBAAmB,CAAC,GAAgB,EAAE,aAAqB;IAChE,IAAA,wBAAQ,EAAC,+BAA+B,EAAE;QACtC,GAAG,EAAE,GAAG,CAAC,aAAa;QACtB,QAAQ,EAAE,MAAM;KACnB,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,IAAA,wBAAQ,EAAC,wCAAwC,EAAE;QAChE,GAAG,EAAE,GAAG,CAAC,aAAa;QACtB,QAAQ,EAAE,MAAM;KACnB,CAAC,CAAC,IAAI,EAAE,CAAC;IACV,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACrC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,iBAAiB,KAAK,gGAAgG,aAAa,IAAI,CAC1I,CAAC,CAAC;IACP,CAAC;IACD,OAAO,EAAE,CAAC;AACd,CAAC;AAED,MAAa,uBAAwB,SAAQ,wBAAuC;IAChF,YAAY,MAAiC,IAAI,KAAK,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC,CAAC;IAEjF,WAAW,GAAG,oEAAoE,CAAC;IAC1E,cAAc,GAAG,EAAE,eAAe,EAAE,sCAAsC,EAAE,CAAC;IACtF,OAAO,GAAG,QAAQ,CAAC;IAE5B,KAAK,CAAC,GAAgB;QAClB,MAAM,aAAa,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrD,IAAI,CAAC,aAAa;YAAE,OAAO,EAAE,CAAC;QAE9B,MAAM,aAAa,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE;YAC9D,GAAG,EAAE,GAAG,CAAC,aAAa;YACtB,QAAQ,EAAE,MAAM;SACnB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC;YAC3B,OAAO,mBAAmB,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QACnD,CAAC;QAED,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,eAAe,aAAa,kDAAkD,CACjF,CAAC,CAAC;IACP,CAAC;CACJ;AA1BD,0DA0BC","sourcesContent":["import { execSync } from 'child_process';\n\nimport { BranchCreationGuardConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\n\nconst FIX_HINT: readonly string[] = [\n \"Run 'git checkout main && git pull origin main', then create your branch from main\",\n \"If you truly need a sub-branch (requires human approval), name it using the convention in webpieces.config.json 'branch-creation-guard.subBranchNaming'\",\n];\n\nconst BRANCH_PATTERNS: RegExp[] = [\n /git\\s+checkout\\s+-[bB]\\s+([^\\s]+)/,\n /git\\s+switch\\s+-[cC]\\s+([^\\s]+)/,\n /git\\s+branch\\s+(?!-[dDmMrRla])([^\\s-][^\\s]*)/,\n];\n\nfunction extractBranchName(command: string): string | null {\n for (const pattern of BRANCH_PATTERNS) {\n const m = pattern.exec(command);\n if (m) return m[1];\n }\n return null;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nfunction checkMainIsUpToDate(ctx: BashContext, requestedName: string): readonly Violation[] {\n execSync('git fetch origin main --quiet', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n });\n const countStr = execSync('git rev-list HEAD..origin/main --count', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n const count = parseInt(countStr, 10);\n if (count > 0) {\n return [new V(\n 1,\n truncate(ctx.command),\n `Local main is ${count} commit(s) behind origin/main. Run 'git pull origin main' first, then retry creating branch '${requestedName}'.`,\n )];\n }\n return [];\n}\n\nexport class BranchCreationGuardRule extends BashRuleBase<BranchCreationGuardConfig> {\n constructor(config: BranchCreationGuardConfig) { super(config, 'branch-creation-guard'); }\n\n readonly description = 'Block new-branch creation when main is stale, or when not on main.';\n override readonly defaultOptions = { subBranchNaming: 'feature/<ticket>/<short-description>' };\n readonly fixHint = FIX_HINT;\n\n check(ctx: BashContext): readonly Violation[] {\n const requestedName = extractBranchName(ctx.command);\n if (!requestedName) return [];\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n if (currentBranch === 'main') {\n return checkMainIsUpToDate(ctx, requestedName);\n }\n\n return [new V(\n 1,\n truncate(ctx.command),\n `You are on '${currentBranch}', not main. Branches must be created from main.`,\n )];\n }\n}\n"]}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { CatchErrorPatternConfig } from '@webpieces/rules-config';
|
|
2
|
+
import type { EditContext, Violation } from '../types';
|
|
3
|
+
import { EditRuleBase } from '../rule-base';
|
|
4
|
+
export declare class CatchErrorPatternRule extends EditRuleBase<CatchErrorPatternConfig> {
|
|
5
|
+
constructor(config: CatchErrorPatternConfig);
|
|
6
|
+
readonly description = "Catch blocks must use: catch (err: unknown) { const error = toError(err); }";
|
|
7
|
+
readonly files: string[];
|
|
8
|
+
readonly fixHint: string[];
|
|
9
|
+
check(ctx: EditContext): readonly Violation[];
|
|
10
|
+
}
|