@webpieces/ai-hook-rules 0.3.227 → 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.
Files changed (69) hide show
  1. package/package.json +2 -2
  2. package/src/core/custom-rule-adapter.d.ts +2 -1
  3. package/src/core/custom-rule-adapter.js.map +1 -1
  4. package/src/core/fix-hint.d.ts +42 -0
  5. package/src/core/fix-hint.js +60 -0
  6. package/src/core/fix-hint.js.map +1 -0
  7. package/src/core/report.js +21 -5
  8. package/src/core/report.js.map +1 -1
  9. package/src/core/rule-base.d.ts +4 -3
  10. package/src/core/rule-base.js.map +1 -1
  11. package/src/core/rules/branch-creation-guard.d.ts +2 -1
  12. package/src/core/rules/branch-creation-guard.js +8 -7
  13. package/src/core/rules/branch-creation-guard.js.map +1 -1
  14. package/src/core/rules/catch-error-pattern.d.ts +2 -1
  15. package/src/core/rules/catch-error-pattern.js +9 -7
  16. package/src/core/rules/catch-error-pattern.js.map +1 -1
  17. package/src/core/rules/feature-branch-guard.d.ts +2 -1
  18. package/src/core/rules/feature-branch-guard.js +6 -6
  19. package/src/core/rules/feature-branch-guard.js.map +1 -1
  20. package/src/core/rules/max-file-lines.d.ts +2 -1
  21. package/src/core/rules/max-file-lines.js +4 -4
  22. package/src/core/rules/max-file-lines.js.map +1 -1
  23. package/src/core/rules/merge-in-progress-guard.d.ts +2 -1
  24. package/src/core/rules/merge-in-progress-guard.js +8 -12
  25. package/src/core/rules/merge-in-progress-guard.js.map +1 -1
  26. package/src/core/rules/no-any-unknown.d.ts +2 -1
  27. package/src/core/rules/no-any-unknown.js +7 -6
  28. package/src/core/rules/no-any-unknown.js.map +1 -1
  29. package/src/core/rules/no-destructure.d.ts +2 -1
  30. package/src/core/rules/no-destructure.js +7 -6
  31. package/src/core/rules/no-destructure.js.map +1 -1
  32. package/src/core/rules/no-implicit-any.d.ts +2 -1
  33. package/src/core/rules/no-implicit-any.js +6 -5
  34. package/src/core/rules/no-implicit-any.js.map +1 -1
  35. package/src/core/rules/no-js-files.d.ts +2 -1
  36. package/src/core/rules/no-js-files.js +6 -5
  37. package/src/core/rules/no-js-files.js.map +1 -1
  38. package/src/core/rules/no-symbol-di-tokens.d.ts +2 -1
  39. package/src/core/rules/no-symbol-di-tokens.js +11 -8
  40. package/src/core/rules/no-symbol-di-tokens.js.map +1 -1
  41. package/src/core/rules/no-unmanaged-exceptions.d.ts +2 -1
  42. package/src/core/rules/no-unmanaged-exceptions.js +15 -13
  43. package/src/core/rules/no-unmanaged-exceptions.js.map +1 -1
  44. package/src/core/rules/pr-creation-guard.d.ts +2 -1
  45. package/src/core/rules/pr-creation-guard.js +8 -13
  46. package/src/core/rules/pr-creation-guard.js.map +1 -1
  47. package/src/core/rules/pr-merge-cleanup.d.ts +3 -1
  48. package/src/core/rules/pr-merge-cleanup.js +11 -11
  49. package/src/core/rules/pr-merge-cleanup.js.map +1 -1
  50. package/src/core/rules/redirect-how-to-merge-main.d.ts +2 -1
  51. package/src/core/rules/redirect-how-to-merge-main.js +3 -10
  52. package/src/core/rules/redirect-how-to-merge-main.js.map +1 -1
  53. package/src/core/rules/require-return-type.d.ts +2 -1
  54. package/src/core/rules/require-return-type.js +7 -6
  55. package/src/core/rules/require-return-type.js.map +1 -1
  56. package/src/core/rules/throw-cause-required.d.ts +2 -1
  57. package/src/core/rules/throw-cause-required.js +11 -8
  58. package/src/core/rules/throw-cause-required.js.map +1 -1
  59. package/src/core/rules/validate-ts-in-src.d.ts +2 -1
  60. package/src/core/rules/validate-ts-in-src.js +5 -4
  61. package/src/core/rules/validate-ts-in-src.js.map +1 -1
  62. package/src/core/runner.js +3 -3
  63. package/src/core/runner.js.map +1 -1
  64. package/src/core/types.d.ts +6 -5
  65. package/src/core/types.js +3 -0
  66. package/src/core/types.js.map +1 -1
  67. package/src/index.d.ts +1 -0
  68. package/src/index.js +6 -1
  69. package/src/index.js.map +1 -1
@@ -1,10 +1,11 @@
1
1
  import { NoUnmanagedExceptionsConfig } 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 NoUnmanagedExceptionsRule extends EditRuleBase<NoUnmanagedExceptionsConfig> {
5
6
  constructor(config: NoUnmanagedExceptionsConfig);
6
7
  readonly description = "try/catch is generally not allowed. Only allowed in chokepoints (filter, globalErrorHandler) or other rare locations.";
7
8
  readonly files: string[];
8
- readonly fixHint: string[];
9
+ get fixHint(): FixHint;
9
10
  check(ctx: EditContext): readonly Violation[];
10
11
  }
@@ -4,6 +4,7 @@ exports.NoUnmanagedExceptionsRule = 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
  const TRY_PATTERN = /\btry\s*\{/;
9
10
  // Both webpieces-disable and the existing ESLint directive suppress this rule
@@ -18,28 +19,29 @@ class NoUnmanagedExceptionsRule extends rule_base_1.EditRuleBase {
18
19
  constructor(config) { super(config, 'no-unmanaged-exceptions'); }
19
20
  description = 'try/catch is generally not allowed. Only allowed in chokepoints (filter, globalErrorHandler) or other rare locations.';
20
21
  files = ['**/*.ts', '**/*.tsx'];
21
- fixHint = [
22
- 'Fix Option 1 (preferred): Remove the try/catchlet the exception bubble to the top-level chokepoint (filter, globalErrorHandler) where it is already logged and handled.',
23
- 'Fix Option 2 (ask the human first): If you genuinely believe this IS a chokepoint, STOP and tell the human:',
24
- ' - What exception could be thrown here',
25
- ' - What the current top-level chokepoint is (the try/catch at the top of the call stack)',
26
- ' - Why throwing to that chokepoint would be wrong in this specific case',
27
- ' Then ask: "Should I add a disable comment or remove the try/catch?"',
28
- ' Only add // webpieces-disable no-unmanaged-exceptions -- <reason> if the human says yes.',
29
- 'NOTE: If the code is calling an external process (execSync, fs, network), the correct answer is almost always Option 1 — let it throw. Hooks have a top-level runner that reports errors properly.',
30
- ];
22
+ get fixHint() {
23
+ return new fix_hint_1.FixHint('try/catch is generally not allowed. READ .webpieces/instruct-ai/webpieces.exceptions.md. Only chokepoints (filter, globalErrorHandler) may catch exceptions.', 'Pick one (NOTE: for external callsexecSync, fs, network the preferred option is almost always right; let it throw, the top-level runner reports it):', [
24
+ new fix_hint_1.Option('Remove the try/catch let the exception bubble to the top-level chokepoint (filter, globalErrorHandler) where it is already logged and handled.', true),
25
+ new fix_hint_1.Option('If you genuinely believe this IS a chokepoint, STOP and ask the human first:\n'
26
+ + ' - What exception could be thrown here\n'
27
+ + ' - What the current top-level chokepoint is (the try/catch at the top of the call stack)\n'
28
+ + ' - Why throwing to that chokepoint would be wrong in this specific case\n'
29
+ + ' Then ask whether to remove the try/catch or add a disable comment.'),
30
+ ], new fix_hint_1.DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-unmanaged-exceptions -- <reason>'));
31
+ }
31
32
  check(ctx) {
33
+ const disableAllowed = this.config.disableAllowed ?? true;
32
34
  const violations = [];
33
35
  for (let i = 0; i < ctx.strippedLines.length; i += 1) {
34
36
  const stripped = ctx.strippedLines[i];
35
37
  if (!TRY_PATTERN.test(stripped))
36
38
  continue;
37
39
  const lineNum = i + 1;
38
- if (ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.NO_UNMANAGED_EXCEPTIONS))
40
+ if (disableAllowed && ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.NO_UNMANAGED_EXCEPTIONS))
39
41
  continue;
40
- if (hasPrecedingDisable(ctx.lines, i))
42
+ if (disableAllowed && hasPrecedingDisable(ctx.lines, i))
41
43
  continue;
42
- violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim(), 'try/catch is generally not allowed. READ .webpieces/instruct-ai/webpieces.exceptions.md to understand why. Only chokepoints (filter, globalErrorHandler) may catch exceptions.'));
44
+ violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim()));
43
45
  }
44
46
  if (violations.length > 0)
45
47
  (0, instruct_ai_writer_1.writeTemplateIfMissing)(ctx.workspaceRoot, 'webpieces.exceptions.md');
