@webpieces/ai-hook-rules 0.3.228 → 0.3.229
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/core/custom-rule-adapter.d.ts +2 -1
- package/src/core/custom-rule-adapter.js.map +1 -1
- package/src/core/fix-hint.d.ts +42 -0
- package/src/core/fix-hint.js +60 -0
- package/src/core/fix-hint.js.map +1 -0
- package/src/core/report.js +21 -5
- package/src/core/report.js.map +1 -1
- package/src/core/rule-base.d.ts +4 -3
- package/src/core/rule-base.js.map +1 -1
- package/src/core/rules/branch-creation-guard.d.ts +2 -1
- package/src/core/rules/branch-creation-guard.js +8 -7
- package/src/core/rules/branch-creation-guard.js.map +1 -1
- package/src/core/rules/catch-error-pattern.d.ts +2 -1
- package/src/core/rules/catch-error-pattern.js +9 -7
- package/src/core/rules/catch-error-pattern.js.map +1 -1
- package/src/core/rules/feature-branch-guard.d.ts +2 -1
- package/src/core/rules/feature-branch-guard.js +6 -6
- package/src/core/rules/feature-branch-guard.js.map +1 -1
- package/src/core/rules/max-file-lines.d.ts +2 -1
- package/src/core/rules/max-file-lines.js +4 -4
- package/src/core/rules/max-file-lines.js.map +1 -1
- package/src/core/rules/merge-in-progress-guard.d.ts +2 -1
- package/src/core/rules/merge-in-progress-guard.js +8 -12
- package/src/core/rules/merge-in-progress-guard.js.map +1 -1
- package/src/core/rules/no-any-unknown.d.ts +2 -1
- package/src/core/rules/no-any-unknown.js +7 -6
- package/src/core/rules/no-any-unknown.js.map +1 -1
- package/src/core/rules/no-destructure.d.ts +2 -1
- package/src/core/rules/no-destructure.js +7 -6
- package/src/core/rules/no-destructure.js.map +1 -1
- package/src/core/rules/no-implicit-any.d.ts +2 -1
- package/src/core/rules/no-implicit-any.js +6 -5
- package/src/core/rules/no-implicit-any.js.map +1 -1
- package/src/core/rules/no-js-files.d.ts +2 -1
- package/src/core/rules/no-js-files.js +6 -5
- package/src/core/rules/no-js-files.js.map +1 -1
- package/src/core/rules/no-symbol-di-tokens.d.ts +2 -1
- package/src/core/rules/no-symbol-di-tokens.js +11 -8
- package/src/core/rules/no-symbol-di-tokens.js.map +1 -1
- package/src/core/rules/no-unmanaged-exceptions.d.ts +2 -1
- package/src/core/rules/no-unmanaged-exceptions.js +15 -13
- package/src/core/rules/no-unmanaged-exceptions.js.map +1 -1
- package/src/core/rules/pr-creation-guard.d.ts +2 -1
- package/src/core/rules/pr-creation-guard.js +8 -13
- package/src/core/rules/pr-creation-guard.js.map +1 -1
- package/src/core/rules/pr-merge-cleanup.d.ts +3 -1
- package/src/core/rules/pr-merge-cleanup.js +11 -11
- package/src/core/rules/pr-merge-cleanup.js.map +1 -1
- package/src/core/rules/redirect-how-to-merge-main.d.ts +2 -1
- package/src/core/rules/redirect-how-to-merge-main.js +3 -10
- package/src/core/rules/redirect-how-to-merge-main.js.map +1 -1
- package/src/core/rules/require-return-type.d.ts +2 -1
- package/src/core/rules/require-return-type.js +7 -6
- package/src/core/rules/require-return-type.js.map +1 -1
- package/src/core/rules/throw-cause-required.d.ts +2 -1
- package/src/core/rules/throw-cause-required.js +11 -8
- package/src/core/rules/throw-cause-required.js.map +1 -1
- package/src/core/rules/validate-ts-in-src.d.ts +2 -1
- package/src/core/rules/validate-ts-in-src.js +5 -4
- package/src/core/rules/validate-ts-in-src.js.map +1 -1
- package/src/core/runner.js +3 -3
- package/src/core/runner.js.map +1 -1
- package/src/core/types.d.ts +6 -5
- package/src/core/types.js +3 -0
- package/src/core/types.js.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +6 -1
- package/src/index.js.map +1 -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.229",
|
|
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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"directory": "packages/tooling/ai-hook-rules"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@webpieces/rules-config": "0.3.
|
|
38
|
+
"@webpieces/rules-config": "0.3.229"
|
|
39
39
|
},
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AbstractRule, BaseRuleConfig, RuleOptions } from '@webpieces/rules-config';
|
|
2
2
|
import type { PlainRule, Rule, RuleScope, EditContext, FileContext, BashContext, Violation } from './types';
|
|
3
|
+
import type { FixHint } from './fix-hint';
|
|
3
4
|
/**
|
|
4
5
|
* Wraps a custom rule loaded from a `rulesDir` (a plain object) so it satisfies the same
|
|
5
6
|
* runtime `Rule` contract as the built-in rule classes: it gains `shouldRun()` (driven by the
|
|
@@ -11,7 +12,7 @@ export declare class CustomRuleAdapter extends AbstractRule<BaseRuleConfig> impl
|
|
|
11
12
|
readonly scope: RuleScope;
|
|
12
13
|
readonly files: readonly string[];
|
|
13
14
|
readonly description: string;
|
|
14
|
-
readonly fixHint:
|
|
15
|
+
readonly fixHint: FixHint;
|
|
15
16
|
readonly defaultOptions: RuleOptions;
|
|
16
17
|
private readonly impl;
|
|
17
18
|
private readonly rawConfig;
|
|
@@ -1 +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;
|
|
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;AAQpF;;;;;;GAMG;AACH,MAAa,iBAAkB,SAAQ,2BAA4B;IACtD,KAAK,CAAY;IACjB,KAAK,CAAoB;IACzB,WAAW,CAAS;IACpB,OAAO,CAAU;IACjB,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';\nimport type { FixHint } from './fix-hint';\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: FixHint;\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"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured fix guidance shown under a violation in a blocked-write / blocked-bash report.
|
|
3
|
+
*
|
|
4
|
+
* A rule authors its user-facing text once here: a required `violation` (the "what's wrong"
|
|
5
|
+
* line — the rule-level default for the `→` line; a dynamic per-occurrence `Violation.message`
|
|
6
|
+
* overrides it), a `mainMessage` (fix prose, or a lead-in to the options), an optional list of
|
|
7
|
+
* genuinely distinct `fixOptions`, and — for the disable-able code-style rules — a framework-
|
|
8
|
+
* owned `escape`.
|
|
9
|
+
*
|
|
10
|
+
* The framework (report.ts) — not the rule author — owns the "Fix Option N:" numbering, the
|
|
11
|
+
* "(preferred)" tag, and the escape/`disableAllowed` rendering. So a multi-line message can
|
|
12
|
+
* never be mis-split into fake options, and authors never hand-write those labels.
|
|
13
|
+
*/
|
|
14
|
+
export declare class Option {
|
|
15
|
+
/** The fix text. May be multi-line; continuation lines are indented under the option. */
|
|
16
|
+
readonly text: string;
|
|
17
|
+
/** When true the framework prefixes the rendered option with "(preferred) ". */
|
|
18
|
+
readonly preferred: boolean;
|
|
19
|
+
constructor(text: string, preferred?: boolean);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Framework-owned `// webpieces-disable` escape hatch for a rule, gated by the team's
|
|
23
|
+
* `disableAllowed` config (default true). `comment` is the exact suppress syntax to show
|
|
24
|
+
* (usually `// webpieces-disable <rule> -- <reason>`; max-file-lines uses the eslint form).
|
|
25
|
+
* When `allowed` is false the framework prints a "must be followed" line instead of the escape.
|
|
26
|
+
*/
|
|
27
|
+
export declare class DisableEscape {
|
|
28
|
+
readonly allowed: boolean;
|
|
29
|
+
readonly comment: string;
|
|
30
|
+
constructor(allowed: boolean, comment: string);
|
|
31
|
+
}
|
|
32
|
+
export declare class FixHint {
|
|
33
|
+
/** Required: the "what's wrong" line (rule-level default for the `→` line). */
|
|
34
|
+
readonly violation: string;
|
|
35
|
+
/** Required (may be ''): fix prose, or a lead-in to the options. Multi-line ok. */
|
|
36
|
+
readonly mainMessage: string;
|
|
37
|
+
/** Real fixes only — NEVER the disable escape (that is `escape`). */
|
|
38
|
+
readonly fixOptions: readonly Option[];
|
|
39
|
+
/** Present only for disable-able rules; absent for guards. */
|
|
40
|
+
readonly escape?: DisableEscape;
|
|
41
|
+
constructor(violation: string, mainMessage: string, fixOptions?: readonly Option[], escape?: DisableEscape);
|
|
42
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FixHint = exports.DisableEscape = exports.Option = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Structured fix guidance shown under a violation in a blocked-write / blocked-bash report.
|
|
6
|
+
*
|
|
7
|
+
* A rule authors its user-facing text once here: a required `violation` (the "what's wrong"
|
|
8
|
+
* line — the rule-level default for the `→` line; a dynamic per-occurrence `Violation.message`
|
|
9
|
+
* overrides it), a `mainMessage` (fix prose, or a lead-in to the options), an optional list of
|
|
10
|
+
* genuinely distinct `fixOptions`, and — for the disable-able code-style rules — a framework-
|
|
11
|
+
* owned `escape`.
|
|
12
|
+
*
|
|
13
|
+
* The framework (report.ts) — not the rule author — owns the "Fix Option N:" numbering, the
|
|
14
|
+
* "(preferred)" tag, and the escape/`disableAllowed` rendering. So a multi-line message can
|
|
15
|
+
* never be mis-split into fake options, and authors never hand-write those labels.
|
|
16
|
+
*/
|
|
17
|
+
class Option {
|
|
18
|
+
/** The fix text. May be multi-line; continuation lines are indented under the option. */
|
|
19
|
+
text;
|
|
20
|
+
/** When true the framework prefixes the rendered option with "(preferred) ". */
|
|
21
|
+
preferred;
|
|
22
|
+
constructor(text, preferred = false) {
|
|
23
|
+
this.text = text;
|
|
24
|
+
this.preferred = preferred;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.Option = Option;
|
|
28
|
+
/**
|
|
29
|
+
* Framework-owned `// webpieces-disable` escape hatch for a rule, gated by the team's
|
|
30
|
+
* `disableAllowed` config (default true). `comment` is the exact suppress syntax to show
|
|
31
|
+
* (usually `// webpieces-disable <rule> -- <reason>`; max-file-lines uses the eslint form).
|
|
32
|
+
* When `allowed` is false the framework prints a "must be followed" line instead of the escape.
|
|
33
|
+
*/
|
|
34
|
+
class DisableEscape {
|
|
35
|
+
allowed;
|
|
36
|
+
comment;
|
|
37
|
+
constructor(allowed, comment) {
|
|
38
|
+
this.allowed = allowed;
|
|
39
|
+
this.comment = comment;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.DisableEscape = DisableEscape;
|
|
43
|
+
class FixHint {
|
|
44
|
+
/** Required: the "what's wrong" line (rule-level default for the `→` line). */
|
|
45
|
+
violation;
|
|
46
|
+
/** Required (may be ''): fix prose, or a lead-in to the options. Multi-line ok. */
|
|
47
|
+
mainMessage;
|
|
48
|
+
/** Real fixes only — NEVER the disable escape (that is `escape`). */
|
|
49
|
+
fixOptions;
|
|
50
|
+
/** Present only for disable-able rules; absent for guards. */
|
|
51
|
+
escape;
|
|
52
|
+
constructor(violation, mainMessage, fixOptions = [], escape) {
|
|
53
|
+
this.violation = violation;
|
|
54
|
+
this.mainMessage = mainMessage;
|
|
55
|
+
this.fixOptions = fixOptions;
|
|
56
|
+
this.escape = escape;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.FixHint = FixHint;
|
|
60
|
+
//# sourceMappingURL=fix-hint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fix-hint.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/fix-hint.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;GAYG;AACH,MAAa,MAAM;IACf,yFAAyF;IAChF,IAAI,CAAS;IACtB,gFAAgF;IACvE,SAAS,CAAU;IAE5B,YAAY,IAAY,EAAE,SAAS,GAAG,KAAK;QACvC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/B,CAAC;CACJ;AAVD,wBAUC;AAED;;;;;GAKG;AACH,MAAa,aAAa;IACb,OAAO,CAAU;IACjB,OAAO,CAAS;IAEzB,YAAY,OAAgB,EAAE,OAAe;QACzC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;CACJ;AARD,sCAQC;AAED,MAAa,OAAO;IAChB,+EAA+E;IACtE,SAAS,CAAS;IAC3B,mFAAmF;IAC1E,WAAW,CAAS;IAC7B,qEAAqE;IAC5D,UAAU,CAAoB;IACvC,8DAA8D;IACrD,MAAM,CAAiB;IAEhC,YACI,SAAiB,EACjB,WAAmB,EACnB,aAAgC,EAAE,EAClC,MAAsB;QAEtB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;CACJ;AArBD,0BAqBC","sourcesContent":["/**\n * Structured fix guidance shown under a violation in a blocked-write / blocked-bash report.\n *\n * A rule authors its user-facing text once here: a required `violation` (the \"what's wrong\"\n * line — the rule-level default for the `→` line; a dynamic per-occurrence `Violation.message`\n * overrides it), a `mainMessage` (fix prose, or a lead-in to the options), an optional list of\n * genuinely distinct `fixOptions`, and — for the disable-able code-style rules — a framework-\n * owned `escape`.\n *\n * The framework (report.ts) — not the rule author — owns the \"Fix Option N:\" numbering, the\n * \"(preferred)\" tag, and the escape/`disableAllowed` rendering. So a multi-line message can\n * never be mis-split into fake options, and authors never hand-write those labels.\n */\nexport class Option {\n /** The fix text. May be multi-line; continuation lines are indented under the option. */\n readonly text: string;\n /** When true the framework prefixes the rendered option with \"(preferred) \". */\n readonly preferred: boolean;\n\n constructor(text: string, preferred = false) {\n this.text = text;\n this.preferred = preferred;\n }\n}\n\n/**\n * Framework-owned `// webpieces-disable` escape hatch for a rule, gated by the team's\n * `disableAllowed` config (default true). `comment` is the exact suppress syntax to show\n * (usually `// webpieces-disable <rule> -- <reason>`; max-file-lines uses the eslint form).\n * When `allowed` is false the framework prints a \"must be followed\" line instead of the escape.\n */\nexport class DisableEscape {\n readonly allowed: boolean;\n readonly comment: string;\n\n constructor(allowed: boolean, comment: string) {\n this.allowed = allowed;\n this.comment = comment;\n }\n}\n\nexport class FixHint {\n /** Required: the \"what's wrong\" line (rule-level default for the `→` line). */\n readonly violation: string;\n /** Required (may be ''): fix prose, or a lead-in to the options. Multi-line ok. */\n readonly mainMessage: string;\n /** Real fixes only — NEVER the disable escape (that is `escape`). */\n readonly fixOptions: readonly Option[];\n /** Present only for disable-able rules; absent for guards. */\n readonly escape?: DisableEscape;\n\n constructor(\n violation: string,\n mainMessage: string,\n fixOptions: readonly Option[] = [],\n escape?: DisableEscape,\n ) {\n this.violation = violation;\n this.mainMessage = mainMessage;\n this.fixOptions = fixOptions;\n this.escape = escape;\n }\n}\n"]}
|
package/src/core/report.js
CHANGED
|
@@ -9,15 +9,31 @@ function formatReport(relativePath, ruleGroups) {
|
|
|
9
9
|
const count = group.violations.length;
|
|
10
10
|
const label = count === 1 ? '1 violation' : `${count} violations`;
|
|
11
11
|
lines.push(`[${group.ruleName}] (${label})`);
|
|
12
|
+
const fh = group.fixHint;
|
|
12
13
|
for (const v of group.violations) {
|
|
13
14
|
const editPrefix = formatEditPrefix(v);
|
|
14
15
|
lines.push(` ${editPrefix}L${String(v.line)}: ${v.snippet}`);
|
|
15
|
-
|
|
16
|
+
// Per-occurrence override (dynamic rules), else the rule-level FixHint.violation.
|
|
17
|
+
lines.push(` \u2192 ${v.message ?? fh.violation}`);
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
// mainMessage may be '' (guidance already on the violation line) \u2014 skip when empty.
|
|
20
|
+
if (fh.mainMessage)
|
|
21
|
+
for (const l of fh.mainMessage.split('\n'))
|
|
22
|
+
lines.push(` ${l}`);
|
|
23
|
+
// "Fix Option N:" numbering + "(preferred)" are framework-owned so a multi-line message
|
|
24
|
+
// can never become fake options and authors never hand-write those labels.
|
|
25
|
+
fh.fixOptions.forEach((opt, i) => {
|
|
26
|
+
const optLines = opt.text.split('\n');
|
|
27
|
+
const tag = opt.preferred ? '(preferred) ' : '';
|
|
28
|
+
lines.push(` Fix Option ${String(i + 1)}: ${tag}${optLines[0]}`);
|
|
29
|
+
for (const l of optLines.slice(1))
|
|
30
|
+
lines.push(` ${l}`);
|
|
31
|
+
});
|
|
32
|
+
// Framework-owned disable escape (only the 9 disable-able code-style rules set this).
|
|
33
|
+
if (fh.escape) {
|
|
34
|
+
lines.push(fh.escape.allowed
|
|
35
|
+
? ` Escape (if truly needed): ${fh.escape.comment}`
|
|
36
|
+
: ' \u{1F512} The team disabled escaping via webpieces-disable for this rule (disableAllowed:false) — it must be followed.');
|
|
21
37
|
}
|
|
22
38
|
lines.push('');
|
|
23
39
|
}
|
package/src/core/report.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/report.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/report.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RuleGroup, Violation } from './types';\nimport type { Option } from './fix-hint';\n\nexport function formatReport(relativePath: string, ruleGroups: readonly RuleGroup[]): string {\n const lines: string[] = [];\n lines.push(`\\u274c webpieces ai-hooks blocked this write: ${relativePath}`);\n lines.push('');\n\n for (const group of ruleGroups) {\n const count = group.violations.length;\n const label = count === 1 ? '1 violation' : `${count} violations`;\n lines.push(`[${group.ruleName}] (${label})`);\n const fh = group.fixHint;\n for (const v of group.violations) {\n const editPrefix = formatEditPrefix(v);\n lines.push(` ${editPrefix}L${String(v.line)}: ${v.snippet}`);\n // Per-occurrence override (dynamic rules), else the rule-level FixHint.violation.\n lines.push(` \\u2192 ${v.message ?? fh.violation}`);\n }\n // mainMessage may be '' (guidance already on the violation line) \\u2014 skip when empty.\n if (fh.mainMessage) for (const l of fh.mainMessage.split('\\n')) lines.push(` ${l}`);\n // \"Fix Option N:\" numbering + \"(preferred)\" are framework-owned so a multi-line message\n // can never become fake options and authors never hand-write those labels.\n fh.fixOptions.forEach((opt: Option, i: number) => {\n const optLines = opt.text.split('\\n');\n const tag = opt.preferred ? '(preferred) ' : '';\n lines.push(` Fix Option ${String(i + 1)}: ${tag}${optLines[0]}`);\n for (const l of optLines.slice(1)) lines.push(` ${l}`);\n });\n // Framework-owned disable escape (only the 9 disable-able code-style rules set this).\n if (fh.escape) {\n lines.push(fh.escape.allowed\n ? ` Escape (if truly needed): ${fh.escape.comment}`\n : ' \\u{1F512} The team disabled escaping via webpieces-disable for this rule (disableAllowed:false) — it must be followed.');\n }\n lines.push('');\n }\n\n lines.push('This is a pre-write check. Fix and retry the Write/Edit.');\n lines.push('');\n return lines.join('\\n');\n}\n\nfunction formatEditPrefix(v: Violation): string {\n if (v.editIndex !== undefined && v.editCount !== undefined && v.editCount > 1) {\n return `edit ${String(v.editIndex + 1)}/${String(v.editCount)} `;\n }\n return '';\n}\n"]}
|
package/src/core/rule-base.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AbstractRule, BaseRuleConfig, RuleOptions } from '@webpieces/rules-config';
|
|
2
2
|
import type { EditContext, BashContext, FileContext, Violation, Rule } from './types';
|
|
3
|
+
import type { FixHint } from './fix-hint';
|
|
3
4
|
/**
|
|
4
5
|
* A concrete, instantiable BaseRuleConfig used as a fallback when a built-in rule has no
|
|
5
6
|
* entry in webpieces.config.json yet. It carries no values, so the rule's `description`,
|
|
@@ -17,7 +18,7 @@ export declare abstract class EditRuleBase<C extends BaseRuleConfig> extends Abs
|
|
|
17
18
|
readonly files: readonly string[];
|
|
18
19
|
readonly defaultOptions: RuleOptions;
|
|
19
20
|
abstract readonly description: string;
|
|
20
|
-
abstract readonly fixHint:
|
|
21
|
+
abstract readonly fixHint: FixHint;
|
|
21
22
|
abstract check(ctx: EditContext): readonly Violation[];
|
|
22
23
|
}
|
|
23
24
|
export declare abstract class FileRuleBase<C extends BaseRuleConfig> extends AbstractRule<C> implements Rule {
|
|
@@ -25,7 +26,7 @@ export declare abstract class FileRuleBase<C extends BaseRuleConfig> extends Abs
|
|
|
25
26
|
readonly files: readonly string[];
|
|
26
27
|
readonly defaultOptions: RuleOptions;
|
|
27
28
|
abstract readonly description: string;
|
|
28
|
-
abstract readonly fixHint:
|
|
29
|
+
abstract readonly fixHint: FixHint;
|
|
29
30
|
abstract check(ctx: FileContext): readonly Violation[];
|
|
30
31
|
}
|
|
31
32
|
export declare abstract class BashRuleBase<C extends BaseRuleConfig> extends AbstractRule<C> implements Rule {
|
|
@@ -33,6 +34,6 @@ export declare abstract class BashRuleBase<C extends BaseRuleConfig> extends Abs
|
|
|
33
34
|
readonly files: readonly string[];
|
|
34
35
|
readonly defaultOptions: RuleOptions;
|
|
35
36
|
abstract readonly description: string;
|
|
36
|
-
abstract readonly fixHint:
|
|
37
|
+
abstract readonly fixHint: FixHint;
|
|
37
38
|
abstract check(ctx: BashContext): readonly Violation[];
|
|
38
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-base.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/rule-base.ts"],"names":[],"mappings":";;;AAAA,0DAAoF;
|
|
1
|
+
{"version":3,"file":"rule-base.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/rule-base.ts"],"names":[],"mappings":";;;AAAA,0DAAoF;AAKpF;;;;;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';\nimport type { FixHint } from './fix-hint';\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: FixHint;\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: FixHint;\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: FixHint;\n\n abstract check(ctx: BashContext): readonly Violation[];\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BranchCreationGuardConfig } from '@webpieces/rules-config';
|
|
2
2
|
import type { BashContext, Violation } from '../types';
|
|
3
3
|
import { BashRuleBase } from '../rule-base';
|
|
4
|
+
import { FixHint } from '../fix-hint';
|
|
4
5
|
export declare class BranchCreationGuardRule extends BashRuleBase<BranchCreationGuardConfig> {
|
|
5
6
|
constructor(config: BranchCreationGuardConfig);
|
|
6
7
|
readonly description = "Block new-branch creation when main is stale, or when branching off a non-main branch.";
|
|
@@ -10,6 +11,6 @@ export declare class BranchCreationGuardRule extends BashRuleBase<BranchCreation
|
|
|
10
11
|
};
|
|
11
12
|
private get branchFormat();
|
|
12
13
|
private get subBranchNaming();
|
|
13
|
-
get fixHint():
|
|
14
|
+
get fixHint(): FixHint;
|
|
14
15
|
check(ctx: BashContext): readonly Violation[];
|
|
15
16
|
}
|
|
@@ -4,6 +4,7 @@ exports.BranchCreationGuardRule = void 0;
|
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const types_1 = require("../types");
|
|
6
6
|
const rule_base_1 = require("../rule-base");
|
|
7
|
+
const fix_hint_1 = require("../fix-hint");
|
|
7
8
|
// Defaults used when the rule has no explicit value in webpieces.config.json.
|
|
8
9
|
// branchFormat is a human sentence telling the AI how to name a branch created off main; it is
|
|
9
10
|
// intentionally NOT the sub-branch convention (sub-branches are a separate, human-approved path).
|
|
@@ -62,18 +63,18 @@ class BranchCreationGuardRule extends rule_base_1.BashRuleBase {
|
|
|
62
63
|
// convention. The sub-branch affordance only appears under mode 'ON'; 'ON_NO_SUBBRANCHES'
|
|
63
64
|
// hard-blocks it and points instead at the ignoreModifiedUntilEpoch escape hatch.
|
|
64
65
|
get fixHint() {
|
|
65
|
-
const
|
|
66
|
-
"Run 'git checkout main && git pull origin main', then create your branch FROM main",
|
|
67
|
-
`Name a branch off main per branch-creation-guard.branchFormat: ${this.branchFormat}
|
|
66
|
+
const options = [
|
|
67
|
+
new fix_hint_1.Option("Run 'git checkout main && git pull origin main', then create your branch FROM main", true),
|
|
68
|
+
new fix_hint_1.Option(`Name a branch off main per branch-creation-guard.branchFormat: ${this.branchFormat}`),
|
|
68
69
|
];
|
|
69
70
|
if (this.config.mode === 'ON_NO_SUBBRANCHES') {
|
|
70
|
-
|
|
71
|
-
"branch-creation-guard.ignoreModifiedUntilEpoch to a future epoch in webpieces.config.json");
|
|
71
|
+
options.push(new fix_hint_1.Option('Sub-branches (branching off another feature branch) are disabled. To temporarily allow one, set ' +
|
|
72
|
+
"branch-creation-guard.ignoreModifiedUntilEpoch to a future epoch in webpieces.config.json"));
|
|
72
73
|
}
|
|
73
74
|
else {
|
|
74
|
-
|
|
75
|
+
options.push(new fix_hint_1.Option(`If you truly need a stacked sub-branch (requires human approval), name it per branch-creation-guard.subBranchNaming: ${this.subBranchNaming}`));
|
|
75
76
|
}
|
|
76
|
-
return
|
|
77
|
+
return new fix_hint_1.FixHint('Cannot create this branch (main is stale, or branching off a non-main branch).', 'Create your branch from an up-to-date main. Pick one:', options);
|
|
77
78
|
}
|
|
78
79
|
check(ctx) {
|
|
79
80
|
const requestedName = extractBranchName(ctx.command);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/branch-creation-guard.ts"],"names":[],"mappings":";;;AAAA,iDAAyC;AAKzC,oCAA0C;AAC1C,4CAA4C;
|
|
1
|
+
{"version":3,"file":"branch-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/branch-creation-guard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\n\nimport { BranchCreationGuardConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\nimport { FixHint, Option } from '../fix-hint';\n\n// Defaults used when the rule has no explicit value in webpieces.config.json.\n// branchFormat is a human sentence telling the AI how to name a branch created off main; it is\n// intentionally NOT the sub-branch convention (sub-branches are a separate, human-approved path).\nconst DEFAULT_BRANCH_FORMAT =\n 'Name it {whoami}/<short-feature-description> — lowercase, no version numbers, no sub/ prefix (e.g. dean/upgrade-webpieces)';\nconst DEFAULT_SUB_BRANCH_NAMING = 'feature/<ticket>/<short-description>';\n\nconst BRANCH_PATTERNS: RegExp[] = [\n /git\\s+checkout\\s+-[bB]\\s+([^\\s]+)/,\n /git\\s+switch\\s+-[cC]\\s+([^\\s]+)/,\n /git\\s+branch\\s+(?!-[dDmMrRla])([^\\s-][^\\s]*)/,\n];\n\n// The squash-merge tooling reserves a trailing `wp<number>` as its generation marker\n// (base → basewp2 → basewp3). A human branch ending that way would collide with it, so\n// block it at creation time and steer the name back to the plain feature form.\nconst RESERVED_GENERATION_SUFFIX = /wp\\d+$/;\n\nfunction extractBranchName(command: string): string | null {\n for (const pattern of BRANCH_PATTERNS) {\n const m = pattern.exec(command);\n if (m) return m[1];\n }\n return null;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nfunction checkMainIsUpToDate(ctx: BashContext, requestedName: string): readonly Violation[] {\n execSync('git fetch origin main --quiet', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n });\n const countStr = execSync('git rev-list HEAD..origin/main --count', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n const count = parseInt(countStr, 10);\n if (count > 0) {\n return [new V(\n 1,\n truncate(ctx.command),\n `Local main is ${count} commit(s) behind origin/main. Run 'git pull origin main' first, then retry creating branch '${requestedName}'.`,\n )];\n }\n return [];\n}\n\nexport class BranchCreationGuardRule extends BashRuleBase<BranchCreationGuardConfig> {\n constructor(config: BranchCreationGuardConfig) { super(config, 'branch-creation-guard'); }\n\n readonly description = 'Block new-branch creation when main is stale, or when branching off a non-main branch.';\n override readonly defaultOptions = {\n subBranchNaming: DEFAULT_SUB_BRANCH_NAMING,\n branchFormat: DEFAULT_BRANCH_FORMAT,\n };\n\n private get branchFormat(): string {\n return this.config.branchFormat ?? DEFAULT_BRANCH_FORMAT;\n }\n\n private get subBranchNaming(): string {\n return this.config.subBranchNaming ?? DEFAULT_SUB_BRANCH_NAMING;\n }\n\n // Mode-aware fix hints. Branches off main follow branchFormat — never the sub-branch\n // convention. The sub-branch affordance only appears under mode 'ON'; 'ON_NO_SUBBRANCHES'\n // hard-blocks it and points instead at the ignoreModifiedUntilEpoch escape hatch.\n get fixHint(): FixHint {\n const options = [\n new Option(\"Run 'git checkout main && git pull origin main', then create your branch FROM main\", true),\n new Option(`Name a branch off main per branch-creation-guard.branchFormat: ${this.branchFormat}`),\n ];\n if (this.config.mode === 'ON_NO_SUBBRANCHES') {\n options.push(new Option(\n 'Sub-branches (branching off another feature branch) are disabled. To temporarily allow one, set ' +\n \"branch-creation-guard.ignoreModifiedUntilEpoch to a future epoch in webpieces.config.json\",\n ));\n } else {\n options.push(new Option(\n `If you truly need a stacked sub-branch (requires human approval), name it per branch-creation-guard.subBranchNaming: ${this.subBranchNaming}`,\n ));\n }\n return new FixHint(\n 'Cannot create this branch (main is stale, or branching off a non-main branch).',\n 'Create your branch from an up-to-date main. Pick one:',\n options,\n );\n }\n\n check(ctx: BashContext): readonly Violation[] {\n const requestedName = extractBranchName(ctx.command);\n if (!requestedName) return [];\n\n if (RESERVED_GENERATION_SUFFIX.test(requestedName)) {\n return [new V(\n 1,\n truncate(ctx.command),\n `Branch name '${requestedName}' ends in 'wp<number>', which is reserved for the ` +\n `squash-merge tool's generation marker (base → basewp2 → basewp3). ` +\n `Rename it to a plain feature branch. ${this.branchFormat}.`,\n )];\n }\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n if (currentBranch === 'main') {\n return checkMainIsUpToDate(ctx, requestedName);\n }\n\n // Not on main: creating this branch would stack it on a feature branch (a sub-branch).\n if (this.config.mode === 'ON_NO_SUBBRANCHES') {\n return [new V(\n 1,\n truncate(ctx.command),\n `You are on '${currentBranch}', not main. You need to run ` +\n `git checkout main && git pull && git checkout -b ${requestedName} ` +\n `instead of branching from this branch!!! ${this.branchFormat}. ` +\n `You can temporarily turn this off if you truly need a sub-branch by setting ` +\n `branch-creation-guard.ignoreModifiedUntilEpoch (a future epoch) in webpieces.config.json.`,\n )];\n }\n\n return [new V(\n 1,\n truncate(ctx.command),\n `You are on '${currentBranch}', not main. Branches must be created from main: ` +\n `git checkout main && git pull && git checkout -b ${requestedName}. ${this.branchFormat}. ` +\n `If you truly need a stacked sub-branch (requires human approval), name it per ` +\n `branch-creation-guard.subBranchNaming ('${this.subBranchNaming}').`,\n )];\n }\n}\n"]}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { CatchErrorPatternConfig } from '@webpieces/rules-config';
|
|
2
2
|
import type { EditContext, Violation } from '../types';
|
|
3
3
|
import { EditRuleBase } from '../rule-base';
|
|
4
|
+
import { FixHint } from '../fix-hint';
|
|
4
5
|
export declare class CatchErrorPatternRule extends EditRuleBase<CatchErrorPatternConfig> {
|
|
5
6
|
constructor(config: CatchErrorPatternConfig);
|
|
6
7
|
readonly description = "Catch blocks must use: catch (err: unknown) { const error = toError(err); }";
|
|
7
8
|
readonly files: string[];
|
|
8
|
-
|
|
9
|
+
get fixHint(): FixHint;
|
|
9
10
|
check(ctx: EditContext): readonly Violation[];
|
|
10
11
|
}
|
|
@@ -4,6 +4,7 @@ exports.CatchErrorPatternRule = void 0;
|
|
|
4
4
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
5
5
|
const types_1 = require("../types");
|
|
6
6
|
const rule_base_1 = require("../rule-base");
|
|
7
|
+
const fix_hint_1 = require("../fix-hint");
|
|
7
8
|
const instruct_ai_writer_1 = require("../instruct-ai-writer");
|
|
8
9
|
/**
|
|
9
10
|
* Matches a catch clause opening: } catch (paramName: typeAnnotation) {
|
|
@@ -34,13 +35,14 @@ class CatchErrorPatternRule extends rule_base_1.EditRuleBase {
|
|
|
34
35
|
constructor(config) { super(config, 'catch-error-pattern'); }
|
|
35
36
|
description = 'Catch blocks must use: catch (err: unknown) { const error = toError(err); }'; // webpieces-disable catch-error-pattern -- example text in a description string
|
|
36
37
|
files = ['**/*.ts', '**/*.tsx'];
|
|
37
|
-
fixHint
|
|
38
|
-
'
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
get fixHint() {
|
|
39
|
+
return new fix_hint_1.FixHint('Catch block does not follow the toError(err) pattern.', 'Name the catch parameter err (err2/err3 when nested), then pick one:', [
|
|
40
|
+
new fix_hint_1.Option('Add as the first statement in the catch block: const error = toError(err);', true),
|
|
41
|
+
new fix_hint_1.Option('To explicitly ignore the error: //const error = toError(err);'),
|
|
42
|
+
], new fix_hint_1.DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable catch-error-pattern -- <reason>'));
|
|
43
|
+
}
|
|
43
44
|
check(ctx) {
|
|
45
|
+
const disableAllowed = this.config.disableAllowed ?? true;
|
|
44
46
|
const violations = [];
|
|
45
47
|
const lines = ctx.strippedLines;
|
|
46
48
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -49,7 +51,7 @@ class CatchErrorPatternRule extends rule_base_1.EditRuleBase {
|
|
|
49
51
|
if (!catchMatch)
|
|
50
52
|
continue;
|
|
51
53
|
const lineNum = i + 1;
|
|
52
|
-
if (ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.CATCH_ERROR_PATTERN))
|
|
54
|
+
if (disableAllowed && ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.CATCH_ERROR_PATTERN))
|
|
53
55
|
continue;
|
|
54
56
|
const actualParam = catchMatch[1];
|
|
55
57
|
const typeAnnotation = catchMatch[2];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catch-error-pattern.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/catch-error-pattern.ts"],"names":[],"mappings":";;;AAAA,0DAA8E;AAG9E,oCAA0C;AAC1C,4CAA4C;AAC5C,8DAA+D;AAE/D;;;GAGG;AACH,MAAM,aAAa,GAAG,4CAA4C,CAAC;AAEnE;;;GAGG;AACH,MAAM,gBAAgB,GAAG,qEAAqE,CAAC;AAQ/F,SAAS,oBAAoB,CAAC,KAAwB,EAAE,UAAkB;IACtE,KAAK,IAAI,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG;YAAE,SAAS;QAE1C,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,KAAK,EAAE,CAAC;YACR,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;QACpE,CAAC;QACD,6CAA6C;QAC7C,OAAO,WAAW,CAAC;IACvB,CAAC;IACD,+DAA+D;IAC/D,OAAO,gBAAgB,CAAC;AAC5B,CAAC;AAED,MAAa,qBAAsB,SAAQ,wBAAqC;IAC5E,YAAY,MAA+B,IAAI,KAAK,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAE7E,WAAW,GAAG,6EAA6E,CAAC,CAAC,gFAAgF;IACpK,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"catch-error-pattern.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/catch-error-pattern.ts"],"names":[],"mappings":";;;AAAA,0DAA8E;AAG9E,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAA6D;AAC7D,8DAA+D;AAE/D;;;GAGG;AACH,MAAM,aAAa,GAAG,4CAA4C,CAAC;AAEnE;;;GAGG;AACH,MAAM,gBAAgB,GAAG,qEAAqE,CAAC;AAQ/F,SAAS,oBAAoB,CAAC,KAAwB,EAAE,UAAkB;IACtE,KAAK,IAAI,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,GAAG;YAAE,SAAS;QAE1C,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,KAAK,EAAE,CAAC;YACR,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;QACpE,CAAC;QACD,6CAA6C;QAC7C,OAAO,WAAW,CAAC;IACvB,CAAC;IACD,+DAA+D;IAC/D,OAAO,gBAAgB,CAAC;AAC5B,CAAC;AAED,MAAa,qBAAsB,SAAQ,wBAAqC;IAC5E,YAAY,MAA+B,IAAI,KAAK,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAE7E,WAAW,GAAG,6EAA6E,CAAC,CAAC,gFAAgF;IACpK,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAClD,IAAI,OAAO;QACP,OAAO,IAAI,kBAAO,CACd,uDAAuD,EACvD,sEAAsE,EACtE;YACI,IAAI,iBAAM,CAAC,4EAA4E,EAAE,IAAI,CAAC;YAC9F,IAAI,iBAAM,CAAC,+DAA+D,CAAC;SAC9E,EACD,IAAI,wBAAa,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,EAAE,sDAAsD,CAAC,CAChH,CAAC;IACN,CAAC;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,CAAC;QAC1D,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,GAAG,CAAC,aAAa,CAAC;QAEhC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAC1B,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChD,IAAI,CAAC,UAAU;gBAAE,SAAS;YAE1B,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,cAAc,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAU,CAAC,mBAAmB,CAAC;gBAAE,SAAS;YAE5F,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAClC,MAAM,cAAc,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAErC,gFAAgF;YAChF,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,CAAC;YACrC,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;YAErC,uBAAuB;YACvB,IAAI,WAAW,KAAK,aAAa,EAAE,CAAC;gBAChC,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,kCAAkC,aAAa,kDAAkD,WAAW,GAAG,CAClH,CAAC,CAAC;YACP,CAAC;YAED,mCAAmC;YACnC,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,GAAG,GAAG,cAAc;oBACtB,CAAC,CAAC,sDAAsD,aAAa,oBAAoB,cAAc,GAAG;oBAC1G,CAAC,CAAC,sDAAsD,aAAa,YAAY,CAAC;gBACtF,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;YAC9D,CAAC;YAED,wEAAwE;YACxE,MAAM,aAAa,GAAG,oBAAoB,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACzD,IAAI,aAAa,KAAK,WAAW,EAAE,CAAC;gBAChC,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,iCAAiC,WAAW,+BAA+B,WAAW,cAAc,WAAW,iBAAiB,WAAW,cAAc,WAAW,IAAI,CAC3K,CAAC,CAAC;YACP,CAAC;iBAAM,IAAI,aAAa,KAAK,gBAAgB,EAAE,CAAC;gBAC5C,yCAAyC;gBACzC,IAAI,aAAa,CAAC,OAAO,KAAK,WAAW,EAAE,CAAC;oBACxC,MAAM,cAAc,GAAG,aAAa,CAAC,SAAS,GAAG,CAAC,CAAC;oBACnD,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,cAAc,EACd,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EACzC,iCAAiC,WAAW,WAAW,aAAa,CAAC,OAAO,GAAG,CAClF,CAAC,CAAC;gBACP,CAAC;gBACD,IAAI,aAAa,CAAC,SAAS,KAAK,WAAW,EAAE,CAAC;oBAC1C,MAAM,cAAc,GAAG,aAAa,CAAC,SAAS,GAAG,CAAC,CAAC;oBACnD,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,cAAc,EACd,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EACzC,kCAAkC,WAAW,WAAW,aAAa,CAAC,SAAS,GAAG,CACrF,CAAC,CAAC;gBACP,CAAC;YACL,CAAC;QACL,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;AAvFD,sDAuFC","sourcesContent":["import { CatchErrorPatternConfig, 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 { FixHint, Option, DisableEscape } from '../fix-hint';\nimport { writeTemplateIfMissing } from '../instruct-ai-writer';\n\n/**\n * Matches a catch clause opening: } catch (paramName: typeAnnotation) {\n * Captures: group 1 = param name, group 2 = type annotation (if present)\n */\nconst CATCH_PATTERN = /\\bcatch\\s*\\(\\s*(\\w+)(?:\\s*:\\s*(\\w+))?\\s*\\)/;\n\n/**\n * Matches the required toError first statement (with or without comment-out).\n * Group 1 = variable name, group 2 = param passed to toError\n */\nconst TO_ERROR_PATTERN = /^\\s*(?:\\/\\/\\s*)?const\\s+(\\w+)\\s*=\\s*toError\\(\\s*(\\w+)\\s*\\)\\s*;?\\s*$/;\n\ninterface ToErrorMatch {\n varName: string;\n paramName: string;\n lineIndex: number;\n}\n\nfunction findToErrorStatement(lines: readonly string[], startIndex: number): ToErrorMatch | 'not-found' | 'end-of-content' {\n for (let j = startIndex; j < lines.length; j += 1) {\n const line = lines[j].trim();\n if (line === '' || line === '{') continue;\n\n const match = TO_ERROR_PATTERN.exec(line);\n if (match) {\n return { varName: match[1], paramName: match[2], lineIndex: j };\n }\n // First non-blank line is not a toError call\n return 'not-found';\n }\n // Ran off the end of the edit content — can't validate further\n return 'end-of-content';\n}\n\nexport class CatchErrorPatternRule extends EditRuleBase<CatchErrorPatternConfig> {\n constructor(config: CatchErrorPatternConfig) { super(config, 'catch-error-pattern'); }\n\n readonly description = 'Catch blocks must use: catch (err: unknown) { const error = toError(err); }'; // webpieces-disable catch-error-pattern -- example text in a description string\n override readonly files = ['**/*.ts', '**/*.tsx'];\n get fixHint(): FixHint {\n return new FixHint(\n 'Catch block does not follow the toError(err) pattern.',\n 'Name the catch parameter err (err2/err3 when nested), then pick one:',\n [\n new Option('Add as the first statement in the catch block: const error = toError(err);', true),\n new Option('To explicitly ignore the error: //const error = toError(err);'),\n ],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable catch-error-pattern -- <reason>'), // webpieces-disable catch-error-pattern -- example text in a hint string\n );\n }\n\n check(ctx: EditContext): readonly Violation[] {\n const disableAllowed = this.config.disableAllowed ?? true;\n const violations: V[] = [];\n const lines = ctx.strippedLines;\n\n for (let i = 0; i < lines.length; i += 1) {\n const stripped = lines[i];\n const catchMatch = CATCH_PATTERN.exec(stripped);\n if (!catchMatch) continue;\n\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, RULE_NAMES.CATCH_ERROR_PATTERN)) continue;\n\n const actualParam = catchMatch[1];\n const typeAnnotation = catchMatch[2];\n\n // Determine expected names from suffix on the actual param (err, err2, err3...)\n const suffixMatch = actualParam.match(/^err(\\d*)$/);\n const suffix = suffixMatch ? suffixMatch[1] : '';\n const expectedParam = 'err' + suffix;\n const expectedVar = 'error' + suffix;\n\n // Check parameter name\n if (actualParam !== expectedParam) {\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Catch parameter must be named \"${expectedParam}\" (or \"err2\", \"err3\" for nested catches), got \"${actualParam}\"`,\n ));\n }\n\n // Check type annotation is unknown\n if (typeAnnotation !== 'unknown') {\n const msg = typeAnnotation\n ? `Catch parameter must be typed as \"unknown\": catch (${expectedParam}: unknown), got \"${typeAnnotation}\"`\n : `Catch parameter must be typed as \"unknown\": catch (${expectedParam}: unknown)`;\n violations.push(new V(lineNum, ctx.lines[i].trim(), msg));\n }\n\n // Find next non-blank line after the catch opening to check for toError\n const toErrorResult = findToErrorStatement(lines, i + 1);\n if (toErrorResult === 'not-found') {\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n `Catch block must call toError(${actualParam}) as first statement: const ${expectedVar} = toError(${actualParam}); or //const ${expectedVar} = toError(${actualParam});`,\n ));\n } else if (toErrorResult !== 'end-of-content') {\n // Validate variable name and param match\n if (toErrorResult.varName !== expectedVar) {\n const toErrorLineNum = toErrorResult.lineIndex + 1;\n violations.push(new V(\n toErrorLineNum,\n ctx.lines[toErrorResult.lineIndex].trim(),\n `Error variable must be named \"${expectedVar}\", got \"${toErrorResult.varName}\"`,\n ));\n }\n if (toErrorResult.paramName !== actualParam) {\n const toErrorLineNum = toErrorResult.lineIndex + 1;\n violations.push(new V(\n toErrorLineNum,\n ctx.lines[toErrorResult.lineIndex].trim(),\n `toError() must be called with \"${actualParam}\", got \"${toErrorResult.paramName}\"`,\n ));\n }\n }\n }\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n }\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FeatureBranchGuardConfig } from '@webpieces/rules-config';
|
|
2
2
|
import type { FileContext, Violation } from '../types';
|
|
3
3
|
import { FileRuleBase } from '../rule-base';
|
|
4
|
+
import { FixHint } from '../fix-hint';
|
|
4
5
|
/**
|
|
5
6
|
* Comprehensive "are you on a proper feature branch?" guard — the single rule that blocks edits when
|
|
6
7
|
* the branch isn't a healthy place to work. Four states, in priority order:
|
|
@@ -22,7 +23,7 @@ export declare class FeatureBranchGuardRule extends FileRuleBase<FeatureBranchGu
|
|
|
22
23
|
branchNamingConvention: string;
|
|
23
24
|
hangTimeoutMinutes: number;
|
|
24
25
|
};
|
|
25
|
-
readonly fixHint:
|
|
26
|
+
readonly fixHint: FixHint;
|
|
26
27
|
check(ctx: FileContext): readonly Violation[];
|
|
27
28
|
private cacheSummary;
|
|
28
29
|
private allow;
|
|
@@ -5,6 +5,7 @@ const child_process_1 = require("child_process");
|
|
|
5
5
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
6
6
|
const types_1 = require("../types");
|
|
7
7
|
const rule_base_1 = require("../rule-base");
|
|
8
|
+
const fix_hint_1 = require("../fix-hint");
|
|
8
9
|
const to_error_1 = require("../to-error");
|
|
9
10
|
const main_sync_refresh_1 = require("../main-sync-refresh");
|
|
10
11
|
const decision_log_1 = require("../decision-log");
|
|
@@ -29,12 +30,11 @@ class FeatureBranchGuardRule extends rule_base_1.FileRuleBase {
|
|
|
29
30
|
branchNamingConvention: '{whoami}/{featurename}',
|
|
30
31
|
hangTimeoutMinutes: rules_config_1.DEFAULT_HANG_TIMEOUT_MINUTES,
|
|
31
32
|
};
|
|
32
|
-
fixHint = [
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
|
|
37
|
-
];
|
|
33
|
+
fixHint = new fix_hint_1.FixHint('You are not on a clean, up-to-date feature branch.', 'You must be on a clean, up-to-date feature branch to edit code. Pick one:', [
|
|
34
|
+
new fix_hint_1.Option('On main → create a feature branch. Already merged → branch off fresh main.', true),
|
|
35
|
+
new fix_hint_1.Option('main moved/conflicts → `pnpm wp-start-upsert-pr` (merge), `/wp-merge` (resolve), `pnpm wp-finish-upsert-pr`.'),
|
|
36
|
+
new fix_hint_1.Option('Disable in webpieces.config.json under feature-branch-guard (mode OFF) if intentional.'),
|
|
37
|
+
]);
|
|
38
38
|
check(ctx) {
|
|
39
39
|
// Only files inside the workspace root — guard has no jurisdiction, nothing worth logging.
|
|
40
40
|
if (ctx.relativePath.startsWith('..'))
|