@webpieces/ai-hook-rules 0.4.622 → 0.4.624

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +49 -19
  2. package/package.json +2 -2
  3. package/src/adapters/hook-core.js +13 -14
  4. package/src/adapters/hook-core.js.map +1 -1
  5. package/src/bin/hook-registration.d.ts +160 -50
  6. package/src/bin/hook-registration.js +227 -96
  7. package/src/bin/hook-registration.js.map +1 -1
  8. package/src/bin/managed-env.d.ts +46 -0
  9. package/src/bin/managed-env.js +50 -0
  10. package/src/bin/managed-env.js.map +1 -0
  11. package/src/bin/setup.d.ts +1 -2
  12. package/src/bin/setup.js +38 -46
  13. package/src/bin/setup.js.map +1 -1
  14. package/src/bin/shim-audit-log.js +12 -1
  15. package/src/bin/shim-audit-log.js.map +1 -1
  16. package/src/bin/shim-deny-reason.d.ts +6 -0
  17. package/src/bin/shim-deny-reason.js +82 -0
  18. package/src/bin/shim-deny-reason.js.map +1 -0
  19. package/src/bin/shim.d.ts +0 -1
  20. package/src/bin/shim.js +3 -57
  21. package/src/bin/shim.js.map +1 -1
  22. package/src/bin/upgrade-shim.js +140 -30
  23. package/src/bin/upgrade-shim.js.map +1 -1
  24. package/src/core/decision-log.d.ts +3 -3
  25. package/src/core/decision-log.js +8 -8
  26. package/src/core/decision-log.js.map +1 -1
  27. package/src/core/effective-tree.d.ts +5 -2
  28. package/src/core/effective-tree.js +1 -1
  29. package/src/core/effective-tree.js.map +1 -1
  30. package/src/core/l0-matrix.js +15 -13
  31. package/src/core/l0-matrix.js.map +1 -1
  32. package/src/core/l1-doc.js +29 -68
  33. package/src/core/l1-doc.js.map +1 -1
  34. package/src/core/l1-rows.d.ts +17 -9
  35. package/src/core/l1-rows.js +18 -13
  36. package/src/core/l1-rows.js.map +1 -1
  37. package/src/core/log-stream.d.ts +6 -4
  38. package/src/core/log-stream.js +6 -4
  39. package/src/core/log-stream.js.map +1 -1
  40. package/src/core/log-streams.d.ts +13 -3
  41. package/src/core/log-streams.js +15 -5
  42. package/src/core/log-streams.js.map +1 -1
  43. package/src/core/runner.d.ts +1 -2
  44. package/src/core/runner.js +29 -24
  45. package/src/core/runner.js.map +1 -1
  46. package/src/core/version-sync.d.ts +67 -0
  47. package/src/core/version-sync.js +148 -0
  48. package/src/core/version-sync.js.map +1 -0
  49. package/src/core/webpieces-versions.d.ts +83 -0
  50. package/src/core/webpieces-versions.js +169 -0
  51. package/src/core/webpieces-versions.js.map +1 -0
  52. package/templates/ai-hook.sh +15 -4
  53. package/templates/claude-settings-hook.json +6 -3
  54. package/src/bin/guarantee-root.d.ts +0 -95
  55. package/src/bin/guarantee-root.js +0 -297
  56. package/src/bin/guarantee-root.js.map +0 -1
  57. package/src/core/coordinator-worktree.d.ts +0 -61
  58. package/src/core/coordinator-worktree.js +0 -94
  59. package/src/core/coordinator-worktree.js.map +0 -1
  60. package/templates/guarantee-root.sh +0 -113