@@ -1 +1 @@
1
- {"version":3,"file":"no-unmanaged-exceptions.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-unmanaged-exceptions.ts"],"names":[],"mappings":";;;AAAA,0DAAkF;AAGlF,oCAA0C;AAC1C,4CAA4C;AAC5C,8DAA+D;AAE/D,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,8EAA8E;AAC9E,MAAM,eAAe,GAAG,gGAAgG,CAAC;AAEzH,SAAS,mBAAmB,CAAC,KAAwB,EAAE,GAAW;IAC9D,IAAI,GAAG,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5B,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAChC,OAAO,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,MAAa,yBAA0B,SAAQ,wBAAyC;IACpF,YAAY,MAAmC,IAAI,KAAK,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC;IAErF,WAAW,GAAG,uHAAuH,CAAC;IAC7H,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACzC,OAAO,GAAG;QACf,4KAA4K;QAC5K,6GAA6G;QAC7G,yCAAyC;QACzC,2FAA2F;QAC3F,0EAA0E;QAC1E,uEAAuE;QACvE,4FAA4F;QAC5F,oMAAoM;KACvM,CAAC;IAEF,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAU,CAAC,uBAAuB,CAAC;gBAAE,SAAS;YAC9E,IAAI,mBAAmB,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;gBAAE,SAAS;YAChD,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,gLAAgL,CACnL,CAAC,CAAC;QACP,CAAC;QACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,IAAA,2CAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;QAChG,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AAjCD,8DAiCC","sourcesContent":["import { NoUnmanagedExceptionsConfig, RULE_NAMES } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { EditRuleBase } from '../rule-base';\nimport { writeTemplateIfMissing } from '../instruct-ai-writer';\n\nconst TRY_PATTERN = /\\btry\\s*\\{/;\n\n// Both webpieces-disable and the existing ESLint directive suppress this rule\nconst DISABLE_PATTERN = /@webpieces\\/no-unmanaged-exceptions|webpieces-disable\\s+(?:[\\w-]+,\\s*)*no-unmanaged-exceptions/;\n\nfunction hasPrecedingDisable(lines: readonly string[], idx: number): boolean {\n if (idx === 0) return false;\n const prevLine = lines[idx - 1];\n return DISABLE_PATTERN.test(prevLine);\n}\n\nexport class NoUnmanagedExceptionsRule extends EditRuleBase<NoUnmanagedExceptionsConfig> {\n constructor(config: NoUnmanagedExceptionsConfig) { super(config, 'no-unmanaged-exceptions'); }\n\n readonly description = 'try/catch is generally not allowed. Only allowed in chokepoints (filter, globalErrorHandler) or other rare locations.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n readonly fixHint = [\n 'Fix Option 1 (preferred): Remove the try/catch — let the exception bubble to the top-level chokepoint (filter, globalErrorHandler) where it is already logged and handled.',\n 'Fix Option 2 (ask the human first): If you genuinely believe this IS a chokepoint, STOP and tell the human:',\n ' - What exception could be thrown here',\n ' - What the current top-level chokepoint is (the try/catch at the top of the call stack)',\n ' - Why throwing to that chokepoint would be wrong in this specific case',\n ' Then ask: \"Should I add a disable comment or remove the try/catch?\"',\n ' Only add // webpieces-disable no-unmanaged-exceptions -- <reason> if the human says yes.',\n 'NOTE: If the code is calling an external process (execSync, fs, network), the correct answer is almost always Option 1 — let it throw. Hooks have a top-level runner that reports errors properly.',\n ];\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!TRY_PATTERN.test(stripped)) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, RULE_NAMES.NO_UNMANAGED_EXCEPTIONS)) continue;\n if (hasPrecedingDisable(ctx.lines, i)) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'try/catch is generally not allowed. READ .webpieces/instruct-ai/webpieces.exceptions.md to understand why. Only chokepoints (filter, globalErrorHandler) may catch exceptions.',\n ));\n }\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n }\n}\n"]}
1
+ {"version":3,"file":"no-unmanaged-exceptions.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-unmanaged-exceptions.ts"],"names":[],"mappings":";;;AAAA,0DAAkF;AAGlF,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAA6D;AAC7D,8DAA+D;AAE/D,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,8EAA8E;AAC9E,MAAM,eAAe,GAAG,gGAAgG,CAAC;AAEzH,SAAS,mBAAmB,CAAC,KAAwB,EAAE,GAAW;IAC9D,IAAI,GAAG,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5B,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAChC,OAAO,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,MAAa,yBAA0B,SAAQ,wBAAyC;IACpF,YAAY,MAAmC,IAAI,KAAK,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC;IAErF,WAAW,GAAG,uHAAuH,CAAC;IAC7H,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAClD,IAAI,OAAO;QACP,OAAO,IAAI,kBAAO,CACd,8JAA8J,EAC9J,2JAA2J,EAC3J;YACI,IAAI,iBAAM,CAAC,kJAAkJ,EAAE,IAAI,CAAC;YACpK,IAAI,iBAAM,CACN,gFAAgF;kBAC9E,2CAA2C;kBAC3C,6FAA6F;kBAC7F,4EAA4E;kBAC5E,sEAAsE,CAC3E;SACJ,EACD,IAAI,wBAAa,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,EAAE,0DAA0D,CAAC,CACpH,CAAC;IACN,CAAC;IAED,KAAK,CAAC,GAAgB;QAClB,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,CAAC;QAC1D,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,cAAc,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAU,CAAC,uBAAuB,CAAC;gBAAE,SAAS;YAChG,IAAI,cAAc,IAAI,mBAAmB,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;gBAAE,SAAS;YAClE,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACzD,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;AArCD,8DAqCC","sourcesContent":["import { NoUnmanagedExceptionsConfig, RULE_NAMES } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { EditRuleBase } from '../rule-base';\nimport { FixHint, Option, DisableEscape } from '../fix-hint';\nimport { writeTemplateIfMissing } from '../instruct-ai-writer';\n\nconst TRY_PATTERN = /\\btry\\s*\\{/;\n\n// Both webpieces-disable and the existing ESLint directive suppress this rule\nconst DISABLE_PATTERN = /@webpieces\\/no-unmanaged-exceptions|webpieces-disable\\s+(?:[\\w-]+,\\s*)*no-unmanaged-exceptions/;\n\nfunction hasPrecedingDisable(lines: readonly string[], idx: number): boolean {\n if (idx === 0) return false;\n const prevLine = lines[idx - 1];\n return DISABLE_PATTERN.test(prevLine);\n}\n\nexport class NoUnmanagedExceptionsRule extends EditRuleBase<NoUnmanagedExceptionsConfig> {\n constructor(config: NoUnmanagedExceptionsConfig) { super(config, 'no-unmanaged-exceptions'); }\n\n readonly description = 'try/catch is generally not allowed. Only allowed in chokepoints (filter, globalErrorHandler) or other rare locations.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n get fixHint(): FixHint {\n return new FixHint(\n 'try/catch is generally not allowed. READ .webpieces/instruct-ai/webpieces.exceptions.md. Only chokepoints (filter, globalErrorHandler) may catch exceptions.',\n 'Pick one (NOTE: for external calls — execSync, fs, network — the preferred option is almost always right; let it throw, the top-level runner reports it):',\n [\n new Option('Remove the try/catch — let the exception bubble to the top-level chokepoint (filter, globalErrorHandler) where it is already logged and handled.', true),\n new Option(\n 'If you genuinely believe this IS a chokepoint, STOP and ask the human first:\\n'\n + ' - What exception could be thrown here\\n'\n + ' - What the current top-level chokepoint is (the try/catch at the top of the call stack)\\n'\n + ' - Why throwing to that chokepoint would be wrong in this specific case\\n'\n + ' Then ask whether to remove the try/catch or add a disable comment.',\n ),\n ],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable no-unmanaged-exceptions -- <reason>'),\n );\n }\n\n check(ctx: EditContext): readonly Violation[] {\n const disableAllowed = this.config.disableAllowed ?? true;\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!TRY_PATTERN.test(stripped)) continue;\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, RULE_NAMES.NO_UNMANAGED_EXCEPTIONS)) continue;\n if (disableAllowed && hasPrecedingDisable(ctx.lines, i)) continue;\n violations.push(new V(lineNum, ctx.lines[i].trim()));\n }\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n }\n}\n"]}
@@ -1,10 +1,11 @@
1
1
  import { PrCreationGuardConfig } 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 PrCreationGuardRule extends BashRuleBase<PrCreationGuardConfig> {
5
6
  private readonly upsertPrCommand;
6
7
  constructor(config: PrCreationGuardConfig);
7
8
  readonly description = "Block direct PR creation/edit (gh pr / gh api / curl) so PRs go only through the gated upsert-pr command.";
8
- get fixHint(): readonly string[];
9
+ get fixHint(): FixHint;
9
10
  check(ctx: BashContext): readonly Violation[];
10
11
  }
@@ -3,16 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PrCreationGuardRule = void 0;
4
4
  const types_1 = require("../types");
5
5
  const rule_base_1 = require("../rule-base");
6
+ const fix_hint_1 = require("../fix-hint");
6
7
  const DEFAULT_UPSERT_PR_COMMAND = 'pnpm wp-start-upsert-pr';
7
8
  function fixHintFor(upsertPrCommand) {
8
- return [
9
- 'Direct PR creation is blocked. Create or update a PR ONLY via the gated flow:',
10
- ` ${upsertPrCommand}`,
11
- 'It updates the branch from main (3-point merge) and runs the real build (nx affected), then',
12
- 'instructs you to write review.json and run `pnpm wp-finish-upsert-pr`, which assembles the',
13
- 'dashboard and creates/updates the PR itself. A failing build = no PR.',
14
- 'There is nothing to paste or attest to; the commands do the work.',
15
- ];
9
+ return new fix_hint_1.FixHint('Direct PR creation/update is blocked.', 'Create or update a PR ONLY via the gated flow:\n'
10
+ + ` ${upsertPrCommand}\n`
11
+ + 'It updates the branch from main (3-point merge) and runs the real build (nx affected), then\n'
12
+ + 'instructs you to write review.json and run `pnpm wp-finish-upsert-pr`, which assembles the\n'
13
+ + 'dashboard and creates/updates the PR itself. A failing build = no PR.\n'
14
+ + 'There is nothing to paste or attest to; the commands do the work.');
16
15
  }
17
16
  // Detect every way an agent could open/update a PR directly, so the ONLY path left is the gated
18
17
  // flow (wp-start-upsert-pr → wp-finish-upsert-pr, whose internal `gh pr create` runs as a child
@@ -45,11 +44,7 @@ class PrCreationGuardRule extends rule_base_1.BashRuleBase {
45
44
  check(ctx) {
46
45
  if (!isDirectPrCreation(ctx.command))
47
46
  return [];
48
- return [new types_1.Violation(1, truncate(ctx.command), [
49
- 'Direct PR creation/update is blocked.',
50
- 'Use the gated command instead — it runs the build and builds the dashboard:',
51
- ` ${this.upsertPrCommand}`,
52
- ].join('\n'))];
47
+ return [new types_1.Violation(1, truncate(ctx.command))];
53
48
  }
54
49
  }
55
50
  exports.PrCreationGuardRule = PrCreationGuardRule;
