@webpieces/ai-hook-rules 0.3.136 → 0.3.138

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/ai-hook-rules",
3
- "version": "0.3.136",
3
+ "version": "0.3.138",
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",
@@ -32,7 +32,7 @@
32
32
  "directory": "packages/tooling/ai-hook-rules"
33
33
  },
34
34
  "dependencies": {
35
- "@webpieces/rules-config": "0.3.136"
35
+ "@webpieces/rules-config": "0.3.138"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
@@ -16,6 +16,7 @@ const require_return_type_1 = tslib_1.__importDefault(require("./rules/require-r
16
16
  const no_unmanaged_exceptions_1 = tslib_1.__importDefault(require("./rules/no-unmanaged-exceptions"));
17
17
  const catch_error_pattern_1 = tslib_1.__importDefault(require("./rules/catch-error-pattern"));
18
18
  const no_shell_substitution_1 = tslib_1.__importDefault(require("./rules/no-shell-substitution"));
19
+ const no_symbol_di_tokens_1 = tslib_1.__importDefault(require("./rules/no-symbol-di-tokens"));
19
20
  const REQUIRED_FIELDS = ['name', 'description', 'scope', 'files', 'check'];
20
21
  const VALID_SCOPES = new Set(['edit', 'file', 'bash']);
21
22
  const BUILT_IN_RULE_MAP = {
@@ -28,6 +29,7 @@ const BUILT_IN_RULE_MAP = {
28
29
  'no-unmanaged-exceptions': no_unmanaged_exceptions_1.default,
29
30
  'catch-error-pattern': catch_error_pattern_1.default,
30
31
  'no-shell-substitution': no_shell_substitution_1.default,
32
+ 'no-symbol-di-tokens': no_symbol_di_tokens_1.default,
31
33
  };
32
34
  function loadRules(config, workspaceRoot) {
33
35
  const builtIns = loadBuiltInRules();
@@ -1 +1 @@
1
- {"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport type { Rule, ResolvedConfig } from './types';\nimport { toError } from './to-error';\nimport { builtInRuleNames } from './rules/index';\nimport noAnyUnknown from './rules/no-any-unknown';\nimport noImplicitAny from './rules/no-implicit-any';\nimport maxFileLines from './rules/max-file-lines';\nimport fileLocation from './rules/file-location';\nimport noDestructure from './rules/no-destructure';\nimport requireReturnType from './rules/require-return-type';\nimport noUnmanagedExceptions from './rules/no-unmanaged-exceptions';\nimport catchErrorPattern from './rules/catch-error-pattern';\nimport noShellSubstitution from './rules/no-shell-substitution';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\nconst BUILT_IN_RULE_MAP: Record<string, Rule> = {\n 'no-any-unknown': noAnyUnknown as Rule,\n 'no-implicit-any': noImplicitAny as Rule,\n 'max-file-lines': maxFileLines as Rule,\n 'file-location': fileLocation as Rule,\n 'no-destructure': noDestructure as Rule,\n 'require-return-type': requireReturnType as Rule,\n 'no-unmanaged-exceptions': noUnmanagedExceptions as Rule,\n 'catch-error-pattern': catchErrorPattern as Rule,\n 'no-shell-substitution': noShellSubstitution as Rule,\n};\n\nexport function loadRules(config: ResolvedConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules();\n const custom = loadCustomRules(config.rulesDir, workspaceRoot);\n const all: Rule[] = [...builtIns, ...custom];\n return all.filter((rule: Rule) => validateRule(rule));\n}\n\nfunction loadBuiltInRules(): Rule[] {\n const modules: Rule[] = [];\n for (const name of builtInRuleNames) {\n const mod = BUILT_IN_RULE_MAP[name];\n if (mod) {\n modules.push(mod);\n } else {\n process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\\n`);\n }\n }\n return modules;\n}\n\nfunction loadCustomRules(rulesDirs: readonly string[], workspaceRoot: string): Rule[] {\n const modules: Rule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n process.stderr.write(`[ai-hooks] cannot read rulesDir ${absDir}: ${error.message}\\n`);\n continue;\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n modules.push(mod.default || mod);\n } catch (err: unknown) {\n const error = toError(err);\n process.stderr.write(`[ai-hooks] failed to load custom rule ${full}: ${error.message}\\n`);\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is Rule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n"]}
1
+ {"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":";;AAiCA,8BAKC;AA6ED,kCAGC;;AAtHD,+CAAyB;AACzB,mDAA6B;AAG7B,yCAAqC;AACrC,yCAAiD;AACjD,oFAAkD;AAClD,sFAAoD;AACpD,oFAAkD;AAClD,kFAAiD;AACjD,oFAAmD;AACnD,8FAA4D;AAC5D,sGAAoE;AACpE,8FAA4D;AAC5D,kGAAgE;AAChE,8FAA2D;AAE3D,MAAM,eAAe,GAAsB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC9F,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEvD,MAAM,iBAAiB,GAAyB;IAC5C,gBAAgB,EAAE,wBAAoB;IACtC,iBAAiB,EAAE,yBAAqB;IACxC,gBAAgB,EAAE,wBAAoB;IACtC,eAAe,EAAE,uBAAoB;IACrC,gBAAgB,EAAE,wBAAqB;IACvC,qBAAqB,EAAE,6BAAyB;IAChD,yBAAyB,EAAE,iCAA6B;IACxD,qBAAqB,EAAE,6BAAyB;IAChD,uBAAuB,EAAE,+BAA2B;IACpD,qBAAqB,EAAE,6BAAwB;CAClD,CAAC;AAEF,SAAgB,SAAS,CAAC,MAAsB,EAAE,aAAqB;IACnE,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IAC/D,MAAM,GAAG,GAAW,CAAC,GAAG,QAAQ,EAAE,GAAG,MAAM,CAAC,CAAC;IAC7C,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,IAAU,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,gBAAgB;IACrB,MAAM,OAAO,GAAW,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,wBAAgB,EAAE,CAAC;QAClC,MAAM,GAAG,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,GAAG,EAAE,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,IAAI,IAAI,CAAC,CAAC;QACxE,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,SAAS,eAAe,CAAC,SAA4B,EAAE,aAAqB;IACxE,MAAM,OAAO,GAAW,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC1E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,MAAM,IAAI,CAAC,CAAC;YACnE,SAAS;QACb,CAAC;QACD,IAAI,OAAiB,CAAC;QACtB,8DAA8D;QAC9D,IAAI,CAAC;YACD,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QACtE,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,MAAM,KAAK,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YACtF,SAAS;QACb,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YACtC,8DAA8D;YAC9D,IAAI,CAAC;gBACD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,CAAC;YACrC,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;gBAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yCAAyC,IAAI,KAAK,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YAC9F,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,8FAA8F;AAC9F,SAAS,YAAY,CAAC,IAAa;IAC/B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;QACrE,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,gFAAgF;IAChF,MAAM,GAAG,GAAG,IAA+B,CAAC;IAC5C,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;QAClC,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;YACzE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,IAAI,6BAA6B,KAAK,IAAI,CAAC,CAAC;YACrF,OAAO,KAAK,CAAC;QACjB,CAAC;IACL,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAW,CAAC,EAAE,CAAC;QAC5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,GAAG,CAAC,MAAM,CAAC,wBAAwB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;QACtG,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,GAAG,CAAC,MAAM,CAAC,4BAA4B,CAAC,CAAC;QAClF,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,UAAU,EAAE,CAAC;QACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,GAAG,CAAC,MAAM,CAAC,8BAA8B,CAAC,CAAC;QACpF,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAgB,WAAW,CAAC,OAAe,EAAE,QAAgB;IACzD,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAChC,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACb,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBACzB,EAAE,IAAI,IAAI,CAAC;gBACX,CAAC,IAAI,CAAC,CAAC;gBACP,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG;oBAAE,CAAC,IAAI,CAAC,CAAC;gBAC/B,SAAS;YACb,CAAC;YACD,EAAE,IAAI,OAAO,CAAC;YACd,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACb,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACb,EAAE,IAAI,MAAM,CAAC;YACb,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACb,CAAC;QACD,IAAI,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YAC/B,EAAE,IAAI,IAAI,GAAG,EAAE,CAAC;YAChB,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACb,CAAC;QACD,EAAE,IAAI,EAAE,CAAC;QACT,CAAC,IAAI,CAAC,CAAC;IACX,CAAC;IACD,OAAO,IAAI,MAAM,CAAC,GAAG,GAAG,EAAE,GAAG,GAAG,CAAC,CAAC;AACtC,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport type { Rule, ResolvedConfig } from './types';\nimport { toError } from './to-error';\nimport { builtInRuleNames } from './rules/index';\nimport noAnyUnknown from './rules/no-any-unknown';\nimport noImplicitAny from './rules/no-implicit-any';\nimport maxFileLines from './rules/max-file-lines';\nimport fileLocation from './rules/file-location';\nimport noDestructure from './rules/no-destructure';\nimport requireReturnType from './rules/require-return-type';\nimport noUnmanagedExceptions from './rules/no-unmanaged-exceptions';\nimport catchErrorPattern from './rules/catch-error-pattern';\nimport noShellSubstitution from './rules/no-shell-substitution';\nimport noSymbolDiTokens from './rules/no-symbol-di-tokens';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\nconst BUILT_IN_RULE_MAP: Record<string, Rule> = {\n 'no-any-unknown': noAnyUnknown as Rule,\n 'no-implicit-any': noImplicitAny as Rule,\n 'max-file-lines': maxFileLines as Rule,\n 'file-location': fileLocation as Rule,\n 'no-destructure': noDestructure as Rule,\n 'require-return-type': requireReturnType as Rule,\n 'no-unmanaged-exceptions': noUnmanagedExceptions as Rule,\n 'catch-error-pattern': catchErrorPattern as Rule,\n 'no-shell-substitution': noShellSubstitution as Rule,\n 'no-symbol-di-tokens': noSymbolDiTokens as Rule,\n};\n\nexport function loadRules(config: ResolvedConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules();\n const custom = loadCustomRules(config.rulesDir, workspaceRoot);\n const all: Rule[] = [...builtIns, ...custom];\n return all.filter((rule: Rule) => validateRule(rule));\n}\n\nfunction loadBuiltInRules(): Rule[] {\n const modules: Rule[] = [];\n for (const name of builtInRuleNames) {\n const mod = BUILT_IN_RULE_MAP[name];\n if (mod) {\n modules.push(mod);\n } else {\n process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\\n`);\n }\n }\n return modules;\n}\n\nfunction loadCustomRules(rulesDirs: readonly string[], workspaceRoot: string): Rule[] {\n const modules: Rule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n process.stderr.write(`[ai-hooks] cannot read rulesDir ${absDir}: ${error.message}\\n`);\n continue;\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n modules.push(mod.default || mod);\n } catch (err: unknown) {\n const error = toError(err);\n process.stderr.write(`[ai-hooks] failed to load custom rule ${full}: ${error.message}\\n`);\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is Rule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n"]}
@@ -11,5 +11,6 @@ exports.builtInRuleNames = [
11
11
  'no-unmanaged-exceptions',
12
12
  'catch-error-pattern',
13
13
  'no-shell-substitution',
14
+ 'no-symbol-di-tokens',
14
15
  ];
15
16
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/index.ts"],"names":[],"mappings":";;;AAAa,QAAA,gBAAgB,GAAsB;IAC/C,gBAAgB;IAChB,iBAAiB;IACjB,gBAAgB;IAChB,eAAe;IACf,gBAAgB;IAChB,qBAAqB;IACrB,yBAAyB;IACzB,qBAAqB;IACrB,uBAAuB;CAC1B,CAAC","sourcesContent":["export const builtInRuleNames: readonly string[] = [\n 'no-any-unknown',\n 'no-implicit-any',\n 'max-file-lines',\n 'file-location',\n 'no-destructure',\n 'require-return-type',\n 'no-unmanaged-exceptions',\n 'catch-error-pattern',\n 'no-shell-substitution',\n];\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/index.ts"],"names":[],"mappings":";;;AAAa,QAAA,gBAAgB,GAAsB;IAC/C,gBAAgB;IAChB,iBAAiB;IACjB,gBAAgB;IAChB,eAAe;IACf,gBAAgB;IAChB,qBAAqB;IACrB,yBAAyB;IACzB,qBAAqB;IACrB,uBAAuB;IACvB,qBAAqB;CACxB,CAAC","sourcesContent":["export const builtInRuleNames: readonly string[] = [\n 'no-any-unknown',\n 'no-implicit-any',\n 'max-file-lines',\n 'file-location',\n 'no-destructure',\n 'require-return-type',\n 'no-unmanaged-exceptions',\n 'catch-error-pattern',\n 'no-shell-substitution',\n 'no-symbol-di-tokens',\n];\n"]}
@@ -0,0 +1,3 @@
1
+ import type { EditRule } from '../types';
2
+ declare const noSymbolDiTokensRule: EditRule;
3
+ export default noSymbolDiTokensRule;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const types_1 = require("../types");
4
+ const SYMBOL_DI_REGEX = /=\s*Symbol(?:\.for)?\(/;
5
+ const ALLOWED_PATHS = [
6
+ /^libraries\/apis\//,
7
+ /^libraries\/apis-external\//,
8
+ /^packages\/http\/http-api\//,
9
+ ];
10
+ const TEST_PATHS = [/\.test\.ts$/, /\.spec\.ts$/, /__tests__\//];
11
+ function isAllowedPath(relativePath) {
12
+ return ALLOWED_PATHS.some((re) => re.test(relativePath)) ||
13
+ TEST_PATHS.some((re) => re.test(relativePath));
14
+ }
15
+ const noSymbolDiTokensRule = {
16
+ name: 'no-symbol-di-tokens',
17
+ description: 'Disallow Symbol() DI tokens outside api(-external) packages. Use @provideSingleton() + inject-by-type instead.',
18
+ scope: 'edit',
19
+ files: ['**/*.ts', '**/*.tsx'],
20
+ defaultOptions: {},
21
+ fixHint: [
22
+ 'Do not create a dependency-injection token with Symbol(). Symbol() for DI is allowed in ONLY two places:',
23
+ ' 1. INTERNAL apis (libraries/apis/**) — bind the generated client to its API.',
24
+ ' 2. EXTERNAL apis (libraries/apis-external/**) — bind the impl to its API (impl wraps the external SDK).',
25
+ 'EVERYWHERE ELSE, do NOT define a Symbol token and do NOT use @inject(TOKEN).',
26
+ 'Instead: annotate the implementation class with @provideSingleton() and inject it by its concrete class TYPE,',
27
+ 'e.g. constructor(private readonly identityResolver: IdentityResolver) {} // no Symbol, no @inject.',
28
+ 'For a swappable default-impl-behind-an-interface, use @provideSingletonAs(TOKEN) — only inside libraries/apis(-external).',
29
+ 'If this specific line is a legitimate binding or framework primitive, append: // webpieces-disable no-symbol-di-tokens -- <reason>',
30
+ ],
31
+ check(ctx) {
32
+ if (isAllowedPath(ctx.relativePath))
33
+ return [];
34
+ const violations = [];
35
+ for (let i = 0; i < ctx.strippedLines.length; i += 1) {
36
+ const stripped = ctx.strippedLines[i];
37
+ if (!SYMBOL_DI_REGEX.test(stripped ?? ''))
38
+ continue;
39
+ const lineNum = i + 1;
40
+ if (ctx.isLineDisabled(lineNum, 'no-symbol-di-tokens'))
41
+ continue;
42
+ violations.push(new types_1.Violation(lineNum, ctx.lines[i]?.trim() ?? '', 'Symbol() used as a DI token. Use @provideSingleton() + inject by concrete class type instead.'));
43
+ }
44
+ return violations;
45
+ },
46
+ };
47
+ exports.default = noSymbolDiTokensRule;
48
+ //# sourceMappingURL=no-symbol-di-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-symbol-di-tokens.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/no-symbol-di-tokens.ts"],"names":[],"mappings":";;AACA,oCAA0C;AAE1C,MAAM,eAAe,GAAG,wBAAwB,CAAC;AAEjD,MAAM,aAAa,GAAa;IAC5B,oBAAoB;IACpB,6BAA6B;IAC7B,6BAA6B;CAChC,CAAC;AAEF,MAAM,UAAU,GAAa,CAAC,aAAa,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;AAE3E,SAAS,aAAa,CAAC,YAAoB;IACvC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC5D,UAAU,CAAC,IAAI,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,oBAAoB,GAAa;IACnC,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EAAE,gHAAgH;IAC7H,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;IAC9B,cAAc,EAAE,EAAE;IAClB,OAAO,EAAE;QACL,0GAA0G;QAC1G,yFAAyF;QACzF,4GAA4G;QAC5G,8EAA8E;QAC9E,+GAA+G;QAC/G,uGAAuG;QACvG,2HAA2H;QAC3H,qIAAqI;KACxI;IAED,KAAK,CAAC,GAAgB;QAClB,IAAI,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC;YAAE,OAAO,EAAE,CAAC;QAE/C,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,eAAe,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;gBAAE,SAAS;YACpD,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,cAAc,CAAC,OAAO,EAAE,qBAAqB,CAAC;gBAAE,SAAS;YACjE,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAC,CACjB,OAAO,EACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAC1B,+FAA+F,CAClG,CAAC,CAAC;QACP,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAC;AAEF,kBAAe,oBAAoB,CAAC","sourcesContent":["import type { EditRule, EditContext, Violation } from '../types';\nimport { Violation as V } from '../types';\n\nconst SYMBOL_DI_REGEX = /=\\s*Symbol(?:\\.for)?\\(/;\n\nconst ALLOWED_PATHS: RegExp[] = [\n /^libraries\\/apis\\//,\n /^libraries\\/apis-external\\//,\n /^packages\\/http\\/http-api\\//,\n];\n\nconst TEST_PATHS: RegExp[] = [/\\.test\\.ts$/, /\\.spec\\.ts$/, /__tests__\\//];\n\nfunction isAllowedPath(relativePath: string): boolean {\n return ALLOWED_PATHS.some((re: RegExp) => re.test(relativePath)) ||\n TEST_PATHS.some((re: RegExp) => re.test(relativePath));\n}\n\nconst noSymbolDiTokensRule: EditRule = {\n name: 'no-symbol-di-tokens',\n description: 'Disallow Symbol() DI tokens outside api(-external) packages. Use @provideSingleton() + inject-by-type instead.',\n scope: 'edit',\n files: ['**/*.ts', '**/*.tsx'],\n defaultOptions: {},\n fixHint: [\n 'Do not create a dependency-injection token with Symbol(). Symbol() for DI is allowed in ONLY two places:',\n ' 1. INTERNAL apis (libraries/apis/**) — bind the generated client to its API.',\n ' 2. EXTERNAL apis (libraries/apis-external/**) — bind the impl to its API (impl wraps the external SDK).',\n 'EVERYWHERE ELSE, do NOT define a Symbol token and do NOT use @inject(TOKEN).',\n 'Instead: annotate the implementation class with @provideSingleton() and inject it by its concrete class TYPE,',\n 'e.g. constructor(private readonly identityResolver: IdentityResolver) {} // no Symbol, no @inject.',\n 'For a swappable default-impl-behind-an-interface, use @provideSingletonAs(TOKEN) — only inside libraries/apis(-external).',\n 'If this specific line is a legitimate binding or framework primitive, append: // webpieces-disable no-symbol-di-tokens -- <reason>',\n ],\n\n check(ctx: EditContext): readonly Violation[] {\n if (isAllowedPath(ctx.relativePath)) return [];\n\n const violations: V[] = [];\n for (let i = 0; i < ctx.strippedLines.length; i += 1) {\n const stripped = ctx.strippedLines[i];\n if (!SYMBOL_DI_REGEX.test(stripped ?? '')) continue;\n const lineNum = i + 1;\n if (ctx.isLineDisabled(lineNum, 'no-symbol-di-tokens')) continue;\n violations.push(new V(\n lineNum,\n ctx.lines[i]?.trim() ?? '',\n 'Symbol() used as a DI token. Use @provideSingleton() + inject by concrete class type instead.',\n ));\n }\n return violations;\n },\n};\n\nexport default noSymbolDiTokensRule;\n"]}