@webpieces/ai-hook-rules 0.4.635 → 0.4.637
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/README.md +11 -4
- package/package.json +2 -2
- package/src/adapters/hook-core.js +4 -3
- package/src/adapters/hook-core.js.map +1 -1
- package/src/bin/setup.js +72 -6
- package/src/bin/setup.js.map +1 -1
- package/src/core/custom-rule-adapter.js +3 -1
- package/src/core/custom-rule-adapter.js.map +1 -1
- package/src/core/decision-log.d.ts +38 -5
- package/src/core/decision-log.js +45 -6
- package/src/core/decision-log.js.map +1 -1
- package/src/core/l1-doc.js +6 -3
- package/src/core/l1-doc.js.map +1 -1
- package/src/core/l2-doc.d.ts +7 -0
- package/src/core/l2-doc.js +274 -0
- package/src/core/l2-doc.js.map +1 -0
- package/src/core/l2-rows.d.ts +97 -0
- package/src/core/l2-rows.js +229 -0
- package/src/core/l2-rows.js.map +1 -0
- package/src/core/load-rules.d.ts +8 -2
- package/src/core/load-rules.js +51 -31
- package/src/core/load-rules.js.map +1 -1
- package/src/core/main-sync-refresh.d.ts +8 -1
- package/src/core/main-sync-refresh.js +9 -2
- package/src/core/main-sync-refresh.js.map +1 -1
- package/src/core/main-sync-timeout.d.ts +32 -0
- package/src/core/main-sync-timeout.js +93 -0
- package/src/core/main-sync-timeout.js.map +1 -0
- package/src/core/rules/branch-creation-guard.js +1 -1
- package/src/core/rules/branch-creation-guard.js.map +1 -1
- package/src/core/rules/catch-error-pattern.js +1 -1
- package/src/core/rules/catch-error-pattern.js.map +1 -1
- package/src/core/rules/feature-branch-guard.d.ts +3 -3
- package/src/core/rules/feature-branch-guard.js +17 -4
- package/src/core/rules/feature-branch-guard.js.map +1 -1
- package/src/core/rules/index.d.ts +15 -1
- package/src/core/rules/index.js +18 -10
- package/src/core/rules/index.js.map +1 -1
- package/src/core/rules/match-rule.js +4 -1
- package/src/core/rules/match-rule.js.map +1 -1
- package/src/core/rules/max-file-lines.js +1 -1
- package/src/core/rules/max-file-lines.js.map +1 -1
- package/src/core/rules/merge-in-progress-guard.d.ts +9 -3
- package/src/core/rules/merge-in-progress-guard.js +9 -4
- package/src/core/rules/merge-in-progress-guard.js.map +1 -1
- package/src/core/rules/merged-branch-bash-guard.d.ts +3 -3
- package/src/core/rules/merged-branch-bash-guard.js +17 -6
- package/src/core/rules/merged-branch-bash-guard.js.map +1 -1
- package/src/core/rules/no-any-unknown.js +1 -1
- package/src/core/rules/no-any-unknown.js.map +1 -1
- package/src/core/rules/no-custom-css.js +1 -1
- package/src/core/rules/no-custom-css.js.map +1 -1
- package/src/core/rules/no-destructure.js +1 -1
- package/src/core/rules/no-destructure.js.map +1 -1
- package/src/core/rules/no-implicit-any.js +1 -1
- package/src/core/rules/no-implicit-any.js.map +1 -1
- package/src/core/rules/no-js-files.js +1 -1
- package/src/core/rules/no-js-files.js.map +1 -1
- package/src/core/rules/no-process-exit-outside-main.js +1 -1
- package/src/core/rules/no-process-exit-outside-main.js.map +1 -1
- package/src/core/rules/no-symbol-di-tokens.js +1 -1
- package/src/core/rules/no-symbol-di-tokens.js.map +1 -1
- package/src/core/rules/no-unmanaged-exceptions.js +1 -1
- package/src/core/rules/no-unmanaged-exceptions.js.map +1 -1
- package/src/core/rules/pr-creation-or-push-guard.d.ts +10 -3
- package/src/core/rules/pr-creation-or-push-guard.js +10 -4
- package/src/core/rules/pr-creation-or-push-guard.js.map +1 -1
- package/src/core/rules/pr-merge-guard.d.ts +3 -3
- package/src/core/rules/pr-merge-guard.js +2 -1
- package/src/core/rules/pr-merge-guard.js.map +1 -1
- package/src/core/rules/read-stale-guard.d.ts +3 -3
- package/src/core/rules/read-stale-guard.js +15 -6
- package/src/core/rules/read-stale-guard.js.map +1 -1
- package/src/core/rules/redirect-how-to-merge-main.d.ts +3 -3
- package/src/core/rules/redirect-how-to-merge-main.js +1 -1
- package/src/core/rules/redirect-how-to-merge-main.js.map +1 -1
- package/src/core/rules/require-return-type.js +1 -1
- package/src/core/rules/require-return-type.js.map +1 -1
- package/src/core/rules/stale-main-bash-guard.d.ts +3 -3
- package/src/core/rules/stale-main-bash-guard.js +5 -4
- package/src/core/rules/stale-main-bash-guard.js.map +1 -1
- package/src/core/rules/throw-cause-required.js +1 -1
- package/src/core/rules/throw-cause-required.js.map +1 -1
- package/src/core/rules/validate-ts-in-src.js +1 -1
- package/src/core/rules/validate-ts-in-src.js.map +1 -1
- package/src/core/rules/whole-repo-build-guard.js +6 -2
- package/src/core/rules/whole-repo-build-guard.js.map +1 -1
- package/src/core/runner.js +41 -22
- package/src/core/runner.js.map +1 -1
- package/src/core/sync-main.d.ts +9 -0
- package/src/core/sync-main.js +17 -1
- package/src/core/sync-main.js.map +1 -1
- package/src/core/types.d.ts +11 -0
- package/src/core/types.js.map +1 -1
package/README.md
CHANGED
|
@@ -37,9 +37,16 @@ openclaw plugins enable @webpieces/ai-hook-rules
|
|
|
37
37
|
with an absolute `$CLAUDE_PROJECT_DIR/` path** so they resolve from any cwd:
|
|
38
38
|
|
|
39
39
|
- `wp-ai-rules-hook` — matcher `Write|Edit|MultiEdit`. Runs the code-style rules.
|
|
40
|
-
- `wp-ai-guards-hook` — matcher `Write|Edit|MultiEdit|Bash|Read`. Runs the git/PR/branch guards
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
- `wp-ai-guards-hook` — matcher `Write|Edit|MultiEdit|Bash|Read`. Runs the git/PR/branch guards. The
|
|
41
|
+
`hookGuards` section carries exactly THREE keys, one per POLICY — `branch-state-guard`
|
|
42
|
+
("may I work here, and is what I read current?"), `branch-creation-guard` ("should this branch or
|
|
43
|
+
worktree exist?") and `pr-lifecycle-guard` ("do PRs and merges go through the gated flow?"). Several
|
|
44
|
+
guard CLASSES sit behind each key, because the tool wiring differs even where the policy does not:
|
|
45
|
+
bash git/PR guards on `Bash`, and file guards like `feature-branch-guard` (a class under
|
|
46
|
+
`branch-state-guard`) on `Write|Edit|MultiEdit`. A class name is what a deny report and a decision-log
|
|
47
|
+
line carry as `rule=`; a config key is what you switch. `Read` is guarded ONLY by
|
|
48
|
+
`read-stale-guard` — another class under `branch-state-guard` — which blocks a read of a stale tree;
|
|
49
|
+
otherwise it is a
|
|
43
50
|
log-and-allow fast path that records every file the AI opens in
|
|
44
51
|
`.webpieces/logs/calls/<session>-<agent>-<hook>.log`
|
|
45
52
|
(never blocked), so you can see whether the AI read a project's `design.json` before editing it.
|
|
@@ -101,7 +108,7 @@ self-heals it; a settings file that registers no webpieces hooks is never touche
|
|
|
101
108
|
|
|
102
109
|
There is no runtime escape-hatch file. To stop enforcement, **uninstall the hook**
|
|
103
110
|
(re-run `pnpm wp-install-ai-hooks` and choose "none" for it). Per-rule opt-outs stay in
|
|
104
|
-
`webpieces.config.json` (`mode: "OFF"`, `
|
|
111
|
+
`webpieces.config.json` (`mode: "OFF"`, `turnOffRuleUntilEpoch`, `turnOffRuleWhileOnBranch`)
|
|
105
112
|
and per-line opt-outs use `// webpieces-disable <rule> -- reason`.
|
|
106
113
|
|
|
107
114
|
## Starter rules
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/ai-hook-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.637",
|
|
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",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"directory": "packages/tooling/ai-hook-rules"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@webpieces/rules-config": "0.4.
|
|
28
|
+
"@webpieces/rules-config": "0.4.637"
|
|
29
29
|
},
|
|
30
30
|
"publishConfig": {
|
|
31
31
|
"access": "public"
|
|
@@ -5,6 +5,7 @@ exports.runMain = runMain;
|
|
|
5
5
|
const tslib_1 = require("tslib");
|
|
6
6
|
const path = tslib_1.__importStar(require("path"));
|
|
7
7
|
const runner_1 = require("../core/runner");
|
|
8
|
+
const main_sync_timeout_1 = require("../core/main-sync-timeout");
|
|
8
9
|
const rejection_log_1 = require("../core/rejection-log");
|
|
9
10
|
const decision_log_1 = require("../core/decision-log");
|
|
10
11
|
const main_sync_refresh_1 = require("../core/main-sync-refresh");
|
|
@@ -149,11 +150,11 @@ function handleFileTool(payload, cwd, mode) {
|
|
|
149
150
|
// root, not the AI's cwd — resolve it so a config edit from a subdir doesn't create a
|
|
150
151
|
// stray `<subdir>/.webpieces` tree.
|
|
151
152
|
const root = new rules_config_1.RepoRootFinder().resolveRepoRoot(cwd);
|
|
152
|
-
(0, decision_log_1.logGuardDecision)(root, new decision_log_1.GuardDecision('feature-branch-guard', toolKind, input.filePath, (0, decision_log_1.branchForLog)(root), 'ALLOW_EXEMPT', 'config-bypass (feature-branch-guard skipped)', '-', l0_fault_codes_1.L0_FAULT_NONE, decision_log_1.
|
|
153
|
+
(0, decision_log_1.logGuardDecision)(root, new decision_log_1.GuardDecision('feature-branch-guard', toolKind, input.filePath, (0, decision_log_1.branchForLog)(root), 'ALLOW_EXEMPT', 'config-bypass (feature-branch-guard skipped)', '-', l0_fault_codes_1.L0_FAULT_NONE, (0, decision_log_1.matrixL2Row)('config-bypass (feature-branch-guard skipped)')));
|
|
153
154
|
// The guard's own refresh trigger lives inside its check(), which we skip here — so warm
|
|
154
155
|
// the cache directly, otherwise a session that only edits webpieces.config.json never
|
|
155
156
|
// refreshes the sync status. Fire-and-forget; never blocks the edit.
|
|
156
|
-
(0, main_sync_refresh_1.triggerMainSyncRefresh)(root);
|
|
157
|
+
(0, main_sync_refresh_1.triggerMainSyncRefresh)(root, (0, main_sync_timeout_1.branchStateHangTimeoutFor)(cwd));
|
|
157
158
|
}
|
|
158
159
|
(0, claude_code_response_1.emitAllow)();
|
|
159
160
|
}
|
|
@@ -269,7 +270,7 @@ async function runMain(mode) {
|
|
|
269
270
|
decision_log_1.invocationLog.begin(cwd, payload.tool_name, readPath);
|
|
270
271
|
// Reads vastly outnumber edits, so refreshing here is what actually keeps the shared
|
|
271
272
|
// main-sync cache warm for feature-branch-guard. Detached; never slows the read.
|
|
272
|
-
(0, main_sync_refresh_1.triggerMainSyncRefresh)(cwd);
|
|
273
|
+
(0, main_sync_refresh_1.triggerMainSyncRefresh)(cwd, (0, main_sync_timeout_1.branchStateHangTimeoutFor)(cwd));
|
|
273
274
|
}
|
|
274
275
|
handleRead(readPath, cwd, mode);
|
|
275
276
|
(0, claude_code_response_1.emitAllow)();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook-core.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/hook-core.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\n\nimport { run, runBash, runRead } from '../core/runner';\nimport { logRejection, extractRuleNames } from '../core/rejection-log';\nimport { logGuardDecision, GuardDecision, branchForLog, invocationLog, MATRIX_L0_BLOCK, MATRIX_L2 } from '../core/decision-log';\nimport { triggerMainSyncRefresh } from '../core/main-sync-refresh';\nimport { CONFIG_FILENAME } from '../core/load-config';\nimport { RepoRootFinder } from '@webpieces/rules-config';\nimport { NormalizedToolInput, NormalizedEdit, ToolKind, InformAiError, RuleFailError, HookMode, BlockedResult } from '../core/types';\nimport { toError } from '../core/to-error';\nimport { emitDeny, emitAllow } from './claude-code-response';\nimport { governingShimRoot, isAllowed, installedShimRulesVersion } from '../bin/shim';\nimport { shimStaleDenyReason } from '../bin/shim-deny-reason';\nimport { managedSurfaceDrift } from '../bin/hook-registration';\nimport { writeGuardMatrixDoc, guardMatrixPointer } from '../core/l0-matrix';\nimport { logStream, StreamIdentity } from '../core/log-stream';\nimport { L0_FAULT_SHIM_STALE, L0_FAULT_NONE } from '../core/l0-fault-codes';\n\n// Which category of rules this hook invocation runs. The hook is split into two independently\n// installable PreToolUse hooks; each runs ONE category (the runner filters by it), and both can\n// receive file AND bash payloads:\n// - 'rules' → code-style rules (file/edit scope). Bash payloads pass through (no code rules apply).\n// - 'guards' → hookGuards section: bash git/PR guards on Bash AND file guards (feature-branch-guard)\n// on Write/Edit, PLUS a log-and-allow audit of Read. Matcher is Write|Edit|MultiEdit|Bash|Read.\n// - 'all' → both categories, used by the openclaw plugin adapter (a single before_tool_call hook).\nexport type { HookMode };\n\nconst HANDLED_FILE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit']);\n\n// Read-only tools carry NO guard or code rule, but the guards hook owns the per-invocation audit log\n// (the `calls/` stream). When the guards matcher includes these (see setup.ts GUARDS_HOOK), a\n// log-and-allow fast path records every file the AI opens — so a human can later inspect whether it\n// read a project's design.json BEFORE editing the project. Never blocked. Scoped to Read for now;\n// widen (Grep/Glob/NotebookRead) later if desired.\nconst READ_ONLY_TOOLS = new Set(['Read']);\n\ninterface ClaudeCodePayload {\n tool_name: string;\n tool_input: ClaudeCodeToolInput;\n // Claude Code sends the session's current working directory (follows a persisted `cd`). Used to\n // scope guards to the git repo the AI is actually in — see runner git-repo-boundary governance.\n cwd?: string;\n // Present ONLY when the hook fires inside a SUBAGENT; absent = the coordinator. Used to NAME the log\n // writer file and nothing else — never to decide which tree a call acts on, which is measured from\n // the path (see core/version-sync.ts). A worktree-isolated agent was measured resuming on the primary\n // clone after its tree was reaped, so identity cannot stand in for location.\n session_id?: string;\n agent_id?: string;\n agent_type?: string;\n}\n\ninterface ClaudeCodeToolInput {\n file_path?: string;\n content?: string;\n old_string?: string;\n new_string?: string;\n edits?: ClaudeCodeEditEntry[];\n command?: string;\n}\n\ninterface ClaudeCodeEditEntry {\n old_string?: string;\n new_string?: string;\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve: (value: string) => void) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => { data += chunk; });\n process.stdin.on('end', () => resolve(data));\n process.stdin.on('error', () => resolve(''));\n if (process.stdin.isTTY) resolve('');\n });\n}\n\nfunction safeParse(raw: string): ClaudeCodePayload | null {\n if (!raw || raw.trim() === '') return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(raw) as ClaudeCodePayload;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Malformed hook input from Claude Code stdin: ${error.message}`, { cause: error });\n }\n}\n\nfunction normalizeToolKind(toolName: string): ToolKind | null {\n if (HANDLED_FILE_TOOLS.has(toolName)) return toolName as ToolKind;\n return null;\n}\n\nfunction normalizeToolInput(toolKind: ToolKind, toolInput: ClaudeCodeToolInput): NormalizedToolInput | null {\n const filePath = toolInput.file_path;\n if (!filePath) return null;\n\n if (toolKind === 'Write') {\n return new NormalizedToolInput(filePath, [\n new NormalizedEdit('', toolInput.content || ''),\n ]);\n }\n if (toolKind === 'Edit') {\n return new NormalizedToolInput(filePath, [\n new NormalizedEdit(toolInput.old_string || '', toolInput.new_string || ''),\n ]);\n }\n if (toolKind === 'MultiEdit') {\n const raw = Array.isArray(toolInput.edits) ? toolInput.edits : [];\n const edits = raw.map((e: ClaudeCodeEditEntry) => new NormalizedEdit(e.old_string || '', e.new_string || ''));\n return new NormalizedToolInput(filePath, edits);\n }\n return null;\n}\n\n// The rule name for a block's audit line: the FIRST rule the report cites, or `fallback` when the\n// report opens with no `[rule]` header (a hand-written guard message). Comma-joined when a report\n// cites several, so `rule=` never silently drops one.\n// webpieces-disable no-function-outside-class -- sibling of handleBash()/handleFileTool() in this module; the adapter is module-scope functions by design\nfunction blockingRule(report: string, fallback: string): string {\n const names = extractRuleNames(report);\n return names.length > 0 ? names.join(',') : fallback;\n}\n\nfunction handleBash(payload: ClaudeCodePayload, cwd: string, mode: HookMode): void {\n const command = payload.tool_input.command;\n if (!command || command.trim() === '') { emitAllow(); }\n const result = runBash(command, cwd, mode);\n if (!result) { emitAllow(); }\n // NO DECISION LINE HERE. This used to write a generic `bash-guard` line because a Bash deny once\n // had no audit trail at all — but every layer now records its own: L1 into `L1-location/` with its\n // row, L2's guards into `L2-decisions/` with their rule and cache, and emitDeny below stamps the\n // call-level outcome onto `calls/`. So this was the THIRD line for one block, and the worst of the\n // three: it re-resolved the root from `cwd` via RepoRootFinder, which is not necessarily the tree\n // the guard actually judged, so a `cd`-relocated command scattered one block across two different\n // `.webpieces` directories.\n //\n // Bash deny → pass 'Bash' so denyJson adds the ANSI-red systemMessage (the only field a Bash deny\n // shows the human; permissionDecisionReason is invisible on Bash). See claude-code-response.ts.\n emitDeny(result.report, 'Bash', blockingRule(result.report, 'bash-guard'), result.fault);\n}\n\n/**\n * The read-scoped guard pass. Returns normally to ALLOW; only calls emitDeny when the guard fires.\n *\n * Wrapped in its own catch that swallows into an allow. Every other path in this hook fails CLOSED,\n * and that is right for edits and shell commands — but a crash here would block the agent from\n * READING, which includes reading webpieces.config.json to turn the offending guard off. So this one\n * path deliberately inverts the policy: a broken read-guard degrades to a no-op, never to a wedge.\n */\n// webpieces-disable no-function-outside-class -- sibling of handleBash()/handleFileTool() in this module; the adapter is module-scope functions by design\nfunction handleRead(filePath: string, cwd: string, mode: HookMode): void {\n if (filePath === '') return;\n let result: BlockedResult | null = null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n result = runRead(filePath, cwd, mode);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return; // fail OPEN — see the doc comment\n }\n if (!result) return;\n logRejection('Read', new NormalizedToolInput(filePath, []), result, cwd);\n emitDeny(result.report, 'Read', blockingRule(result.report, 'read-guard'), result.fault);\n}\n\nfunction handleFileTool(payload: ClaudeCodePayload, cwd: string, mode: HookMode): void {\n const toolKind = normalizeToolKind(payload.tool_name);\n if (!toolKind) { emitAllow(); }\n\n const input = normalizeToolInput(toolKind, payload.tool_input);\n if (!input) { emitAllow(); }\n\n // Always allow edits to webpieces.config.json — it's the fix target when the config is broken.\n // This exits BEFORE run(), so feature-branch-guard never sees a config edit; record that so the\n // audit trail explains why a config edit on a bad branch was not blocked (see decision-log.ts).\n if (path.basename(input.filePath) === CONFIG_FILENAME) {\n if (mode !== 'rules') {\n // `.webpieces/` (the decision log + sync cache these two calls write) lives at the repo\n // root, not the AI's cwd — resolve it so a config edit from a subdir doesn't create a\n // stray `<subdir>/.webpieces` tree.\n const root = new RepoRootFinder().resolveRepoRoot(cwd);\n logGuardDecision(\n root,\n new GuardDecision('feature-branch-guard', toolKind, input.filePath, branchForLog(root), 'ALLOW_EXEMPT', 'config-bypass (feature-branch-guard skipped)', '-', L0_FAULT_NONE, MATRIX_L2),\n );\n // The guard's own refresh trigger lives inside its check(), which we skip here — so warm\n // the cache directly, otherwise a session that only edits webpieces.config.json never\n // refreshes the sync status. Fire-and-forget; never blocks the edit.\n triggerMainSyncRefresh(root);\n }\n emitAllow();\n }\n\n const result = run(toolKind, input, cwd, mode);\n if (!result) { emitAllow(); }\n\n logRejection(toolKind, input, result, cwd);\n // File-tool deny → pass the Write/Edit/MultiEdit kind so denyJson omits systemMessage (the reason\n // already renders red natively for these tools). See claude-code-response.ts.\n emitDeny(result.report, toolKind, blockingRule(result.report, 'file-guard'), result.fault);\n}\n\n// What a stale committed shim lets through — now a thin adapter over the ONE L0 allowlist (isAllowed in\n// ../bin/shim), not a list of its own. A stale shim must NEVER trap the actions needed to recover: the\n// original \"block everything but the cures\" version also shadowed the always-allowed\n// webpieces.config.json edit (handleFileTool) and blocked reads, so a repo that ALSO needed its config\n// fixed would deadlock — blocked from editing the one file whose edit is normally always allowed, and\n// blocked from reading it to know how.\n//\n// It used to carry its OWN narrower list (isShimCureCommand: the three shim cures only), and that\n// narrowness was a defect, not a safety property: `pnpm install` and `git pull` — the two commands that\n// resolve the version disagreement underneath a stale shim — were denied. Consulting the shared\n// allowlist fixes that by construction.\n//\n// What is NOT a defect, and must not be \"fixed\": those cures rewrite the committed shim from the\n// INSTALLED binary's renderShim(), overwriting whatever was there. That is the invariant, not\n// collateral damage. The shim (D/X/K, in POSIX sh, pre-binary) and this binary (S/C/Y, in JS) are two\n// halves of ONE L0 and they exchange assumptions — the shim parses file_path and carries ALLOW-READ /\n// ALLOW-CONFIG entries this binary relies on. Pair a binary with a shim rendered by a DIFFERENT\n// release and L0 acquires holes that nothing reports. So the rule is absolute: the committed shim\n// equals renderShim() of the binary in node_modules, and a cure that forces that is the cure working.\n// See healShim's header, which states the same invariant from the other side.\n//\n// Corollary for anyone regenerating the shim in a webpieces PR: commit `templates/ai-hook.sh` (source,\n// locked to renderShim() by unit test) and leave `.claude/webpieces/ai-hook.sh` (generated artifact)\n// alone. In THIS repo the local source runs ahead of the pinned node_modules, so committing a shim\n// rendered from local source produces a commit whose shim and whose @webpieces pin come from different\n// releases — precisely the mismatch above. The artifact heals on the next upgrade; that is its job.\n//\n// - 'allow-cure' → a Bash cure on the allowlist: emitAllow directly, bypassing the git guards.\n// - 'pass' → a recovery action the normal flow already permits, so fall THROUGH and let it: ANY\n// Read (you must read to know how to fix — see handleRead, which itself fails open),\n// or an edit to webpieces.config.json (the always-allowed recovery target).\n// - 'deny' → all OTHER work: blocked until the committed shim matches renderShim() again.\nexport type ShimStaleDecision = 'allow-cure' | 'pass' | 'deny';\n// webpieces-disable no-function-outside-class -- pure decision helper beside the adapter's other module-scope functions; exported for direct unit testing.\nexport function shimStaleRecoveryDecision(toolName: string, command: string, filePath: string): ShimStaleDecision {\n const allowed = isAllowed(toolName, command, filePath);\n if (allowed === 'pass') return 'pass';\n if (allowed === 'allow') return 'allow-cure';\n return 'deny';\n}\n\n// MANAGED-HOOK-SURFACE self-guard, moved here from the rendered shim (2026-07-24) and widened from one\n// file to three (2026-08-07). The committed\n// .claude/webpieces/ai-hook.sh is webpieces-MANAGED and generated from renderShim(); if it no longer\n// matches, it was reverted / hand-edited / predates this binary, so its OWN fail-closed logic can't be\n// trusted. We are the CURRENT binary from node_modules — the trustworthy party — so WE decide here\n// instead of the (possibly stale) shim. It used to `cmp` itself inside the shim: a double-edged trap,\n// since the check lived in the very file it guarded and a fix could only ship by regenerating that\n// file. Now we fail closed on all real WORK while always leaving the recovery path open (see\n// shimStaleRecoveryDecision): the whole L0 allowlist, any Read, and editing webpieces.config.json. We deny +\n// tell the AI; we do NOT silently rewrite the file under it. 'rules' hook skips it (guards owns the\n// shim). Returns normally (pass / nothing to do) or exits via emitAllow/emitDeny.\n// webpieces-disable no-function-outside-class -- sibling of handleBash()/handleFileTool() in this module; the adapter is module-scope functions by design\nfunction enforceCommittedShim(payload: ClaudeCodePayload, cwd: string, mode: HookMode): void {\n // ONE root for the whole decision, resolved from the RUNNING MODULE (governingShimRoot), never from\n // `cwd`: the shim file we compare and the renderShim() we compare it TO must come from the same\n // install, or the check straddles two trees and can never converge (see governingShimRoot's header).\n // `cwd` still selects where the L0 matrix doc is dropped — that is a \"where does the AI read\" question,\n // not part of the judgement.\n const shimRoot = governingShimRoot();\n if (mode === 'rules') return;\n // WHICH of the three managed things moved — ai-hook.sh, the settings.json registration, or its\n // managed env entry (the Bash-cwd pin that keeps a guard's verdict independent of where an earlier\n // `cd` left the shell; see managed-env.ts). Nothing validated the registration before it joined this\n // fault, so a settings file left on a superseded form silently changed WHO GOVERNS, with no signal\n // anywhere — which is the whole reason the registration is a drift surface and not just an install\n // step.\n const drifted = managedSurfaceDrift(shimRoot);\n if (drifted.length === 0) return;\n const decision = shimStaleRecoveryDecision(payload.tool_name, payload.tool_input.command ?? '', payload.tool_input.file_path ?? '');\n if (decision === 'pass') return;\n if (decision === 'allow-cure') emitAllow();\n // Drop the L0 matrix doc where the AI can read it and point the deny at it — a Read is entry 1 of\n // the same allowlist, so the pointer is always followable. Best-effort: no doc → no pointer.\n const root = new RepoRootFinder().resolveRepoRoot(cwd);\n const docPath = writeGuardMatrixDoc(root);\n // WRITE THE AUDIT LINE HERE. This block happens BEFORE invocationLog.begin() — it has to, since a\n // stale shim invalidates everything downstream — so emitDeny's flush finds nothing pending and an\n // `S` storm left NO trace at all: the one fault most likely to block twenty consecutive tool calls\n // was the one fault the trail could not show. A decision line is the fix that costs no reordering.\n const target = payload.tool_input.command ?? payload.tool_input.file_path ?? '';\n logGuardDecision(\n root,\n new GuardDecision('committed-shim-stale', payload.tool_name, target, branchForLog(root), 'BLOCK_AI_CURE', 'L0 fault S (committed shim != renderShim)', '-', L0_FAULT_SHIM_STALE, MATRIX_L0_BLOCK),\n );\n // L0 fault S in GUARD_MATRIX.md's codebook — named as the blocking rule so the invocation line\n // says WHAT stopped the call, not merely that something did, and stamped as `fault=S` so the same\n // grep finds it here as in the sh half's `L0-shim/` stream.\n // A subagent is discriminated by `agent_id`, which Claude Code populates on stdin only off the main\n // loop (main falls back to the session id, so the field is absent). Its cure differs: the hooks\n // blocking it resolve through CLAUDE_PROJECT_DIR, which names the MAIN tree.\n const inSubagent = (payload.agent_id ?? '') !== '';\n emitDeny(shimStaleDenyReason(installedShimRulesVersion(), shimRoot ?? '', drifted, inSubagent) + guardMatrixPointer(docPath), payload.tool_name, 'committed-shim-stale', L0_FAULT_SHIM_STALE);\n}\n\n/**\n * Shared entry point for all three Claude Code PreToolUse adapters. `mode` selects which tool kinds\n * to validate; payloads outside the mode's scope pass through (emitAllow). Blocks by emitting a\n * PreToolUse `permissionDecision:\"deny\"` JSON on stdout (exit 0) — see claude-code-response.ts. Fails\n * CLOSED on any unexpected crash (emits a deny) so a broken hook never silently lets an edit through,\n * and the reason now surfaces in the Claude Code UI instead of being hidden on a stderr+exit-2 block.\n */\nexport async function runMain(mode: HookMode): Promise<void> {\n // Captured from the payload as soon as it parses so the fail-closed catch below can tell denyJson\n // which tool it is denying — a crash on a Bash call still gets the visible red systemMessage, a\n // crash on a file tool does not. Empty (before parse / malformed input) → treated as non-Bash.\n let toolName = '';\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = await readStdin();\n const payload = safeParse(raw);\n if (!payload) { emitAllow(); }\n toolName = payload.tool_name;\n\n // BEFORE enforceCommittedShim(), which can itself write a BLOCK line. See LogStream for why\n // all three of session/agent/hook are needed to keep concurrent writers off one file.\n logStream.identify(new StreamIdentity(payload.session_id ?? '', payload.agent_id ?? '', mode));\n\n // Prefer the payload cwd (the AI's actual working dir, follows a persisted `cd`) over\n // process.cwd(); they match today, but the payload is the authoritative signal and stays\n // correct if the hook is ever invoked from a fixed dir (e.g. via $CLAUDE_PROJECT_DIR).\n const cwd = payload.cwd ?? process.cwd();\n\n // Committed-shim self-guard: blocks real work while the committed shim is stale, but keeps the\n // recovery path open (cures, reads, config edit). See enforceCommittedShim / shimStaleRecoveryDecision.\n enforceCommittedShim(payload, cwd, mode);\n\n // Read-only tools (Read): audit-log, warm the main-sync cache, then run the ONE read-scoped\n // guard (read-stale-guard) and allow. Runs BEFORE the general rule engine — no code-style rule\n // ever sees a Read, and the only way this path can deny is a stale `main`.\n // The audit trail still records every file the AI opened (see setup.ts).\n if (READ_ONLY_TOOLS.has(payload.tool_name)) {\n const readPath = payload.tool_input.file_path ?? '';\n if (mode !== 'rules') {\n invocationLog.begin(cwd, payload.tool_name, readPath);\n // Reads vastly outnumber edits, so refreshing here is what actually keeps the shared\n // main-sync cache warm for feature-branch-guard. Detached; never slows the read.\n triggerMainSyncRefresh(cwd);\n }\n handleRead(readPath, cwd, mode);\n emitAllow();\n }\n\n // Per-invocation guard log (the `calls/` stream): tool + command/file + live branch +\n // main-sync-status snapshot, on EVERY guards call, for later cleanup automation. Best-effort;\n // never blocks the call. (The committed shim is no longer silently healed here — a mismatch is\n // reported by the self-guard above, not rewritten out from under the AI.)\n if (mode !== 'rules') {\n const target = payload.tool_name === 'Bash' ? (payload.tool_input.command ?? '') : (payload.tool_input.file_path ?? '');\n invocationLog.begin(cwd, payload.tool_name, target);\n }\n\n if (payload.tool_name === 'Bash') {\n // No code-style rule is bash-scoped, so the rules hook ignores Bash.\n if (mode === 'rules') { emitAllow(); }\n handleBash(payload, cwd, mode);\n return;\n }\n\n // File payloads run in 'rules' (code-style), 'guards' (file-scoped guards like\n // feature-branch-guard), and 'all'. The runner filters to the right category.\n handleFileTool(payload, cwd, mode);\n } catch (err: unknown) {\n const error = toError(err);\n denyForCrash(error, toolName);\n }\n}\n\n/**\n * The fail-closed boundary for anything that escaped the hook body. An escaped RuleFailError (a rule\n * that threw past the runner's per-rule catch) or an InformAiError (bad config/stdin) both carry an\n * AI-readable message; anything else is an unexpected bug. All three DENY and surface their reason,\n * because a hook that crashed established nothing and must never be read as an allow.\n */\n// webpieces-disable no-function-outside-class -- sibling of the module-scope hook entry points in this adapter; a lone class for one terminal boundary would break the file's shape\nfunction denyForCrash(error: Error, toolName: string): never {\n if (error instanceof RuleFailError) {\n emitDeny(error.aiMessage, toolName, 'rule-crash');\n }\n if (error instanceof InformAiError) {\n emitDeny(error.message, toolName, 'bad-config-or-stdin');\n }\n emitDeny(`[ai-hooks] hook crashed unexpectedly — failing closed: ${error.message}`, toolName, 'hook-crash');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"hook-core.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/hook-core.ts"],"names":[],"mappings":";;AA8OA,8DAKC;AA+DD,0BAgEC;;AAlXD,mDAA6B;AAE7B,2CAAuD;AACvD,iEAAsE;AACtE,yDAAuE;AACvE,uDAAkI;AAClI,iEAAmE;AACnE,qDAAsD;AACtD,0DAAyD;AACzD,yCAAqI;AACrI,+CAA2C;AAC3C,iEAA6D;AAC7D,sCAAsF;AACtF,8DAA8D;AAC9D,gEAA+D;AAC/D,iDAA4E;AAC5E,mDAA+D;AAC/D,2DAA4E;AAW5E,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;AAEnE,qGAAqG;AACrG,8FAA8F;AAC9F,oGAAoG;AACpG,kGAAkG;AAClG,mDAAmD;AACnD,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AA+B1C,SAAS,SAAS;IACd,OAAO,IAAI,OAAO,CAAC,CAAC,OAAgC,EAAE,EAAE;QACpD,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7C,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK;YAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC1B,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC3C,8DAA8D;IAC9D,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAC;IAChD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,IAAI,qBAAa,CAAC,gDAAgD,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/G,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB;IACvC,IAAI,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAoB,CAAC;IAClE,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,kBAAkB,CAAC,QAAkB,EAAE,SAA8B;IAC1E,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,IAAI,2BAAmB,CAAC,QAAQ,EAAE;YACrC,IAAI,sBAAc,CAAC,EAAE,EAAE,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC;SAClD,CAAC,CAAC;IACP,CAAC;IACD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,IAAI,2BAAmB,CAAC,QAAQ,EAAE;YACrC,IAAI,sBAAc,CAAC,SAAS,CAAC,UAAU,IAAI,EAAE,EAAE,SAAS,CAAC,UAAU,IAAI,EAAE,CAAC;SAC7E,CAAC,CAAC;IACP,CAAC;IACD,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAsB,EAAE,EAAE,CAAC,IAAI,sBAAc,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC;QAC9G,OAAO,IAAI,2BAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,kGAAkG;AAClG,kGAAkG;AAClG,sDAAsD;AACtD,0JAA0J;AAC1J,SAAS,YAAY,CAAC,MAAc,EAAE,QAAgB;IAClD,MAAM,KAAK,GAAG,IAAA,gCAAgB,EAAC,MAAM,CAAC,CAAC;IACvC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AACzD,CAAC;AAED,SAAS,UAAU,CAAC,OAA0B,EAAE,GAAW,EAAE,IAAc;IACvE,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;IAC3C,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAAC,IAAA,gCAAS,GAAE,CAAC;IAAC,CAAC;IACvD,MAAM,MAAM,GAAG,IAAA,gBAAO,EAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;QAAC,IAAA,gCAAS,GAAE,CAAC;IAAC,CAAC;IAC7B,iGAAiG;IACjG,mGAAmG;IACnG,iGAAiG;IACjG,mGAAmG;IACnG,kGAAkG;IAClG,kGAAkG;IAClG,4BAA4B;IAC5B,EAAE;IACF,kGAAkG;IAClG,gGAAgG;IAChG,IAAA,+BAAQ,EAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;AAC7F,CAAC;AAED;;;;;;;GAOG;AACH,0JAA0J;AAC1J,SAAS,UAAU,CAAC,QAAgB,EAAE,GAAW,EAAE,IAAc;IAC7D,IAAI,QAAQ,KAAK,EAAE;QAAE,OAAO;IAC5B,IAAI,MAAM,GAAyB,IAAI,CAAC;IACxC,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,GAAG,IAAA,gBAAO,EAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,KAAK,KAAK,CAAC;QACX,OAAO,CAAC,kCAAkC;IAC9C,CAAC;IACD,IAAI,CAAC,MAAM;QAAE,OAAO;IACpB,IAAA,4BAAY,EAAC,MAAM,EAAE,IAAI,2BAAmB,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;IACzE,IAAA,+BAAQ,EAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;AAC7F,CAAC;AAED,SAAS,cAAc,CAAC,OAA0B,EAAE,GAAW,EAAE,IAAc;IAC3E,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACtD,IAAI,CAAC,QAAQ,EAAE,CAAC;QAAC,IAAA,gCAAS,GAAE,CAAC;IAAC,CAAC;IAE/B,MAAM,KAAK,GAAG,kBAAkB,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,IAAI,CAAC,KAAK,EAAE,CAAC;QAAC,IAAA,gCAAS,GAAE,CAAC;IAAC,CAAC;IAE5B,+FAA+F;IAC/F,gGAAgG;IAChG,gGAAgG;IAChG,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,6BAAe,EAAE,CAAC;QACpD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACnB,wFAAwF;YACxF,sFAAsF;YACtF,oCAAoC;YACpC,MAAM,IAAI,GAAG,IAAI,6BAAc,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;YACvD,IAAA,+BAAgB,EACZ,IAAI,EACJ,IAAI,4BAAa,CAAC,sBAAsB,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAA,2BAAY,EAAC,IAAI,CAAC,EAAE,cAAc,EAAE,8CAA8C,EAAE,GAAG,EAAE,8BAAa,EAAE,IAAA,0BAAW,EAAC,8CAA8C,CAAC,CAAC,CAC3O,CAAC;YACF,yFAAyF;YACzF,sFAAsF;YACtF,qEAAqE;YACrE,IAAA,0CAAsB,EAAC,IAAI,EAAE,IAAA,6CAAyB,EAAC,GAAG,CAAC,CAAC,CAAC;QACjE,CAAC;QACD,IAAA,gCAAS,GAAE,CAAC;IAChB,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,YAAG,EAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,EAAE,CAAC;QAAC,IAAA,gCAAS,GAAE,CAAC;IAAC,CAAC;IAE7B,IAAA,4BAAY,EAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3C,kGAAkG;IAClG,8EAA8E;IAC9E,IAAA,+BAAQ,EAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;AAC/F,CAAC;AAmCD,2JAA2J;AAC3J,SAAgB,yBAAyB,CAAC,QAAgB,EAAE,OAAe,EAAE,QAAgB;IACzF,MAAM,OAAO,GAAG,IAAA,gBAAS,EAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACvD,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC;IACtC,IAAI,OAAO,KAAK,OAAO;QAAE,OAAO,YAAY,CAAC;IAC7C,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,uGAAuG;AACvG,4CAA4C;AAC5C,qGAAqG;AACrG,uGAAuG;AACvG,mGAAmG;AACnG,sGAAsG;AACtG,mGAAmG;AACnG,6FAA6F;AAC7F,6GAA6G;AAC7G,oGAAoG;AACpG,kFAAkF;AAClF,0JAA0J;AAC1J,SAAS,oBAAoB,CAAC,OAA0B,EAAE,GAAW,EAAE,IAAc;IACjF,oGAAoG;IACpG,gGAAgG;IAChG,qGAAqG;IACrG,wGAAwG;IACxG,6BAA6B;IAC7B,MAAM,QAAQ,GAAG,IAAA,wBAAiB,GAAE,CAAC;IACrC,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO;IAC7B,+FAA+F;IAC/F,mGAAmG;IACnG,qGAAqG;IACrG,mGAAmG;IACnG,mGAAmG;IACnG,QAAQ;IACR,MAAM,OAAO,GAAG,IAAA,uCAAmB,EAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACjC,MAAM,QAAQ,GAAG,yBAAyB,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,IAAI,EAAE,EAAE,OAAO,CAAC,UAAU,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IACpI,IAAI,QAAQ,KAAK,MAAM;QAAE,OAAO;IAChC,IAAI,QAAQ,KAAK,YAAY;QAAE,IAAA,gCAAS,GAAE,CAAC;IAC3C,kGAAkG;IAClG,6FAA6F;IAC7F,MAAM,IAAI,GAAG,IAAI,6BAAc,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,IAAA,+BAAmB,EAAC,IAAI,CAAC,CAAC;IAC1C,kGAAkG;IAClG,kGAAkG;IAClG,mGAAmG;IACnG,mGAAmG;IACnG,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,IAAI,EAAE,CAAC;IAChF,IAAA,+BAAgB,EACZ,IAAI,EACJ,IAAI,4BAAa,CAAC,sBAAsB,EAAE,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAA,2BAAY,EAAC,IAAI,CAAC,EAAE,eAAe,EAAE,2CAA2C,EAAE,GAAG,EAAE,oCAAmB,EAAE,8BAAe,CAAC,CACpM,CAAC;IACF,+FAA+F;IAC/F,kGAAkG;IAClG,4DAA4D;IAC5D,oGAAoG;IACpG,gGAAgG;IAChG,6EAA6E;IAC7E,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;IACnD,IAAA,+BAAQ,EAAC,IAAA,sCAAmB,EAAC,IAAA,gCAAyB,GAAE,EAAE,QAAQ,IAAI,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,GAAG,IAAA,8BAAkB,EAAC,OAAO,CAAC,EAAE,OAAO,CAAC,SAAS,EAAE,sBAAsB,EAAE,oCAAmB,CAAC,CAAC;AAClM,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,OAAO,CAAC,IAAc;IACxC,kGAAkG;IAClG,gGAAgG;IAChG,+FAA+F;IAC/F,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;YAAC,IAAA,gCAAS,GAAE,CAAC;QAAC,CAAC;QAC9B,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;QAE7B,4FAA4F;QAC5F,sFAAsF;QACtF,sBAAS,CAAC,QAAQ,CAAC,IAAI,2BAAc,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QAE/F,sFAAsF;QACtF,yFAAyF;QACzF,uFAAuF;QACvF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAEzC,+FAA+F;QAC/F,wGAAwG;QACxG,oBAAoB,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QAEzC,4FAA4F;QAC5F,+FAA+F;QAC/F,2EAA2E;QAC3E,yEAAyE;QACzE,IAAI,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YACzC,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,SAAS,IAAI,EAAE,CAAC;YACpD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACnB,4BAAa,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;gBACtD,qFAAqF;gBACrF,iFAAiF;gBACjF,IAAA,0CAAsB,EAAC,GAAG,EAAE,IAAA,6CAAyB,EAAC,GAAG,CAAC,CAAC,CAAC;YAChE,CAAC;YACD,UAAU,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YAChC,IAAA,gCAAS,GAAE,CAAC;QAChB,CAAC;QAED,sFAAsF;QACtF,8FAA8F;QAC9F,+FAA+F;QAC/F,0EAA0E;QAC1E,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;YACxH,4BAAa,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACxD,CAAC;QAED,IAAI,OAAO,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;YAC/B,qEAAqE;YACrE,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBAAC,IAAA,gCAAS,GAAE,CAAC;YAAC,CAAC;YACtC,UAAU,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YAC/B,OAAO;QACX,CAAC;QAED,+EAA+E;QAC/E,8EAA8E;QAC9E,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,oLAAoL;AACpL,SAAS,YAAY,CAAC,KAAY,EAAE,QAAgB;IAChD,IAAI,KAAK,YAAY,qBAAa,EAAE,CAAC;QACjC,IAAA,+BAAQ,EAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,KAAK,YAAY,qBAAa,EAAE,CAAC;QACjC,IAAA,+BAAQ,EAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC;IAC7D,CAAC;IACD,IAAA,+BAAQ,EAAC,0DAA0D,KAAK,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;AAChH,CAAC","sourcesContent":["import * as path from 'path';\n\nimport { run, runBash, runRead } from '../core/runner';\nimport { branchStateHangTimeoutFor } from '../core/main-sync-timeout';\nimport { logRejection, extractRuleNames } from '../core/rejection-log';\nimport { logGuardDecision, GuardDecision, branchForLog, invocationLog, MATRIX_L0_BLOCK, matrixL2Row } from '../core/decision-log';\nimport { triggerMainSyncRefresh } from '../core/main-sync-refresh';\nimport { CONFIG_FILENAME } from '../core/load-config';\nimport { RepoRootFinder } from '@webpieces/rules-config';\nimport { NormalizedToolInput, NormalizedEdit, ToolKind, InformAiError, RuleFailError, HookMode, BlockedResult } from '../core/types';\nimport { toError } from '../core/to-error';\nimport { emitDeny, emitAllow } from './claude-code-response';\nimport { governingShimRoot, isAllowed, installedShimRulesVersion } from '../bin/shim';\nimport { shimStaleDenyReason } from '../bin/shim-deny-reason';\nimport { managedSurfaceDrift } from '../bin/hook-registration';\nimport { writeGuardMatrixDoc, guardMatrixPointer } from '../core/l0-matrix';\nimport { logStream, StreamIdentity } from '../core/log-stream';\nimport { L0_FAULT_SHIM_STALE, L0_FAULT_NONE } from '../core/l0-fault-codes';\n\n// Which category of rules this hook invocation runs. The hook is split into two independently\n// installable PreToolUse hooks; each runs ONE category (the runner filters by it), and both can\n// receive file AND bash payloads:\n// - 'rules' → code-style rules (file/edit scope). Bash payloads pass through (no code rules apply).\n// - 'guards' → hookGuards section: bash git/PR guards on Bash AND file guards (feature-branch-guard)\n// on Write/Edit, PLUS a log-and-allow audit of Read. Matcher is Write|Edit|MultiEdit|Bash|Read.\n// - 'all' → both categories, used by the openclaw plugin adapter (a single before_tool_call hook).\nexport type { HookMode };\n\nconst HANDLED_FILE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit']);\n\n// Read-only tools carry NO guard or code rule, but the guards hook owns the per-invocation audit log\n// (the `calls/` stream). When the guards matcher includes these (see setup.ts GUARDS_HOOK), a\n// log-and-allow fast path records every file the AI opens — so a human can later inspect whether it\n// read a project's design.json BEFORE editing the project. Never blocked. Scoped to Read for now;\n// widen (Grep/Glob/NotebookRead) later if desired.\nconst READ_ONLY_TOOLS = new Set(['Read']);\n\ninterface ClaudeCodePayload {\n tool_name: string;\n tool_input: ClaudeCodeToolInput;\n // Claude Code sends the session's current working directory (follows a persisted `cd`). Used to\n // scope guards to the git repo the AI is actually in — see runner git-repo-boundary governance.\n cwd?: string;\n // Present ONLY when the hook fires inside a SUBAGENT; absent = the coordinator. Used to NAME the log\n // writer file and nothing else — never to decide which tree a call acts on, which is measured from\n // the path (see core/version-sync.ts). A worktree-isolated agent was measured resuming on the primary\n // clone after its tree was reaped, so identity cannot stand in for location.\n session_id?: string;\n agent_id?: string;\n agent_type?: string;\n}\n\ninterface ClaudeCodeToolInput {\n file_path?: string;\n content?: string;\n old_string?: string;\n new_string?: string;\n edits?: ClaudeCodeEditEntry[];\n command?: string;\n}\n\ninterface ClaudeCodeEditEntry {\n old_string?: string;\n new_string?: string;\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve: (value: string) => void) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => { data += chunk; });\n process.stdin.on('end', () => resolve(data));\n process.stdin.on('error', () => resolve(''));\n if (process.stdin.isTTY) resolve('');\n });\n}\n\nfunction safeParse(raw: string): ClaudeCodePayload | null {\n if (!raw || raw.trim() === '') return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(raw) as ClaudeCodePayload;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Malformed hook input from Claude Code stdin: ${error.message}`, { cause: error });\n }\n}\n\nfunction normalizeToolKind(toolName: string): ToolKind | null {\n if (HANDLED_FILE_TOOLS.has(toolName)) return toolName as ToolKind;\n return null;\n}\n\nfunction normalizeToolInput(toolKind: ToolKind, toolInput: ClaudeCodeToolInput): NormalizedToolInput | null {\n const filePath = toolInput.file_path;\n if (!filePath) return null;\n\n if (toolKind === 'Write') {\n return new NormalizedToolInput(filePath, [\n new NormalizedEdit('', toolInput.content || ''),\n ]);\n }\n if (toolKind === 'Edit') {\n return new NormalizedToolInput(filePath, [\n new NormalizedEdit(toolInput.old_string || '', toolInput.new_string || ''),\n ]);\n }\n if (toolKind === 'MultiEdit') {\n const raw = Array.isArray(toolInput.edits) ? toolInput.edits : [];\n const edits = raw.map((e: ClaudeCodeEditEntry) => new NormalizedEdit(e.old_string || '', e.new_string || ''));\n return new NormalizedToolInput(filePath, edits);\n }\n return null;\n}\n\n// The rule name for a block's audit line: the FIRST rule the report cites, or `fallback` when the\n// report opens with no `[rule]` header (a hand-written guard message). Comma-joined when a report\n// cites several, so `rule=` never silently drops one.\n// webpieces-disable no-function-outside-class -- sibling of handleBash()/handleFileTool() in this module; the adapter is module-scope functions by design\nfunction blockingRule(report: string, fallback: string): string {\n const names = extractRuleNames(report);\n return names.length > 0 ? names.join(',') : fallback;\n}\n\nfunction handleBash(payload: ClaudeCodePayload, cwd: string, mode: HookMode): void {\n const command = payload.tool_input.command;\n if (!command || command.trim() === '') { emitAllow(); }\n const result = runBash(command, cwd, mode);\n if (!result) { emitAllow(); }\n // NO DECISION LINE HERE. This used to write a generic `bash-guard` line because a Bash deny once\n // had no audit trail at all — but every layer now records its own: L1 into `L1-location/` with its\n // row, L2's guards into `L2-decisions/` with their rule and cache, and emitDeny below stamps the\n // call-level outcome onto `calls/`. So this was the THIRD line for one block, and the worst of the\n // three: it re-resolved the root from `cwd` via RepoRootFinder, which is not necessarily the tree\n // the guard actually judged, so a `cd`-relocated command scattered one block across two different\n // `.webpieces` directories.\n //\n // Bash deny → pass 'Bash' so denyJson adds the ANSI-red systemMessage (the only field a Bash deny\n // shows the human; permissionDecisionReason is invisible on Bash). See claude-code-response.ts.\n emitDeny(result.report, 'Bash', blockingRule(result.report, 'bash-guard'), result.fault);\n}\n\n/**\n * The read-scoped guard pass. Returns normally to ALLOW; only calls emitDeny when the guard fires.\n *\n * Wrapped in its own catch that swallows into an allow. Every other path in this hook fails CLOSED,\n * and that is right for edits and shell commands — but a crash here would block the agent from\n * READING, which includes reading webpieces.config.json to turn the offending guard off. So this one\n * path deliberately inverts the policy: a broken read-guard degrades to a no-op, never to a wedge.\n */\n// webpieces-disable no-function-outside-class -- sibling of handleBash()/handleFileTool() in this module; the adapter is module-scope functions by design\nfunction handleRead(filePath: string, cwd: string, mode: HookMode): void {\n if (filePath === '') return;\n let result: BlockedResult | null = null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n result = runRead(filePath, cwd, mode);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return; // fail OPEN — see the doc comment\n }\n if (!result) return;\n logRejection('Read', new NormalizedToolInput(filePath, []), result, cwd);\n emitDeny(result.report, 'Read', blockingRule(result.report, 'read-guard'), result.fault);\n}\n\nfunction handleFileTool(payload: ClaudeCodePayload, cwd: string, mode: HookMode): void {\n const toolKind = normalizeToolKind(payload.tool_name);\n if (!toolKind) { emitAllow(); }\n\n const input = normalizeToolInput(toolKind, payload.tool_input);\n if (!input) { emitAllow(); }\n\n // Always allow edits to webpieces.config.json — it's the fix target when the config is broken.\n // This exits BEFORE run(), so feature-branch-guard never sees a config edit; record that so the\n // audit trail explains why a config edit on a bad branch was not blocked (see decision-log.ts).\n if (path.basename(input.filePath) === CONFIG_FILENAME) {\n if (mode !== 'rules') {\n // `.webpieces/` (the decision log + sync cache these two calls write) lives at the repo\n // root, not the AI's cwd — resolve it so a config edit from a subdir doesn't create a\n // stray `<subdir>/.webpieces` tree.\n const root = new RepoRootFinder().resolveRepoRoot(cwd);\n logGuardDecision(\n root,\n new GuardDecision('feature-branch-guard', toolKind, input.filePath, branchForLog(root), 'ALLOW_EXEMPT', 'config-bypass (feature-branch-guard skipped)', '-', L0_FAULT_NONE, matrixL2Row('config-bypass (feature-branch-guard skipped)')),\n );\n // The guard's own refresh trigger lives inside its check(), which we skip here — so warm\n // the cache directly, otherwise a session that only edits webpieces.config.json never\n // refreshes the sync status. Fire-and-forget; never blocks the edit.\n triggerMainSyncRefresh(root, branchStateHangTimeoutFor(cwd));\n }\n emitAllow();\n }\n\n const result = run(toolKind, input, cwd, mode);\n if (!result) { emitAllow(); }\n\n logRejection(toolKind, input, result, cwd);\n // File-tool deny → pass the Write/Edit/MultiEdit kind so denyJson omits systemMessage (the reason\n // already renders red natively for these tools). See claude-code-response.ts.\n emitDeny(result.report, toolKind, blockingRule(result.report, 'file-guard'), result.fault);\n}\n\n// What a stale committed shim lets through — now a thin adapter over the ONE L0 allowlist (isAllowed in\n// ../bin/shim), not a list of its own. A stale shim must NEVER trap the actions needed to recover: the\n// original \"block everything but the cures\" version also shadowed the always-allowed\n// webpieces.config.json edit (handleFileTool) and blocked reads, so a repo that ALSO needed its config\n// fixed would deadlock — blocked from editing the one file whose edit is normally always allowed, and\n// blocked from reading it to know how.\n//\n// It used to carry its OWN narrower list (isShimCureCommand: the three shim cures only), and that\n// narrowness was a defect, not a safety property: `pnpm install` and `git pull` — the two commands that\n// resolve the version disagreement underneath a stale shim — were denied. Consulting the shared\n// allowlist fixes that by construction.\n//\n// What is NOT a defect, and must not be \"fixed\": those cures rewrite the committed shim from the\n// INSTALLED binary's renderShim(), overwriting whatever was there. That is the invariant, not\n// collateral damage. The shim (D/X/K, in POSIX sh, pre-binary) and this binary (S/C/Y, in JS) are two\n// halves of ONE L0 and they exchange assumptions — the shim parses file_path and carries ALLOW-READ /\n// ALLOW-CONFIG entries this binary relies on. Pair a binary with a shim rendered by a DIFFERENT\n// release and L0 acquires holes that nothing reports. So the rule is absolute: the committed shim\n// equals renderShim() of the binary in node_modules, and a cure that forces that is the cure working.\n// See healShim's header, which states the same invariant from the other side.\n//\n// Corollary for anyone regenerating the shim in a webpieces PR: commit `templates/ai-hook.sh` (source,\n// locked to renderShim() by unit test) and leave `.claude/webpieces/ai-hook.sh` (generated artifact)\n// alone. In THIS repo the local source runs ahead of the pinned node_modules, so committing a shim\n// rendered from local source produces a commit whose shim and whose @webpieces pin come from different\n// releases — precisely the mismatch above. The artifact heals on the next upgrade; that is its job.\n//\n// - 'allow-cure' → a Bash cure on the allowlist: emitAllow directly, bypassing the git guards.\n// - 'pass' → a recovery action the normal flow already permits, so fall THROUGH and let it: ANY\n// Read (you must read to know how to fix — see handleRead, which itself fails open),\n// or an edit to webpieces.config.json (the always-allowed recovery target).\n// - 'deny' → all OTHER work: blocked until the committed shim matches renderShim() again.\nexport type ShimStaleDecision = 'allow-cure' | 'pass' | 'deny';\n// webpieces-disable no-function-outside-class -- pure decision helper beside the adapter's other module-scope functions; exported for direct unit testing.\nexport function shimStaleRecoveryDecision(toolName: string, command: string, filePath: string): ShimStaleDecision {\n const allowed = isAllowed(toolName, command, filePath);\n if (allowed === 'pass') return 'pass';\n if (allowed === 'allow') return 'allow-cure';\n return 'deny';\n}\n\n// MANAGED-HOOK-SURFACE self-guard, moved here from the rendered shim (2026-07-24) and widened from one\n// file to three (2026-08-07). The committed\n// .claude/webpieces/ai-hook.sh is webpieces-MANAGED and generated from renderShim(); if it no longer\n// matches, it was reverted / hand-edited / predates this binary, so its OWN fail-closed logic can't be\n// trusted. We are the CURRENT binary from node_modules — the trustworthy party — so WE decide here\n// instead of the (possibly stale) shim. It used to `cmp` itself inside the shim: a double-edged trap,\n// since the check lived in the very file it guarded and a fix could only ship by regenerating that\n// file. Now we fail closed on all real WORK while always leaving the recovery path open (see\n// shimStaleRecoveryDecision): the whole L0 allowlist, any Read, and editing webpieces.config.json. We deny +\n// tell the AI; we do NOT silently rewrite the file under it. 'rules' hook skips it (guards owns the\n// shim). Returns normally (pass / nothing to do) or exits via emitAllow/emitDeny.\n// webpieces-disable no-function-outside-class -- sibling of handleBash()/handleFileTool() in this module; the adapter is module-scope functions by design\nfunction enforceCommittedShim(payload: ClaudeCodePayload, cwd: string, mode: HookMode): void {\n // ONE root for the whole decision, resolved from the RUNNING MODULE (governingShimRoot), never from\n // `cwd`: the shim file we compare and the renderShim() we compare it TO must come from the same\n // install, or the check straddles two trees and can never converge (see governingShimRoot's header).\n // `cwd` still selects where the L0 matrix doc is dropped — that is a \"where does the AI read\" question,\n // not part of the judgement.\n const shimRoot = governingShimRoot();\n if (mode === 'rules') return;\n // WHICH of the three managed things moved — ai-hook.sh, the settings.json registration, or its\n // managed env entry (the Bash-cwd pin that keeps a guard's verdict independent of where an earlier\n // `cd` left the shell; see managed-env.ts). Nothing validated the registration before it joined this\n // fault, so a settings file left on a superseded form silently changed WHO GOVERNS, with no signal\n // anywhere — which is the whole reason the registration is a drift surface and not just an install\n // step.\n const drifted = managedSurfaceDrift(shimRoot);\n if (drifted.length === 0) return;\n const decision = shimStaleRecoveryDecision(payload.tool_name, payload.tool_input.command ?? '', payload.tool_input.file_path ?? '');\n if (decision === 'pass') return;\n if (decision === 'allow-cure') emitAllow();\n // Drop the L0 matrix doc where the AI can read it and point the deny at it — a Read is entry 1 of\n // the same allowlist, so the pointer is always followable. Best-effort: no doc → no pointer.\n const root = new RepoRootFinder().resolveRepoRoot(cwd);\n const docPath = writeGuardMatrixDoc(root);\n // WRITE THE AUDIT LINE HERE. This block happens BEFORE invocationLog.begin() — it has to, since a\n // stale shim invalidates everything downstream — so emitDeny's flush finds nothing pending and an\n // `S` storm left NO trace at all: the one fault most likely to block twenty consecutive tool calls\n // was the one fault the trail could not show. A decision line is the fix that costs no reordering.\n const target = payload.tool_input.command ?? payload.tool_input.file_path ?? '';\n logGuardDecision(\n root,\n new GuardDecision('committed-shim-stale', payload.tool_name, target, branchForLog(root), 'BLOCK_AI_CURE', 'L0 fault S (committed shim != renderShim)', '-', L0_FAULT_SHIM_STALE, MATRIX_L0_BLOCK),\n );\n // L0 fault S in GUARD_MATRIX.md's codebook — named as the blocking rule so the invocation line\n // says WHAT stopped the call, not merely that something did, and stamped as `fault=S` so the same\n // grep finds it here as in the sh half's `L0-shim/` stream.\n // A subagent is discriminated by `agent_id`, which Claude Code populates on stdin only off the main\n // loop (main falls back to the session id, so the field is absent). Its cure differs: the hooks\n // blocking it resolve through CLAUDE_PROJECT_DIR, which names the MAIN tree.\n const inSubagent = (payload.agent_id ?? '') !== '';\n emitDeny(shimStaleDenyReason(installedShimRulesVersion(), shimRoot ?? '', drifted, inSubagent) + guardMatrixPointer(docPath), payload.tool_name, 'committed-shim-stale', L0_FAULT_SHIM_STALE);\n}\n\n/**\n * Shared entry point for all three Claude Code PreToolUse adapters. `mode` selects which tool kinds\n * to validate; payloads outside the mode's scope pass through (emitAllow). Blocks by emitting a\n * PreToolUse `permissionDecision:\"deny\"` JSON on stdout (exit 0) — see claude-code-response.ts. Fails\n * CLOSED on any unexpected crash (emits a deny) so a broken hook never silently lets an edit through,\n * and the reason now surfaces in the Claude Code UI instead of being hidden on a stderr+exit-2 block.\n */\nexport async function runMain(mode: HookMode): Promise<void> {\n // Captured from the payload as soon as it parses so the fail-closed catch below can tell denyJson\n // which tool it is denying — a crash on a Bash call still gets the visible red systemMessage, a\n // crash on a file tool does not. Empty (before parse / malformed input) → treated as non-Bash.\n let toolName = '';\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = await readStdin();\n const payload = safeParse(raw);\n if (!payload) { emitAllow(); }\n toolName = payload.tool_name;\n\n // BEFORE enforceCommittedShim(), which can itself write a BLOCK line. See LogStream for why\n // all three of session/agent/hook are needed to keep concurrent writers off one file.\n logStream.identify(new StreamIdentity(payload.session_id ?? '', payload.agent_id ?? '', mode));\n\n // Prefer the payload cwd (the AI's actual working dir, follows a persisted `cd`) over\n // process.cwd(); they match today, but the payload is the authoritative signal and stays\n // correct if the hook is ever invoked from a fixed dir (e.g. via $CLAUDE_PROJECT_DIR).\n const cwd = payload.cwd ?? process.cwd();\n\n // Committed-shim self-guard: blocks real work while the committed shim is stale, but keeps the\n // recovery path open (cures, reads, config edit). See enforceCommittedShim / shimStaleRecoveryDecision.\n enforceCommittedShim(payload, cwd, mode);\n\n // Read-only tools (Read): audit-log, warm the main-sync cache, then run the ONE read-scoped\n // guard (read-stale-guard) and allow. Runs BEFORE the general rule engine — no code-style rule\n // ever sees a Read, and the only way this path can deny is a stale `main`.\n // The audit trail still records every file the AI opened (see setup.ts).\n if (READ_ONLY_TOOLS.has(payload.tool_name)) {\n const readPath = payload.tool_input.file_path ?? '';\n if (mode !== 'rules') {\n invocationLog.begin(cwd, payload.tool_name, readPath);\n // Reads vastly outnumber edits, so refreshing here is what actually keeps the shared\n // main-sync cache warm for feature-branch-guard. Detached; never slows the read.\n triggerMainSyncRefresh(cwd, branchStateHangTimeoutFor(cwd));\n }\n handleRead(readPath, cwd, mode);\n emitAllow();\n }\n\n // Per-invocation guard log (the `calls/` stream): tool + command/file + live branch +\n // main-sync-status snapshot, on EVERY guards call, for later cleanup automation. Best-effort;\n // never blocks the call. (The committed shim is no longer silently healed here — a mismatch is\n // reported by the self-guard above, not rewritten out from under the AI.)\n if (mode !== 'rules') {\n const target = payload.tool_name === 'Bash' ? (payload.tool_input.command ?? '') : (payload.tool_input.file_path ?? '');\n invocationLog.begin(cwd, payload.tool_name, target);\n }\n\n if (payload.tool_name === 'Bash') {\n // No code-style rule is bash-scoped, so the rules hook ignores Bash.\n if (mode === 'rules') { emitAllow(); }\n handleBash(payload, cwd, mode);\n return;\n }\n\n // File payloads run in 'rules' (code-style), 'guards' (file-scoped guards like\n // feature-branch-guard), and 'all'. The runner filters to the right category.\n handleFileTool(payload, cwd, mode);\n } catch (err: unknown) {\n const error = toError(err);\n denyForCrash(error, toolName);\n }\n}\n\n/**\n * The fail-closed boundary for anything that escaped the hook body. An escaped RuleFailError (a rule\n * that threw past the runner's per-rule catch) or an InformAiError (bad config/stdin) both carry an\n * AI-readable message; anything else is an unexpected bug. All three DENY and surface their reason,\n * because a hook that crashed established nothing and must never be read as an allow.\n */\n// webpieces-disable no-function-outside-class -- sibling of the module-scope hook entry points in this adapter; a lone class for one terminal boundary would break the file's shape\nfunction denyForCrash(error: Error, toolName: string): never {\n if (error instanceof RuleFailError) {\n emitDeny(error.aiMessage, toolName, 'rule-crash');\n }\n if (error instanceof InformAiError) {\n emitDeny(error.message, toolName, 'bad-config-or-stdin');\n }\n emitDeny(`[ai-hooks] hook crashed unexpectedly — failing closed: ${error.message}`, toolName, 'hook-crash');\n}\n"]}
|
package/src/bin/setup.js
CHANGED
|
@@ -245,14 +245,80 @@ function migrateRetiredRuleNames(section, changes) {
|
|
|
245
245
|
changes.push(`deleted retired "${entry.key}" (it moved to ${entry.movedTo})`);
|
|
246
246
|
continue;
|
|
247
247
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
248
|
+
mergeIntoDestination(section, entry.key, entry.movedTo, changes);
|
|
249
|
+
}
|
|
250
|
+
fillRequiredFields(section, changes);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Fold one retired key's entry into its destination, whether the destination exists yet or not.
|
|
254
|
+
*
|
|
255
|
+
* THIS IS N→1, NOT 1:1, and the difference is the whole reason this helper exists. Four retired keys
|
|
256
|
+
* now point at ONE destination (`branch-state-guard`, `pr-lifecycle-guard`). The previous code renamed
|
|
257
|
+
* the first key it met and then, finding the destination already present, DELETED each of the other
|
|
258
|
+
* three outright — so which guard's settings survived depended on RETIRED_CONFIG_KEYS declaration
|
|
259
|
+
* order rather than on the consumer's file, and the survivor carried only that one guard's fields, so
|
|
260
|
+
* it was missing required fields of the merged schema. `wp-install-ai-hooks` is the command advertised
|
|
261
|
+
* as the migration path; half-migrating every consumer into an invalid config is not an option.
|
|
262
|
+
*
|
|
263
|
+
* UNION, first writer wins per field. Earlier-declared keys are the more specific ones (only
|
|
264
|
+
* feature-branch-guard carries `branchNamingConvention`), and a field already present on the
|
|
265
|
+
* destination — because the consumer wrote it, or an earlier key contributed it — is never overwritten.
|
|
266
|
+
* Fields the merged schema does not know are dropped by the same pass, since carrying a deleted field
|
|
267
|
+
* across (`upsertPrCommand`) would produce a config the validator immediately rejects.
|
|
268
|
+
*/
|
|
269
|
+
// webpieces-disable no-function-outside-class -- sibling of the other seed*/migrate* helpers; this module is config-shape builders by design
|
|
270
|
+
function mergeIntoDestination(section, key, destination, changes) {
|
|
271
|
+
const source = asSection(section[key]);
|
|
272
|
+
delete section[key];
|
|
273
|
+
const fields = (0, rules_config_1.schemaFieldNames)(destination);
|
|
274
|
+
const target = asSection(section[destination]);
|
|
275
|
+
const existed = destination in section;
|
|
276
|
+
const carried = [];
|
|
277
|
+
const dropped = [];
|
|
278
|
+
for (const field of Object.keys(source)) {
|
|
279
|
+
if (fields !== null && !fields.includes(field)) {
|
|
280
|
+
dropped.push(field);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (field in target)
|
|
251
284
|
continue;
|
|
285
|
+
target[field] = source[field];
|
|
286
|
+
carried.push(field);
|
|
287
|
+
}
|
|
288
|
+
section[destination] = target;
|
|
289
|
+
const verb = existed ? 'merged' : 'renamed';
|
|
290
|
+
const droppedNote = dropped.length > 0 ? `; dropped deleted field(s) ${dropped.join(', ')}` : '';
|
|
291
|
+
changes.push(`${verb} retired "${key}" -> "${destination}" (carried ${carried.join(', ') || 'nothing new'}${droppedNote})`);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Fill any schema-REQUIRED field a migrated entry ended up without.
|
|
295
|
+
*
|
|
296
|
+
* A union of four partial entries is not guaranteed to satisfy the destination's schema — the merged
|
|
297
|
+
* `branch-state-guard` needs `mode` and both escape hatches, and a consumer whose four old entries
|
|
298
|
+
* predate one of them would land short. Seeding the gap from the SAME source the installer and the
|
|
299
|
+
* validator use (seedEntryForRule) is what makes the install command a complete instruction
|
|
300
|
+
* rather than a first step. Only ever ADDS; a value the consumer stated is never touched.
|
|
301
|
+
*/
|
|
302
|
+
// webpieces-disable no-function-outside-class -- sibling of the other seed*/migrate* helpers; this module is config-shape builders by design
|
|
303
|
+
function fillRequiredFields(section, changes) {
|
|
304
|
+
for (const name of Object.keys(section)) {
|
|
305
|
+
if ((0, rules_config_1.schemaFieldNames)(name) === null)
|
|
306
|
+
continue;
|
|
307
|
+
// A rule ENTRY is a flat bag of scalars, so it is read as Json here rather than through
|
|
308
|
+
// asSection (whose values are whole entries). Same object either way; only the view differs.
|
|
309
|
+
const entry = asSection(section[name]);
|
|
310
|
+
const seed = (0, rules_config_1.seedEntryForRule)(name);
|
|
311
|
+
const added = [];
|
|
312
|
+
for (const field of Object.keys(seed)) {
|
|
313
|
+
if (field in entry)
|
|
314
|
+
continue;
|
|
315
|
+
entry[field] = seed[field];
|
|
316
|
+
added.push(field);
|
|
252
317
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
318
|
+
if (added.length === 0)
|
|
319
|
+
continue;
|
|
320
|
+
section[name] = entry;
|
|
321
|
+
changes.push(`filled required field(s) on "${name}": ${added.join(', ')}`);
|
|
256
322
|
}
|
|
257
323
|
}
|
|
258
324
|
// Deep-copy the framework's default match-rules (the no-fetch guard) into plain JSON for the config
|
package/src/bin/setup.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/setup.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { homedir } from 'os';\nimport { createInterface } from 'readline';\n\nimport { allRuleNames, seedEntryForRule, sectionForRule, isHookGuard, DEFAULT_MATCH_RULES, DEFAULT_BUILD_COMMAND, RETIRED_CONFIG_KEYS, RETIRED_SCOPE_RULE, RepoRootFinder, writeTemplate, writeTemplateIfMissing, CONFIG_POLICY_DOC } from '@webpieces/rules-config';\n\nimport { toError } from '../core/to-error';\nimport { SHIM_MARKER, shimPath, renderShim } from './shim';\nimport {\n ClaudeSettings, HookCommand, HookEntry, HookRegistrationEntry, GUARDS_BIN, LEGACY_GUARANTEE_ROOT_MARKER,\n GUARDS_MATCHER, RULES_BIN, RULES_MATCHER, addHookEntry, applyManagedEnv, readSettings, shimCommand,\n writeSettings,\n} from './hook-registration';\nimport { BASH_CWD_ENV_KEY, BASH_CWD_ENV_VALUE } from './managed-env';\n\nconst CONFIG_FILENAME = 'webpieces.config.json';\n// The seeded buildCommand comes from @webpieces/rules-config, NOT from a copy here. This file used to\n// hold its own — with `--base=origin/main`, a DIFFERENT base from the one the gate documents — so a\n// freshly set-up repo was seeded with a command that rebuilds projects touched by other people's\n// merged PRs, and whole-repo-build-guard then quoted that command back in its refusals.\nconst DEFAULT_UPSERT_PR = 'pnpm wp-start-upsert-pr';\nconst DEFAULT_MERGE_COMPLETE = 'pnpm wp-finish-upsert-pr';\n\n// ---------------------------------------------------------------------------\n// The two independently-installable GUARD hooks. Each can land in a different settings file (see\n// InstallTarget) so a team can ship the guards while a developer keeps the code-style rules local\n// while iterating. Both are registered ABSOLUTE via $CLAUDE_PROJECT_DIR, so the MAIN tree governs every\n// tree. There used to be a third — the L-1 hook, which existed only to keep a RELATIVE registration\n// resolvable; it is retired, and purgeRetiredGuaranteeRoot() removes anything left of it.\n// ---------------------------------------------------------------------------\nclass HookSpec {\n constructor(\n readonly key: string,\n readonly label: string,\n readonly matcher: string,\n readonly bin: string,\n ) {}\n\n // Absolute targets (global) need the exact path to this repo's bin — no ~/.webpieces bridge.\n // Project targets get the ABSOLUTE shim command, `$CLAUDE_PROJECT_DIR/.claude/webpieces/ai-hook.sh`\n // (see hook-registration.ts for why): it resolves from ANY cwd, so a hook can never fail to launch —\n // which per the hooks reference would be exit 127, a SILENT UNGUARDED ALLOW rather than a block.\n commandFor(target: InstallTarget, projectRoot: string): string {\n if (target.absolute) {\n return `node ${path.join(projectRoot, 'node_modules', '.bin', this.bin)}`;\n }\n return shimCommand(this.bin);\n }\n}\n\n// Idempotent: re-running the installer overwrites the managed shim in place.\nfunction writeShim(projectRoot: string): void {\n const target = shimPath(projectRoot);\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, renderShim(), { mode: 0o755 });\n // writeFileSync's mode is only applied when creating the file; force it on overwrite too.\n fs.chmodSync(target, 0o755);\n}\n\nfunction removeShim(projectRoot: string): void {\n const target = shimPath(projectRoot);\n if (fs.existsSync(target)) fs.rmSync(target);\n}\n\n// A managed .sh is shared by the project hooks — only safe to delete once no project settings file\n// references it anymore (i.e. the other hook was moved to global or uninstalled too).\n// webpieces-disable no-function-outside-class -- setup.ts is deliberately DI-free (it must run on a half-written node_modules; see install-entry.ts), so every function here is module-scope\nfunction markerReferenced(targets: InstallTarget[], marker: string): boolean {\n return targets.some((t: InstallTarget) => {\n const entries = readSettings(t.settingsPath).hooks?.PreToolUse ?? [];\n return entries.some((e: HookEntry) => e.hooks.some((h: HookCommand) => h.command.includes(marker)));\n });\n}\n\n/**\n * Remove every trace of the RETIRED L-1 hook (`guarantee-root.sh`) — the file and any PreToolUse entry\n * still pointing at it. REMOVAL ONLY: nothing here can ever write one back.\n *\n * L-1 existed to guarantee a RELATIVE guard-hook path resolved, by refusing any `cd` that would park the\n * shell in a subdirectory. The guard hooks are ABSOLUTE now (`$CLAUDE_PROJECT_DIR/...`), so they resolve\n * from any cwd and the guarantee is structural — there is nothing left to police, and the subdirectory\n * denial that used to pay for it is gone with it.\n */\n// webpieces-disable no-function-outside-class -- setup.ts is deliberately DI-free (it must run on a half-written node_modules; see install-entry.ts), so every function here is module-scope\nfunction purgeRetiredGuaranteeRoot(targets: InstallTarget[], projectRoot: string): void {\n for (const target of targets) {\n const settings = readSettings(target.settingsPath);\n if (removeHookByMarker(settings, LEGACY_GUARANTEE_ROOT_MARKER)) {\n writeSettings(target.settingsPath, settings);\n console.log(` 🗑️ removed the retired L-1 guarantee-root hook from ${target.label}`);\n }\n }\n const legacyFile = path.join(projectRoot, LEGACY_GUARANTEE_ROOT_MARKER);\n if (fs.existsSync(legacyFile)) {\n fs.rmSync(legacyFile, { force: true });\n console.log(' 🗑️ deleted the retired .claude/webpieces/guarantee-root.sh');\n }\n}\n\nexport class InstallTarget {\n constructor(\n readonly choice: string,\n readonly label: string,\n readonly settingsPath: string,\n readonly absolute: boolean,\n ) {}\n}\n\n// The matchers and bin names come from ./hook-registration, which is also what the drift check and\n// wp-upgrade-shim compare against — one spelling of the registration, or the installer and the\n// validator can disagree about what \"installed\" means.\nexport const RULES_HOOK = new HookSpec('rules', 'Rules hook (code-style validation)', RULES_MATCHER, RULES_BIN);\nexport const GUARDS_HOOK = new HookSpec('guards', 'Guards hook (git/PR/branch protection)', GUARDS_MATCHER, GUARDS_BIN);\n\n// `homeDir` is injectable so tests can point the global target at a temp dir instead of the real\n// ~/.claude/settings.json (a unit test must never write the user's actual global settings).\nexport function installTargets(projectRoot: string, homeDir: string = homedir()): InstallTarget[] {\n return [\n new InstallTarget('1', 'project (.claude/settings.json — committed, for the team)',\n path.join(projectRoot, '.claude', 'settings.json'), false),\n new InstallTarget('2', 'project for you (.claude/settings.local.json — personal)',\n path.join(projectRoot, '.claude', 'settings.local.json'), false),\n new InstallTarget('3', 'global (~/.claude/settings.json — exact path, this repo only)',\n path.join(homeDir, '.claude', 'settings.json'), true),\n ];\n}\n\n// ---------------------------------------------------------------------------\n// webpieces.config.json seeding + migration to the rules / hookGuards / commands layout.\n// ---------------------------------------------------------------------------\n// webpieces-disable no-any-unknown -- webpieces.config.json / settings.json are opaque consumer JSON\ntype Json = Record<string, unknown>;\ntype RuleEntry = Json;\ntype Section = Record<string, RuleEntry>;\n\ninterface ConfigFile {\n extends?: string;\n rules: Section;\n hookGuards: Section;\n commands: Json;\n excludePaths: string[];\n 'match-rules': Json[];\n rulesDir: string[];\n}\n\ninterface MigrateResult {\n config: ConfigFile;\n changes: string[];\n}\n\n// webpieces-disable no-function-outside-class -- sibling of the other seed* helpers; this module is config-shape builders by design\nfunction seedRule(ruleName: string): RuleEntry {\n // Both escape hatches are seeded (and REQUIRED) so every rule block shows them: 0 = active,\n // null = no branch scoping. A human/AI edits these to time-box or branch-scope a rule off.\n //\n // The ENTIRE entry comes from rules-config's seedEntryForRule() — the same module that owns the\n // schema the loader validates against, so the installer can never emit an entry the loader\n // rejects. It supplies: the recommended mode (the SAME recommendation the validator prints in its\n // copy-paste snippet, so seed and advice cannot disagree), both hatches, and a default for every\n // other schema-REQUIRED field. Seeding used to be a flat 'OFF' plus the two hatches, which was\n // wrong twice over: adopters got nothing enforced, AND the entry was missing required fields\n // (e.g. branch-creation-guard.autoReapMergedBranches), so the config failed to load on first run.\n return seedEntryForRule(ruleName);\n}\n\n// The guard-hint command strings live under `guardHints`. The flat `upsertPr`/`mergeComplete` keys this\n// used to seed are RETIRED and now fail validation — seeding them meant every freshly installed repo was\n// born on a shape the validator rejects.\nfunction seedCommands(): Json {\n return {\n 'pr-gate': { mode: 'OFF', buildCommand: DEFAULT_BUILD_COMMAND, gates: [] },\n guardHints: { prCreationOrPush: DEFAULT_UPSERT_PR, mergeInProgress: DEFAULT_MERGE_COMPLETE },\n };\n}\n\n// Required excludePaths block: ONE glob list suppressing hook enforcement per file path. Seeded empty\n// (enforce everywhere) — a client adds paths (e.g. \"repositories/**\") to exempt vendored trees.\n// webpieces-disable no-function-outside-class -- sibling of the other seed* helpers; this module is config-shape builders by design\nfunction seedExcludePaths(): string[] {\n return [];\n}\n\n// Bring an existing `excludePaths` forward to the single-list shape. Already a list → untouched.\n// Legacy `{ rules, guards }` → unioned (order preserved, duplicates dropped) and recorded as a change\n// so `wp-install-ai-hooks` is the migration path rather than a hand-edit. Anything else → seeded [].\n// webpieces-disable no-any-unknown -- `raw` is opaque consumer JSON until narrowed here\n// webpieces-disable no-function-outside-class -- sibling of the other seed*/migrate* helpers; this module is config-shape builders by design\nfunction migrateExcludePaths(raw: unknown, changes: string[]): string[] {\n if (Array.isArray(raw)) return (raw as string[]).filter(p => typeof p === 'string');\n if (typeof raw === 'object' && raw !== null) {\n // webpieces-disable no-any-unknown -- narrowing the opaque legacy block from consumer JSON\n const legacy = raw as Record<string, unknown>;\n const rules = Array.isArray(legacy['rules']) ? (legacy['rules'] as string[]) : [];\n const guards = Array.isArray(legacy['guards']) ? (legacy['guards'] as string[]) : [];\n const merged = [...new Set([...rules, ...guards].filter(p => typeof p === 'string'))];\n changes.push(`migrated excludePaths {rules,guards} -> one list (${merged.length} path(s))`);\n return merged;\n }\n changes.push('added excludePaths ([])');\n return [];\n}\n\n/** One retired flat command string and the guardHints field it becomes. Data-only (per CLAUDE.md). */\nclass GuardHintMove {\n retiredKey: string;\n hintKey: string;\n fallback: string;\n\n constructor(retiredKey: string, hintKey: string, fallback: string) {\n this.retiredKey = retiredKey;\n this.hintKey = hintKey;\n this.fallback = fallback;\n }\n}\n\n/**\n * Bring `commands` forward to the `guardHints` shape, moving the RETIRED flat `upsertPr`/`mergeComplete`\n * strings and DELETING them. Deleting is the point: the validator now rejects them, so leaving them behind\n * would keep the config failing after a \"successful\" sync.\n *\n * The consumer's own value wins over the default — a repo that renamed its gated command keeps that name.\n */\n// webpieces-disable no-function-outside-class -- sibling of the other seed*/migrate* helpers; this module is config-shape builders by design\nfunction migrateGuardHints(commands: Json, changes: string[]): void {\n const hints: Json = (typeof commands['guardHints'] === 'object' && commands['guardHints'] !== null)\n ? (commands['guardHints'] as Json) : {};\n const moves: readonly GuardHintMove[] = [\n new GuardHintMove('upsertPr', 'prCreationOrPush', DEFAULT_UPSERT_PR),\n new GuardHintMove('mergeComplete', 'mergeInProgress', DEFAULT_MERGE_COMPLETE),\n ];\n for (const move of moves) {\n const retiredKey = move.retiredKey;\n const hintKey = move.hintKey;\n const fallback = move.fallback;\n const carried = commands[retiredKey];\n if (carried !== undefined) {\n delete commands[retiredKey];\n if (hints[hintKey] === undefined) hints[hintKey] = carried;\n changes.push(`moved retired commands.${retiredKey} -> commands.guardHints.${hintKey}`);\n }\n if (hints[hintKey] === undefined) {\n hints[hintKey] = fallback;\n changes.push(`added commands.guardHints.${hintKey}`);\n }\n }\n commands['guardHints'] = hints;\n}\n\n/**\n * Apply the RETIRED rule/guard retirements in place. These used to be rewritten silently at load time, so\n * a consumer's file kept the dead name forever; the loader now rejects it, which makes this the one\n * command that can fix the file. Skips a rename when the new name is already configured, so an explicit\n * entry is never clobbered by a stale one.\n *\n * NOT EVERY RETIREMENT IS A RENAME, and treating them all as one produced garbage. `whole-repo-build-guard`\n * moved OUT of webpieces.config.json entirely — its `movedTo` is the PROSE destination\n * `~/.webpieces/config.json → experimental.whole-repo-build-guard`, not a sibling key — so the rename\n * branch below would have created a hookGuards entry literally named that whole sentence, which no\n * validator knows and which the next run reports as another unknown rule. `prunable` is the discriminator:\n * when the entry says deleting is the whole fix, DELETE it, exactly as `ConfigPruner` does.\n */\n// webpieces-disable no-function-outside-class -- sibling of the other seed*/migrate* helpers; this module is config-shape builders by design\nfunction migrateRetiredRuleNames(section: Section, changes: string[]): void {\n for (const entry of RETIRED_CONFIG_KEYS) {\n if (entry.scope !== RETIRED_SCOPE_RULE) continue;\n if (!(entry.key in section)) continue;\n if (entry.prunable) {\n delete section[entry.key];\n changes.push(`deleted retired \"${entry.key}\" (it moved to ${entry.movedTo})`);\n continue;\n }\n if (entry.movedTo in section) {\n delete section[entry.key];\n changes.push(`dropped retired \"${entry.key}\" (\"${entry.movedTo}\" is already configured)`);\n continue;\n }\n section[entry.movedTo] = section[entry.key];\n delete section[entry.key];\n changes.push(`renamed retired \"${entry.key}\" -> \"${entry.movedTo}\"`);\n }\n}\n\n// Deep-copy the framework's default match-rules (the no-fetch guard) into plain JSON for the config\n// file. Round-tripping through JSON turns the MatchRuleConfig instances into plain objects.\nfunction seedMatchRules(): Json[] {\n return JSON.parse(JSON.stringify(DEFAULT_MATCH_RULES)) as Json[];\n}\n\nfunction buildSeedConfig(): ConfigFile {\n const rules: Section = {};\n const hookGuards: Section = {};\n for (const name of allRuleNames()) {\n if (sectionForRule(name) === 'hookGuards') hookGuards[name] = seedRule(name);\n else rules[name] = seedRule(name);\n }\n return {\n rules, hookGuards, commands: seedCommands(), excludePaths: seedExcludePaths(),\n // Seed the required match-rules array with the framework's default no-fetch guard. A fresh\n // project gets contract-first enforcement out of the box; clients edit it and add more entries.\n 'match-rules': seedMatchRules(),\n rulesDir: [],\n };\n}\n\nfunction writeConfig(configPath: string, config: ConfigFile): void {\n fs.writeFileSync(configPath, JSON.stringify(config, null, 4) + '\\n');\n}\n\nfunction readConfig(configPath: string): Json {\n const raw = fs.readFileSync(configPath, 'utf8');\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(raw) as Json;\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`${CONFIG_FILENAME} has invalid JSON — fix it, then retry: ${error.message}`, { cause: error });\n }\n}\n\nfunction asSection(value: Json[string]): Section {\n return (typeof value === 'object' && value !== null && !Array.isArray(value)) ? (value as Section) : {};\n}\n\n// Migrate an existing config to the rules / hookGuards / commands layout and add any missing rules.\n// Returns a human-readable list of what changed (empty = already up to date).\nexport function migrate(existing: Json): MigrateResult {\n const changes: string[] = [];\n const rules: Section = asSection(existing['rules']);\n const hookGuards: Section = asSection(existing['hookGuards']);\n const commands: Json = (typeof existing['commands'] === 'object' && existing['commands'] !== null)\n ? (existing['commands'] as Json) : {};\n\n // Move a deprecated top-level pr-gate block under commands.\n if (existing['pr-gate'] !== undefined && commands['pr-gate'] === undefined) {\n commands['pr-gate'] = existing['pr-gate'];\n changes.push('moved top-level \"pr-gate\" → commands[\"pr-gate\"]');\n }\n // Apply retired RENAMES first, so a renamed guard is placed and presence-checked under its new name\n // rather than being treated as unknown and re-added alongside its own stale entry.\n migrateRetiredRuleNames(rules, changes);\n migrateRetiredRuleNames(hookGuards, changes);\n\n // Move guards mistakenly left in rules into hookGuards.\n for (const name of Object.keys(rules)) {\n if (isHookGuard(name)) {\n hookGuards[name] = rules[name];\n delete rules[name];\n changes.push(`moved \"${name}\" from rules → hookGuards`);\n }\n }\n // Move code rules mistakenly placed in hookGuards back into rules.\n for (const name of Object.keys(hookGuards)) {\n if (!isHookGuard(name) && allRuleNames().includes(name)) {\n rules[name] = hookGuards[name];\n delete hookGuards[name];\n changes.push(`moved \"${name}\" from hookGuards → rules`);\n }\n }\n // Add any missing built-in into its correct section, ENFORCING at its recommended mode (not OFF).\n for (const name of allRuleNames()) {\n const target = sectionForRule(name) === 'hookGuards' ? hookGuards : rules;\n if (!(name in target)) {\n const entry = seedRule(name);\n target[name] = entry;\n changes.push(`added \"${name}\" (${String(entry['mode'])}) to ${sectionForRule(name)}`);\n }\n }\n // Fill command defaults.\n if (commands['pr-gate'] === undefined) {\n commands['pr-gate'] = { mode: 'OFF', buildCommand: DEFAULT_BUILD_COMMAND, gates: [] };\n changes.push('added commands[\"pr-gate\"] (OFF)');\n }\n migrateGuardHints(commands, changes);\n\n // Seed the now-required excludePaths list (empty = enforce everywhere) if the config predates it,\n // and MIGRATE the legacy `{ rules: [], guards: [] }` object to the single list by unioning them.\n // The union is behaviour-preserving for every config we have seen (both lists set identically), and\n // widening is the safe direction anyway: a path either side excluded stays excluded.\n const excludePaths: string[] = migrateExcludePaths(existing['excludePaths'], changes);\n\n // Seed the now-required match-rules array (with the default no-fetch guard) if the config predates\n // it. A client that has already customized it keeps their array untouched.\n let matchRules: Json[];\n if (Array.isArray(existing['match-rules'])) {\n matchRules = existing['match-rules'] as Json[];\n } else {\n matchRules = seedMatchRules();\n changes.push('added \"match-rules\" (seeded with the no-fetch guard)');\n }\n\n const rulesDir: string[] = Array.isArray(existing['rulesDir']) ? (existing['rulesDir'] as string[]) : [];\n const config: ConfigFile = { rules, hookGuards, commands, excludePaths, 'match-rules': matchRules, rulesDir };\n if (typeof existing['extends'] === 'string') config.extends = existing['extends'];\n return { config, changes };\n}\n\n// Seed the config when it is missing, migrate it when it is not. ONE behaviour, always — there is no\n// \"migrate but stop here\" mode any more. The flag that used to select it was never NECESSARY (the validator prints\n// the exact edit for every error at once, and editing webpieces.config.json is always allowed through the\n// guard — the documented primary cure), it REFUSED to act when the config was missing (useless in the one\n// case automation would have helped), and it gave deny messages a second competing path when they are\n// supposed to end in exactly one action. Readers also mistook it for the shim-repair command, which it\n// never was — `wp-upgrade-shim` is that.\n// webpieces-disable no-function-outside-class -- setup.ts is deliberately DI-free (it must run on a half-written node_modules; see install-entry.ts), so every function here is module-scope\nfunction seedOrSyncConfig(projectRoot: string): void {\n const configPath = path.join(projectRoot, CONFIG_FILENAME);\n if (!fs.existsSync(configPath)) {\n writeConfig(configPath, buildSeedConfig());\n console.log(` [ai-hooks] Created ${CONFIG_FILENAME} (rules / hookGuards / commands); each rule seeded at its recommended mode — gradual where supported, so only code you change is enforced.`);\n console.log(' Enable the ones you want by changing \"mode\".');\n return;\n }\n const result = migrate(readConfig(configPath));\n if (result.changes.length === 0) {\n console.log(` [ai-hooks] ${CONFIG_FILENAME} already uses the rules / hookGuards / commands layout — no changes.`);\n return;\n }\n writeConfig(configPath, result.config);\n console.log(` [ai-hooks] Migrated ${CONFIG_FILENAME}:`);\n for (const change of result.changes) console.log(` - ${change}`);\n}\n\n// ---------------------------------------------------------------------------\n// Claude Code settings.json hook wiring.\n// ---------------------------------------------------------------------------\nexport function hasHook(settings: ClaudeSettings, bin: string): boolean {\n const entries = settings.hooks?.PreToolUse ?? [];\n return entries.some((e: HookEntry) => e.hooks.some((h: HookCommand) => h.command.includes(bin)));\n}\n\n// Drop every PreToolUse command containing `marker` (a bin name, or a managed .sh path); returns true\n// if anything was removed. REMOVE-then-ADD is what keeps an upgrade from leaving a superseded spelling\n// (a relative command, or the retired L-1 entry) beside the current one — two spellings of one\n// registration is the compatibility shim the backwards-compat reviewer rejects.\n// webpieces-disable no-function-outside-class -- setup.ts is deliberately DI-free (it must run on a half-written node_modules; see install-entry.ts), so every function here is module-scope\nfunction removeHookByMarker(settings: ClaudeSettings, marker: string): boolean {\n const entries = settings.hooks?.PreToolUse;\n if (!entries) return false;\n let changed = false;\n const kept: HookEntry[] = [];\n for (const entry of entries) {\n const hooks = entry.hooks.filter((h: HookCommand) => !h.command.includes(marker));\n if (hooks.length !== entry.hooks.length) changed = true;\n if (hooks.length > 0) kept.push({ matcher: entry.matcher, hooks });\n }\n if (changed) settings.hooks!.PreToolUse = kept;\n return changed;\n}\n\n// Apply the chosen install for one hook: remove it from every target file, then add it back to the\n// chosen one (or nowhere, for uninstall). Writes only the files that changed.\nexport function applyHook(hook: HookSpec, chosen: InstallTarget | null, targets: InstallTarget[], projectRoot: string): void {\n for (const target of targets) {\n const settings = readSettings(target.settingsPath);\n const removed = removeHookByMarker(settings, hook.bin);\n const isChosen = chosen !== null && chosen.settingsPath === target.settingsPath;\n if (isChosen) {\n addHookEntry(settings, new HookRegistrationEntry(hook.matcher, hook.commandFor(target, projectRoot)));\n // The managed `env` entry goes into the SAME file the hooks go into, on every path that\n // writes hooks — interactive or `--target=`. It pins the Bash cwd to the project root, so a\n // guard's answer depends on the command rather than on where an earlier `cd` left the shell,\n // and settings `env` is inherited, so every subagent gets the identical cwd and therefore the\n // identical guard verdict. (It no longer has a RESOLUTION job — the hooks are absolute.) See\n // hook-registration.ts for the full argument; `wp-upgrade-shim` self-heals it afterwards.\n if (applyManagedEnv(settings)) {\n console.log(` ✅ env.${BASH_CWD_ENV_KEY}=${BASH_CWD_ENV_VALUE} → ${target.label} (pins the Bash cwd to the project root, for this session and every subagent)`);\n }\n writeSettings(target.settingsPath, settings);\n console.log(` ✅ ${hook.label} → ${target.label}`);\n } else if (removed) {\n writeSettings(target.settingsPath, settings);\n }\n }\n // Manage the shared checked-in shim: (re)write it whenever a project (relative) install exists,\n // otherwise clean it up once neither hook references it anymore.\n if (chosen !== null && !chosen.absolute) {\n writeShim(projectRoot);\n } else if (!markerReferenced(targets, SHIM_MARKER)) {\n removeShim(projectRoot);\n }\n // The RETIRED L-1 hook rode with the GUARDS hook, so its removal does too. Doing it here rather than\n // at a separate call site means every existing caller of applyHook — the installer's interactive and\n // --target paths, and every test — converges on the two-hook absolute form with no second step.\n if (hook.bin === GUARDS_BIN) purgeRetiredGuaranteeRoot(targets, projectRoot);\n if (chosen === null) console.log(` ⛔ ${hook.label} not installed (removed from all locations).`);\n}\n\nfunction currentLocation(hook: HookSpec, targets: InstallTarget[]): string {\n const here = targets.filter((t: InstallTarget) => hasHook(readSettings(t.settingsPath), hook.bin));\n return here.length === 0 ? 'none' : here.map((t: InstallTarget) => t.label.split(' (')[0]).join(', ');\n}\n\nfunction prompt(question: string): Promise<string> {\n return new Promise((resolve: (answer: string) => void) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(question, (answer: string) => { rl.close(); resolve(answer.trim()); });\n });\n}\n\n// Map a friendly `--target` name to an InstallTarget choice id (see installTargets). Returns null\n// for an unknown name so the caller can error out. Kept separate + exported for unit testing.\nexport function resolveTargetChoice(name: string): string | null {\n switch (name) {\n case 'project': return '1';\n case 'project-personal':\n case 'projectpersonal':\n case 'local': return '2';\n case 'global': return '3';\n case 'none':\n case 'uninstall': return '4';\n default: return null;\n }\n}\n\n// Extract the value of `--target=<name>` from argv (null if the flag is absent).\nexport function parseTargetArg(args: string[]): string | null {\n const flag = args.find((a: string): boolean => a.startsWith('--target='));\n return flag ? flag.slice('--target='.length) : null;\n}\n\nasync function wireHook(hook: HookSpec, targets: InstallTarget[], projectRoot: string): Promise<void> {\n console.log('');\n console.log(`${hook.label} [matcher: ${hook.matcher}]`);\n console.log(` currently installed in: ${currentLocation(hook, targets)}`);\n for (const target of targets) console.log(` ${target.choice}) ${target.label}`);\n console.log(' 4) none / uninstall');\n const answer = await prompt(' Where should it live? [1/2/3/4, default 4]: ');\n const chosen = targets.find((t: InstallTarget) => t.choice === answer) ?? null;\n applyHook(hook, chosen, targets, projectRoot);\n}\n\n/**\n * Scaffold the SERVER-SIDE PR gate: the CI workflow plus the doc explaining how to turn it on.\n *\n * This lives in the installer, not the PR flow. `wp-start-upsert-pr` used to do it — printing\n * copy-to-`.github` and branch-protection instructions on EVERY run, at an agent doing feature work\n * that could not act on them anyway (marking a check required needs a repo admin). Setup is a\n * one-time, admin-shaped act, so it belongs with the other one-time setup.\n *\n * Written UNCONDITIONALLY, unlike the old version which required a `gateSalt` to already be set: the\n * whole point of the doc is to tell you to set one, so gating it on the thing it teaches meant the\n * instructions only appeared to repos that no longer needed them.\n *\n * Both land in gitignored `.webpieces/instruct-ai/`, never `.github/` directly — writing there would\n * dirty the tree, and copying it is the human's decision. `IfMissing` for the yml so a repo that has\n * customized its workflow never gets it clobbered; the doc itself is refreshed so it cannot go stale.\n */\n// webpieces-disable no-function-outside-class -- setup.ts is deliberately DI-free (it must run on a half-written node_modules; see install-entry.ts), so every function here is module-scope\nfunction scaffoldCiGate(projectRoot: string): void {\n writeTemplateIfMissing(projectRoot, 'webpieces-pr-gate.yml');\n writeTemplate(projectRoot, 'webpieces.ci-gate-setup.md');\n console.log('');\n console.log('ℹ️ Optional: server-side PR gate (stops an UNHOOKED teammate opening a PR in the web UI).');\n console.log(' It is OFF until you set a gateSalt. Three steps, one of which needs a repo admin:');\n console.log(' .webpieces/instruct-ai/webpieces.ci-gate-setup.md');\n}\n\nexport async function main(): Promise<void> {\n const args = process.argv.slice(2);\n // Anchor the install at the repo root (git toplevel — webpieces.config.json may not exist yet on\n // a first install), never a subdir cwd, so `.webpieces`/hooks/config all land at the root.\n const projectRoot = new RepoRootFinder().resolveRepoRoot(process.cwd());\n\n seedOrSyncConfig(projectRoot);\n // Always refreshed: it explains why a retired key is rejected rather than accepted, and what to do\n // about it — which is exactly what an agent needs on the run where a migration just moved keys out\n // from under its config.\n writeTemplate(projectRoot, CONFIG_POLICY_DOC);\n\n scaffoldCiGate(projectRoot);\n\n const targets = installTargets(projectRoot);\n\n // Non-interactive: `--target=project|project-personal|global|none` installs BOTH hooks at that\n // location without prompting, so an agent or CI can run the installer unattended (e.g. after a\n // @webpieces upgrade that changed the hook entry). Omit the flag for the interactive per-hook chooser.\n const targetName = parseTargetArg(args);\n if (targetName !== null) {\n const choice = resolveTargetChoice(targetName);\n if (choice === null) {\n console.error(`❌ Unknown --target '${targetName}'. Use one of: project | project-personal | global | none`);\n process.exitCode = 1;\n return;\n }\n const chosen = targets.find((t: InstallTarget): boolean => t.choice === choice) ?? null;\n applyHook(RULES_HOOK, chosen, targets, projectRoot);\n applyHook(GUARDS_HOOK, chosen, targets, projectRoot);\n console.log(`\\nDone. Both hooks set to: ${targetName}.`);\n return;\n }\n\n console.log('');\n console.log('Two webpieces hooks can be installed independently — choose a location for each:');\n await wireHook(RULES_HOOK, targets, projectRoot);\n await wireHook(GUARDS_HOOK, targets, projectRoot);\n console.log('');\n console.log('Done. Re-run `pnpm wp-install-ai-hooks` anytime to move or uninstall a hook.');\n console.log('(Non-interactive: pnpm wp-install-ai-hooks --target=project|project-personal|global|none)');\n}\n\nif (require.main === module) {\n void main();\n}\n"]}
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/setup.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { homedir } from 'os';\nimport { createInterface } from 'readline';\n\nimport { allRuleNames, seedEntryForRule, schemaFieldNames, sectionForRule, isHookGuard, DEFAULT_MATCH_RULES, DEFAULT_BUILD_COMMAND, RETIRED_CONFIG_KEYS, RETIRED_SCOPE_RULE, RepoRootFinder, writeTemplate, writeTemplateIfMissing, CONFIG_POLICY_DOC } from '@webpieces/rules-config';\n\nimport { toError } from '../core/to-error';\nimport { SHIM_MARKER, shimPath, renderShim } from './shim';\nimport {\n ClaudeSettings, HookCommand, HookEntry, HookRegistrationEntry, GUARDS_BIN, LEGACY_GUARANTEE_ROOT_MARKER,\n GUARDS_MATCHER, RULES_BIN, RULES_MATCHER, addHookEntry, applyManagedEnv, readSettings, shimCommand,\n writeSettings,\n} from './hook-registration';\nimport { BASH_CWD_ENV_KEY, BASH_CWD_ENV_VALUE } from './managed-env';\n\nconst CONFIG_FILENAME = 'webpieces.config.json';\n// The seeded buildCommand comes from @webpieces/rules-config, NOT from a copy here. This file used to\n// hold its own — with `--base=origin/main`, a DIFFERENT base from the one the gate documents — so a\n// freshly set-up repo was seeded with a command that rebuilds projects touched by other people's\n// merged PRs, and whole-repo-build-guard then quoted that command back in its refusals.\nconst DEFAULT_UPSERT_PR = 'pnpm wp-start-upsert-pr';\nconst DEFAULT_MERGE_COMPLETE = 'pnpm wp-finish-upsert-pr';\n\n// ---------------------------------------------------------------------------\n// The two independently-installable GUARD hooks. Each can land in a different settings file (see\n// InstallTarget) so a team can ship the guards while a developer keeps the code-style rules local\n// while iterating. Both are registered ABSOLUTE via $CLAUDE_PROJECT_DIR, so the MAIN tree governs every\n// tree. There used to be a third — the L-1 hook, which existed only to keep a RELATIVE registration\n// resolvable; it is retired, and purgeRetiredGuaranteeRoot() removes anything left of it.\n// ---------------------------------------------------------------------------\nclass HookSpec {\n constructor(\n readonly key: string,\n readonly label: string,\n readonly matcher: string,\n readonly bin: string,\n ) {}\n\n // Absolute targets (global) need the exact path to this repo's bin — no ~/.webpieces bridge.\n // Project targets get the ABSOLUTE shim command, `$CLAUDE_PROJECT_DIR/.claude/webpieces/ai-hook.sh`\n // (see hook-registration.ts for why): it resolves from ANY cwd, so a hook can never fail to launch —\n // which per the hooks reference would be exit 127, a SILENT UNGUARDED ALLOW rather than a block.\n commandFor(target: InstallTarget, projectRoot: string): string {\n if (target.absolute) {\n return `node ${path.join(projectRoot, 'node_modules', '.bin', this.bin)}`;\n }\n return shimCommand(this.bin);\n }\n}\n\n// Idempotent: re-running the installer overwrites the managed shim in place.\nfunction writeShim(projectRoot: string): void {\n const target = shimPath(projectRoot);\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, renderShim(), { mode: 0o755 });\n // writeFileSync's mode is only applied when creating the file; force it on overwrite too.\n fs.chmodSync(target, 0o755);\n}\n\nfunction removeShim(projectRoot: string): void {\n const target = shimPath(projectRoot);\n if (fs.existsSync(target)) fs.rmSync(target);\n}\n\n// A managed .sh is shared by the project hooks — only safe to delete once no project settings file\n// references it anymore (i.e. the other hook was moved to global or uninstalled too).\n// webpieces-disable no-function-outside-class -- setup.ts is deliberately DI-free (it must run on a half-written node_modules; see install-entry.ts), so every function here is module-scope\nfunction markerReferenced(targets: InstallTarget[], marker: string): boolean {\n return targets.some((t: InstallTarget) => {\n const entries = readSettings(t.settingsPath).hooks?.PreToolUse ?? [];\n return entries.some((e: HookEntry) => e.hooks.some((h: HookCommand) => h.command.includes(marker)));\n });\n}\n\n/**\n * Remove every trace of the RETIRED L-1 hook (`guarantee-root.sh`) — the file and any PreToolUse entry\n * still pointing at it. REMOVAL ONLY: nothing here can ever write one back.\n *\n * L-1 existed to guarantee a RELATIVE guard-hook path resolved, by refusing any `cd` that would park the\n * shell in a subdirectory. The guard hooks are ABSOLUTE now (`$CLAUDE_PROJECT_DIR/...`), so they resolve\n * from any cwd and the guarantee is structural — there is nothing left to police, and the subdirectory\n * denial that used to pay for it is gone with it.\n */\n// webpieces-disable no-function-outside-class -- setup.ts is deliberately DI-free (it must run on a half-written node_modules; see install-entry.ts), so every function here is module-scope\nfunction purgeRetiredGuaranteeRoot(targets: InstallTarget[], projectRoot: string): void {\n for (const target of targets) {\n const settings = readSettings(target.settingsPath);\n if (removeHookByMarker(settings, LEGACY_GUARANTEE_ROOT_MARKER)) {\n writeSettings(target.settingsPath, settings);\n console.log(` 🗑️ removed the retired L-1 guarantee-root hook from ${target.label}`);\n }\n }\n const legacyFile = path.join(projectRoot, LEGACY_GUARANTEE_ROOT_MARKER);\n if (fs.existsSync(legacyFile)) {\n fs.rmSync(legacyFile, { force: true });\n console.log(' 🗑️ deleted the retired .claude/webpieces/guarantee-root.sh');\n }\n}\n\nexport class InstallTarget {\n constructor(\n readonly choice: string,\n readonly label: string,\n readonly settingsPath: string,\n readonly absolute: boolean,\n ) {}\n}\n\n// The matchers and bin names come from ./hook-registration, which is also what the drift check and\n// wp-upgrade-shim compare against — one spelling of the registration, or the installer and the\n// validator can disagree about what \"installed\" means.\nexport const RULES_HOOK = new HookSpec('rules', 'Rules hook (code-style validation)', RULES_MATCHER, RULES_BIN);\nexport const GUARDS_HOOK = new HookSpec('guards', 'Guards hook (git/PR/branch protection)', GUARDS_MATCHER, GUARDS_BIN);\n\n// `homeDir` is injectable so tests can point the global target at a temp dir instead of the real\n// ~/.claude/settings.json (a unit test must never write the user's actual global settings).\nexport function installTargets(projectRoot: string, homeDir: string = homedir()): InstallTarget[] {\n return [\n new InstallTarget('1', 'project (.claude/settings.json — committed, for the team)',\n path.join(projectRoot, '.claude', 'settings.json'), false),\n new InstallTarget('2', 'project for you (.claude/settings.local.json — personal)',\n path.join(projectRoot, '.claude', 'settings.local.json'), false),\n new InstallTarget('3', 'global (~/.claude/settings.json — exact path, this repo only)',\n path.join(homeDir, '.claude', 'settings.json'), true),\n ];\n}\n\n// ---------------------------------------------------------------------------\n// webpieces.config.json seeding + migration to the rules / hookGuards / commands layout.\n// ---------------------------------------------------------------------------\n// webpieces-disable no-any-unknown -- webpieces.config.json / settings.json are opaque consumer JSON\ntype Json = Record<string, unknown>;\ntype RuleEntry = Json;\ntype Section = Record<string, RuleEntry>;\n\ninterface ConfigFile {\n extends?: string;\n rules: Section;\n hookGuards: Section;\n commands: Json;\n excludePaths: string[];\n 'match-rules': Json[];\n rulesDir: string[];\n}\n\ninterface MigrateResult {\n config: ConfigFile;\n changes: string[];\n}\n\n// webpieces-disable no-function-outside-class -- sibling of the other seed* helpers; this module is config-shape builders by design\nfunction seedRule(ruleName: string): RuleEntry {\n // Both escape hatches are seeded (and REQUIRED) so every rule block shows them: 0 = active,\n // null = no branch scoping. A human/AI edits these to time-box or branch-scope a rule off.\n //\n // The ENTIRE entry comes from rules-config's seedEntryForRule() — the same module that owns the\n // schema the loader validates against, so the installer can never emit an entry the loader\n // rejects. It supplies: the recommended mode (the SAME recommendation the validator prints in its\n // copy-paste snippet, so seed and advice cannot disagree), both hatches, and a default for every\n // other schema-REQUIRED field. Seeding used to be a flat 'OFF' plus the two hatches, which was\n // wrong twice over: adopters got nothing enforced, AND the entry was missing required fields\n // (e.g. branch-creation-guard.autoReapMergedBranches), so the config failed to load on first run.\n return seedEntryForRule(ruleName);\n}\n\n// The guard-hint command strings live under `guardHints`. The flat `upsertPr`/`mergeComplete` keys this\n// used to seed are RETIRED and now fail validation — seeding them meant every freshly installed repo was\n// born on a shape the validator rejects.\nfunction seedCommands(): Json {\n return {\n 'pr-gate': { mode: 'OFF', buildCommand: DEFAULT_BUILD_COMMAND, gates: [] },\n guardHints: { prCreationOrPush: DEFAULT_UPSERT_PR, mergeInProgress: DEFAULT_MERGE_COMPLETE },\n };\n}\n\n// Required excludePaths block: ONE glob list suppressing hook enforcement per file path. Seeded empty\n// (enforce everywhere) — a client adds paths (e.g. \"repositories/**\") to exempt vendored trees.\n// webpieces-disable no-function-outside-class -- sibling of the other seed* helpers; this module is config-shape builders by design\nfunction seedExcludePaths(): string[] {\n return [];\n}\n\n// Bring an existing `excludePaths` forward to the single-list shape. Already a list → untouched.\n// Legacy `{ rules, guards }` → unioned (order preserved, duplicates dropped) and recorded as a change\n// so `wp-install-ai-hooks` is the migration path rather than a hand-edit. Anything else → seeded [].\n// webpieces-disable no-any-unknown -- `raw` is opaque consumer JSON until narrowed here\n// webpieces-disable no-function-outside-class -- sibling of the other seed*/migrate* helpers; this module is config-shape builders by design\nfunction migrateExcludePaths(raw: unknown, changes: string[]): string[] {\n if (Array.isArray(raw)) return (raw as string[]).filter(p => typeof p === 'string');\n if (typeof raw === 'object' && raw !== null) {\n // webpieces-disable no-any-unknown -- narrowing the opaque legacy block from consumer JSON\n const legacy = raw as Record<string, unknown>;\n const rules = Array.isArray(legacy['rules']) ? (legacy['rules'] as string[]) : [];\n const guards = Array.isArray(legacy['guards']) ? (legacy['guards'] as string[]) : [];\n const merged = [...new Set([...rules, ...guards].filter(p => typeof p === 'string'))];\n changes.push(`migrated excludePaths {rules,guards} -> one list (${merged.length} path(s))`);\n return merged;\n }\n changes.push('added excludePaths ([])');\n return [];\n}\n\n/** One retired flat command string and the guardHints field it becomes. Data-only (per CLAUDE.md). */\nclass GuardHintMove {\n retiredKey: string;\n hintKey: string;\n fallback: string;\n\n constructor(retiredKey: string, hintKey: string, fallback: string) {\n this.retiredKey = retiredKey;\n this.hintKey = hintKey;\n this.fallback = fallback;\n }\n}\n\n/**\n * Bring `commands` forward to the `guardHints` shape, moving the RETIRED flat `upsertPr`/`mergeComplete`\n * strings and DELETING them. Deleting is the point: the validator now rejects them, so leaving them behind\n * would keep the config failing after a \"successful\" sync.\n *\n * The consumer's own value wins over the default — a repo that renamed its gated command keeps that name.\n */\n// webpieces-disable no-function-outside-class -- sibling of the other seed*/migrate* helpers; this module is config-shape builders by design\nfunction migrateGuardHints(commands: Json, changes: string[]): void {\n const hints: Json = (typeof commands['guardHints'] === 'object' && commands['guardHints'] !== null)\n ? (commands['guardHints'] as Json) : {};\n const moves: readonly GuardHintMove[] = [\n new GuardHintMove('upsertPr', 'prCreationOrPush', DEFAULT_UPSERT_PR),\n new GuardHintMove('mergeComplete', 'mergeInProgress', DEFAULT_MERGE_COMPLETE),\n ];\n for (const move of moves) {\n const retiredKey = move.retiredKey;\n const hintKey = move.hintKey;\n const fallback = move.fallback;\n const carried = commands[retiredKey];\n if (carried !== undefined) {\n delete commands[retiredKey];\n if (hints[hintKey] === undefined) hints[hintKey] = carried;\n changes.push(`moved retired commands.${retiredKey} -> commands.guardHints.${hintKey}`);\n }\n if (hints[hintKey] === undefined) {\n hints[hintKey] = fallback;\n changes.push(`added commands.guardHints.${hintKey}`);\n }\n }\n commands['guardHints'] = hints;\n}\n\n/**\n * Apply the RETIRED rule/guard retirements in place. These used to be rewritten silently at load time, so\n * a consumer's file kept the dead name forever; the loader now rejects it, which makes this the one\n * command that can fix the file. Skips a rename when the new name is already configured, so an explicit\n * entry is never clobbered by a stale one.\n *\n * NOT EVERY RETIREMENT IS A RENAME, and treating them all as one produced garbage. `whole-repo-build-guard`\n * moved OUT of webpieces.config.json entirely — its `movedTo` is the PROSE destination\n * `~/.webpieces/config.json → experimental.whole-repo-build-guard`, not a sibling key — so the rename\n * branch below would have created a hookGuards entry literally named that whole sentence, which no\n * validator knows and which the next run reports as another unknown rule. `prunable` is the discriminator:\n * when the entry says deleting is the whole fix, DELETE it, exactly as `ConfigPruner` does.\n */\n// webpieces-disable no-function-outside-class -- sibling of the other seed*/migrate* helpers; this module is config-shape builders by design\nfunction migrateRetiredRuleNames(section: Section, changes: string[]): void {\n for (const entry of RETIRED_CONFIG_KEYS) {\n if (entry.scope !== RETIRED_SCOPE_RULE) continue;\n if (!(entry.key in section)) continue;\n if (entry.prunable) {\n delete section[entry.key];\n changes.push(`deleted retired \"${entry.key}\" (it moved to ${entry.movedTo})`);\n continue;\n }\n mergeIntoDestination(section, entry.key, entry.movedTo, changes);\n }\n fillRequiredFields(section, changes);\n}\n\n/**\n * Fold one retired key's entry into its destination, whether the destination exists yet or not.\n *\n * THIS IS N→1, NOT 1:1, and the difference is the whole reason this helper exists. Four retired keys\n * now point at ONE destination (`branch-state-guard`, `pr-lifecycle-guard`). The previous code renamed\n * the first key it met and then, finding the destination already present, DELETED each of the other\n * three outright — so which guard's settings survived depended on RETIRED_CONFIG_KEYS declaration\n * order rather than on the consumer's file, and the survivor carried only that one guard's fields, so\n * it was missing required fields of the merged schema. `wp-install-ai-hooks` is the command advertised\n * as the migration path; half-migrating every consumer into an invalid config is not an option.\n *\n * UNION, first writer wins per field. Earlier-declared keys are the more specific ones (only\n * feature-branch-guard carries `branchNamingConvention`), and a field already present on the\n * destination — because the consumer wrote it, or an earlier key contributed it — is never overwritten.\n * Fields the merged schema does not know are dropped by the same pass, since carrying a deleted field\n * across (`upsertPrCommand`) would produce a config the validator immediately rejects.\n */\n// webpieces-disable no-function-outside-class -- sibling of the other seed*/migrate* helpers; this module is config-shape builders by design\nfunction mergeIntoDestination(section: Section, key: string, destination: string, changes: string[]): void {\n const source = asSection(section[key]);\n delete section[key];\n const fields = schemaFieldNames(destination);\n const target = asSection(section[destination]);\n const existed = destination in section;\n const carried: string[] = [];\n const dropped: string[] = [];\n for (const field of Object.keys(source)) {\n if (fields !== null && !fields.includes(field)) { dropped.push(field); continue; }\n if (field in target) continue;\n target[field] = source[field];\n carried.push(field);\n }\n section[destination] = target;\n const verb = existed ? 'merged' : 'renamed';\n const droppedNote = dropped.length > 0 ? `; dropped deleted field(s) ${dropped.join(', ')}` : '';\n changes.push(`${verb} retired \"${key}\" -> \"${destination}\" (carried ${carried.join(', ') || 'nothing new'}${droppedNote})`);\n}\n\n/**\n * Fill any schema-REQUIRED field a migrated entry ended up without.\n *\n * A union of four partial entries is not guaranteed to satisfy the destination's schema — the merged\n * `branch-state-guard` needs `mode` and both escape hatches, and a consumer whose four old entries\n * predate one of them would land short. Seeding the gap from the SAME source the installer and the\n * validator use (seedEntryForRule) is what makes the install command a complete instruction\n * rather than a first step. Only ever ADDS; a value the consumer stated is never touched.\n */\n// webpieces-disable no-function-outside-class -- sibling of the other seed*/migrate* helpers; this module is config-shape builders by design\nfunction fillRequiredFields(section: Section, changes: string[]): void {\n for (const name of Object.keys(section)) {\n if (schemaFieldNames(name) === null) continue;\n // A rule ENTRY is a flat bag of scalars, so it is read as Json here rather than through\n // asSection (whose values are whole entries). Same object either way; only the view differs.\n const entry: Json = asSection(section[name]);\n const seed = seedEntryForRule(name);\n const added: string[] = [];\n for (const field of Object.keys(seed)) {\n if (field in entry) continue;\n entry[field] = seed[field];\n added.push(field);\n }\n if (added.length === 0) continue;\n section[name] = entry;\n changes.push(`filled required field(s) on \"${name}\": ${added.join(', ')}`);\n }\n}\n\n// Deep-copy the framework's default match-rules (the no-fetch guard) into plain JSON for the config\n// file. Round-tripping through JSON turns the MatchRuleConfig instances into plain objects.\nfunction seedMatchRules(): Json[] {\n return JSON.parse(JSON.stringify(DEFAULT_MATCH_RULES)) as Json[];\n}\n\nfunction buildSeedConfig(): ConfigFile {\n const rules: Section = {};\n const hookGuards: Section = {};\n for (const name of allRuleNames()) {\n if (sectionForRule(name) === 'hookGuards') hookGuards[name] = seedRule(name);\n else rules[name] = seedRule(name);\n }\n return {\n rules, hookGuards, commands: seedCommands(), excludePaths: seedExcludePaths(),\n // Seed the required match-rules array with the framework's default no-fetch guard. A fresh\n // project gets contract-first enforcement out of the box; clients edit it and add more entries.\n 'match-rules': seedMatchRules(),\n rulesDir: [],\n };\n}\n\nfunction writeConfig(configPath: string, config: ConfigFile): void {\n fs.writeFileSync(configPath, JSON.stringify(config, null, 4) + '\\n');\n}\n\nfunction readConfig(configPath: string): Json {\n const raw = fs.readFileSync(configPath, 'utf8');\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(raw) as Json;\n } catch (err: unknown) {\n const error = toError(err);\n throw new Error(`${CONFIG_FILENAME} has invalid JSON — fix it, then retry: ${error.message}`, { cause: error });\n }\n}\n\nfunction asSection(value: Json[string]): Section {\n return (typeof value === 'object' && value !== null && !Array.isArray(value)) ? (value as Section) : {};\n}\n\n// Migrate an existing config to the rules / hookGuards / commands layout and add any missing rules.\n// Returns a human-readable list of what changed (empty = already up to date).\nexport function migrate(existing: Json): MigrateResult {\n const changes: string[] = [];\n const rules: Section = asSection(existing['rules']);\n const hookGuards: Section = asSection(existing['hookGuards']);\n const commands: Json = (typeof existing['commands'] === 'object' && existing['commands'] !== null)\n ? (existing['commands'] as Json) : {};\n\n // Move a deprecated top-level pr-gate block under commands.\n if (existing['pr-gate'] !== undefined && commands['pr-gate'] === undefined) {\n commands['pr-gate'] = existing['pr-gate'];\n changes.push('moved top-level \"pr-gate\" → commands[\"pr-gate\"]');\n }\n // Apply retired RENAMES first, so a renamed guard is placed and presence-checked under its new name\n // rather than being treated as unknown and re-added alongside its own stale entry.\n migrateRetiredRuleNames(rules, changes);\n migrateRetiredRuleNames(hookGuards, changes);\n\n // Move guards mistakenly left in rules into hookGuards.\n for (const name of Object.keys(rules)) {\n if (isHookGuard(name)) {\n hookGuards[name] = rules[name];\n delete rules[name];\n changes.push(`moved \"${name}\" from rules → hookGuards`);\n }\n }\n // Move code rules mistakenly placed in hookGuards back into rules.\n for (const name of Object.keys(hookGuards)) {\n if (!isHookGuard(name) && allRuleNames().includes(name)) {\n rules[name] = hookGuards[name];\n delete hookGuards[name];\n changes.push(`moved \"${name}\" from hookGuards → rules`);\n }\n }\n // Add any missing built-in into its correct section, ENFORCING at its recommended mode (not OFF).\n for (const name of allRuleNames()) {\n const target = sectionForRule(name) === 'hookGuards' ? hookGuards : rules;\n if (!(name in target)) {\n const entry = seedRule(name);\n target[name] = entry;\n changes.push(`added \"${name}\" (${String(entry['mode'])}) to ${sectionForRule(name)}`);\n }\n }\n // Fill command defaults.\n if (commands['pr-gate'] === undefined) {\n commands['pr-gate'] = { mode: 'OFF', buildCommand: DEFAULT_BUILD_COMMAND, gates: [] };\n changes.push('added commands[\"pr-gate\"] (OFF)');\n }\n migrateGuardHints(commands, changes);\n\n // Seed the now-required excludePaths list (empty = enforce everywhere) if the config predates it,\n // and MIGRATE the legacy `{ rules: [], guards: [] }` object to the single list by unioning them.\n // The union is behaviour-preserving for every config we have seen (both lists set identically), and\n // widening is the safe direction anyway: a path either side excluded stays excluded.\n const excludePaths: string[] = migrateExcludePaths(existing['excludePaths'], changes);\n\n // Seed the now-required match-rules array (with the default no-fetch guard) if the config predates\n // it. A client that has already customized it keeps their array untouched.\n let matchRules: Json[];\n if (Array.isArray(existing['match-rules'])) {\n matchRules = existing['match-rules'] as Json[];\n } else {\n matchRules = seedMatchRules();\n changes.push('added \"match-rules\" (seeded with the no-fetch guard)');\n }\n\n const rulesDir: string[] = Array.isArray(existing['rulesDir']) ? (existing['rulesDir'] as string[]) : [];\n const config: ConfigFile = { rules, hookGuards, commands, excludePaths, 'match-rules': matchRules, rulesDir };\n if (typeof existing['extends'] === 'string') config.extends = existing['extends'];\n return { config, changes };\n}\n\n// Seed the config when it is missing, migrate it when it is not. ONE behaviour, always — there is no\n// \"migrate but stop here\" mode any more. The flag that used to select it was never NECESSARY (the validator prints\n// the exact edit for every error at once, and editing webpieces.config.json is always allowed through the\n// guard — the documented primary cure), it REFUSED to act when the config was missing (useless in the one\n// case automation would have helped), and it gave deny messages a second competing path when they are\n// supposed to end in exactly one action. Readers also mistook it for the shim-repair command, which it\n// never was — `wp-upgrade-shim` is that.\n// webpieces-disable no-function-outside-class -- setup.ts is deliberately DI-free (it must run on a half-written node_modules; see install-entry.ts), so every function here is module-scope\nfunction seedOrSyncConfig(projectRoot: string): void {\n const configPath = path.join(projectRoot, CONFIG_FILENAME);\n if (!fs.existsSync(configPath)) {\n writeConfig(configPath, buildSeedConfig());\n console.log(` [ai-hooks] Created ${CONFIG_FILENAME} (rules / hookGuards / commands); each rule seeded at its recommended mode — gradual where supported, so only code you change is enforced.`);\n console.log(' Enable the ones you want by changing \"mode\".');\n return;\n }\n const result = migrate(readConfig(configPath));\n if (result.changes.length === 0) {\n console.log(` [ai-hooks] ${CONFIG_FILENAME} already uses the rules / hookGuards / commands layout — no changes.`);\n return;\n }\n writeConfig(configPath, result.config);\n console.log(` [ai-hooks] Migrated ${CONFIG_FILENAME}:`);\n for (const change of result.changes) console.log(` - ${change}`);\n}\n\n// ---------------------------------------------------------------------------\n// Claude Code settings.json hook wiring.\n// ---------------------------------------------------------------------------\nexport function hasHook(settings: ClaudeSettings, bin: string): boolean {\n const entries = settings.hooks?.PreToolUse ?? [];\n return entries.some((e: HookEntry) => e.hooks.some((h: HookCommand) => h.command.includes(bin)));\n}\n\n// Drop every PreToolUse command containing `marker` (a bin name, or a managed .sh path); returns true\n// if anything was removed. REMOVE-then-ADD is what keeps an upgrade from leaving a superseded spelling\n// (a relative command, or the retired L-1 entry) beside the current one — two spellings of one\n// registration is the compatibility shim the backwards-compat reviewer rejects.\n// webpieces-disable no-function-outside-class -- setup.ts is deliberately DI-free (it must run on a half-written node_modules; see install-entry.ts), so every function here is module-scope\nfunction removeHookByMarker(settings: ClaudeSettings, marker: string): boolean {\n const entries = settings.hooks?.PreToolUse;\n if (!entries) return false;\n let changed = false;\n const kept: HookEntry[] = [];\n for (const entry of entries) {\n const hooks = entry.hooks.filter((h: HookCommand) => !h.command.includes(marker));\n if (hooks.length !== entry.hooks.length) changed = true;\n if (hooks.length > 0) kept.push({ matcher: entry.matcher, hooks });\n }\n if (changed) settings.hooks!.PreToolUse = kept;\n return changed;\n}\n\n// Apply the chosen install for one hook: remove it from every target file, then add it back to the\n// chosen one (or nowhere, for uninstall). Writes only the files that changed.\nexport function applyHook(hook: HookSpec, chosen: InstallTarget | null, targets: InstallTarget[], projectRoot: string): void {\n for (const target of targets) {\n const settings = readSettings(target.settingsPath);\n const removed = removeHookByMarker(settings, hook.bin);\n const isChosen = chosen !== null && chosen.settingsPath === target.settingsPath;\n if (isChosen) {\n addHookEntry(settings, new HookRegistrationEntry(hook.matcher, hook.commandFor(target, projectRoot)));\n // The managed `env` entry goes into the SAME file the hooks go into, on every path that\n // writes hooks — interactive or `--target=`. It pins the Bash cwd to the project root, so a\n // guard's answer depends on the command rather than on where an earlier `cd` left the shell,\n // and settings `env` is inherited, so every subagent gets the identical cwd and therefore the\n // identical guard verdict. (It no longer has a RESOLUTION job — the hooks are absolute.) See\n // hook-registration.ts for the full argument; `wp-upgrade-shim` self-heals it afterwards.\n if (applyManagedEnv(settings)) {\n console.log(` ✅ env.${BASH_CWD_ENV_KEY}=${BASH_CWD_ENV_VALUE} → ${target.label} (pins the Bash cwd to the project root, for this session and every subagent)`);\n }\n writeSettings(target.settingsPath, settings);\n console.log(` ✅ ${hook.label} → ${target.label}`);\n } else if (removed) {\n writeSettings(target.settingsPath, settings);\n }\n }\n // Manage the shared checked-in shim: (re)write it whenever a project (relative) install exists,\n // otherwise clean it up once neither hook references it anymore.\n if (chosen !== null && !chosen.absolute) {\n writeShim(projectRoot);\n } else if (!markerReferenced(targets, SHIM_MARKER)) {\n removeShim(projectRoot);\n }\n // The RETIRED L-1 hook rode with the GUARDS hook, so its removal does too. Doing it here rather than\n // at a separate call site means every existing caller of applyHook — the installer's interactive and\n // --target paths, and every test — converges on the two-hook absolute form with no second step.\n if (hook.bin === GUARDS_BIN) purgeRetiredGuaranteeRoot(targets, projectRoot);\n if (chosen === null) console.log(` ⛔ ${hook.label} not installed (removed from all locations).`);\n}\n\nfunction currentLocation(hook: HookSpec, targets: InstallTarget[]): string {\n const here = targets.filter((t: InstallTarget) => hasHook(readSettings(t.settingsPath), hook.bin));\n return here.length === 0 ? 'none' : here.map((t: InstallTarget) => t.label.split(' (')[0]).join(', ');\n}\n\nfunction prompt(question: string): Promise<string> {\n return new Promise((resolve: (answer: string) => void) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(question, (answer: string) => { rl.close(); resolve(answer.trim()); });\n });\n}\n\n// Map a friendly `--target` name to an InstallTarget choice id (see installTargets). Returns null\n// for an unknown name so the caller can error out. Kept separate + exported for unit testing.\nexport function resolveTargetChoice(name: string): string | null {\n switch (name) {\n case 'project': return '1';\n case 'project-personal':\n case 'projectpersonal':\n case 'local': return '2';\n case 'global': return '3';\n case 'none':\n case 'uninstall': return '4';\n default: return null;\n }\n}\n\n// Extract the value of `--target=<name>` from argv (null if the flag is absent).\nexport function parseTargetArg(args: string[]): string | null {\n const flag = args.find((a: string): boolean => a.startsWith('--target='));\n return flag ? flag.slice('--target='.length) : null;\n}\n\nasync function wireHook(hook: HookSpec, targets: InstallTarget[], projectRoot: string): Promise<void> {\n console.log('');\n console.log(`${hook.label} [matcher: ${hook.matcher}]`);\n console.log(` currently installed in: ${currentLocation(hook, targets)}`);\n for (const target of targets) console.log(` ${target.choice}) ${target.label}`);\n console.log(' 4) none / uninstall');\n const answer = await prompt(' Where should it live? [1/2/3/4, default 4]: ');\n const chosen = targets.find((t: InstallTarget) => t.choice === answer) ?? null;\n applyHook(hook, chosen, targets, projectRoot);\n}\n\n/**\n * Scaffold the SERVER-SIDE PR gate: the CI workflow plus the doc explaining how to turn it on.\n *\n * This lives in the installer, not the PR flow. `wp-start-upsert-pr` used to do it — printing\n * copy-to-`.github` and branch-protection instructions on EVERY run, at an agent doing feature work\n * that could not act on them anyway (marking a check required needs a repo admin). Setup is a\n * one-time, admin-shaped act, so it belongs with the other one-time setup.\n *\n * Written UNCONDITIONALLY, unlike the old version which required a `gateSalt` to already be set: the\n * whole point of the doc is to tell you to set one, so gating it on the thing it teaches meant the\n * instructions only appeared to repos that no longer needed them.\n *\n * Both land in gitignored `.webpieces/instruct-ai/`, never `.github/` directly — writing there would\n * dirty the tree, and copying it is the human's decision. `IfMissing` for the yml so a repo that has\n * customized its workflow never gets it clobbered; the doc itself is refreshed so it cannot go stale.\n */\n// webpieces-disable no-function-outside-class -- setup.ts is deliberately DI-free (it must run on a half-written node_modules; see install-entry.ts), so every function here is module-scope\nfunction scaffoldCiGate(projectRoot: string): void {\n writeTemplateIfMissing(projectRoot, 'webpieces-pr-gate.yml');\n writeTemplate(projectRoot, 'webpieces.ci-gate-setup.md');\n console.log('');\n console.log('ℹ️ Optional: server-side PR gate (stops an UNHOOKED teammate opening a PR in the web UI).');\n console.log(' It is OFF until you set a gateSalt. Three steps, one of which needs a repo admin:');\n console.log(' .webpieces/instruct-ai/webpieces.ci-gate-setup.md');\n}\n\nexport async function main(): Promise<void> {\n const args = process.argv.slice(2);\n // Anchor the install at the repo root (git toplevel — webpieces.config.json may not exist yet on\n // a first install), never a subdir cwd, so `.webpieces`/hooks/config all land at the root.\n const projectRoot = new RepoRootFinder().resolveRepoRoot(process.cwd());\n\n seedOrSyncConfig(projectRoot);\n // Always refreshed: it explains why a retired key is rejected rather than accepted, and what to do\n // about it — which is exactly what an agent needs on the run where a migration just moved keys out\n // from under its config.\n writeTemplate(projectRoot, CONFIG_POLICY_DOC);\n\n scaffoldCiGate(projectRoot);\n\n const targets = installTargets(projectRoot);\n\n // Non-interactive: `--target=project|project-personal|global|none` installs BOTH hooks at that\n // location without prompting, so an agent or CI can run the installer unattended (e.g. after a\n // @webpieces upgrade that changed the hook entry). Omit the flag for the interactive per-hook chooser.\n const targetName = parseTargetArg(args);\n if (targetName !== null) {\n const choice = resolveTargetChoice(targetName);\n if (choice === null) {\n console.error(`❌ Unknown --target '${targetName}'. Use one of: project | project-personal | global | none`);\n process.exitCode = 1;\n return;\n }\n const chosen = targets.find((t: InstallTarget): boolean => t.choice === choice) ?? null;\n applyHook(RULES_HOOK, chosen, targets, projectRoot);\n applyHook(GUARDS_HOOK, chosen, targets, projectRoot);\n console.log(`\\nDone. Both hooks set to: ${targetName}.`);\n return;\n }\n\n console.log('');\n console.log('Two webpieces hooks can be installed independently — choose a location for each:');\n await wireHook(RULES_HOOK, targets, projectRoot);\n await wireHook(GUARDS_HOOK, targets, projectRoot);\n console.log('');\n console.log('Done. Re-run `pnpm wp-install-ai-hooks` anytime to move or uninstall a hook.');\n console.log('(Non-interactive: pnpm wp-install-ai-hooks --target=project|project-personal|global|none)');\n}\n\nif (require.main === module) {\n void main();\n}\n"]}
|