@@ -1 +1 @@
1
- {"version":3,"file":"pr-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-creation-guard.ts"],"names":[],"mappings":";;;AAGA,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAM,yBAAyB,GAAG,yBAAyB,CAAC;AAE5D,SAAS,UAAU,CAAC,eAAuB;IACvC,OAAO;QACH,+EAA+E;QAC/E,KAAK,eAAe,EAAE;QACtB,6FAA6F;QAC7F,4FAA4F;QAC5F,uEAAuE;QACvE,mEAAmE;KACtE,CAAC;AACN,CAAC;AAED,gGAAgG;AAChG,gGAAgG;AAChG,2GAA2G;AAC3G,SAAS,kBAAkB,CAAC,GAAW;IACnC,IAAI,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,UAAU,GAAG,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3D,IAAI,UAAU,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC9I,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,SAAS,GAAG,+CAA+C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5E,IAAI,SAAS,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC1G,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,MAAa,mBAAoB,SAAQ,wBAAmC;IACvD,eAAe,CAAS;IAEzC,YAAY,MAA6B;QACrC,KAAK,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;QACnC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,eAAe,IAAI,yBAAyB,CAAC;IAC/E,CAAC;IAEQ,WAAW,GAAG,2GAA2G,CAAC;IACnI,IAAI,OAAO,KAAwB,OAAO,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAE7E,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAChD,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;gBACI,uCAAuC;gBACvC,6EAA6E;gBAC7E,KAAK,IAAI,CAAC,eAAe,EAAE;aAC9B,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ;AAvBD,kDAuBC","sourcesContent":["import { PrCreationGuardConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\n\nconst DEFAULT_UPSERT_PR_COMMAND = 'pnpm wp-start-upsert-pr';\n\nfunction fixHintFor(upsertPrCommand: string): readonly string[] {\n return [\n 'Direct PR creation is blocked. Create or update a PR ONLY via the gated flow:',\n ` ${upsertPrCommand}`,\n 'It updates the branch from main (3-point merge) and runs the real build (nx affected), then',\n 'instructs you to write review.json and run `pnpm wp-finish-upsert-pr`, which assembles the',\n 'dashboard and creates/updates the PR itself. A failing build = no PR.',\n 'There is nothing to paste or attest to; the commands do the work.',\n ];\n}\n\n// Detect every way an agent could open/update a PR directly, so the ONLY path left is the gated\n// flow (wp-start-upsert-pr → wp-finish-upsert-pr, whose internal `gh pr create` runs as a child\n// process the hook never sees). Read-only `gh pr list` / `gh api .../pulls` GET are intentionally allowed.\nfunction isDirectPrCreation(cmd: string): boolean {\n if (/\\bgh\\s+pr\\s+(create|edit)\\b/.test(cmd)) return true;\n\n const ghApiPulls = /\\bgh\\s+api\\b[^\\n]*\\/pulls\\b/.test(cmd);\n if (ghApiPulls && (/--method\\s+POST/i.test(cmd) || /-X\\s+POST/i.test(cmd) || /\\s-f\\b/.test(cmd) || /\\s-F\\b/.test(cmd) || /--field\\b/.test(cmd))) {\n return true;\n }\n\n const curlPulls = /\\bcurl\\b[^\\n]*api\\.github\\.com[^\\n]*\\/pulls\\b/.test(cmd);\n if (curlPulls && (/-X\\s*POST/i.test(cmd) || /--request\\s+POST/i.test(cmd) || /(\\s-d\\b|--data\\b)/.test(cmd))) {\n return true;\n }\n return false;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport class PrCreationGuardRule extends BashRuleBase<PrCreationGuardConfig> {\n private readonly upsertPrCommand: string;\n\n constructor(config: PrCreationGuardConfig) {\n super(config, 'pr-creation-guard');\n this.upsertPrCommand = config.upsertPrCommand ?? DEFAULT_UPSERT_PR_COMMAND;\n }\n\n readonly description = 'Block direct PR creation/edit (gh pr / gh api / curl) so PRs go only through the gated upsert-pr command.';\n get fixHint(): readonly string[] { return fixHintFor(this.upsertPrCommand); }\n\n check(ctx: BashContext): readonly Violation[] {\n if (!isDirectPrCreation(ctx.command)) return [];\n return [new V(\n 1,\n truncate(ctx.command),\n [\n 'Direct PR creation/update is blocked.',\n 'Use the gated command instead — it runs the build and builds the dashboard:',\n ` ${this.upsertPrCommand}`,\n ].join('\\n'),\n )];\n }\n}\n"]}
1
+ {"version":3,"file":"pr-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-creation-guard.ts"],"names":[],"mappings":";;;AAGA,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAAsC;AAEtC,MAAM,yBAAyB,GAAG,yBAAyB,CAAC;AAE5D,SAAS,UAAU,CAAC,eAAuB;IACvC,OAAO,IAAI,kBAAO,CACd,uCAAuC,EACvC,kDAAkD;UAChD,KAAK,eAAe,IAAI;UACxB,+FAA+F;UAC/F,8FAA8F;UAC9F,yEAAyE;UACzE,mEAAmE,CACxE,CAAC;AACN,CAAC;AAED,gGAAgG;AAChG,gGAAgG;AAChG,2GAA2G;AAC3G,SAAS,kBAAkB,CAAC,GAAW;IACnC,IAAI,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,UAAU,GAAG,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3D,IAAI,UAAU,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC9I,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,SAAS,GAAG,+CAA+C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5E,IAAI,SAAS,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC1G,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,MAAa,mBAAoB,SAAQ,wBAAmC;IACvD,eAAe,CAAS;IAEzC,YAAY,MAA6B;QACrC,KAAK,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;QACnC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,eAAe,IAAI,yBAAyB,CAAC;IAC/E,CAAC;IAEQ,WAAW,GAAG,2GAA2G,CAAC;IACnI,IAAI,OAAO,KAAc,OAAO,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAEnE,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAChD,OAAO,CAAC,IAAI,iBAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC;CACJ;AAfD,kDAeC","sourcesContent":["import { PrCreationGuardConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\nimport { FixHint } from '../fix-hint';\n\nconst DEFAULT_UPSERT_PR_COMMAND = 'pnpm wp-start-upsert-pr';\n\nfunction fixHintFor(upsertPrCommand: string): FixHint {\n return new FixHint(\n 'Direct PR creation/update is blocked.',\n 'Create or update a PR ONLY via the gated flow:\\n'\n + ` ${upsertPrCommand}\\n`\n + 'It updates the branch from main (3-point merge) and runs the real build (nx affected), then\\n'\n + 'instructs you to write review.json and run `pnpm wp-finish-upsert-pr`, which assembles the\\n'\n + 'dashboard and creates/updates the PR itself. A failing build = no PR.\\n'\n + 'There is nothing to paste or attest to; the commands do the work.',\n );\n}\n\n// Detect every way an agent could open/update a PR directly, so the ONLY path left is the gated\n// flow (wp-start-upsert-pr → wp-finish-upsert-pr, whose internal `gh pr create` runs as a child\n// process the hook never sees). Read-only `gh pr list` / `gh api .../pulls` GET are intentionally allowed.\nfunction isDirectPrCreation(cmd: string): boolean {\n if (/\\bgh\\s+pr\\s+(create|edit)\\b/.test(cmd)) return true;\n\n const ghApiPulls = /\\bgh\\s+api\\b[^\\n]*\\/pulls\\b/.test(cmd);\n if (ghApiPulls && (/--method\\s+POST/i.test(cmd) || /-X\\s+POST/i.test(cmd) || /\\s-f\\b/.test(cmd) || /\\s-F\\b/.test(cmd) || /--field\\b/.test(cmd))) {\n return true;\n }\n\n const curlPulls = /\\bcurl\\b[^\\n]*api\\.github\\.com[^\\n]*\\/pulls\\b/.test(cmd);\n if (curlPulls && (/-X\\s*POST/i.test(cmd) || /--request\\s+POST/i.test(cmd) || /(\\s-d\\b|--data\\b)/.test(cmd))) {\n return true;\n }\n return false;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport class PrCreationGuardRule extends BashRuleBase<PrCreationGuardConfig> {\n private readonly upsertPrCommand: string;\n\n constructor(config: PrCreationGuardConfig) {\n super(config, 'pr-creation-guard');\n this.upsertPrCommand = config.upsertPrCommand ?? DEFAULT_UPSERT_PR_COMMAND;\n }\n\n readonly description = 'Block direct PR creation/edit (gh pr / gh api / curl) so PRs go only through the gated upsert-pr command.';\n get fixHint(): FixHint { return fixHintFor(this.upsertPrCommand); }\n\n check(ctx: BashContext): readonly Violation[] {\n if (!isDirectPrCreation(ctx.command)) return [];\n return [new V(1, truncate(ctx.command))];\n }\n}\n"]}
@@ -1,9 +1,11 @@
1
1
  import { PrMergeCleanupConfig } 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 PrMergeCleanupRule extends BashRuleBase<PrMergeCleanupConfig> {
5
6
  constructor(config: PrMergeCleanupConfig);
6
7
  readonly description = "After merging a PR, require switching to main, pulling, and deleting the local branch.";
7
- readonly fixHint: string[];
8
+ private currentBranch;
9
+ get fixHint(): FixHint;
8
10
  check(ctx: BashContext): readonly Violation[];
9
11
  }