@@ -0,0 +1,46 @@
1
+ /**
2
+ * THE MANAGED `env` ENTRY — `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1`, the FOURTH managed surface.
3
+ *
4
+ * Claude Code reads this variable once per Bash tool call and, when it is truthy, resets the shell's
5
+ * cwd back to the project directory after EVERY call. With it unset, the reset is CONDITIONAL (only
6
+ * when the cwd drifted to a dir that is not an allowed one) and prints a visible "Shell cwd was reset
7
+ * to …" notice, so an ordinary `cd` persists from one Bash call to the next. Verified against the
8
+ * 2.1.226 binary, where the variable also appears in the settings-`env` allowlist beside
9
+ * `BASH_DEFAULT_TIMEOUT_MS` — which is what makes `.claude/settings.json` → `env` a supported home for
10
+ * it rather than a guess.
11
+ *
12
+ * WHY WEBPIECES MANAGES IT — it is VERDICT STABILITY, not ergonomics:
13
+ *
14
+ * 1. A guard's answer must not depend on where an earlier, unrelated command left the shell. Every
15
+ * location guard reasons about the tree a call acts on; with the cwd pinned, that is a function of
16
+ * the COMMAND alone rather than of session history. Same command, same verdict, every time.
17
+ * 2. Settings `env` is INHERITED, so the main agent and every subagent it spawns share one cwd — and
18
+ * therefore one verdict — instead of each carrying whatever `cd` happened earlier in its own
19
+ * session. Consistency across agents is the point, and it is why this lives in settings rather
20
+ * than in a shell profile.
21
+ * 3. It mechanically enforces the long-standing "do not `cd` into a sub-package and leave the shell
22
+ * there" rule, rather than relying on an agent remembering it.
23
+ *
24
+ * WHAT IT NO LONGER HAS TO DO. This entry was originally justified by hook RESOLUTION: the guard hooks
25
+ * were registered RELATIVE, a relative hook that cannot resolve exits 127 (a NON-BLOCKING error, i.e. a
26
+ * SILENT UNGUARDED ALLOW), and pinning the cwd kept them resolvable. That job is gone — both hooks are
27
+ * registered ABSOLUTE via `$CLAUDE_PROJECT_DIR` now, so they resolve from any cwd by construction and
28
+ * the L-1 hook that policed the same hazard is deleted. The reasons above are what remain, and they are
29
+ * sufficient on their own; the entry is NOT load-bearing for guard integrity any more.
30
+ *
31
+ * THE TRADE, said out loud: with the flag ON the cwd reset is SILENT and UNCONDITIONAL, where without
32
+ * it the reset is conditional and prints a notice. A deliberate `cd` therefore no longer persists
33
+ * across Bash calls. That is the intended cost of (1) and (2), not an oversight — chain into the
34
+ * directory you need (`cd <dir> && <cmd>`) instead of relying on a sticky cwd.
35
+ *
36
+ * ONE spelling, one required value: the entry is either exactly `1` or it is drift. There is no
37
+ * "accepted alternative truthy value" and no legacy key — `envStale()` in hook-registration.ts is the
38
+ * whole contract, and `applyManagedEnv()` brings any other value (including a user-set `0`) to `1`.
39
+ *
40
+ * WHY THESE TWO CONSTANTS LIVE IN THEIR OWN MODULE: `shim.ts` names them in the fault-S deny text and
41
+ * `hook-registration.ts` owns the predicate that reads them, and hook-registration already imports
42
+ * shim. Defining them here — a leaf with no imports at all — keeps that graph acyclic while leaving
43
+ * exactly one definition of each, in the same way SHIM_MARKER lives in shim.ts.
44
+ */
45
+ export declare const BASH_CWD_ENV_KEY = "CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR";
46
+ export declare const BASH_CWD_ENV_VALUE = "1";
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BASH_CWD_ENV_VALUE = exports.BASH_CWD_ENV_KEY = void 0;
4
+ /**
5
+ * THE MANAGED `env` ENTRY — `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1`, the FOURTH managed surface.
6
+ *
7
+ * Claude Code reads this variable once per Bash tool call and, when it is truthy, resets the shell's
8
+ * cwd back to the project directory after EVERY call. With it unset, the reset is CONDITIONAL (only
9
+ * when the cwd drifted to a dir that is not an allowed one) and prints a visible "Shell cwd was reset
10
+ * to …" notice, so an ordinary `cd` persists from one Bash call to the next. Verified against the
11
+ * 2.1.226 binary, where the variable also appears in the settings-`env` allowlist beside
12
+ * `BASH_DEFAULT_TIMEOUT_MS` — which is what makes `.claude/settings.json` → `env` a supported home for
13
+ * it rather than a guess.
14
+ *
15
+ * WHY WEBPIECES MANAGES IT — it is VERDICT STABILITY, not ergonomics:
16
+ *
17
+ * 1. A guard's answer must not depend on where an earlier, unrelated command left the shell. Every
18
+ * location guard reasons about the tree a call acts on; with the cwd pinned, that is a function of
19
+ * the COMMAND alone rather than of session history. Same command, same verdict, every time.
20
+ * 2. Settings `env` is INHERITED, so the main agent and every subagent it spawns share one cwd — and
21
+ * therefore one verdict — instead of each carrying whatever `cd` happened earlier in its own
22
+ * session. Consistency across agents is the point, and it is why this lives in settings rather
23
+ * than in a shell profile.
24
+ * 3. It mechanically enforces the long-standing "do not `cd` into a sub-package and leave the shell
25
+ * there" rule, rather than relying on an agent remembering it.
26
+ *
27
+ * WHAT IT NO LONGER HAS TO DO. This entry was originally justified by hook RESOLUTION: the guard hooks
28
+ * were registered RELATIVE, a relative hook that cannot resolve exits 127 (a NON-BLOCKING error, i.e. a
29
+ * SILENT UNGUARDED ALLOW), and pinning the cwd kept them resolvable. That job is gone — both hooks are
30
+ * registered ABSOLUTE via `$CLAUDE_PROJECT_DIR` now, so they resolve from any cwd by construction and
31
+ * the L-1 hook that policed the same hazard is deleted. The reasons above are what remain, and they are
32
+ * sufficient on their own; the entry is NOT load-bearing for guard integrity any more.
33
+ *
34
+ * THE TRADE, said out loud: with the flag ON the cwd reset is SILENT and UNCONDITIONAL, where without
35
+ * it the reset is conditional and prints a notice. A deliberate `cd` therefore no longer persists
36
+ * across Bash calls. That is the intended cost of (1) and (2), not an oversight — chain into the
37
+ * directory you need (`cd <dir> && <cmd>`) instead of relying on a sticky cwd.
38
+ *
39
+ * ONE spelling, one required value: the entry is either exactly `1` or it is drift. There is no
40
+ * "accepted alternative truthy value" and no legacy key — `envStale()` in hook-registration.ts is the
41
+ * whole contract, and `applyManagedEnv()` brings any other value (including a user-set `0`) to `1`.
42
+ *
43
+ * WHY THESE TWO CONSTANTS LIVE IN THEIR OWN MODULE: `shim.ts` names them in the fault-S deny text and
44
+ * `hook-registration.ts` owns the predicate that reads them, and hook-registration already imports
45
+ * shim. Defining them here — a leaf with no imports at all — keeps that graph acyclic while leaving
46
+ * exactly one definition of each, in the same way SHIM_MARKER lives in shim.ts.
47
+ */
48
+ exports.BASH_CWD_ENV_KEY = 'CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR';
49
+ exports.BASH_CWD_ENV_VALUE = '1';
50
+ //# sourceMappingURL=managed-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed-env.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/managed-env.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACU,QAAA,gBAAgB,GAAG,0CAA0C,CAAC;AAC9D,QAAA,kBAAkB,GAAG,GAAG,CAAC","sourcesContent":["/**\n * THE MANAGED `env` ENTRY — `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR=1`, the FOURTH managed surface.\n *\n * Claude Code reads this variable once per Bash tool call and, when it is truthy, resets the shell's\n * cwd back to the project directory after EVERY call. With it unset, the reset is CONDITIONAL (only\n * when the cwd drifted to a dir that is not an allowed one) and prints a visible \"Shell cwd was reset\n * to …\" notice, so an ordinary `cd` persists from one Bash call to the next. Verified against the\n * 2.1.226 binary, where the variable also appears in the settings-`env` allowlist beside\n * `BASH_DEFAULT_TIMEOUT_MS` — which is what makes `.claude/settings.json` → `env` a supported home for\n * it rather than a guess.\n *\n * WHY WEBPIECES MANAGES IT — it is VERDICT STABILITY, not ergonomics:\n *\n * 1. A guard's answer must not depend on where an earlier, unrelated command left the shell. Every\n * location guard reasons about the tree a call acts on; with the cwd pinned, that is a function of\n * the COMMAND alone rather than of session history. Same command, same verdict, every time.\n * 2. Settings `env` is INHERITED, so the main agent and every subagent it spawns share one cwd — and\n * therefore one verdict — instead of each carrying whatever `cd` happened earlier in its own\n * session. Consistency across agents is the point, and it is why this lives in settings rather\n * than in a shell profile.\n * 3. It mechanically enforces the long-standing \"do not `cd` into a sub-package and leave the shell\n * there\" rule, rather than relying on an agent remembering it.\n *\n * WHAT IT NO LONGER HAS TO DO. This entry was originally justified by hook RESOLUTION: the guard hooks\n * were registered RELATIVE, a relative hook that cannot resolve exits 127 (a NON-BLOCKING error, i.e. a\n * SILENT UNGUARDED ALLOW), and pinning the cwd kept them resolvable. That job is gone — both hooks are\n * registered ABSOLUTE via `$CLAUDE_PROJECT_DIR` now, so they resolve from any cwd by construction and\n * the L-1 hook that policed the same hazard is deleted. The reasons above are what remain, and they are\n * sufficient on their own; the entry is NOT load-bearing for guard integrity any more.\n *\n * THE TRADE, said out loud: with the flag ON the cwd reset is SILENT and UNCONDITIONAL, where without\n * it the reset is conditional and prints a notice. A deliberate `cd` therefore no longer persists\n * across Bash calls. That is the intended cost of (1) and (2), not an oversight — chain into the\n * directory you need (`cd <dir> && <cmd>`) instead of relying on a sticky cwd.\n *\n * ONE spelling, one required value: the entry is either exactly `1` or it is drift. There is no\n * \"accepted alternative truthy value\" and no legacy key — `envStale()` in hook-registration.ts is the\n * whole contract, and `applyManagedEnv()` brings any other value (including a user-set `0`) to `1`.\n *\n * WHY THESE TWO CONSTANTS LIVE IN THEIR OWN MODULE: `shim.ts` names them in the fault-S deny text and\n * `hook-registration.ts` owns the predicate that reads them, and hook-registration already imports\n * shim. Defining them here — a leaf with no imports at all — keeps that graph acyclic while leaving\n * exactly one definition of each, in the same way SHIM_MARKER lives in shim.ts.\n */\nexport const BASH_CWD_ENV_KEY = 'CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR';\nexport const BASH_CWD_ENV_VALUE = '1';\n"]}
@@ -1,6 +1,4 @@
1
- import { renderShim } from './shim';
2
1
  import { ClaudeSettings } from './hook-registration';
