@webpieces/ai-hook-rules 0.3.261 → 0.3.263

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.261",
3
+ "version": "0.3.263",
4
4
  "description": "Pluggable write-time validation framework for AI coding agents (@webpieces/ai-hook-rules). Claude Code PreToolUse + openclaw before_tool_call adapters share one rule engine.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -35,7 +35,7 @@
35
35
  "directory": "packages/tooling/ai-hook-rules"
36
36
  },
37
37
  "dependencies": {
38
- "@webpieces/rules-config": "0.3.261"
38
+ "@webpieces/rules-config": "0.3.263"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public"
@@ -23,7 +23,7 @@ const throw_cause_required_1 = require("./rules/throw-cause-required");
23
23
  const no_symbol_di_tokens_1 = require("./rules/no-symbol-di-tokens");
24
24
  const enforce_controller_naming_1 = require("./rules/enforce-controller-naming");
25
25
  const branch_creation_guard_1 = require("./rules/branch-creation-guard");
26
- const pr_creation_guard_1 = require("./rules/pr-creation-guard");
26
+ const pr_creation_or_push_guard_1 = require("./rules/pr-creation-or-push-guard");
27
27
  const merge_in_progress_guard_1 = require("./rules/merge-in-progress-guard");
28
28
  const pr_merge_guard_1 = require("./rules/pr-merge-guard");
29
29
  const redirect_how_to_merge_main_1 = require("./rules/redirect-how-to-merge-main");
@@ -45,7 +45,7 @@ const BUILT_IN_RULE_MAP = {
45
45
  'no-symbol-di-tokens': (c) => new no_symbol_di_tokens_1.NoSymbolDiTokensRule(c),
46
46
  'enforce-controller-naming': (c) => new enforce_controller_naming_1.EnforceControllerNamingRule(c),
47
47
  'branch-creation-guard': (c) => new branch_creation_guard_1.BranchCreationGuardRule(c),
48
- 'pr-creation-guard': (c) => new pr_creation_guard_1.PrCreationGuardRule(c),
48
+ 'pr-creation-or-push-guard': (c) => new pr_creation_or_push_guard_1.PrCreationOrPushGuardRule(c),
49
49
  'merge-in-progress-guard': (c) => new merge_in_progress_guard_1.MergeInProgressGuardRule(c),
50
50
  'pr-merge-guard': (c) => new pr_merge_guard_1.PrMergeGuardRule(c),
51
51
  'redirect-how-to-merge-main': (c) => new redirect_how_to_merge_main_1.RedirectHowToMergeMainRule(c),
@@ -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 {\n BaseRuleConfig, RuleOptions, WebpiecesRulesConfig,\n NoAnyUnknownConfig, NoImplicitAnyConfig, MaxFileLinesConfig, ValidateTsInSrcConfig,\n NoDestructureConfig, RequireReturnTypeConfig, NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig, ThrowCauseRequiredConfig,\n NoSymbolDiTokensConfig, EnforceControllerNamingConfig, BranchCreationGuardConfig, PrCreationGuardConfig,\n MergeInProgressGuardConfig, PrMergeGuardConfig, RedirectHowToMergeMainConfig,\n NoJsFilesConfig, FeatureBranchGuardConfig, MatchRuleConfig,\n} from '@webpieces/rules-config';\n\nimport type { Rule, PlainRule } from './types';\nimport { InformAiError } from './types';\nimport { toError } from './to-error';\nimport { EmptyRuleConfig } from './rule-base';\nimport { CustomRuleAdapter } from './custom-rule-adapter';\nimport { builtInRuleNames } from './rules/index';\nimport { NoAnyUnknownRule } from './rules/no-any-unknown';\nimport { NoImplicitAnyRule } from './rules/no-implicit-any';\nimport { MaxFileLinesRule } from './rules/max-file-lines';\nimport { ValidateTsInSrcRule } from './rules/validate-ts-in-src';\nimport { NoDestructureRule } from './rules/no-destructure';\nimport { RequireReturnTypeRule } from './rules/require-return-type';\nimport { NoUnmanagedExceptionsRule } from './rules/no-unmanaged-exceptions';\nimport { CatchErrorPatternRule } from './rules/catch-error-pattern';\nimport { ThrowCauseRequiredRule } from './rules/throw-cause-required';\nimport { NoSymbolDiTokensRule } from './rules/no-symbol-di-tokens';\nimport { EnforceControllerNamingRule } from './rules/enforce-controller-naming';\nimport { BranchCreationGuardRule } from './rules/branch-creation-guard';\nimport { PrCreationGuardRule } from './rules/pr-creation-guard';\nimport { MergeInProgressGuardRule } from './rules/merge-in-progress-guard';\nimport { PrMergeGuardRule } from './rules/pr-merge-guard';\nimport { RedirectHowToMergeMainRule } from './rules/redirect-how-to-merge-main';\nimport { NoJsFilesRule } from './rules/no-js-files';\nimport { FeatureBranchGuardRule } from './rules/feature-branch-guard';\nimport { MatchRule } from './rules/match-rule';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\n// Each built-in rule is constructed from its typed *Config (the entry in webpieces.config.json).\n// The config arrives as a plain object structurally typed as the *Config class, so the `as`\n// narrows the shared BaseRuleConfig param back to the concrete config the rule consumes.\ntype RuleFactory = (config: BaseRuleConfig) => Rule;\n\nconst BUILT_IN_RULE_MAP: Record<string, RuleFactory> = {\n 'no-any-unknown': (c: BaseRuleConfig) => new NoAnyUnknownRule(c as NoAnyUnknownConfig),\n 'no-implicit-any': (c: BaseRuleConfig) => new NoImplicitAnyRule(c as NoImplicitAnyConfig),\n 'max-file-lines': (c: BaseRuleConfig) => new MaxFileLinesRule(c as MaxFileLinesConfig),\n 'validate-ts-in-src': (c: BaseRuleConfig) => new ValidateTsInSrcRule(c as ValidateTsInSrcConfig),\n 'no-destructure': (c: BaseRuleConfig) => new NoDestructureRule(c as NoDestructureConfig),\n 'require-return-type': (c: BaseRuleConfig) => new RequireReturnTypeRule(c as RequireReturnTypeConfig),\n 'no-unmanaged-exceptions': (c: BaseRuleConfig) => new NoUnmanagedExceptionsRule(c as NoUnmanagedExceptionsConfig),\n 'catch-error-pattern': (c: BaseRuleConfig) => new CatchErrorPatternRule(c as CatchErrorPatternConfig),\n 'throw-cause-required': (c: BaseRuleConfig) => new ThrowCauseRequiredRule(c as ThrowCauseRequiredConfig),\n 'no-symbol-di-tokens': (c: BaseRuleConfig) => new NoSymbolDiTokensRule(c as NoSymbolDiTokensConfig),\n 'enforce-controller-naming': (c: BaseRuleConfig) => new EnforceControllerNamingRule(c as EnforceControllerNamingConfig),\n 'branch-creation-guard': (c: BaseRuleConfig) => new BranchCreationGuardRule(c as BranchCreationGuardConfig),\n 'pr-creation-guard': (c: BaseRuleConfig) => new PrCreationGuardRule(c as PrCreationGuardConfig),\n 'merge-in-progress-guard': (c: BaseRuleConfig) => new MergeInProgressGuardRule(c as MergeInProgressGuardConfig),\n 'pr-merge-guard': (c: BaseRuleConfig) => new PrMergeGuardRule(c as PrMergeGuardConfig),\n 'redirect-how-to-merge-main': (c: BaseRuleConfig) => new RedirectHowToMergeMainRule(c as RedirectHowToMergeMainConfig),\n 'no-js-files': (c: BaseRuleConfig) => new NoJsFilesRule(c as NoJsFilesConfig),\n 'feature-branch-guard': (c: BaseRuleConfig) => new FeatureBranchGuardRule(c as FeatureBranchGuardConfig),\n};\n\n// Index the typed config by rule name. Each value is the rule's *Config (a plain object from\n// JSON), or undefined when the rule has no entry yet (the sync check reports those).\nfunction asConfigMap(config: WebpiecesRulesConfig): Record<string, BaseRuleConfig | undefined> {\n // webpieces-disable no-any-unknown -- index the typed config by dynamic rule name\n return config as unknown as Record<string, BaseRuleConfig | undefined>;\n}\n\nexport function loadRules(config: WebpiecesRulesConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules(config);\n const custom = loadCustomRules(config, workspaceRoot);\n return [...builtIns, ...custom];\n}\n\n// One MatchRule per entry of the `match-rules` array. Kept separate from loadRules (built-ins/custom)\n// because match-rules live in their own validated section — they must NOT flow through the\n// config-sync check, which compares rule names against the `rules`/`hookGuards` map.\nexport function loadMatchRules(matchRules: readonly MatchRuleConfig[]): Rule[] {\n return matchRules.map((c: MatchRuleConfig) => new MatchRule(c));\n}\n\nfunction loadBuiltInRules(config: WebpiecesRulesConfig): Rule[] {\n const map = asConfigMap(config);\n const rules: Rule[] = [];\n for (const name of builtInRuleNames) {\n const factory = BUILT_IN_RULE_MAP[name];\n if (!factory) {\n process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\\n`);\n continue;\n }\n const ruleConfig = map[name] ?? new EmptyRuleConfig();\n rules.push(factory(ruleConfig));\n }\n return rules;\n}\n\nfunction loadCustomRules(config: WebpiecesRulesConfig, workspaceRoot: string): Rule[] {\n const dirs = config.rulesDir ?? [];\n // webpieces-disable no-any-unknown -- index the typed config by dynamic custom-rule name\n const map = config as unknown as Record<string, RuleOptions | undefined>;\n const rules: Rule[] = [];\n for (const plain of loadCustomPlainRules(dirs, workspaceRoot)) {\n const rawConfig = map[plain.name] ?? {};\n rules.push(new CustomRuleAdapter(plain, rawConfig));\n }\n return rules;\n}\n\nfunction loadCustomPlainRules(rulesDirs: readonly string[], workspaceRoot: string): PlainRule[] {\n const modules: PlainRule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e: string) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot read custom rules directory '${absDir}'`, { cause: error });\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n const candidate = mod.default || mod;\n if (validateRule(candidate)) modules.push(candidate);\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot load custom rule '${full}'`, { cause: error });\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is PlainRule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n"]}
1
+ {"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport {\n BaseRuleConfig, RuleOptions, WebpiecesRulesConfig,\n NoAnyUnknownConfig, NoImplicitAnyConfig, MaxFileLinesConfig, ValidateTsInSrcConfig,\n NoDestructureConfig, RequireReturnTypeConfig, NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig, ThrowCauseRequiredConfig,\n NoSymbolDiTokensConfig, EnforceControllerNamingConfig, BranchCreationGuardConfig, PrCreationOrPushGuardConfig,\n MergeInProgressGuardConfig, PrMergeGuardConfig, RedirectHowToMergeMainConfig,\n NoJsFilesConfig, FeatureBranchGuardConfig, MatchRuleConfig,\n} from '@webpieces/rules-config';\n\nimport type { Rule, PlainRule } from './types';\nimport { InformAiError } from './types';\nimport { toError } from './to-error';\nimport { EmptyRuleConfig } from './rule-base';\nimport { CustomRuleAdapter } from './custom-rule-adapter';\nimport { builtInRuleNames } from './rules/index';\nimport { NoAnyUnknownRule } from './rules/no-any-unknown';\nimport { NoImplicitAnyRule } from './rules/no-implicit-any';\nimport { MaxFileLinesRule } from './rules/max-file-lines';\nimport { ValidateTsInSrcRule } from './rules/validate-ts-in-src';\nimport { NoDestructureRule } from './rules/no-destructure';\nimport { RequireReturnTypeRule } from './rules/require-return-type';\nimport { NoUnmanagedExceptionsRule } from './rules/no-unmanaged-exceptions';\nimport { CatchErrorPatternRule } from './rules/catch-error-pattern';\nimport { ThrowCauseRequiredRule } from './rules/throw-cause-required';\nimport { NoSymbolDiTokensRule } from './rules/no-symbol-di-tokens';\nimport { EnforceControllerNamingRule } from './rules/enforce-controller-naming';\nimport { BranchCreationGuardRule } from './rules/branch-creation-guard';\nimport { PrCreationOrPushGuardRule } from './rules/pr-creation-or-push-guard';\nimport { MergeInProgressGuardRule } from './rules/merge-in-progress-guard';\nimport { PrMergeGuardRule } from './rules/pr-merge-guard';\nimport { RedirectHowToMergeMainRule } from './rules/redirect-how-to-merge-main';\nimport { NoJsFilesRule } from './rules/no-js-files';\nimport { FeatureBranchGuardRule } from './rules/feature-branch-guard';\nimport { MatchRule } from './rules/match-rule';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\n// Each built-in rule is constructed from its typed *Config (the entry in webpieces.config.json).\n// The config arrives as a plain object structurally typed as the *Config class, so the `as`\n// narrows the shared BaseRuleConfig param back to the concrete config the rule consumes.\ntype RuleFactory = (config: BaseRuleConfig) => Rule;\n\nconst BUILT_IN_RULE_MAP: Record<string, RuleFactory> = {\n 'no-any-unknown': (c: BaseRuleConfig) => new NoAnyUnknownRule(c as NoAnyUnknownConfig),\n 'no-implicit-any': (c: BaseRuleConfig) => new NoImplicitAnyRule(c as NoImplicitAnyConfig),\n 'max-file-lines': (c: BaseRuleConfig) => new MaxFileLinesRule(c as MaxFileLinesConfig),\n 'validate-ts-in-src': (c: BaseRuleConfig) => new ValidateTsInSrcRule(c as ValidateTsInSrcConfig),\n 'no-destructure': (c: BaseRuleConfig) => new NoDestructureRule(c as NoDestructureConfig),\n 'require-return-type': (c: BaseRuleConfig) => new RequireReturnTypeRule(c as RequireReturnTypeConfig),\n 'no-unmanaged-exceptions': (c: BaseRuleConfig) => new NoUnmanagedExceptionsRule(c as NoUnmanagedExceptionsConfig),\n 'catch-error-pattern': (c: BaseRuleConfig) => new CatchErrorPatternRule(c as CatchErrorPatternConfig),\n 'throw-cause-required': (c: BaseRuleConfig) => new ThrowCauseRequiredRule(c as ThrowCauseRequiredConfig),\n 'no-symbol-di-tokens': (c: BaseRuleConfig) => new NoSymbolDiTokensRule(c as NoSymbolDiTokensConfig),\n 'enforce-controller-naming': (c: BaseRuleConfig) => new EnforceControllerNamingRule(c as EnforceControllerNamingConfig),\n 'branch-creation-guard': (c: BaseRuleConfig) => new BranchCreationGuardRule(c as BranchCreationGuardConfig),\n 'pr-creation-or-push-guard': (c: BaseRuleConfig) => new PrCreationOrPushGuardRule(c as PrCreationOrPushGuardConfig),\n 'merge-in-progress-guard': (c: BaseRuleConfig) => new MergeInProgressGuardRule(c as MergeInProgressGuardConfig),\n 'pr-merge-guard': (c: BaseRuleConfig) => new PrMergeGuardRule(c as PrMergeGuardConfig),\n 'redirect-how-to-merge-main': (c: BaseRuleConfig) => new RedirectHowToMergeMainRule(c as RedirectHowToMergeMainConfig),\n 'no-js-files': (c: BaseRuleConfig) => new NoJsFilesRule(c as NoJsFilesConfig),\n 'feature-branch-guard': (c: BaseRuleConfig) => new FeatureBranchGuardRule(c as FeatureBranchGuardConfig),\n};\n\n// Index the typed config by rule name. Each value is the rule's *Config (a plain object from\n// JSON), or undefined when the rule has no entry yet (the sync check reports those).\nfunction asConfigMap(config: WebpiecesRulesConfig): Record<string, BaseRuleConfig | undefined> {\n // webpieces-disable no-any-unknown -- index the typed config by dynamic rule name\n return config as unknown as Record<string, BaseRuleConfig | undefined>;\n}\n\nexport function loadRules(config: WebpiecesRulesConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules(config);\n const custom = loadCustomRules(config, workspaceRoot);\n return [...builtIns, ...custom];\n}\n\n// One MatchRule per entry of the `match-rules` array. Kept separate from loadRules (built-ins/custom)\n// because match-rules live in their own validated section — they must NOT flow through the\n// config-sync check, which compares rule names against the `rules`/`hookGuards` map.\nexport function loadMatchRules(matchRules: readonly MatchRuleConfig[]): Rule[] {\n return matchRules.map((c: MatchRuleConfig) => new MatchRule(c));\n}\n\nfunction loadBuiltInRules(config: WebpiecesRulesConfig): Rule[] {\n const map = asConfigMap(config);\n const rules: Rule[] = [];\n for (const name of builtInRuleNames) {\n const factory = BUILT_IN_RULE_MAP[name];\n if (!factory) {\n process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\\n`);\n continue;\n }\n const ruleConfig = map[name] ?? new EmptyRuleConfig();\n rules.push(factory(ruleConfig));\n }\n return rules;\n}\n\nfunction loadCustomRules(config: WebpiecesRulesConfig, workspaceRoot: string): Rule[] {\n const dirs = config.rulesDir ?? [];\n // webpieces-disable no-any-unknown -- index the typed config by dynamic custom-rule name\n const map = config as unknown as Record<string, RuleOptions | undefined>;\n const rules: Rule[] = [];\n for (const plain of loadCustomPlainRules(dirs, workspaceRoot)) {\n const rawConfig = map[plain.name] ?? {};\n rules.push(new CustomRuleAdapter(plain, rawConfig));\n }\n return rules;\n}\n\nfunction loadCustomPlainRules(rulesDirs: readonly string[], workspaceRoot: string): PlainRule[] {\n const modules: PlainRule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e: string) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot read custom rules directory '${absDir}'`, { cause: error });\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n const candidate = mod.default || mod;\n if (validateRule(candidate)) modules.push(candidate);\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot load custom rule '${full}'`, { cause: error });\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is PlainRule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n"]}
@@ -15,7 +15,7 @@ exports.builtInRuleNames = [
15
15
  'no-symbol-di-tokens',
16
16
  'enforce-controller-naming',
17
17
  'branch-creation-guard',
18
- 'pr-creation-guard',
18
+ 'pr-creation-or-push-guard',
19
19
  'merge-in-progress-guard',
20
20
  'pr-merge-guard',
21
21
  'redirect-how-to-merge-main',
@@ -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,oBAAoB;IACpB,aAAa;IACb,gBAAgB;IAChB,qBAAqB;IACrB,yBAAyB;IACzB,qBAAqB;IACrB,sBAAsB;IACtB,qBAAqB;IACrB,2BAA2B;IAC3B,uBAAuB;IACvB,mBAAmB;IACnB,yBAAyB;IACzB,gBAAgB;IAChB,4BAA4B;IAC5B,sBAAsB;CACzB,CAAC","sourcesContent":["export const builtInRuleNames: readonly string[] = [\n 'no-any-unknown',\n 'no-implicit-any',\n 'max-file-lines',\n 'validate-ts-in-src',\n 'no-js-files',\n 'no-destructure',\n 'require-return-type',\n 'no-unmanaged-exceptions',\n 'catch-error-pattern',\n 'throw-cause-required',\n 'no-symbol-di-tokens',\n 'enforce-controller-naming',\n 'branch-creation-guard',\n 'pr-creation-guard',\n 'merge-in-progress-guard',\n 'pr-merge-guard',\n 'redirect-how-to-merge-main',\n 'feature-branch-guard',\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,oBAAoB;IACpB,aAAa;IACb,gBAAgB;IAChB,qBAAqB;IACrB,yBAAyB;IACzB,qBAAqB;IACrB,sBAAsB;IACtB,qBAAqB;IACrB,2BAA2B;IAC3B,uBAAuB;IACvB,2BAA2B;IAC3B,yBAAyB;IACzB,gBAAgB;IAChB,4BAA4B;IAC5B,sBAAsB;CACzB,CAAC","sourcesContent":["export const builtInRuleNames: readonly string[] = [\n 'no-any-unknown',\n 'no-implicit-any',\n 'max-file-lines',\n 'validate-ts-in-src',\n 'no-js-files',\n 'no-destructure',\n 'require-return-type',\n 'no-unmanaged-exceptions',\n 'catch-error-pattern',\n 'throw-cause-required',\n 'no-symbol-di-tokens',\n 'enforce-controller-naming',\n 'branch-creation-guard',\n 'pr-creation-or-push-guard',\n 'merge-in-progress-guard',\n 'pr-merge-guard',\n 'redirect-how-to-merge-main',\n 'feature-branch-guard',\n];\n"]}
@@ -0,0 +1,11 @@
1
+ import { PrCreationOrPushGuardConfig } from '@webpieces/rules-config';
2
+ import type { BashContext, Violation } from '../types';
3
+ import { BashRuleBase } from '../rule-base';
4
+ import { FixHint } from '../fix-hint';
5
+ export declare class PrCreationOrPushGuardRule extends BashRuleBase<PrCreationOrPushGuardConfig> {
6
+ private readonly upsertPrCommand;
7
+ constructor(config: PrCreationOrPushGuardConfig);
8
+ readonly description = "Block manual `git push` and direct PR creation/edit (gh pr / gh api / curl) so pushes and PRs go only through the gated upsert-pr command.";
9
+ get fixHint(): FixHint;
10
+ check(ctx: BashContext): readonly Violation[];
11
+ }
@@ -1,23 +1,31 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PrCreationGuardRule = void 0;
3
+ exports.PrCreationOrPushGuardRule = void 0;
4
4
  const types_1 = require("../types");
5
5
  const rule_base_1 = require("../rule-base");
6
6
  const fix_hint_1 = require("../fix-hint");
7
7
  const DEFAULT_UPSERT_PR_COMMAND = 'pnpm wp-start-upsert-pr';
8
8
  function fixHintFor(upsertPrCommand) {
9
- return new fix_hint_1.FixHint('Direct PR creation/update is blocked.', 'Create or update a PR ONLY via the gated flow:\n'
9
+ return new fix_hint_1.FixHint('Direct PR creation/update AND manual `git push` are blocked.', 'Never push or open/update a PR by hand — everything goes through the gated flow:\n'
10
10
  + ` ${upsertPrCommand}\n`
11
11
  + 'It updates the branch from main (3-point merge) and runs the real build (nx affected), then\n'
12
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'
13
+ + 'dashboard and creates/updates the PR and pushes for you (its internal `git push` is a child\n'
14
+ + 'process this hook never sees, so the gated commands are unaffected by this guard). A failing\n'
15
+ + 'build = no push and no PR.\n'
14
16
  + 'There is nothing to paste or attest to; the commands do the work.\n'
17
+ + 'If a HUMAN genuinely needs an out-of-band push (no PR), do NOT do it yourself — ask them to run\n'
18
+ + 'the push, since a manual push bypasses the build gate, review.json, and dashboard.\n'
15
19
  + 'Add this to your memory so you don\'t forget next time and waste tokens.');
16
20
  }
17
- // Detect every way an agent could open/update a PR directly, so the ONLY path left is the gated
18
- // flow (wp-start-upsert-pr → wp-finish-upsert-pr, whose internal `gh pr create` runs as a child
19
- // process the hook never sees). Read-only `gh pr list` / `gh api .../pulls` GET are intentionally allowed.
20
- function isDirectPrCreation(cmd) {
21
+ // Detect every way an agent could push or open/update a PR directly, so the ONLY path left is the
22
+ // gated flow (wp-start-upsert-pr → wp-finish-upsert-pr, whose internal `git push` / `gh pr create`
23
+ // run as child processes the hook never sees). Read-only `gh pr list` / `gh api .../pulls` GET are
24
+ // intentionally allowed.
25
+ function isBlockedPrOrPush(cmd) {
26
+ // A manual push is always blocked — the gated flow pushes for you behind the build gate.
27
+ if (/\bgit\s+push\b/.test(cmd))
28
+ return true;
21
29
  if (/\bgh\s+pr\s+(create|edit)\b/.test(cmd))
22
30
  return true;
23
31
  const ghApiPulls = /\bgh\s+api\b[^\n]*\/pulls\b/.test(cmd);
@@ -34,19 +42,19 @@ function truncate(s) {
34
42
  const MAX = 120;
35
43
  return s.length <= MAX ? s : s.slice(0, MAX) + '…';
36
44
  }
37
- class PrCreationGuardRule extends rule_base_1.BashRuleBase {
45
+ class PrCreationOrPushGuardRule extends rule_base_1.BashRuleBase {
38
46
  upsertPrCommand;
39
47
  constructor(config) {
40
- super(config, 'pr-creation-guard');
48
+ super(config, 'pr-creation-or-push-guard');
41
49
  this.upsertPrCommand = config.upsertPrCommand ?? DEFAULT_UPSERT_PR_COMMAND;
42
50
  }
43
- description = 'Block direct PR creation/edit (gh pr / gh api / curl) so PRs go only through the gated upsert-pr command.';
51
+ description = 'Block manual `git push` and direct PR creation/edit (gh pr / gh api / curl) so pushes and PRs go only through the gated upsert-pr command.';
44
52
  get fixHint() { return fixHintFor(this.upsertPrCommand); }
45
53
  check(ctx) {
46
- if (!isDirectPrCreation(ctx.command))
54
+ if (!isBlockedPrOrPush(ctx.command))
47
55
  return [];
48
56
  return [new types_1.Violation(1, truncate(ctx.command))];
49
57
  }
50
58
  }
51
- exports.PrCreationGuardRule = PrCreationGuardRule;
52
- //# sourceMappingURL=pr-creation-guard.js.map
59
+ exports.PrCreationOrPushGuardRule = PrCreationOrPushGuardRule;
60
+ //# sourceMappingURL=pr-creation-or-push-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr-creation-or-push-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/pr-creation-or-push-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,8DAA8D,EAC9D,oFAAoF;UAClF,KAAK,eAAe,IAAI;UACxB,+FAA+F;UAC/F,8FAA8F;UAC9F,iGAAiG;UACjG,gGAAgG;UAChG,8BAA8B;UAC9B,qEAAqE;UACrE,mGAAmG;UACnG,sFAAsF;UACtF,0EAA0E,CAC/E,CAAC;AACN,CAAC;AAED,kGAAkG;AAClG,mGAAmG;AACnG,mGAAmG;AACnG,yBAAyB;AACzB,SAAS,iBAAiB,CAAC,GAAW;IAClC,yFAAyF;IACzF,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAE5C,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,yBAA0B,SAAQ,wBAAyC;IACnE,eAAe,CAAS;IAEzC,YAAY,MAAmC;QAC3C,KAAK,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;QAC3C,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,eAAe,IAAI,yBAAyB,CAAC;IAC/E,CAAC;IAEQ,WAAW,GAAG,4IAA4I,CAAC;IACpK,IAAI,OAAO,KAAc,OAAO,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAEnE,KAAK,CAAC,GAAgB;QAClB,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,CAAC;QAC/C,OAAO,CAAC,IAAI,iBAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC;CACJ;AAfD,8DAeC","sourcesContent":["import { PrCreationOrPushGuardConfig } 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 AND manual `git push` are blocked.',\n 'Never push or open/update a PR by hand — everything goes through 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 — and pushes for you (its internal `git push` is a child\\n'\n + 'process this hook never sees, so the gated commands are unaffected by this guard). A failing\\n'\n + 'build = no push and no PR.\\n'\n + 'There is nothing to paste or attest to; the commands do the work.\\n'\n + 'If a HUMAN genuinely needs an out-of-band push (no PR), do NOT do it yourself — ask them to run\\n'\n + 'the push, since a manual push bypasses the build gate, review.json, and dashboard.\\n'\n + 'Add this to your memory so you don\\'t forget next time and waste tokens.',\n );\n}\n\n// Detect every way an agent could push or open/update a PR directly, so the ONLY path left is the\n// gated flow (wp-start-upsert-pr → wp-finish-upsert-pr, whose internal `git push` / `gh pr create`\n// run as child processes the hook never sees). Read-only `gh pr list` / `gh api .../pulls` GET are\n// intentionally allowed.\nfunction isBlockedPrOrPush(cmd: string): boolean {\n // A manual push is always blocked — the gated flow pushes for you behind the build gate.\n if (/\\bgit\\s+push\\b/.test(cmd)) return true;\n\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 PrCreationOrPushGuardRule extends BashRuleBase<PrCreationOrPushGuardConfig> {\n private readonly upsertPrCommand: string;\n\n constructor(config: PrCreationOrPushGuardConfig) {\n super(config, 'pr-creation-or-push-guard');\n this.upsertPrCommand = config.upsertPrCommand ?? DEFAULT_UPSERT_PR_COMMAND;\n }\n\n readonly description = 'Block manual `git push` and direct PR creation/edit (gh pr / gh api / curl) so pushes and 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 (!isBlockedPrOrPush(ctx.command)) return [];\n return [new V(1, truncate(ctx.command))];\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { ThrowCauseRequiredRule } from './core/rules/throw-cause-required';
14
14
  export { NoSymbolDiTokensRule } from './core/rules/no-symbol-di-tokens';
15
15
  export { EnforceControllerNamingRule } from './core/rules/enforce-controller-naming';
16
16
  export { BranchCreationGuardRule } from './core/rules/branch-creation-guard';
17
- export { PrCreationGuardRule } from './core/rules/pr-creation-guard';
17
+ export { PrCreationOrPushGuardRule } from './core/rules/pr-creation-or-push-guard';
18
18
  export { MergeInProgressGuardRule } from './core/rules/merge-in-progress-guard';
19
19
  export { PrMergeGuardRule } from './core/rules/pr-merge-guard';
20
20
  export { RedirectHowToMergeMainRule } from './core/rules/redirect-how-to-merge-main';
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatReport = exports.createIsLineDisabled = exports.DirectiveIndex = exports.parseDirectives = exports.stripTsNoise = exports.run = exports.FeatureBranchGuardRule = exports.NoJsFilesRule = exports.RedirectHowToMergeMainRule = exports.PrMergeGuardRule = exports.MergeInProgressGuardRule = exports.PrCreationGuardRule = exports.BranchCreationGuardRule = exports.EnforceControllerNamingRule = exports.NoSymbolDiTokensRule = exports.ThrowCauseRequiredRule = exports.CatchErrorPatternRule = exports.NoUnmanagedExceptionsRule = exports.RequireReturnTypeRule = exports.NoDestructureRule = exports.ValidateTsInSrcRule = exports.MaxFileLinesRule = exports.NoImplicitAnyRule = exports.NoAnyUnknownRule = exports.CustomRuleAdapter = exports.EmptyRuleConfig = exports.BashRuleBase = exports.FileRuleBase = exports.EditRuleBase = exports.DisableEscape = exports.Option = exports.FixHint = exports.ResolvedRuleConfig = exports.ResolvedConfig = exports.BlockedResult = exports.RuleGroup = exports.BashContext = exports.FileContext = exports.EditContext = exports.NormalizedToolInput = exports.NormalizedEdit = exports.Violation = void 0;
3
+ exports.formatReport = exports.createIsLineDisabled = exports.DirectiveIndex = exports.parseDirectives = exports.stripTsNoise = exports.run = exports.FeatureBranchGuardRule = exports.NoJsFilesRule = exports.RedirectHowToMergeMainRule = exports.PrMergeGuardRule = exports.MergeInProgressGuardRule = exports.PrCreationOrPushGuardRule = exports.BranchCreationGuardRule = exports.EnforceControllerNamingRule = exports.NoSymbolDiTokensRule = exports.ThrowCauseRequiredRule = exports.CatchErrorPatternRule = exports.NoUnmanagedExceptionsRule = exports.RequireReturnTypeRule = exports.NoDestructureRule = exports.ValidateTsInSrcRule = exports.MaxFileLinesRule = exports.NoImplicitAnyRule = exports.NoAnyUnknownRule = exports.CustomRuleAdapter = exports.EmptyRuleConfig = exports.BashRuleBase = exports.FileRuleBase = exports.EditRuleBase = exports.DisableEscape = exports.Option = exports.FixHint = exports.ResolvedRuleConfig = exports.ResolvedConfig = exports.BlockedResult = exports.RuleGroup = exports.BashContext = exports.FileContext = exports.EditContext = exports.NormalizedToolInput = exports.NormalizedEdit = exports.Violation = void 0;
4
4
  // Pluggable write-time validation framework for AI coding agents
5
5
  var types_1 = require("./core/types");
6
6
  Object.defineProperty(exports, "Violation", { enumerable: true, get: function () { return types_1.Violation; } });
@@ -51,8 +51,8 @@ var enforce_controller_naming_1 = require("./core/rules/enforce-controller-namin
51
51
  Object.defineProperty(exports, "EnforceControllerNamingRule", { enumerable: true, get: function () { return enforce_controller_naming_1.EnforceControllerNamingRule; } });
52
52
  var branch_creation_guard_1 = require("./core/rules/branch-creation-guard");
53
53
  Object.defineProperty(exports, "BranchCreationGuardRule", { enumerable: true, get: function () { return branch_creation_guard_1.BranchCreationGuardRule; } });
54
- var pr_creation_guard_1 = require("./core/rules/pr-creation-guard");
55
- Object.defineProperty(exports, "PrCreationGuardRule", { enumerable: true, get: function () { return pr_creation_guard_1.PrCreationGuardRule; } });
54
+ var pr_creation_or_push_guard_1 = require("./core/rules/pr-creation-or-push-guard");
55
+ Object.defineProperty(exports, "PrCreationOrPushGuardRule", { enumerable: true, get: function () { return pr_creation_or_push_guard_1.PrCreationOrPushGuardRule; } });
56
56
  var merge_in_progress_guard_1 = require("./core/rules/merge-in-progress-guard");
57
57
  Object.defineProperty(exports, "MergeInProgressGuardRule", { enumerable: true, get: function () { return merge_in_progress_guard_1.MergeInProgressGuardRule; } });
58
58
  var pr_merge_guard_1 = require("./core/rules/pr-merge-guard");
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/ai-hook-rules/src/index.ts"],"names":[],"mappings":";;;AAAA,iEAAiE;AACjE,sCAOsB;AALlB,kGAAA,SAAS,OAAA;AAAE,uGAAA,cAAc,OAAA;AAAE,4GAAA,mBAAmB,OAAA;AAC9C,oGAAA,WAAW,OAAA;AAAE,oGAAA,WAAW,OAAA;AAAE,oGAAA,WAAW,OAAA;AAErC,kGAAA,SAAS,OAAA;AAAE,sGAAA,aAAa,OAAA;AACxB,uGAAA,cAAc,OAAA;AAAE,2GAAA,kBAAkB,OAAA;AAGtC,gGAAgG;AAChG,4CAAiE;AAAxD,mGAAA,OAAO,OAAA;AAAE,kGAAA,MAAM,OAAA;AAAE,yGAAA,aAAa,OAAA;AAEvC,gGAAgG;AAChG,8CAA6F;AAApF,yGAAA,YAAY,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,4GAAA,eAAe,OAAA;AAClE,kEAA+D;AAAtD,wHAAA,iBAAiB,OAAA;AAE1B,+FAA+F;AAC/F,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,gEAAiE;AAAxD,oHAAA,iBAAiB,OAAA;AAC1B,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,sEAAsE;AAA7D,yHAAA,mBAAmB,OAAA;AAC5B,8DAAgE;AAAvD,mHAAA,iBAAiB,OAAA;AAC1B,wEAAyE;AAAhE,4HAAA,qBAAqB,OAAA;AAC9B,gFAAiF;AAAxE,oIAAA,yBAAyB,OAAA;AAClC,wEAAyE;AAAhE,4HAAA,qBAAqB,OAAA;AAC9B,0EAA2E;AAAlE,8HAAA,sBAAsB,OAAA;AAC/B,wEAAwE;AAA/D,2HAAA,oBAAoB,OAAA;AAC7B,oFAAqF;AAA5E,wIAAA,2BAA2B,OAAA;AACpC,4EAA6E;AAApE,gIAAA,uBAAuB,OAAA;AAChC,oEAAqE;AAA5D,wHAAA,mBAAmB,OAAA;AAC5B,gFAAgF;AAAvE,mIAAA,wBAAwB,OAAA;AACjC,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,sFAAqF;AAA5E,wIAAA,0BAA0B,OAAA;AACnC,wDAAyD;AAAhD,4GAAA,aAAa,OAAA;AACtB,0EAA2E;AAAlE,8HAAA,sBAAsB,OAAA;AAE/B,wCAAoC;AAA3B,6FAAA,GAAG,OAAA;AACZ,wDAAqD;AAA5C,8GAAA,YAAY,OAAA;AACrB,gEAAkG;AAAzF,qHAAA,eAAe,OAAA;AAAE,oHAAA,cAAc,OAAA;AAAE,0HAAA,oBAAoB,OAAA;AAC9D,wCAA6C;AAApC,sGAAA,YAAY,OAAA","sourcesContent":["// Pluggable write-time validation framework for AI coding agents\nexport {\n ToolKind, RuleScope, RuleOptions, IsLineDisabled,\n Violation, NormalizedEdit, NormalizedToolInput,\n EditContext, FileContext, BashContext,\n Rule, PlainRule,\n RuleGroup, BlockedResult,\n ResolvedConfig, ResolvedRuleConfig,\n} from './core/types';\n\n// Structured fix guidance shown in blocked reports (violation + mainMessage + options + escape)\nexport { FixHint, Option, DisableEscape } from './core/fix-hint';\n\n// Scope-specific rule bases (each extends the shared AbstractRule from @webpieces/rules-config)\nexport { EditRuleBase, FileRuleBase, BashRuleBase, EmptyRuleConfig } from './core/rule-base';\nexport { CustomRuleAdapter } from './core/custom-rule-adapter';\n\n// Built-in rule classes — each constructed with its typed *Config from @webpieces/rules-config\nexport { NoAnyUnknownRule } from './core/rules/no-any-unknown';\nexport { NoImplicitAnyRule } from './core/rules/no-implicit-any';\nexport { MaxFileLinesRule } from './core/rules/max-file-lines';\nexport { ValidateTsInSrcRule } from './core/rules/validate-ts-in-src';\nexport { NoDestructureRule } from './core/rules/no-destructure';\nexport { RequireReturnTypeRule } from './core/rules/require-return-type';\nexport { NoUnmanagedExceptionsRule } from './core/rules/no-unmanaged-exceptions';\nexport { CatchErrorPatternRule } from './core/rules/catch-error-pattern';\nexport { ThrowCauseRequiredRule } from './core/rules/throw-cause-required';\nexport { NoSymbolDiTokensRule } from './core/rules/no-symbol-di-tokens';\nexport { EnforceControllerNamingRule } from './core/rules/enforce-controller-naming';\nexport { BranchCreationGuardRule } from './core/rules/branch-creation-guard';\nexport { PrCreationGuardRule } from './core/rules/pr-creation-guard';\nexport { MergeInProgressGuardRule } from './core/rules/merge-in-progress-guard';\nexport { PrMergeGuardRule } from './core/rules/pr-merge-guard';\nexport { RedirectHowToMergeMainRule } from './core/rules/redirect-how-to-merge-main';\nexport { NoJsFilesRule } from './core/rules/no-js-files';\nexport { FeatureBranchGuardRule } from './core/rules/feature-branch-guard';\n\nexport { run } from './core/runner';\nexport { stripTsNoise } from './core/strip-ts-noise';\nexport { parseDirectives, DirectiveIndex, createIsLineDisabled } from './core/disable-directives';\nexport { formatReport } from './core/report';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/ai-hook-rules/src/index.ts"],"names":[],"mappings":";;;AAAA,iEAAiE;AACjE,sCAOsB;AALlB,kGAAA,SAAS,OAAA;AAAE,uGAAA,cAAc,OAAA;AAAE,4GAAA,mBAAmB,OAAA;AAC9C,oGAAA,WAAW,OAAA;AAAE,oGAAA,WAAW,OAAA;AAAE,oGAAA,WAAW,OAAA;AAErC,kGAAA,SAAS,OAAA;AAAE,sGAAA,aAAa,OAAA;AACxB,uGAAA,cAAc,OAAA;AAAE,2GAAA,kBAAkB,OAAA;AAGtC,gGAAgG;AAChG,4CAAiE;AAAxD,mGAAA,OAAO,OAAA;AAAE,kGAAA,MAAM,OAAA;AAAE,yGAAA,aAAa,OAAA;AAEvC,gGAAgG;AAChG,8CAA6F;AAApF,yGAAA,YAAY,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,4GAAA,eAAe,OAAA;AAClE,kEAA+D;AAAtD,wHAAA,iBAAiB,OAAA;AAE1B,+FAA+F;AAC/F,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,gEAAiE;AAAxD,oHAAA,iBAAiB,OAAA;AAC1B,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,sEAAsE;AAA7D,yHAAA,mBAAmB,OAAA;AAC5B,8DAAgE;AAAvD,mHAAA,iBAAiB,OAAA;AAC1B,wEAAyE;AAAhE,4HAAA,qBAAqB,OAAA;AAC9B,gFAAiF;AAAxE,oIAAA,yBAAyB,OAAA;AAClC,wEAAyE;AAAhE,4HAAA,qBAAqB,OAAA;AAC9B,0EAA2E;AAAlE,8HAAA,sBAAsB,OAAA;AAC/B,wEAAwE;AAA/D,2HAAA,oBAAoB,OAAA;AAC7B,oFAAqF;AAA5E,wIAAA,2BAA2B,OAAA;AACpC,4EAA6E;AAApE,gIAAA,uBAAuB,OAAA;AAChC,oFAAmF;AAA1E,sIAAA,yBAAyB,OAAA;AAClC,gFAAgF;AAAvE,mIAAA,wBAAwB,OAAA;AACjC,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,sFAAqF;AAA5E,wIAAA,0BAA0B,OAAA;AACnC,wDAAyD;AAAhD,4GAAA,aAAa,OAAA;AACtB,0EAA2E;AAAlE,8HAAA,sBAAsB,OAAA;AAE/B,wCAAoC;AAA3B,6FAAA,GAAG,OAAA;AACZ,wDAAqD;AAA5C,8GAAA,YAAY,OAAA;AACrB,gEAAkG;AAAzF,qHAAA,eAAe,OAAA;AAAE,oHAAA,cAAc,OAAA;AAAE,0HAAA,oBAAoB,OAAA;AAC9D,wCAA6C;AAApC,sGAAA,YAAY,OAAA","sourcesContent":["// Pluggable write-time validation framework for AI coding agents\nexport {\n ToolKind, RuleScope, RuleOptions, IsLineDisabled,\n Violation, NormalizedEdit, NormalizedToolInput,\n EditContext, FileContext, BashContext,\n Rule, PlainRule,\n RuleGroup, BlockedResult,\n ResolvedConfig, ResolvedRuleConfig,\n} from './core/types';\n\n// Structured fix guidance shown in blocked reports (violation + mainMessage + options + escape)\nexport { FixHint, Option, DisableEscape } from './core/fix-hint';\n\n// Scope-specific rule bases (each extends the shared AbstractRule from @webpieces/rules-config)\nexport { EditRuleBase, FileRuleBase, BashRuleBase, EmptyRuleConfig } from './core/rule-base';\nexport { CustomRuleAdapter } from './core/custom-rule-adapter';\n\n// Built-in rule classes — each constructed with its typed *Config from @webpieces/rules-config\nexport { NoAnyUnknownRule } from './core/rules/no-any-unknown';\nexport { NoImplicitAnyRule } from './core/rules/no-implicit-any';\nexport { MaxFileLinesRule } from './core/rules/max-file-lines';\nexport { ValidateTsInSrcRule } from './core/rules/validate-ts-in-src';\nexport { NoDestructureRule } from './core/rules/no-destructure';\nexport { RequireReturnTypeRule } from './core/rules/require-return-type';\nexport { NoUnmanagedExceptionsRule } from './core/rules/no-unmanaged-exceptions';\nexport { CatchErrorPatternRule } from './core/rules/catch-error-pattern';\nexport { ThrowCauseRequiredRule } from './core/rules/throw-cause-required';\nexport { NoSymbolDiTokensRule } from './core/rules/no-symbol-di-tokens';\nexport { EnforceControllerNamingRule } from './core/rules/enforce-controller-naming';\nexport { BranchCreationGuardRule } from './core/rules/branch-creation-guard';\nexport { PrCreationOrPushGuardRule } from './core/rules/pr-creation-or-push-guard';\nexport { MergeInProgressGuardRule } from './core/rules/merge-in-progress-guard';\nexport { PrMergeGuardRule } from './core/rules/pr-merge-guard';\nexport { RedirectHowToMergeMainRule } from './core/rules/redirect-how-to-merge-main';\nexport { NoJsFilesRule } from './core/rules/no-js-files';\nexport { FeatureBranchGuardRule } from './core/rules/feature-branch-guard';\n\nexport { run } from './core/runner';\nexport { stripTsNoise } from './core/strip-ts-noise';\nexport { parseDirectives, DirectiveIndex, createIsLineDisabled } from './core/disable-directives';\nexport { formatReport } from './core/report';\n"]}
@@ -1,11 +0,0 @@
1
- import { PrCreationGuardConfig } from '@webpieces/rules-config';
2
- import type { BashContext, Violation } from '../types';
3
- import { BashRuleBase } from '../rule-base';
4
- import { FixHint } from '../fix-hint';
5
- export declare class PrCreationGuardRule extends BashRuleBase<PrCreationGuardConfig> {
6
- private readonly upsertPrCommand;
7
- constructor(config: PrCreationGuardConfig);
8
- readonly description = "Block direct PR creation/edit (gh pr / gh api / curl) so PRs go only through the gated upsert-pr command.";
9
- get fixHint(): FixHint;
10
- check(ctx: BashContext): readonly Violation[];
11
- }
@@ -1 +0,0 @@
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,qEAAqE;UACrE,0EAA0E,CAC/E,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 + 'Add this to your memory so you don\\'t forget next time and waste tokens.',\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"]}