@@ -4,6 +4,7 @@ exports.PrMergeCleanupRule = 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
  function truncate(s) {
8
9
  const MAX = 120;
9
10
  return s.length <= MAX ? s : s.slice(0, MAX) + '…';
@@ -11,11 +12,14 @@ function truncate(s) {
11
12
  class PrMergeCleanupRule extends rule_base_1.BashRuleBase {
12
13
  constructor(config) { super(config, 'pr-merge-cleanup'); }
13
14
  description = 'After merging a PR, require switching to main, pulling, and deleting the local branch.';
14
- fixHint = [
15
- 'After merging a PR you must clean up the local branch.',
16
- 'Run this combined command instead:',
17
- ' gh pr merge --squash && git checkout main && git pull && git branch -d <current-branch>',
18
- ];
15
+ // Substituted with the real branch name in check(); the getter reads it. Placeholder until then.
16
+ currentBranch = '<current-branch>';
17
+ // Single fix, no distinct options — the whole guidance lives in mainMessage so it renders as
18
+ // one coherent block (never split into fake "Fix Option 1/2/3").
19
+ get fixHint() {
20
+ return new fix_hint_1.FixHint('After merging a PR you must clean up the local branch.', 'Run this combined command instead:\n'
21
+ + ` gh pr merge --squash && git checkout main && git pull && git branch -d ${this.currentBranch}`);
22
+ }
19
23
  check(ctx) {
20
24
  if (!/gh\s+pr\s+merge/.test(ctx.command))
21
25
  return [];
@@ -23,15 +27,11 @@ class PrMergeCleanupRule extends rule_base_1.BashRuleBase {
23
27
  const hasDelete = /git\s+branch\s+-[dD]/.test(ctx.command);
24
28
  if (hasCheckout && hasDelete)
25
29
  return [];
26
- const currentBranch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
30
+ this.currentBranch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
27
31
  cwd: ctx.workspaceRoot,
28
32
  encoding: 'utf8',
29
33
  }).trim();
30
- return [new types_1.Violation(1, truncate(ctx.command), [
31
- '[pr-merge-cleanup] After merging a PR you must clean up the local branch.',
32
- 'Run this combined command instead:',
33
- ` gh pr merge --squash && git checkout main && git pull && git branch -d ${currentBranch}`,
34
- ].join('\n'))];
34
+ return [new types_1.Violation(1, truncate(ctx.command))];
35
35
  }
36
36
  }
37
37
  exports.PrMergeCleanupRule = PrMergeCleanupRule;
@@ -1 +1 @@
1
- {"version":3,"file":"pr-merge-cleanup.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-merge-cleanup.ts"],"names":[],"mappings":";;;AAAA,iDAAyC;AAKzC,oCAA0C;AAC1C,4CAA4C;AAE5C,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,MAAa,kBAAmB,SAAQ,wBAAkC;IACtE,YAAY,MAA4B,IAAI,KAAK,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC;IAEvE,WAAW,GAAG,wFAAwF,CAAC;IACvG,OAAO,GAAG;QACf,wDAAwD;QACxD,oCAAoC;QACpC,2FAA2F;KAC9F,CAAC;IAEF,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAEpD,MAAM,WAAW,GAAG,gCAAgC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAE3D,IAAI,WAAW,IAAI,SAAS;YAAE,OAAO,EAAE,CAAC;QAExC,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,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;gBACI,2EAA2E;gBAC3E,oCAAoC;gBACpC,4EAA4E,aAAa,EAAE;aAC9F,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ;AAjCD,gDAiCC","sourcesContent":["import { execSync } from 'child_process';\n\nimport { PrMergeCleanupConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport class PrMergeCleanupRule extends BashRuleBase<PrMergeCleanupConfig> {\n constructor(config: PrMergeCleanupConfig) { super(config, 'pr-merge-cleanup'); }\n\n readonly description = 'After merging a PR, require switching to main, pulling, and deleting the local branch.';\n readonly fixHint = [\n 'After merging a PR you must clean up the local branch.',\n 'Run this combined command instead:',\n ' gh pr merge --squash && git checkout main && git pull && git branch -d <current-branch>',\n ];\n\n check(ctx: BashContext): readonly Violation[] {\n if (!/gh\\s+pr\\s+merge/.test(ctx.command)) return [];\n\n const hasCheckout = /git\\s+(checkout|switch)\\s+main/.test(ctx.command);\n const hasDelete = /git\\s+branch\\s+-[dD]/.test(ctx.command);\n\n if (hasCheckout && hasDelete) return [];\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n return [new V(\n 1,\n truncate(ctx.command),\n [\n '[pr-merge-cleanup] After merging a PR you must clean up the local branch.',\n 'Run this combined command instead:',\n ` gh pr merge --squash && git checkout main && git pull && git branch -d ${currentBranch}`,\n ].join('\\n'),\n )];\n }\n}\n"]}
1
+ {"version":3,"file":"pr-merge-cleanup.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-merge-cleanup.ts"],"names":[],"mappings":";;;AAAA,iDAAyC;AAKzC,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAAsC;AAEtC,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,MAAa,kBAAmB,SAAQ,wBAAkC;IACtE,YAAY,MAA4B,IAAI,KAAK,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC;IAEvE,WAAW,GAAG,wFAAwF,CAAC;IAEhH,iGAAiG;IACzF,aAAa,GAAG,kBAAkB,CAAC;IAE3C,6FAA6F;IAC7F,iEAAiE;IACjE,IAAI,OAAO;QACP,OAAO,IAAI,kBAAO,CACd,wDAAwD,EACxD,sCAAsC;cACpC,4EAA4E,IAAI,CAAC,aAAa,EAAE,CACrG,CAAC;IACN,CAAC;IAED,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAEpD,MAAM,WAAW,GAAG,gCAAgC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAE3D,IAAI,WAAW,IAAI,SAAS;YAAE,OAAO,EAAE,CAAC;QAExC,IAAI,CAAC,aAAa,GAAG,IAAA,wBAAQ,EAAC,iCAAiC,EAAE;YAC7D,GAAG,EAAE,GAAG,CAAC,aAAa;YACtB,QAAQ,EAAE,MAAM;SACnB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,OAAO,CAAC,IAAI,iBAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC;CACJ;AAjCD,gDAiCC","sourcesContent":["import { execSync } from 'child_process';\n\nimport { PrMergeCleanupConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\nimport { FixHint } from '../fix-hint';\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport class PrMergeCleanupRule extends BashRuleBase<PrMergeCleanupConfig> {\n constructor(config: PrMergeCleanupConfig) { super(config, 'pr-merge-cleanup'); }\n\n readonly description = 'After merging a PR, require switching to main, pulling, and deleting the local branch.';\n\n // Substituted with the real branch name in check(); the getter reads it. Placeholder until then.\n private currentBranch = '<current-branch>';\n\n // Single fix, no distinct options — the whole guidance lives in mainMessage so it renders as\n // one coherent block (never split into fake \"Fix Option 1/2/3\").\n get fixHint(): FixHint {\n return new FixHint(\n 'After merging a PR you must clean up the local branch.',\n 'Run this combined command instead:\\n'\n + ` gh pr merge --squash && git checkout main && git pull && git branch -d ${this.currentBranch}`,\n );\n }\n\n check(ctx: BashContext): readonly Violation[] {\n if (!/gh\\s+pr\\s+merge/.test(ctx.command)) return [];\n\n const hasCheckout = /git\\s+(checkout|switch)\\s+main/.test(ctx.command);\n const hasDelete = /git\\s+branch\\s+-[dD]/.test(ctx.command);\n\n if (hasCheckout && hasDelete) return [];\n\n this.currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n return [new V(1, truncate(ctx.command))];\n }\n}\n"]}
@@ -1,9 +1,10 @@
1
1
  import { RedirectHowToMergeMainConfig } 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 RedirectHowToMergeMainRule extends BashRuleBase<RedirectHowToMergeMainConfig> {
5
6
  constructor(config: RedirectHowToMergeMainConfig);
6
7
  readonly description = "Block direct git merge/rebase/pull from main on feature branches. Use the squash-update process instead.";
7
- readonly fixHint: readonly string[];
8
+ readonly fixHint: FixHint;
8
9
  check(ctx: BashContext): readonly Violation[];
9
10
  }
@@ -4,9 +4,8 @@ exports.RedirectHowToMergeMainRule = 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 = [
8
- "Run 'pnpm wp-git-update' to squash-update from main. This preserves the 3-point fork-point system (fork-point=A, feature-HEAD=B, main-HEAD=C) needed for clean PR diffs. See docs/git-workflow.md for details.",
9
- ];
7
+ const fix_hint_1 = require("../fix-hint");
8
+ const FIX_HINT = new fix_hint_1.FixHint('Direct merge/rebase/pull from main on a feature branch is blocked.', "Run 'pnpm wp-git-update' to squash-update from main. This preserves the 3-point fork-point system (fork-point=A, feature-HEAD=B, main-HEAD=C) needed for clean PR diffs. See docs/git-workflow.md for details.");
10
9
  const WRONG_UPDATE_PATTERNS = [
11
10
  /git\s+merge\s+(origin\/main|main)\b/,
12
11
  /git\s+rebase\s+(origin\/main|main)\b/,
@@ -35,13 +34,7 @@ class RedirectHowToMergeMainRule extends rule_base_1.BashRuleBase {
35
34
  if (currentBranch === 'main') {
36
35
  return [];
37
36
  }
38
- return [new types_1.Violation(1, truncate(ctx.command), [
39
- `Direct merge/rebase from main on branch '${currentBranch}' is blocked.`,
40
- 'This breaks the 3-point fork-point system.',
41
- 'Use the squash-update process instead:',
42
- ' pnpm wp-git-update',
43
- 'See docs/git-workflow.md for details.',
44
- ].join('\n'))];
37
+ return [new types_1.Violation(1, truncate(ctx.command), `Direct merge/rebase from main on branch '${currentBranch}' is blocked.`)];
45
38
  }
46
39
  }
47
40
  exports.RedirectHowToMergeMainRule = RedirectHowToMergeMainRule;