3
- export { renderShim };
4
2
  declare class HookSpec {
5
3
  readonly key: string;
6
4
  readonly label: string;
@@ -41,3 +39,4 @@ export declare function applyHook(hook: HookSpec, chosen: InstallTarget | null,
41
39
  export declare function resolveTargetChoice(name: string): string | null;
42
40
  export declare function parseTargetArg(args: string[]): string | null;
43
41
  export declare function main(): Promise<void>;
42
+ export {};
package/src/bin/setup.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GUARDS_HOOK = exports.RULES_HOOK = exports.InstallTarget = exports.renderShim = void 0;
3
+ exports.GUARDS_HOOK = exports.RULES_HOOK = exports.InstallTarget = void 0;
4
4
  exports.installTargets = installTargets;
5
5
  exports.migrate = migrate;
6
6
  exports.hasHook = hasHook;
@@ -16,9 +16,8 @@ const readline_1 = require("readline");
16
16
  const rules_config_1 = require("@webpieces/rules-config");
17
17
  const to_error_1 = require("../core/to-error");
18
18
  const shim_1 = require("./shim");
19
- Object.defineProperty(exports, "renderShim", { enumerable: true, get: function () { return shim_1.renderShim; } });
20
- const guarantee_root_1 = require("./guarantee-root");
21
19
  const hook_registration_1 = require("./hook-registration");
20
+ const managed_env_1 = require("./managed-env");
22
21
  const CONFIG_FILENAME = 'webpieces.config.json';
23
22
  // The seeded buildCommand comes from @webpieces/rules-config, NOT from a copy here. This file used to
24
23
  // hold its own — with `--base=origin/main`, a DIFFERENT base from the one the gate documents — so a
@@ -29,9 +28,9 @@ const DEFAULT_MERGE_COMPLETE = 'pnpm wp-finish-upsert-pr';
29
28
  // ---------------------------------------------------------------------------
30
29
  // The two independently-installable GUARD hooks. Each can land in a different settings file (see
31
30
  // InstallTarget) so a team can ship the guards while a developer keeps the code-style rules local
32
- // while iterating. The L-1 hook is a third registration but is NOT independently installable — it
33
- // rides with the guards hook (applyGuaranteeRoot), because it exists to protect the RELATIVE
34
- // resolution of these two and is meaningless without them.
31
+ // while iterating. Both are registered ABSOLUTE via $CLAUDE_PROJECT_DIR, so the MAIN tree governs every
32
+ // tree. There used to be a third — the L-1 hook, which existed only to keep a RELATIVE registration
33
+ // resolvable; it is retired, and purgeRetiredGuaranteeRoot() removes anything left of it.
35
34
  // ---------------------------------------------------------------------------
36
35
  class HookSpec {
37
36
  key;
@@ -45,9 +44,9 @@ class HookSpec {
45
44
  this.bin = bin;
46
45
  }
47
46
  // Absolute targets (global) need the exact path to this repo's bin — no ~/.webpieces bridge.
48
- // Project targets get the RELATIVE shim command (see hook-registration.ts for why relative is the
49
- // whole point), and the L-1 hook registered beside them is what keeps a relative path from ever
50
- // failing to resolve — which per the hooks reference would be a SILENT UNGUARDED ALLOW, not a block.
47
+ // Project targets get the ABSOLUTE shim command, `$CLAUDE_PROJECT_DIR/.claude/webpieces/ai-hook.sh`
48
+ // (see hook-registration.ts for why): it resolves from ANY cwd, so a hook can never fail to launch
49
+ // which per the hooks reference would be exit 127, a SILENT UNGUARDED ALLOW rather than a block.
51
50
  commandFor(target, projectRoot) {
52
51
  if (target.absolute) {
53
52
  return `node ${path.join(projectRoot, 'node_modules', '.bin', this.bin)}`;
@@ -77,46 +76,29 @@ function markerReferenced(targets, marker) {
77
76
  return entries.some((e) => e.hooks.some((h) => h.command.includes(marker)));
78
77
  });
79
78
  }
80
- // 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
81
- function removeGuaranteeRootFile(projectRoot) {
82
- const target = (0, guarantee_root_1.guaranteeRootPath)(projectRoot);
83
- if (fs.existsSync(target))
84
- fs.rmSync(target);
85
- }
86
- // Drop every PreToolUse command referencing the L-1 hook; returns true if anything was removed.
87
- // 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
88
- function removeGuaranteeRootHook(settings) {
89
- return removeHookByMarker(settings, guarantee_root_1.GUARANTEE_ROOT_MARKER);
90
- }
91
79
  /**
92
- * Install / move / uninstall the L-1 hook (guarantee-root.sh) alongside the GUARDS hook.
80
+ * Remove every trace of the RETIRED L-1 hook (`guarantee-root.sh`) the file and any PreToolUse entry
81
+ * still pointing at it. REMOVAL ONLY: nothing here can ever write one back.
93
82
  *
94
- * It rides with the guards hook and not the rules hook because it judges `cd`, `cd` arrives on Bash,
95
- * and `Bash` is in the guards matcher. It is registered ABSOLUTE while the guard hooks are RELATIVE —
96
- * that asymmetry is the design, not an oversight (hook-registration.ts states it once).
97
- *
98
- * A GLOBAL (absolute) install gets none: those hooks name the bin path directly, so there is no
99
- * relative path that could fail to resolve, which is the only thing L-1 protects against.
83
+ * L-1 existed to guarantee a RELATIVE guard-hook path resolved, by refusing any `cd` that would park the
84
+ * shell in a subdirectory. The guard hooks are ABSOLUTE now (`$CLAUDE_PROJECT_DIR/...`), so they resolve
85
+ * from any cwd and the guarantee is structural there is nothing left to police, and the subdirectory
86
+ * denial that used to pay for it is gone with it.
100
87
  */
101
88
  // 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
102
- function applyGuaranteeRoot(chosen, targets, projectRoot) {
103
- const relative = chosen !== null && !chosen.absolute;
89
+ function purgeRetiredGuaranteeRoot(targets, projectRoot) {
104
90
  for (const target of targets) {
105
91
  const settings = (0, hook_registration_1.readSettings)(target.settingsPath);
106
- const removed = removeGuaranteeRootHook(settings);
107
- if (relative && chosen.settingsPath === target.settingsPath) {
108
- (0, hook_registration_1.addHookEntry)(settings, hook_registration_1.GUARANTEE_ROOT_ENTRY);
109
- (0, hook_registration_1.writeSettings)(target.settingsPath, settings);
110
- console.log(` ✅ L-1 guarantee-root hook (keeps the shell where the relative guard hooks can launch) → ${target.label}`);
111
- }
112
- else if (removed) {
92
+ if (removeHookByMarker(settings, hook_registration_1.LEGACY_GUARANTEE_ROOT_MARKER)) {
113
93
  (0, hook_registration_1.writeSettings)(target.settingsPath, settings);
94
+ console.log(` 🗑️ removed the retired L-1 guarantee-root hook from ${target.label}`);
114
95
  }
115
96
  }
116
- if (relative)
117
- (0, guarantee_root_1.writeGuaranteeRoot)(projectRoot);
118
- else if (!markerReferenced(targets, guarantee_root_1.GUARANTEE_ROOT_MARKER))
119
- removeGuaranteeRootFile(projectRoot);
97
+ const legacyFile = path.join(projectRoot, hook_registration_1.LEGACY_GUARANTEE_ROOT_MARKER);
98
+ if (fs.existsSync(legacyFile)) {
99
+ fs.rmSync(legacyFile, { force: true });
100
+ console.log(' 🗑️ deleted the retired .claude/webpieces/guarantee-root.sh');
101
+ }
120
102
  }
121
103
  class InstallTarget {
122
104
  choice;
@@ -415,8 +397,9 @@ function hasHook(settings, bin) {
415
397
  return entries.some((e) => e.hooks.some((h) => h.command.includes(bin)));
416
398
  }
417
399
  // Drop every PreToolUse command containing `marker` (a bin name, or a managed .sh path); returns true
418
- // if anything was removed. REMOVE-then-ADD is what keeps an upgrade from leaving the old absolute
419
- // spelling beside the new relative one — two spellings of one registration.
400
+ // if anything was removed. REMOVE-then-ADD is what keeps an upgrade from leaving a superseded spelling
401
+ // (a relative command, or the retired L-1 entry) beside the current one — two spellings of one
402
+ // registration is the compatibility shim the backwards-compat reviewer rejects.
420
403
  // 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
421
404
  function removeHookByMarker(settings, marker) {
422
405
  const entries = settings.hooks?.PreToolUse;
@@ -444,6 +427,15 @@ function applyHook(hook, chosen, targets, projectRoot) {
444
427
  const isChosen = chosen !== null && chosen.settingsPath === target.settingsPath;
445
428
  if (isChosen) {
446
429
  (0, hook_registration_1.addHookEntry)(settings, new hook_registration_1.HookRegistrationEntry(hook.matcher, hook.commandFor(target, projectRoot)));
430
+ // The managed `env` entry goes into the SAME file the hooks go into, on every path that
431
+ // writes hooks — interactive or `--target=`. It pins the Bash cwd to the project root, so a
432
+ // guard's answer depends on the command rather than on where an earlier `cd` left the shell,
433
+ // and settings `env` is inherited, so every subagent gets the identical cwd and therefore the
434
+ // identical guard verdict. (It no longer has a RESOLUTION job — the hooks are absolute.) See
435
+ // hook-registration.ts for the full argument; `wp-upgrade-shim` self-heals it afterwards.
436
+ if ((0, hook_registration_1.applyManagedEnv)(settings)) {
437
+ console.log(` ✅ env.${managed_env_1.BASH_CWD_ENV_KEY}=${managed_env_1.BASH_CWD_ENV_VALUE} → ${target.label} (pins the Bash cwd to the project root, for this session and every subagent)`);
438
+ }
447
439
  (0, hook_registration_1.writeSettings)(target.settingsPath, settings);
448
440
  console.log(` ✅ ${hook.label} → ${target.label}`);
449
441
  }
@@ -459,11 +451,11 @@ function applyHook(hook, chosen, targets, projectRoot) {
459
451
  else if (!markerReferenced(targets, shim_1.SHIM_MARKER)) {
460
452
  removeShim(projectRoot);
461
453
  }
462
- // The L-1 hook rides with the GUARDS hook (it judges `cd`, which arrives on Bash). Doing it here
463
- // rather than at a separate call site means every existing caller of applyHook — the installer's
464
- // interactive and --target paths, and every test — gets the three-hook form with no second step.
454
+ // The RETIRED L-1 hook rode with the GUARDS hook, so its removal does too. Doing it here rather than
455
+ // at a separate call site means every existing caller of applyHook — the installer's interactive and
456
+ // --target paths, and every test — converges on the two-hook absolute form with no second step.
465
457
  if (hook.bin === hook_registration_1.GUARDS_BIN)
466
- applyGuaranteeRoot(chosen, targets, projectRoot);
458
+ purgeRetiredGuaranteeRoot(targets, projectRoot);
467
459
  if (chosen === null)
468
460
  console.log(` ⛔ ${hook.label} not installed (removed from all locations).`);
469
461
  }
@@ -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 { GUARANTEE_ROOT_MARKER, guaranteeRootPath, writeGuaranteeRoot } from './guarantee-root';\nimport {\n ClaudeSettings, HookCommand, HookEntry, HookRegistrationEntry, GUARANTEE_ROOT_ENTRY, GUARDS_BIN,\n GUARDS_MATCHER, RULES_BIN, RULES_MATCHER, addHookEntry, readSettings, shimCommand, writeSettings,\n} from './hook-registration';\n\n// Re-exported for back-compat (setup.spec.ts + external callers). The shim body + path now live in\n// ./shim (shared with the runtime self-heal in hook-core). See shim.ts for the single source of truth.\nexport { renderShim };\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. The L-1 hook is a third registration but is NOT independently installable — it\n// rides with the guards hook (applyGuaranteeRoot), because it exists to protect the RELATIVE\n// resolution of these two and is meaningless without them.\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 RELATIVE shim command (see hook-registration.ts for why relative is the\n // whole point), and the L-1 hook registered beside them is what keeps a relative path from ever\n // failing to resolve — which per the hooks reference would be a SILENT UNGUARDED ALLOW, not 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// 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 removeGuaranteeRootFile(projectRoot: string): void {\n const target = guaranteeRootPath(projectRoot);\n if (fs.existsSync(target)) fs.rmSync(target);\n}\n\n// Drop every PreToolUse command referencing the L-1 hook; returns true if anything was removed.\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 removeGuaranteeRootHook(settings: ClaudeSettings): boolean {\n return removeHookByMarker(settings, GUARANTEE_ROOT_MARKER);\n}\n\n/**\n * Install / move / uninstall the L-1 hook (guarantee-root.sh) alongside the GUARDS hook.\n *\n * It rides with the guards hook and not the rules hook because it judges `cd`, `cd` arrives on Bash,\n * and `Bash` is in the guards matcher. It is registered ABSOLUTE while the guard hooks are RELATIVE —\n * that asymmetry is the design, not an oversight (hook-registration.ts states it once).\n *\n * A GLOBAL (absolute) install gets none: those hooks name the bin path directly, so there is no\n * relative path that could fail to resolve, which is the only thing L-1 protects against.\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 applyGuaranteeRoot(chosen: InstallTarget | null, targets: InstallTarget[], projectRoot: string): void {\n const relative = chosen !== null && !chosen.absolute;\n for (const target of targets) {\n const settings = readSettings(target.settingsPath);\n const removed = removeGuaranteeRootHook(settings);\n if (relative && chosen.settingsPath === target.settingsPath) {\n addHookEntry(settings, GUARANTEE_ROOT_ENTRY);\n writeSettings(target.settingsPath, settings);\n console.log(` ✅ L-1 guarantee-root hook (keeps the shell where the relative guard hooks can launch) → ${target.label}`);\n } else if (removed) {\n writeSettings(target.settingsPath, settings);\n }\n }\n if (relative) writeGuaranteeRoot(projectRoot);\n else if (!markerReferenced(targets, GUARANTEE_ROOT_MARKER)) removeGuaranteeRootFile(projectRoot);\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 the old absolute\n// spelling beside the new relative one — two spellings of one registration.\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 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 L-1 hook rides with the GUARDS hook (it judges `cd`, which arrives on Bash). Doing it here\n // rather than at a separate call site means every existing caller of applyHook — the installer's\n // interactive and --target paths, and every test — gets the three-hook form with no second step.\n if (hook.bin === GUARDS_BIN) applyGuaranteeRoot(chosen, 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, 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"]}
@@ -159,7 +159,18 @@ wp_log() { # $1 = L0 fault code (D|X|K|-), $2 = verdict label
159
159
  _wp_sz="$(wc -c < "$_wp_f" 2>/dev/null | tr -d ' ')"
160
160
  case "$_wp_sz" in ''|*[!0-9]*) _wp_sz=0 ;; esac
161
161
  [ "$_wp_sz" -gt ${String(exports.SHIM_LOG_MAX_BYTES)} ] && mv -f "$_wp_f" "$_wp_sd/\${_wp_pfx}.1.log" 2>/dev/null
162
- printf '%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\n' "$(date '+%Y-%m-%dT%H:%M:%S%z' 2>/dev/null)" "$BIN_NAME" "$TOOL" "tree=$WP_TREE" "fault=$1" "$2" "$CMD_LOG" >> "$_wp_f"
162
+ # shim= and bin= are the two facts this log could not previously answer, and they are the ones that
163
+ # decide whether a tree was governed by its OWN release or a borrowed one:
164
+ # shim= WHICH COPY OF ai-hook.sh RAN — $ROOT, resolved from $0. The file is TRACKED, so every
165
+ # worktree carries the version at ITS commit; settings.json registers it ABSOLUTE, so the copy
166
+ # that runs is the SESSION ROOT's. Logged rather than assumed.
167
+ # bin= WHICH TREE SUPPLIED THE BINARY — $BIN_ROOT, the upward walk's answer. A fresh linked
168
+ # worktree has no node_modules, so this is normally the PRIMARY even when shim= is not:
169
+ # per-tree governance is the script and the config, never the enforcement code.
170
+ # Until these existed, no log at any layer recorded either (L-1 logged neither, L0 logged neither,
171
+ # only L1 logged root=/projectDir=), so "which hook governed this call" had to be inferred. Compare
172
+ # shim= against bin= to see a borrow, and either against the tree to see a straddle.
173
+ printf '%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\t%s\\n' "$(date '+%Y-%m-%dT%H:%M:%S%z' 2>/dev/null)" "$BIN_NAME" "$TOOL" "tree=$WP_TREE" "shim=$ROOT" "bin=$BIN_ROOT" "fault=$1" "$2" "$CMD_LOG" >> "$_wp_f"
163
174
  } 2>/dev/null || true
164
175
  }`;
165
176
  //# sourceMappingURL=shim-audit-log.js.map