@@ -1 +1 @@
1
- {"version":3,"file":"redirect-how-to-merge-main.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/redirect-how-to-merge-main.ts"],"names":[],"mappings":";;;AAAA,iDAAyC;AAKzC,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAM,QAAQ,GAAsB;IAChC,gNAAgN;CACnN,CAAC;AAEF,MAAM,qBAAqB,GAAa;IACpC,qCAAqC;IACrC,sCAAsC;IACtC,8BAA8B;CACjC,CAAC;AAEF,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,MAAa,0BAA2B,SAAQ,wBAA0C;IACtF,YAAY,MAAoC,IAAI,KAAK,CAAC,MAAM,EAAE,4BAA4B,CAAC,CAAC,CAAC,CAAC;IAEzF,WAAW,GAAG,0GAA0G,CAAC;IACzH,OAAO,GAAG,QAAQ,CAAC;IAE5B,KAAK,CAAC,GAAgB;QAClB,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/E,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAExB,0GAA0G;QAC1G,IAAI,oCAAoC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACzD,OAAO,EAAE,CAAC;QACd,CAAC;QAED,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,EAAE,CAAC;QACd,CAAC;QAED,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB;gBACI,4CAA4C,aAAa,eAAe;gBACxE,4CAA4C;gBAC5C,wCAAwC;gBACxC,sBAAsB;gBACtB,uCAAuC;aAC1C,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC,CAAC;IACP,CAAC;CACJ;AApCD,gEAoCC","sourcesContent":["import { execSync } from 'child_process';\n\nimport { RedirectHowToMergeMainConfig } 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 'pnpm wp-git-update' to squash-update from main. This preserves the 3-point fork-point system (fork-point=A, feature-HEAD=B, main-HEAD=C) needed for clean PR diffs. See docs/git-workflow.md for details.\",\n];\n\nconst WRONG_UPDATE_PATTERNS: RegExp[] = [\n /git\\s+merge\\s+(origin\\/main|main)\\b/,\n /git\\s+rebase\\s+(origin\\/main|main)\\b/,\n /git\\s+pull\\s+origin\\s+main\\b/,\n];\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport class RedirectHowToMergeMainRule extends BashRuleBase<RedirectHowToMergeMainConfig> {\n constructor(config: RedirectHowToMergeMainConfig) { super(config, 'redirect-how-to-merge-main'); }\n\n readonly description = 'Block direct git merge/rebase/pull from main on feature branches. Use the squash-update process instead.';\n readonly fixHint = FIX_HINT;\n\n check(ctx: BashContext): readonly Violation[] {\n const matched = WRONG_UPDATE_PATTERNS.some((p: RegExp) => p.test(ctx.command));\n if (!matched) return [];\n\n // Allow 'git checkout main && git pull origin main' — switching to main first is the recommended workflow\n if (/git\\s+(?:checkout|switch)\\s+main\\b/.test(ctx.command)) {\n return [];\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 [];\n }\n\n return [new V(\n 1,\n truncate(ctx.command),\n [\n `Direct merge/rebase from main on branch '${currentBranch}' is blocked.`,\n 'This breaks the 3-point fork-point system.',\n 'Use the squash-update process instead:',\n ' pnpm wp-git-update',\n 'See docs/git-workflow.md for details.',\n ].join('\\n'),\n )];\n }\n}\n"]}
1
+ {"version":3,"file":"redirect-how-to-merge-main.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/redirect-how-to-merge-main.ts"],"names":[],"mappings":";;;AAAA,iDAAyC;AAKzC,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAAsC;AAEtC,MAAM,QAAQ,GAAG,IAAI,kBAAO,CACxB,oEAAoE,EACpE,gNAAgN,CACnN,CAAC;AAEF,MAAM,qBAAqB,GAAa;IACpC,qCAAqC;IACrC,sCAAsC;IACtC,8BAA8B;CACjC,CAAC;AAEF,SAAS,QAAQ,CAAC,CAAS;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC;IAChB,OAAO,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AACvD,CAAC;AAED,MAAa,0BAA2B,SAAQ,wBAA0C;IACtF,YAAY,MAAoC,IAAI,KAAK,CAAC,MAAM,EAAE,4BAA4B,CAAC,CAAC,CAAC,CAAC;IAEzF,WAAW,GAAG,0GAA0G,CAAC;IACzH,OAAO,GAAG,QAAQ,CAAC;IAE5B,KAAK,CAAC,GAAgB;QAClB,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/E,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAExB,0GAA0G;QAC1G,IAAI,oCAAoC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACzD,OAAO,EAAE,CAAC;QACd,CAAC;QAED,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,EAAE,CAAC;QACd,CAAC;QAED,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EACrB,4CAA4C,aAAa,eAAe,CAC3E,CAAC,CAAC;IACP,CAAC;CACJ;AA9BD,gEA8BC","sourcesContent":["import { execSync } from 'child_process';\n\nimport { RedirectHowToMergeMainConfig } from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\nimport { FixHint } from '../fix-hint';\n\nconst FIX_HINT = new FixHint(\n 'Direct merge/rebase/pull from main on a feature branch is blocked.',\n \"Run 'pnpm wp-git-update' to squash-update from main. This preserves the 3-point fork-point system (fork-point=A, feature-HEAD=B, main-HEAD=C) needed for clean PR diffs. See docs/git-workflow.md for details.\",\n);\n\nconst WRONG_UPDATE_PATTERNS: RegExp[] = [\n /git\\s+merge\\s+(origin\\/main|main)\\b/,\n /git\\s+rebase\\s+(origin\\/main|main)\\b/,\n /git\\s+pull\\s+origin\\s+main\\b/,\n];\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nexport class RedirectHowToMergeMainRule extends BashRuleBase<RedirectHowToMergeMainConfig> {\n constructor(config: RedirectHowToMergeMainConfig) { super(config, 'redirect-how-to-merge-main'); }\n\n readonly description = 'Block direct git merge/rebase/pull from main on feature branches. Use the squash-update process instead.';\n readonly fixHint = FIX_HINT;\n\n check(ctx: BashContext): readonly Violation[] {\n const matched = WRONG_UPDATE_PATTERNS.some((p: RegExp) => p.test(ctx.command));\n if (!matched) return [];\n\n // Allow 'git checkout main && git pull origin main' — switching to main first is the recommended workflow\n if (/git\\s+(?:checkout|switch)\\s+main\\b/.test(ctx.command)) {\n return [];\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 [];\n }\n\n return [new V(\n 1,\n truncate(ctx.command),\n `Direct merge/rebase from main on branch '${currentBranch}' is blocked.`,\n )];\n }\n}\n"]}
@@ -1,10 +1,11 @@
1
1
  import { RequireReturnTypeConfig } 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 RequireReturnTypeRule extends EditRuleBase<RequireReturnTypeConfig> {
5
6
  constructor(config: RequireReturnTypeConfig);
6
7
  readonly description = "Every function and method must declare its return type.";
7
8
  readonly files: string[];
8
- readonly fixHint: string[];
9
+ get fixHint(): FixHint;
9
10
  check(ctx: EditContext): readonly Violation[];
10
11
  }
@@ -4,6 +4,7 @@ exports.RequireReturnTypeRule = 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
  // Matches function/method signatures that don't have `: ReturnType` before the `{` body opener.
8
9
  // Pattern: function name(<params>) { — missing `: Type` between `)` and `{`
9
10
  const FUNC_DECL_MISSING = /\bfunction\s+\w+\s*(?:<[^>]*>)?\s*\([^)]*\)\s*\{/;
@@ -31,20 +32,20 @@ class RequireReturnTypeRule extends rule_base_1.EditRuleBase {
31
32
  constructor(config) { super(config, 'require-return-type'); }
32
33
  description = 'Every function and method must declare its return type.';
33
34
  files = ['**/*.ts', '**/*.tsx'];
34
- fixHint = [
35
- 'Add return type: function foo(x: T): ReturnType { ... }',
36
- '// webpieces-disable require-return-type -- <reason>',
37
- ];
35
+ get fixHint() {
36
+ return new fix_hint_1.FixHint('Missing return type annotation.', 'Add a return type: function foo(x: T): ReturnType { ... }.', [], new fix_hint_1.DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable require-return-type -- <reason>'));
37
+ }
38
38
  check(ctx) {
39
+ const disableAllowed = this.config.disableAllowed ?? true;
39
40
  const violations = [];
40
41
  for (let i = 0; i < ctx.strippedLines.length; i += 1) {
41
42
  const stripped = ctx.strippedLines[i];
42
43
  if (!isMissingReturnType(stripped))
43
44
  continue;
44
45
  const lineNum = i + 1;
45
- if (ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.REQUIRE_RETURN_TYPE))
46
+ if (disableAllowed && ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.REQUIRE_RETURN_TYPE))
46
47
  continue;
47
- violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim(), 'Missing return type annotation.'));
48
+ violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim()));
48
49
  }
49
50
  return violations;
50
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"require-return-type.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/require-return-type.ts"],"names":[],"mappings":";;;AAAA,0DAA8E;AAG9E,oCAA0C;AAC1C,4CAA4C;AAE5C,gGAAgG;AAChG,4EAA4E;AAC5E,MAAM,iBAAiB,GAAG,kDAAkD,CAAC;AAE7E,8EAA8E;AAC9E,MAAM,cAAc,GAAG,yDAAyD,CAAC;AAEjF,qFAAqF;AACrF,MAAM,aAAa,GAAG,gEAAgE,CAAC;AAEvF,qEAAqE;AACrE,MAAM,eAAe,GAAG,aAAa,CAAC;AAEtC,iEAAiE;AACjE,MAAM,YAAY,GAAG,kFAAkF,CAAC;AAExG,SAAS,mBAAmB,CAAC,IAAY;IACrC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,MAAM,UAAU,GACZ,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QACzB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9B,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,MAAa,qBAAsB,SAAQ,wBAAqC;IAC5E,YAAY,MAA+B,IAAI,KAAK,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAE7E,WAAW,GAAG,yDAAyD,CAAC;IAC/D,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACzC,OAAO,GAAG;QACf,yDAAyD;QACzD,sDAAsD;KACzD,CAAC;IAEF,KAAK,CAAC,GAAgB;QAClB,MAAM,UAAU,GAAQ,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC7C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAU,CAAC,mBAAmB,CAAC;gBAAE,SAAS;YAC1E,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,iCAAiC,CACpC,CAAC,CAAC;QACP,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AAzBD,sDAyBC","sourcesContent":["import { RequireReturnTypeConfig, RULE_NAMES } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { EditRuleBase } from '../rule-base';\n\n// Matches function/method signatures that don't have `: ReturnType` before the `{` body opener.\n// Pattern: function name(<params>) { — missing `: Type` between `)` and `{`\nconst FUNC_DECL_MISSING = /\\bfunction\\s+\\w+\\s*(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*\\{/;\n\n// Matches class method signatures: indented, optional async, name(<params>) {\nconst METHOD_MISSING = /^\\s{2,}(?:async\\s+)?\\w+\\s*(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*\\{/;\n\n// Arrow function: const name = (async)? (<params>) => — missing `: Type` before `=>`\nconst ARROW_MISSING = /\\bconst\\s+\\w+\\s*=\\s*(?:async\\s+)?(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*=>/;\n\n// Lines that have ): ReturnType before { or => — these are COMPLIANT\nconst HAS_RETURN_TYPE = /\\)\\s*:\\s*\\S/;\n\n// Skip constructors, getters, setters, and control flow keywords\nconst SKIP_PATTERN = /\\b(?:constructor|get\\s+\\w+|set\\s+\\w+|if|else|while|for|switch|catch|return)\\s*\\(/;\n\nfunction isMissingReturnType(line: string): boolean {\n if (SKIP_PATTERN.test(line)) return false;\n const isFuncLike =\n FUNC_DECL_MISSING.test(line) ||\n METHOD_MISSING.test(line) ||\n ARROW_MISSING.test(line);\n if (!isFuncLike) return false;\n if (HAS_RETURN_TYPE.test(line)) return false;\n return true;\n}\n\nexport class RequireReturnTypeRule extends EditRuleBase<RequireReturnTypeConfig> {\n constructor(config: RequireReturnTypeConfig) { super(config, 'require-return-type'); }\n\n readonly description = 'Every function and method must declare its return type.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n readonly fixHint = [\n 'Add return type: function foo(x: T): ReturnType { ... }',\n '// webpieces-disable require-return-type -- <reason>',\n ];\n\n check(ctx: EditContext): readonly Violation[] {\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!isMissingReturnType(stripped)) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, RULE_NAMES.REQUIRE_RETURN_TYPE)) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'Missing return type annotation.',\n ));\n }\n return violations;\n }\n}\n"]}
1
+ {"version":3,"file":"require-return-type.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/require-return-type.ts"],"names":[],"mappings":";;;AAAA,0DAA8E;AAG9E,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAAqD;AAErD,gGAAgG;AAChG,4EAA4E;AAC5E,MAAM,iBAAiB,GAAG,kDAAkD,CAAC;AAE7E,8EAA8E;AAC9E,MAAM,cAAc,GAAG,yDAAyD,CAAC;AAEjF,qFAAqF;AACrF,MAAM,aAAa,GAAG,gEAAgE,CAAC;AAEvF,qEAAqE;AACrE,MAAM,eAAe,GAAG,aAAa,CAAC;AAEtC,iEAAiE;AACjE,MAAM,YAAY,GAAG,kFAAkF,CAAC;AAExG,SAAS,mBAAmB,CAAC,IAAY;IACrC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,MAAM,UAAU,GACZ,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QACzB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9B,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,MAAa,qBAAsB,SAAQ,wBAAqC;IAC5E,YAAY,MAA+B,IAAI,KAAK,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAE7E,WAAW,GAAG,yDAAyD,CAAC;IAC/D,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAClD,IAAI,OAAO;QACP,OAAO,IAAI,kBAAO,CACd,iCAAiC,EACjC,4DAA4D,EAC5D,EAAE,EACF,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,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC7C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,cAAc,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAU,CAAC,mBAAmB,CAAC;gBAAE,SAAS;YAC5F,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AA1BD,sDA0BC","sourcesContent":["import { RequireReturnTypeConfig, 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, DisableEscape } from '../fix-hint';\n\n// Matches function/method signatures that don't have `: ReturnType` before the `{` body opener.\n// Pattern: function name(<params>) { — missing `: Type` between `)` and `{`\nconst FUNC_DECL_MISSING = /\\bfunction\\s+\\w+\\s*(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*\\{/;\n\n// Matches class method signatures: indented, optional async, name(<params>) {\nconst METHOD_MISSING = /^\\s{2,}(?:async\\s+)?\\w+\\s*(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*\\{/;\n\n// Arrow function: const name = (async)? (<params>) => — missing `: Type` before `=>`\nconst ARROW_MISSING = /\\bconst\\s+\\w+\\s*=\\s*(?:async\\s+)?(?:<[^>]*>)?\\s*\\([^)]*\\)\\s*=>/;\n\n// Lines that have ): ReturnType before { or => — these are COMPLIANT\nconst HAS_RETURN_TYPE = /\\)\\s*:\\s*\\S/;\n\n// Skip constructors, getters, setters, and control flow keywords\nconst SKIP_PATTERN = /\\b(?:constructor|get\\s+\\w+|set\\s+\\w+|if|else|while|for|switch|catch|return)\\s*\\(/;\n\nfunction isMissingReturnType(line: string): boolean {\n if (SKIP_PATTERN.test(line)) return false;\n const isFuncLike =\n FUNC_DECL_MISSING.test(line) ||\n METHOD_MISSING.test(line) ||\n ARROW_MISSING.test(line);\n if (!isFuncLike) return false;\n if (HAS_RETURN_TYPE.test(line)) return false;\n return true;\n}\n\nexport class RequireReturnTypeRule extends EditRuleBase<RequireReturnTypeConfig> {\n constructor(config: RequireReturnTypeConfig) { super(config, 'require-return-type'); }\n\n readonly description = 'Every function and method must declare its return type.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n get fixHint(): FixHint {\n return new FixHint(\n 'Missing return type annotation.',\n 'Add a return type: function foo(x: T): ReturnType { ... }.',\n [],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable require-return-type -- <reason>'),\n );\n }\n\n check(ctx: EditContext): readonly Violation[] {\n const disableAllowed = this.config.disableAllowed ?? true;\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!isMissingReturnType(stripped)) continue;\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, RULE_NAMES.REQUIRE_RETURN_TYPE)) continue;\n violations.push(new V(lineNum, ctx.lines[i].trim()));\n }\n return violations;\n }\n}\n"]}
@@ -1,10 +1,11 @@
1
1
  import { ThrowCauseRequiredConfig } 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 ThrowCauseRequiredRule extends EditRuleBase<ThrowCauseRequiredConfig> {
5
6
  constructor(config: ThrowCauseRequiredConfig);
6
7
  readonly description = "When rethrowing with added context, chain the original exception: throw new Error(\"msg\", { cause: error })";
7
8
  readonly files: string[];
8
- readonly fixHint: string[];
9
+ get fixHint(): FixHint;
9
10
  check(ctx: EditContext): readonly Violation[];
10
11
  }
@@ -4,6 +4,7 @@ exports.ThrowCauseRequiredRule = 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: throw new SomeClass(
@@ -21,13 +22,15 @@ class ThrowCauseRequiredRule extends rule_base_1.EditRuleBase {
21
22
  constructor(config) { super(config, 'throw-cause-required'); }
22
23
  description = 'When rethrowing with added context, chain the original exception: throw new Error("msg", { cause: error })';
23
24
  files = ['**/*.ts', '**/*.tsx'];
24
- fixHint = [
25
- 'Option 1 Remove the try-catch entirely. Letting the original exception bubble is usually the best option.',
26
- 'Option 2 — throw new Error("add more info here", { cause: error }); chains the error and preserves the full stack trace.',
27
- 'Option 3 — throw new SpecificError("add info here", { cause: error }); e.g. new InformAiError("what happened for AI", { cause: error }).',
28
- '[Only if disableAllowed:true] Option 4 // webpieces-disable throw-cause-required -- <reason>',
29
- ];
25
+ get fixHint() {
26
+ return new fix_hint_1.FixHint('Rethrowing with error.message loses the original stack trace. Use { cause: error } to chain it.', 'Pick one:', [
27
+ new fix_hint_1.Option('Remove the try-catch entirely. Letting the original exception bubble is usually the best option.', true),
28
+ new fix_hint_1.Option('throw new Error("add more info here", { cause: error }); chains the error and preserves the full stack trace.'),
29
+ new fix_hint_1.Option('throw new SpecificError("add info here", { cause: error }); e.g. new InformAiError("what happened for AI", { cause: error }).'),
30
+ ], new fix_hint_1.DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable throw-cause-required -- <reason>'));
31
+ }
30
32
  check(ctx) {
33
+ const disableAllowed = this.config.disableAllowed ?? true;
31
34
  const violations = [];
32
35
  const lines = ctx.strippedLines;
33
36
  for (let i = 0; i < lines.length; i += 1) {
@@ -39,9 +42,9 @@ class ThrowCauseRequiredRule extends rule_base_1.EditRuleBase {
39
42
  if (CAUSE_PATTERN.test(stripped))
40
43
  continue;
41
44
  const lineNum = i + 1;
42
- if (ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.THROW_CAUSE_REQUIRED))
45
+ if (disableAllowed && ctx.isLineDisabled(lineNum, rules_config_1.RULE_NAMES.THROW_CAUSE_REQUIRED))
43
46
  continue;
44
- violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim(), 'Rethrowing with error.message loses the original stack trace. Use { cause: error } to chain it.'));
47
+ violations.push(new types_1.Violation(lineNum, ctx.lines[i].trim()));
45
48
  }
46
49
  if (violations.length > 0)
47
50
  (0, instruct_ai_writer_1.writeTemplateIfMissing)(ctx.workspaceRoot, 'webpieces.exceptions.md');
@@ -1 +1 @@
1
- {"version":3,"file":"throw-cause-required.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/throw-cause-required.ts"],"names":[],"mappings":";;;AAAA,0DAA+E;AAG/E,oCAA0C;AAC1C,4CAA4C;AAC5C,8DAA+D;AAE/D;;GAEG;AACH,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AAErD;;GAEG;AACH,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAEtD;;GAEG;AACH,MAAM,aAAa,GAAG,0BAA0B,CAAC;AAEjD,MAAa,sBAAuB,SAAQ,wBAAsC;IAC9E,YAAY,MAAgC,IAAI,KAAK,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAE/E,WAAW,GAAG,4GAA4G,CAAC;IAClH,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACzC,OAAO,GAAG;QACf,6GAA6G;QAC7G,0HAA0H;QAC1H,0IAA0I;QAC1I,gGAAgG;KACnG,CAAC;IAEF,KAAK,CAAC,GAAgB;QAClB,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,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAChD,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACpD,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAE3C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAU,CAAC,oBAAoB,CAAC;gBAAE,SAAS;YAE3E,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EACnB,iGAAiG,CACpG,CAAC,CAAC;QACP,CAAC;QAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,IAAA,2CAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;QAChG,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AAnCD,wDAmCC","sourcesContent":["import { ThrowCauseRequiredConfig, RULE_NAMES } from '@webpieces/rules-config';\n\nimport type { EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { EditRuleBase } from '../rule-base';\nimport { writeTemplateIfMissing } from '../instruct-ai-writer';\n\n/**\n * Matches: throw new SomeClass(\n */\nconst THROW_NEW_PATTERN = /\\bthrow\\s+new\\s+\\w+\\s*\\(/;\n\n/**\n * Matches: error.message or error2.message or error3.message\n */\nconst ERROR_MESSAGE_PATTERN = /\\berror\\d*\\.message\\b/;\n\n/**\n * Matches: cause: error or cause: error2 etc. (the good pattern)\n */\nconst CAUSE_PATTERN = /\\bcause\\s*:\\s*error\\d*\\b/;\n\nexport class ThrowCauseRequiredRule extends EditRuleBase<ThrowCauseRequiredConfig> {\n constructor(config: ThrowCauseRequiredConfig) { super(config, 'throw-cause-required'); }\n\n readonly description = 'When rethrowing with added context, chain the original exception: throw new Error(\"msg\", { cause: error })';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n readonly fixHint = [\n 'Option 1 Remove the try-catch entirely. Letting the original exception bubble is usually the best option.',\n 'Option 2 — throw new Error(\"add more info here\", { cause: error }); chains the error and preserves the full stack trace.',\n 'Option 3 — throw new SpecificError(\"add info here\", { cause: error }); e.g. new InformAiError(\"what happened for AI\", { cause: error }).',\n '[Only if disableAllowed:true] Option 4 // webpieces-disable throw-cause-required -- <reason>',\n ];\n\n check(ctx: EditContext): readonly Violation[] {\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 if (!THROW_NEW_PATTERN.test(stripped)) continue;\n if (!ERROR_MESSAGE_PATTERN.test(stripped)) continue;\n if (CAUSE_PATTERN.test(stripped)) continue;\n\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, RULE_NAMES.THROW_CAUSE_REQUIRED)) continue;\n\n violations.push(new V(\n lineNum,\n ctx.lines[i].trim(),\n 'Rethrowing with error.message loses the original stack trace. Use { cause: error } to chain it.',\n ));\n }\n\n if (violations.length > 0) writeTemplateIfMissing(ctx.workspaceRoot, 'webpieces.exceptions.md');\n return violations;\n }\n}\n"]}
1
+ {"version":3,"file":"throw-cause-required.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/throw-cause-required.ts"],"names":[],"mappings":";;;AAAA,0DAA+E;AAG/E,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAA6D;AAC7D,8DAA+D;AAE/D;;GAEG;AACH,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AAErD;;GAEG;AACH,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAEtD;;GAEG;AACH,MAAM,aAAa,GAAG,0BAA0B,CAAC;AAEjD,MAAa,sBAAuB,SAAQ,wBAAsC;IAC9E,YAAY,MAAgC,IAAI,KAAK,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAE/E,WAAW,GAAG,4GAA4G,CAAC;IAClH,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAClD,IAAI,OAAO;QACP,OAAO,IAAI,kBAAO,CACd,iGAAiG,EACjG,WAAW,EACX;YACI,IAAI,iBAAM,CAAC,kGAAkG,EAAE,IAAI,CAAC;YACpH,IAAI,iBAAM,CAAC,+GAA+G,CAAC;YAC3H,IAAI,iBAAM,CAAC,+HAA+H,CAAC;SAC9I,EACD,IAAI,wBAAa,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,IAAI,EAAE,uDAAuD,CAAC,CACjH,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,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAChD,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACpD,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAE3C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,cAAc,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,yBAAU,CAAC,oBAAoB,CAAC;gBAAE,SAAS;YAE7F,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACzD,CAAC;QAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,IAAA,2CAAsB,EAAC,GAAG,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;QAChG,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ;AAtCD,wDAsCC","sourcesContent":["import { ThrowCauseRequiredConfig, 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: throw new SomeClass(\n */\nconst THROW_NEW_PATTERN = /\\bthrow\\s+new\\s+\\w+\\s*\\(/;\n\n/**\n * Matches: error.message or error2.message or error3.message\n */\nconst ERROR_MESSAGE_PATTERN = /\\berror\\d*\\.message\\b/;\n\n/**\n * Matches: cause: error or cause: error2 etc. (the good pattern)\n */\nconst CAUSE_PATTERN = /\\bcause\\s*:\\s*error\\d*\\b/;\n\nexport class ThrowCauseRequiredRule extends EditRuleBase<ThrowCauseRequiredConfig> {\n constructor(config: ThrowCauseRequiredConfig) { super(config, 'throw-cause-required'); }\n\n readonly description = 'When rethrowing with added context, chain the original exception: throw new Error(\"msg\", { cause: error })';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n get fixHint(): FixHint {\n return new FixHint(\n 'Rethrowing with error.message loses the original stack trace. Use { cause: error } to chain it.',\n 'Pick one:',\n [\n new Option('Remove the try-catch entirely. Letting the original exception bubble is usually the best option.', true),\n new Option('throw new Error(\"add more info here\", { cause: error }); chains the error and preserves the full stack trace.'),\n new Option('throw new SpecificError(\"add info here\", { cause: error }); e.g. new InformAiError(\"what happened for AI\", { cause: error }).'),\n ],\n new DisableEscape(this.config.disableAllowed ?? true, '// webpieces-disable throw-cause-required -- <reason>'),\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 if (!THROW_NEW_PATTERN.test(stripped)) continue;\n if (!ERROR_MESSAGE_PATTERN.test(stripped)) continue;\n if (CAUSE_PATTERN.test(stripped)) continue;\n\n const lineNum = i + 1;\n if (disableAllowed && ctx.isLineDisabled(lineNum, RULE_NAMES.THROW_CAUSE_REQUIRED)) continue;\n\n violations.push(new V(lineNum, ctx.lines[i].trim()));\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 { ValidateTsInSrcConfig } 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
  export declare class ValidateTsInSrcRule extends FileRuleBase<ValidateTsInSrcConfig> {
5
6
  constructor(config: ValidateTsInSrcConfig);
6
7
  readonly description = "Every .ts file must belong to a project's src/ directory.";
@@ -9,6 +10,6 @@ export declare class ValidateTsInSrcRule extends FileRuleBase<ValidateTsInSrcCon
9
10
  excludePaths: string[];
10
11
  allowedRootFiles: string[];
11
12
  };
12
- readonly fixHint: string[];
13
+ readonly fixHint: FixHint;
13
14
  check(ctx: FileContext): readonly Violation[];
14
15
  }
@@ -7,6 +7,7 @@ const path = tslib_1.__importStar(require("path"));
7
7
  const rules_config_1 = require("@webpieces/rules-config");
8
8
  const types_1 = require("../types");
9
9
  const rule_base_1 = require("../rule-base");
10
+ const fix_hint_1 = require("../fix-hint");
10
11
  const DEFAULT_EXCLUDE_PATHS = [
11
12
  'node_modules', 'dist', '.nx', '.git',
12
13
  '**/*.d.ts', '**/jest.config.ts',
@@ -29,10 +30,10 @@ class ValidateTsInSrcRule extends rule_base_1.FileRuleBase {
29
30
  excludePaths: DEFAULT_EXCLUDE_PATHS,
30
31
  allowedRootFiles: DEFAULT_ALLOWED_ROOT_FILES,
31
32
  };
32
- fixHint = [
33
- 'Move the file into an existing project\'s src/ directory, or create a new project with project.json that owns the directory.',
34
- 'Add a dir or glob (e.g. "**/codegen.ts") to validate-ts-in-src.excludePaths in webpieces.config.json',
35
- ];
33
+ fixHint = new fix_hint_1.FixHint('TypeScript file is outside a project src/ directory.', 'Fix by one of:', [
34
+ new fix_hint_1.Option('Move the file into an existing project\'s src/ directory, or create a new project with project.json that owns the directory.', true),
35
+ new fix_hint_1.Option('Add a dir or glob (e.g. "**/codegen.ts") to validate-ts-in-src.excludePaths in webpieces.config.json'),
36
+ ]);
36
37
  check(ctx) {
37
38
  if (ctx.tool !== 'Write')
38
39
  return [];
@@ -1 +1 @@
1
- {"version":3,"file":"validate-ts-in-src.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/validate-ts-in-src.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAE7B,0DAAgF;AAGhF,oCAA0C;AAC1C,4CAA4C;AAE5C,MAAM,qBAAqB,GAAG;IAC1B,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IACrC,WAAW,EAAE,mBAAmB;CACnC,CAAC;AACF,MAAM,0BAA0B,GAAG,CAAC,eAAe,CAAC,CAAC;AAErD,SAAS,eAAe,CAAC,QAAgB,EAAE,aAAqB;IAC5D,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjC,OAAO,GAAG,KAAK,aAAa,IAAI,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC5D,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QAC9D,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,MAAa,mBAAoB,SAAQ,wBAAmC;IACxE,YAAY,MAA6B,IAAI,KAAK,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;IAE1E,WAAW,GAAG,4DAA4D,CAAC;IAClE,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAChC,cAAc,GAAG;QAC/B,YAAY,EAAE,qBAAqB;QACnC,gBAAgB,EAAE,0BAA0B;KAC/C,CAAC;IACO,OAAO,GAAG;QACf,8HAA8H;QAC9H,sGAAsG;KACzG,CAAC;IAEF,KAAK,CAAC,GAAgB;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,EAAE,CAAC;QAEpC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,qBAAqB,CAAC;QACvE,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,IAAI,0BAA0B,CAAC;QAEpF,kEAAkE;QAClE,IAAI,IAAA,6BAAc,EAAC,GAAG,CAAC,YAAY,EAAE,YAAY,CAAC;YAAE,OAAO,EAAE,CAAC;QAE9D,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QAEzF,MAAM,WAAW,GAAG,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;QAErE,IAAI,CAAC,WAAW,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,GAAG,CAAC,YAAY,EAChB,8EAA8E,CACjF,CAAC,CAAC;QACP,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9D,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;YACvE,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;YAClE,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,GAAG,CAAC,YAAY,EAChB,4BAA4B,WAAW,uDAAuD,CACjG,CAAC,CAAC;QACP,CAAC;QAED,OAAO,EAAE,CAAC;IACd,CAAC;CACJ;AAhDD,kDAgDC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { ValidateTsInSrcConfig, isPathExcluded } from '@webpieces/rules-config';\n\nimport type { FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { FileRuleBase } from '../rule-base';\n\nconst DEFAULT_EXCLUDE_PATHS = [\n 'node_modules', 'dist', '.nx', '.git',\n '**/*.d.ts', '**/jest.config.ts',\n];\nconst DEFAULT_ALLOWED_ROOT_FILES = ['jest.setup.ts'];\n\nfunction findProjectRoot(filePath: string, workspaceRoot: string): string | null {\n let dir = path.dirname(filePath);\n while (dir !== workspaceRoot && dir.startsWith(workspaceRoot)) {\n if (fs.existsSync(path.join(dir, 'project.json'))) return dir;\n dir = path.dirname(dir);\n }\n return null;\n}\n\nexport class ValidateTsInSrcRule extends FileRuleBase<ValidateTsInSrcConfig> {\n constructor(config: ValidateTsInSrcConfig) { super(config, 'validate-ts-in-src'); }\n\n readonly description = 'Every .ts file must belong to a project\\'s src/ directory.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n override readonly defaultOptions = {\n excludePaths: DEFAULT_EXCLUDE_PATHS,\n allowedRootFiles: DEFAULT_ALLOWED_ROOT_FILES,\n };\n readonly fixHint = [\n 'Move the file into an existing project\\'s src/ directory, or create a new project with project.json that owns the directory.',\n 'Add a dir or glob (e.g. \"**/codegen.ts\") to validate-ts-in-src.excludePaths in webpieces.config.json',\n ];\n\n check(ctx: FileContext): readonly Violation[] {\n if (ctx.tool !== 'Write') return [];\n\n const excludePaths = this.config.excludePaths ?? DEFAULT_EXCLUDE_PATHS;\n const allowedRootFiles = this.config.allowedRootFiles ?? DEFAULT_ALLOWED_ROOT_FILES;\n\n // Holistic exclusion (Layer 1 + Layer 2): bare dir names + globs.\n if (isPathExcluded(ctx.relativePath, excludePaths)) return [];\n\n const relParts = ctx.relativePath.split(path.sep);\n if (relParts.length === 1 && allowedRootFiles.indexOf(relParts[0] ?? '') >= 0) return [];\n\n const projectRoot = findProjectRoot(ctx.filePath, ctx.workspaceRoot);\n\n if (!projectRoot) {\n return [new V(\n 1,\n ctx.relativePath,\n 'File is not inside any Nx project. Move it into a project\\'s src/ directory.',\n )];\n }\n\n const relToProject = path.relative(projectRoot, ctx.filePath);\n if (!relToProject.startsWith('src' + path.sep) && relToProject !== 'src') {\n const projectName = path.relative(ctx.workspaceRoot, projectRoot);\n return [new V(\n 1,\n ctx.relativePath,\n `File is inside project \\`${projectName}\\` but outside its src/ directory. Move it into src/.`,\n )];\n }\n\n return [];\n }\n}\n"]}
1
+ {"version":3,"file":"validate-ts-in-src.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/validate-ts-in-src.ts"],"names":[],"mappings":";;;;AAAA,+CAAyB;AACzB,mDAA6B;AAE7B,0DAAgF;AAGhF,oCAA0C;AAC1C,4CAA4C;AAC5C,0CAA8C;AAE9C,MAAM,qBAAqB,GAAG;IAC1B,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IACrC,WAAW,EAAE,mBAAmB;CACnC,CAAC;AACF,MAAM,0BAA0B,GAAG,CAAC,eAAe,CAAC,CAAC;AAErD,SAAS,eAAe,CAAC,QAAgB,EAAE,aAAqB;IAC5D,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjC,OAAO,GAAG,KAAK,aAAa,IAAI,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC5D,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QAC9D,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,MAAa,mBAAoB,SAAQ,wBAAmC;IACxE,YAAY,MAA6B,IAAI,KAAK,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;IAE1E,WAAW,GAAG,4DAA4D,CAAC;IAClE,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAChC,cAAc,GAAG;QAC/B,YAAY,EAAE,qBAAqB;QACnC,gBAAgB,EAAE,0BAA0B;KAC/C,CAAC;IACO,OAAO,GAAG,IAAI,kBAAO,CAC1B,sDAAsD,EACtD,gBAAgB,EAChB;QACI,IAAI,iBAAM,CAAC,8HAA8H,EAAE,IAAI,CAAC;QAChJ,IAAI,iBAAM,CAAC,sGAAsG,CAAC;KACrH,CACJ,CAAC;IAEF,KAAK,CAAC,GAAgB;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,EAAE,CAAC;QAEpC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,qBAAqB,CAAC;QACvE,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,IAAI,0BAA0B,CAAC;QAEpF,kEAAkE;QAClE,IAAI,IAAA,6BAAc,EAAC,GAAG,CAAC,YAAY,EAAE,YAAY,CAAC;YAAE,OAAO,EAAE,CAAC;QAE9D,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QAEzF,MAAM,WAAW,GAAG,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;QAErE,IAAI,CAAC,WAAW,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,GAAG,CAAC,YAAY,EAChB,8EAA8E,CACjF,CAAC,CAAC;QACP,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9D,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;YACvE,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;YAClE,OAAO,CAAC,IAAI,iBAAC,CACT,CAAC,EACD,GAAG,CAAC,YAAY,EAChB,4BAA4B,WAAW,uDAAuD,CACjG,CAAC,CAAC;QACP,CAAC;QAED,OAAO,EAAE,CAAC;IACd,CAAC;CACJ;AApDD,kDAoDC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { ValidateTsInSrcConfig, isPathExcluded } from '@webpieces/rules-config';\n\nimport type { FileContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { FileRuleBase } from '../rule-base';\nimport { FixHint, Option } from '../fix-hint';\n\nconst DEFAULT_EXCLUDE_PATHS = [\n 'node_modules', 'dist', '.nx', '.git',\n '**/*.d.ts', '**/jest.config.ts',\n];\nconst DEFAULT_ALLOWED_ROOT_FILES = ['jest.setup.ts'];\n\nfunction findProjectRoot(filePath: string, workspaceRoot: string): string | null {\n let dir = path.dirname(filePath);\n while (dir !== workspaceRoot && dir.startsWith(workspaceRoot)) {\n if (fs.existsSync(path.join(dir, 'project.json'))) return dir;\n dir = path.dirname(dir);\n }\n return null;\n}\n\nexport class ValidateTsInSrcRule extends FileRuleBase<ValidateTsInSrcConfig> {\n constructor(config: ValidateTsInSrcConfig) { super(config, 'validate-ts-in-src'); }\n\n readonly description = 'Every .ts file must belong to a project\\'s src/ directory.';\n override readonly files = ['**/*.ts', '**/*.tsx'];\n override readonly defaultOptions = {\n excludePaths: DEFAULT_EXCLUDE_PATHS,\n allowedRootFiles: DEFAULT_ALLOWED_ROOT_FILES,\n };\n readonly fixHint = new FixHint(\n 'TypeScript file is outside a project src/ directory.',\n 'Fix by one of:',\n [\n new Option('Move the file into an existing project\\'s src/ directory, or create a new project with project.json that owns the directory.', true),\n new Option('Add a dir or glob (e.g. \"**/codegen.ts\") to validate-ts-in-src.excludePaths in webpieces.config.json'),\n ],\n );\n\n check(ctx: FileContext): readonly Violation[] {\n if (ctx.tool !== 'Write') return [];\n\n const excludePaths = this.config.excludePaths ?? DEFAULT_EXCLUDE_PATHS;\n const allowedRootFiles = this.config.allowedRootFiles ?? DEFAULT_ALLOWED_ROOT_FILES;\n\n // Holistic exclusion (Layer 1 + Layer 2): bare dir names + globs.\n if (isPathExcluded(ctx.relativePath, excludePaths)) return [];\n\n const relParts = ctx.relativePath.split(path.sep);\n if (relParts.length === 1 && allowedRootFiles.indexOf(relParts[0] ?? '') >= 0) return [];\n\n const projectRoot = findProjectRoot(ctx.filePath, ctx.workspaceRoot);\n\n if (!projectRoot) {\n return [new V(\n 1,\n ctx.relativePath,\n 'File is not inside any Nx project. Move it into a project\\'s src/ directory.',\n )];\n }\n\n const relToProject = path.relative(projectRoot, ctx.filePath);\n if (!relToProject.startsWith('src' + path.sep) && relToProject !== 'src') {\n const projectName = path.relative(ctx.workspaceRoot, projectRoot);\n return [new V(\n 1,\n ctx.relativePath,\n `File is inside project \\`${projectName}\\` but outside its src/ directory. Move it into src/.`,\n )];\n }\n\n return [];\n }\n}\n"]}
@@ -229,7 +229,7 @@ function runBashRules(rules, bashContext) {
229
229
  continue;
230
230
  const vs = runRuleCheck(rule, bashContext);
231
231
  if (vs.length > 0) {
232
- groups.push(new types_1.RuleGroup(rule.name, rule.description, [...rule.fixHint], [...vs]));
232
+ groups.push(new types_1.RuleGroup(rule.name, rule.description, rule.fixHint, [...vs]));
233
233
  }
234
234
  }
235
235
  return groups;
@@ -254,7 +254,7 @@ function runEditRules(rules, editContexts) {
254
254
  }
255
255
  }
256
256
  if (allViolations.length > 0) {
257
- groups.push(new types_1.RuleGroup(rule.name, rule.description, [...rule.fixHint], allViolations));
257
+ groups.push(new types_1.RuleGroup(rule.name, rule.description, rule.fixHint, allViolations));
258
258
  }
259
259
  }
260
260
  return groups;
@@ -270,7 +270,7 @@ function runFileRules(rules, fileContext) {
270
270
  continue;
271
271
  const vs = runRuleCheck(rule, fileContext);
272
272
  if (vs.length > 0) {
273
- groups.push(new types_1.RuleGroup(rule.name, rule.description, [...rule.fixHint], [...vs]));
273
+ groups.push(new types_1.RuleGroup(rule.name, rule.description, rule.fixHint, [...vs]));
274
274
  }
275
275
  }
276
276
  return groups;