@webpieces/hook-runtime 0.0.1

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 (103) hide show
  1. package/package.json +37 -0
  2. package/src/adapters/agent-adapters.d.ts +13 -0
  3. package/src/adapters/agent-adapters.js +23 -0
  4. package/src/adapters/agent-adapters.js.map +1 -0
  5. package/src/adapters/agent-payload.d.ts +48 -0
  6. package/src/adapters/agent-payload.js +30 -0
  7. package/src/adapters/agent-payload.js.map +1 -0
  8. package/src/adapters/claude-code-adapter.d.ts +26 -0
  9. package/src/adapters/claude-code-adapter.js +69 -0
  10. package/src/adapters/claude-code-adapter.js.map +1 -0
  11. package/src/adapters/codex-adapter.d.ts +19 -0
  12. package/src/adapters/codex-adapter.js +48 -0
  13. package/src/adapters/codex-adapter.js.map +1 -0
  14. package/src/adapters/detect-ai.d.ts +70 -0
  15. package/src/adapters/detect-ai.js +82 -0
  16. package/src/adapters/detect-ai.js.map +1 -0
  17. package/src/core/apply-patch-parse.d.ts +36 -0
  18. package/src/core/apply-patch-parse.js +155 -0
  19. package/src/core/apply-patch-parse.js.map +1 -0
  20. package/src/core/build-context.d.ts +11 -0
  21. package/src/core/build-context.js +69 -0
  22. package/src/core/build-context.js.map +1 -0
  23. package/src/core/command-scan.d.ts +122 -0
  24. package/src/core/command-scan.js +282 -0
  25. package/src/core/command-scan.js.map +1 -0
  26. package/src/core/custom-rule-adapter.d.ts +23 -0
  27. package/src/core/custom-rule-adapter.js +50 -0
  28. package/src/core/custom-rule-adapter.js.map +1 -0
  29. package/src/core/delete-scoped-rules.d.ts +9 -0
  30. package/src/core/delete-scoped-rules.js +31 -0
  31. package/src/core/delete-scoped-rules.js.map +1 -0
  32. package/src/core/disable-directives.d.ts +8 -0
  33. package/src/core/disable-directives.js +86 -0
  34. package/src/core/disable-directives.js.map +1 -0
  35. package/src/core/effective-tree.d.ts +197 -0
  36. package/src/core/effective-tree.js +269 -0
  37. package/src/core/effective-tree.js.map +1 -0
  38. package/src/core/excluded-paths.d.ts +23 -0
  39. package/src/core/excluded-paths.js +54 -0
  40. package/src/core/excluded-paths.js.map +1 -0
  41. package/src/core/file-evaluation.d.ts +11 -0
  42. package/src/core/file-evaluation.js +45 -0
  43. package/src/core/file-evaluation.js.map +1 -0
  44. package/src/core/fix-hint.d.ts +40 -0
  45. package/src/core/fix-hint.js +53 -0
  46. package/src/core/fix-hint.js.map +1 -0
  47. package/src/core/glob.d.ts +1 -0
  48. package/src/core/glob.js +42 -0
  49. package/src/core/glob.js.map +1 -0
  50. package/src/core/report.d.ts +21 -0
  51. package/src/core/report.js +74 -0
  52. package/src/core/report.js.map +1 -0
  53. package/src/core/root-manifest.d.ts +1 -0
  54. package/src/core/root-manifest.js +21 -0
  55. package/src/core/root-manifest.js.map +1 -0
  56. package/src/core/rule-base.d.ts +40 -0
  57. package/src/core/rule-base.js +37 -0
  58. package/src/core/rule-base.js.map +1 -0
  59. package/src/core/rule-evaluation.d.ts +18 -0
  60. package/src/core/rule-evaluation.js +179 -0
  61. package/src/core/rule-evaluation.js.map +1 -0
  62. package/src/core/rules/shell-segment-scan.d.ts +64 -0
  63. package/src/core/rules/shell-segment-scan.js +88 -0
  64. package/src/core/rules/shell-segment-scan.js.map +1 -0
  65. package/src/core/shell-read-parity.d.ts +34 -0
  66. package/src/core/shell-read-parity.js +157 -0
  67. package/src/core/shell-read-parity.js.map +1 -0
  68. package/src/core/strip-ts-noise.d.ts +1 -0
  69. package/src/core/strip-ts-noise.js +191 -0
  70. package/src/core/strip-ts-noise.js.map +1 -0
  71. package/src/core/target-tree.d.ts +84 -0
  72. package/src/core/target-tree.js +148 -0
  73. package/src/core/target-tree.js.map +1 -0
  74. package/src/core/types.d.ts +149 -0
  75. package/src/core/types.js +190 -0
  76. package/src/core/types.js.map +1 -0
  77. package/src/harness-vocabulary.d.ts +2 -0
  78. package/src/harness-vocabulary.js +10 -0
  79. package/src/harness-vocabulary.js.map +1 -0
  80. package/src/hook-app.d.ts +15 -0
  81. package/src/hook-app.js +58 -0
  82. package/src/hook-app.js.map +1 -0
  83. package/src/hook-evaluator.d.ts +5 -0
  84. package/src/hook-evaluator.js +13 -0
  85. package/src/hook-evaluator.js.map +1 -0
  86. package/src/hook-ports.d.ts +9 -0
  87. package/src/hook-ports.js +40 -0
  88. package/src/hook-ports.js.map +1 -0
  89. package/src/index.d.ts +31 -0
  90. package/src/index.js +59 -0
  91. package/src/index.js.map +1 -0
  92. package/src/outcome.d.ts +14 -0
  93. package/src/outcome.js +20 -0
  94. package/src/outcome.js.map +1 -0
  95. package/src/protocol.d.ts +40 -0
  96. package/src/protocol.js +55 -0
  97. package/src/protocol.js.map +1 -0
  98. package/src/response.d.ts +2 -0
  99. package/src/response.js +21 -0
  100. package/src/response.js.map +1 -0
  101. package/src/to-error.d.ts +1 -0
  102. package/src/to-error.js +9 -0
  103. package/src/to-error.js.map +1 -0
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stripTsNoise = stripTsNoise;
4
+ class StripState {
5
+ source;
6
+ len;
7
+ out;
8
+ stack;
9
+ state;
10
+ braceDepth;
11
+ i;
12
+ constructor(source) {
13
+ this.source = source;
14
+ this.len = source.length;
15
+ this.out = new Array(source.length);
16
+ this.stack = [];
17
+ this.state = 'code';
18
+ this.braceDepth = 0;
19
+ this.i = 0;
20
+ }
21
+ ch() { return this.source[this.i]; }
22
+ next() { return this.i + 1 < this.len ? this.source[this.i + 1] : ''; }
23
+ emit(idx, ch) { this.out[idx] = ch; }
24
+ blank(idx, ch) { this.out[idx] = ch === '\n' ? '\n' : ' '; }
25
+ pushState(newState) {
26
+ this.stack.push({ state: this.state, braceDepth: this.braceDepth });
27
+ this.state = newState;
28
+ }
29
+ popState() {
30
+ const frame = this.stack.pop();
31
+ this.state = frame.state;
32
+ this.braceDepth = frame.braceDepth;
33
+ }
34
+ }
35
+ // webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API
36
+ function handleCodeOrInterp(s) {
37
+ const ch = s.ch();
38
+ const next = s.next();
39
+ if (s.state === 'templateInterp') {
40
+ if (ch === '{') {
41
+ s.braceDepth += 1;
42
+ s.emit(s.i, ch);
43
+ s.i += 1;
44
+ return;
45
+ }
46
+ if (ch === '}') {
47
+ if (s.braceDepth === 0) {
48
+ s.emit(s.i, ch);
49
+ s.i += 1;
50
+ s.popState();
51
+ return;
52
+ }
53
+ s.braceDepth -= 1;
54
+ s.emit(s.i, ch);
55
+ s.i += 1;
56
+ return;
57
+ }
58
+ }
59
+ if (ch === '/' && next === '/') {
60
+ s.emit(s.i, '/');
61
+ s.emit(s.i + 1, '/');
62
+ s.i += 2;
63
+ s.pushState('lineComment');
64
+ return;
65
+ }
66
+ if (ch === '/' && next === '*') {
67
+ s.emit(s.i, '/');
68
+ s.emit(s.i + 1, '*');
69
+ s.i += 2;
70
+ s.pushState('blockComment');
71
+ return;
72
+ }
73
+ if (ch === '"') {
74
+ s.emit(s.i, '"');
75
+ s.i += 1;
76
+ s.pushState('dquote');
77
+ return;
78
+ }
79
+ if (ch === "'") {
80
+ s.emit(s.i, "'");
81
+ s.i += 1;
82
+ s.pushState('squote');
83
+ return;
84
+ }
85
+ if (ch === '`') {
86
+ s.emit(s.i, '`');
87
+ s.i += 1;
88
+ s.pushState('template');
89
+ return;
90
+ }
91
+ s.emit(s.i, ch);
92
+ s.i += 1;
93
+ }
94
+ // webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API
95
+ function handleLineComment(s) {
96
+ if (s.ch() === '\n') {
97
+ s.emit(s.i, '\n');
98
+ s.i += 1;
99
+ s.popState();
100
+ return;
101
+ }
102
+ s.blank(s.i, s.ch());
103
+ s.i += 1;
104
+ }
105
+ // webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API
106
+ function handleBlockComment(s) {
107
+ if (s.ch() === '*' && s.next() === '/') {
108
+ s.emit(s.i, '*');
109
+ s.emit(s.i + 1, '/');
110
+ s.i += 2;
111
+ s.popState();
112
+ return;
113
+ }
114
+ s.blank(s.i, s.ch());
115
+ s.i += 1;
116
+ }
117
+ // webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API
118
+ function handleStringLiteral(s, quoteChar) {
119
+ const ch = s.ch();
120
+ if (ch === '\\' && s.i + 1 < s.len) {
121
+ s.blank(s.i, ch);
122
+ s.blank(s.i + 1, s.source[s.i + 1]);
123
+ s.i += 2;
124
+ return;
125
+ }
126
+ if (ch === quoteChar) {
127
+ s.emit(s.i, quoteChar);
128
+ s.i += 1;
129
+ s.popState();
130
+ return;
131
+ }
132
+ s.blank(s.i, ch);
133
+ s.i += 1;
134
+ }
135
+ // webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API
136
+ function handleTemplate(s) {
137
+ const ch = s.ch();
138
+ if (ch === '\\' && s.i + 1 < s.len) {
139
+ s.blank(s.i, ch);
140
+ s.blank(s.i + 1, s.source[s.i + 1]);
141
+ s.i += 2;
142
+ return;
143
+ }
144
+ if (ch === '`') {
145
+ s.emit(s.i, '`');
146
+ s.i += 1;
147
+ s.popState();
148
+ return;
149
+ }
150
+ if (ch === '$' && s.next() === '{') {
151
+ s.emit(s.i, '$');
152
+ s.emit(s.i + 1, '{');
153
+ s.i += 2;
154
+ s.stack.push({ state: 'template', braceDepth: 0 });
155
+ s.state = 'templateInterp';
156
+ s.braceDepth = 0;
157
+ return;
158
+ }
159
+ s.blank(s.i, ch);
160
+ s.i += 1;
161
+ }
162
+ // webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API
163
+ function stripTsNoise(source) {
164
+ const s = new StripState(source);
165
+ while (s.i < s.len) {
166
+ if (s.state === 'code' || s.state === 'templateInterp') {
167
+ handleCodeOrInterp(s);
168
+ }
169
+ else if (s.state === 'lineComment') {
170
+ handleLineComment(s);
171
+ }
172
+ else if (s.state === 'blockComment') {
173
+ handleBlockComment(s);
174
+ }
175
+ else if (s.state === 'dquote') {
176
+ handleStringLiteral(s, '"');
177
+ }
178
+ else if (s.state === 'squote') {
179
+ handleStringLiteral(s, "'");
180
+ }
181
+ else if (s.state === 'template') {
182
+ handleTemplate(s);
183
+ }
184
+ else {
185
+ s.emit(s.i, s.ch());
186
+ s.i += 1;
187
+ }
188
+ }
189
+ return s.out.join('');
190
+ }
191
+ //# sourceMappingURL=strip-ts-noise.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-ts-noise.js","sourceRoot":"","sources":["../../../../../../packages/tooling/hook-runtime/src/core/strip-ts-noise.ts"],"names":[],"mappings":"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","sourcesContent":["interface StackFrame {\n readonly state: string;\n readonly braceDepth: number;\n}\n\nclass StripState {\n readonly source: string;\n readonly len: number;\n readonly out: string[];\n readonly stack: StackFrame[];\n state: string;\n braceDepth: number;\n i: number;\n\n constructor(source: string) {\n this.source = source;\n this.len = source.length;\n this.out = new Array<string>(source.length);\n this.stack = [];\n this.state = 'code';\n this.braceDepth = 0;\n this.i = 0;\n }\n\n ch(): string { return this.source[this.i]; }\n next(): string { return this.i + 1 < this.len ? this.source[this.i + 1] : ''; }\n emit(idx: number, ch: string): void { this.out[idx] = ch; }\n blank(idx: number, ch: string): void { this.out[idx] = ch === '\\n' ? '\\n' : ' '; }\n\n pushState(newState: string): void {\n this.stack.push({ state: this.state, braceDepth: this.braceDepth });\n this.state = newState;\n }\n\n popState(): void {\n const frame = this.stack.pop()!;\n this.state = frame.state;\n this.braceDepth = frame.braceDepth;\n }\n}\n\n// webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API\nfunction handleCodeOrInterp(s: StripState): void {\n const ch = s.ch();\n const next = s.next();\n\n if (s.state === 'templateInterp') {\n if (ch === '{') { s.braceDepth += 1; s.emit(s.i, ch); s.i += 1; return; }\n if (ch === '}') {\n if (s.braceDepth === 0) { s.emit(s.i, ch); s.i += 1; s.popState(); return; }\n s.braceDepth -= 1; s.emit(s.i, ch); s.i += 1; return;\n }\n }\n if (ch === '/' && next === '/') { s.emit(s.i, '/'); s.emit(s.i + 1, '/'); s.i += 2; s.pushState('lineComment'); return; }\n if (ch === '/' && next === '*') { s.emit(s.i, '/'); s.emit(s.i + 1, '*'); s.i += 2; s.pushState('blockComment'); return; }\n if (ch === '\"') { s.emit(s.i, '\"'); s.i += 1; s.pushState('dquote'); return; }\n if (ch === \"'\") { s.emit(s.i, \"'\"); s.i += 1; s.pushState('squote'); return; }\n if (ch === '`') { s.emit(s.i, '`'); s.i += 1; s.pushState('template'); return; }\n s.emit(s.i, ch); s.i += 1;\n}\n\n// webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API\nfunction handleLineComment(s: StripState): void {\n if (s.ch() === '\\n') { s.emit(s.i, '\\n'); s.i += 1; s.popState(); return; }\n s.blank(s.i, s.ch()); s.i += 1;\n}\n\n// webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API\nfunction handleBlockComment(s: StripState): void {\n if (s.ch() === '*' && s.next() === '/') { s.emit(s.i, '*'); s.emit(s.i + 1, '/'); s.i += 2; s.popState(); return; }\n s.blank(s.i, s.ch()); s.i += 1;\n}\n\n// webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API\nfunction handleStringLiteral(s: StripState, quoteChar: string): void {\n const ch = s.ch();\n if (ch === '\\\\' && s.i + 1 < s.len) { s.blank(s.i, ch); s.blank(s.i + 1, s.source[s.i + 1]); s.i += 2; return; }\n if (ch === quoteChar) { s.emit(s.i, quoteChar); s.i += 1; s.popState(); return; }\n s.blank(s.i, ch); s.i += 1;\n}\n\n// webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API\nfunction handleTemplate(s: StripState): void {\n const ch = s.ch();\n if (ch === '\\\\' && s.i + 1 < s.len) { s.blank(s.i, ch); s.blank(s.i + 1, s.source[s.i + 1]); s.i += 2; return; }\n if (ch === '`') { s.emit(s.i, '`'); s.i += 1; s.popState(); return; }\n if (ch === '$' && s.next() === '{') {\n s.emit(s.i, '$'); s.emit(s.i + 1, '{'); s.i += 2;\n s.stack.push({ state: 'template', braceDepth: 0 });\n s.state = 'templateInterp'; s.braceDepth = 0;\n return;\n }\n s.blank(s.i, ch); s.i += 1;\n}\n\n// webpieces-disable no-function-outside-class -- existing stateless module helper moved intact with its callers; ownership extraction preserves its functional API\nexport function stripTsNoise(source: string): string {\n const s = new StripState(source);\n while (s.i < s.len) {\n if (s.state === 'code' || s.state === 'templateInterp') { handleCodeOrInterp(s); }\n else if (s.state === 'lineComment') { handleLineComment(s); }\n else if (s.state === 'blockComment') { handleBlockComment(s); }\n else if (s.state === 'dquote') { handleStringLiteral(s, '\"'); }\n else if (s.state === 'squote') { handleStringLiteral(s, \"'\"); }\n else if (s.state === 'template') { handleTemplate(s); }\n else { s.emit(s.i, s.ch()); s.i += 1; }\n }\n return s.out.join('');\n}\n"]}
@@ -0,0 +1,84 @@
1
+ import { EffectiveTree, EffectiveTreeResolver } from './effective-tree';
2
+ /**
3
+ * WHICH TREE owns the FILE a Write/Edit/Read is aimed at? The file-path counterpart to
4
+ * EffectiveTreeResolver, which answers the same question for a Bash command's effective cwd.
5
+ *
6
+ * WHY IT HAS TO EXIST (issue #851). The bash half of this question has been answered by git since
7
+ * effective-tree.ts was written — `isLinkedWorktree` is `--git-dir !== --git-common-dir`, and its header
8
+ * states the principle in capitals: *PLACEMENT IS NOT IDENTITY*. The FILE half never consumed that
9
+ * answer. Every file-scoped guard judged `ctx.workspaceRoot`, which is the walk-up from the SESSION's
10
+ * cwd to whichever `webpieces.config.json` governs it — the PRIMARY clone, for a main-session edit into
11
+ * an agent worktree. Because Claude Code checks a worktree out INSIDE the repo at
12
+ * `<repo>/.claude/worktrees/agent-XXXX`, that walk-up answers "primary" for a path that is manifestly
13
+ * not the primary's, and nothing downstream re-examined it.
14
+ *
15
+ * Measured live on 2026-09-03: an Edit of one import line inside a worktree on a CLEAN branch was
16
+ * refused, citing `pnpm-lock.yaml` and `pnpm-workspace.yaml` — the PRIMARY clone's branch's conflict
17
+ * files. The main-sync cache is already keyed by branch and already held the correct, non-conflicting
18
+ * entry for the worktree's branch, one key over. Only the lookup key was wrong. So this class exists to
19
+ * make the lookup ask the same authority the bash half asks, rather than to introduce a second one.
20
+ *
21
+ * IT ASKS EffectiveTreeResolver, and adds NOTHING of its own beyond finding a directory to ask about.
22
+ * That is deliberate and it is the whole design: two resolvers WILL disagree about which tree a path is
23
+ * in, and this module exists precisely because two layers already did. There is no `.claude/worktrees`
24
+ * string match here and there must never be one — matching on placement is the bug, not the fix.
25
+ *
26
+ * The only thing worth naming is the walk-up: a Write legitimately names a file, and directories, that
27
+ * do not exist yet, and git cannot be asked about a directory that is not there. So we climb to the
28
+ * nearest EXISTING ancestor and ask about that. A parent directory is in the same worktree as the child
29
+ * it is about to contain — that is a property of the filesystem, not a guess.
30
+ */
31
+ export declare class TargetTreeResolver {
32
+ private readonly trees;
33
+ constructor(trees?: EffectiveTreeResolver);
34
+ /**
35
+ * The tree that owns `targetPath`, classified exactly as a Bash command's cwd would be.
36
+ *
37
+ * `governedRoot` is the fallback for every case with no better answer — an empty path, a path with
38
+ * no existing ancestor at all — and is also what `EffectiveTree.governedRoot` keeps meaning: whose
39
+ * config and excludePaths apply. It is NOT the tree to judge; `EffectiveTree.root` is.
40
+ */
41
+ resolve(targetPath: string, governedRoot: string): EffectiveTree;
42
+ /**
43
+ * The two relative spellings of one target path, resolved together so a caller cannot accidentally
44
+ * use the wrong one. See GovernedPath for why there are two.
45
+ */
46
+ governedPath(targetPath: string, governedRoot: string): GovernedPath;
47
+ private nearestExistingDir;
48
+ /**
49
+ * `targetPath` made absolute AND symlink-resolved, including when it does not exist yet.
50
+ *
51
+ * `fs.realpathSync` throws on a missing path, and a Write's target is missing by definition, so the
52
+ * resolvable HEAD of the path (its nearest existing ancestor) is resolved and the not-yet-existing
53
+ * TAIL is re-appended verbatim. That keeps it comparable with `tree.root`, which is git's answer and
54
+ * always real.
55
+ */
56
+ private realAbsolute;
57
+ private realDir;
58
+ }
59
+ /**
60
+ * One target path, in the TWO relative spellings the hook path needs — data-only, per CLAUDE.md.
61
+ *
62
+ * They are different questions and conflating them is issue #851's secondary defect:
63
+ *
64
+ * `relativePath` — relative to the GOVERNED root. What `excludePaths`' globs are written against,
65
+ * and what the violation report prints.
66
+ * `treeRelativePath` — relative to the tree that OWNS the file. What `isWebpiecesStateDir` must be
67
+ * asked about, because `.webpieces/` is the tooling's state dir *of a tree*, and
68
+ * a worktree has its own. Governed-root-relative, a reviewer subagent's
69
+ * `<primary>/.claude/worktrees/agent-X/.webpieces/pr-review/<branch>/summary.json`
70
+ * reads as `.claude/...` and was NOT exempt — while
71
+ * `<primary>/.webpieces/worktrees/agent-X/pr-review/.../review-1.json`, the same
72
+ * kind of file one directory over, was. `wp-review-upsert-pr` REQUIRES that file
73
+ * before `wp-finish-upsert-pr` will open a PR, so the guard could forbid a file
74
+ * the gate demands.
75
+ *
76
+ * In the primary clone the two strings are identical, which is why the gap was invisible for so long.
77
+ */
78
+ export declare class GovernedPath {
79
+ readonly relativePath: string;
80
+ readonly treeRelativePath: string;
81
+ /** The git tree that owns the target path; policy still belongs to the separately loaded governed root. */
82
+ readonly treeRoot: string;
83
+ constructor(relativePath: string, treeRelativePath: string, treeRoot: string);
84
+ }
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GovernedPath = exports.TargetTreeResolver = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const effective_tree_1 = require("./effective-tree");
8
+ /**
9
+ * WHICH TREE owns the FILE a Write/Edit/Read is aimed at? The file-path counterpart to
10
+ * EffectiveTreeResolver, which answers the same question for a Bash command's effective cwd.
11
+ *
12
+ * WHY IT HAS TO EXIST (issue #851). The bash half of this question has been answered by git since
13
+ * effective-tree.ts was written — `isLinkedWorktree` is `--git-dir !== --git-common-dir`, and its header
14
+ * states the principle in capitals: *PLACEMENT IS NOT IDENTITY*. The FILE half never consumed that
15
+ * answer. Every file-scoped guard judged `ctx.workspaceRoot`, which is the walk-up from the SESSION's
16
+ * cwd to whichever `webpieces.config.json` governs it — the PRIMARY clone, for a main-session edit into
17
+ * an agent worktree. Because Claude Code checks a worktree out INSIDE the repo at
18
+ * `<repo>/.claude/worktrees/agent-XXXX`, that walk-up answers "primary" for a path that is manifestly
19
+ * not the primary's, and nothing downstream re-examined it.
20
+ *
21
+ * Measured live on 2026-09-03: an Edit of one import line inside a worktree on a CLEAN branch was
22
+ * refused, citing `pnpm-lock.yaml` and `pnpm-workspace.yaml` — the PRIMARY clone's branch's conflict
23
+ * files. The main-sync cache is already keyed by branch and already held the correct, non-conflicting
24
+ * entry for the worktree's branch, one key over. Only the lookup key was wrong. So this class exists to
25
+ * make the lookup ask the same authority the bash half asks, rather than to introduce a second one.
26
+ *
27
+ * IT ASKS EffectiveTreeResolver, and adds NOTHING of its own beyond finding a directory to ask about.
28
+ * That is deliberate and it is the whole design: two resolvers WILL disagree about which tree a path is
29
+ * in, and this module exists precisely because two layers already did. There is no `.claude/worktrees`
30
+ * string match here and there must never be one — matching on placement is the bug, not the fix.
31
+ *
32
+ * The only thing worth naming is the walk-up: a Write legitimately names a file, and directories, that
33
+ * do not exist yet, and git cannot be asked about a directory that is not there. So we climb to the
34
+ * nearest EXISTING ancestor and ask about that. A parent directory is in the same worktree as the child
35
+ * it is about to contain — that is a property of the filesystem, not a guess.
36
+ */
37
+ class TargetTreeResolver {
38
+ trees;
39
+ constructor(trees = new effective_tree_1.EffectiveTreeResolver()) {
40
+ this.trees = trees;
41
+ }
42
+ /**
43
+ * The tree that owns `targetPath`, classified exactly as a Bash command's cwd would be.
44
+ *
45
+ * `governedRoot` is the fallback for every case with no better answer — an empty path, a path with
46
+ * no existing ancestor at all — and is also what `EffectiveTree.governedRoot` keeps meaning: whose
47
+ * config and excludePaths apply. It is NOT the tree to judge; `EffectiveTree.root` is.
48
+ */
49
+ resolve(targetPath, governedRoot) {
50
+ // REAL paths on BOTH sides, or git's answers cannot be compared with each other. Identity here
51
+ // is `commonDir(target) === commonDir(governed)`, and git prints an ABSOLUTE, symlink-resolved
52
+ // path from a linked worktree while printing a bare relative `.git` from the primary clone. So
53
+ // on any repo reached through a symlink — `/var/folders/...` on macOS, which is `/private/var`,
54
+ // and every `os.tmpdir()` path under it — the two strings differ for the same `.git` and the
55
+ // repo's OWN worktree classifies as `foreign`, i.e. every guard silently off. Resolving both
56
+ // sides first is what makes the comparison an answer about git rather than about spelling.
57
+ const governed = this.realDir(governedRoot);
58
+ return this.trees.resolve('', this.nearestExistingDir(targetPath, governed), governed);
59
+ }
60
+ /**
61
+ * The two relative spellings of one target path, resolved together so a caller cannot accidentally
62
+ * use the wrong one. See GovernedPath for why there are two.
63
+ */
64
+ governedPath(targetPath, governedRoot) {
65
+ const absolute = this.realAbsolute(targetPath);
66
+ const tree = this.resolve(targetPath, governedRoot);
67
+ // `tree.root` and `tree.governedRoot` are already resolved by resolve() above, so both
68
+ // subtractions are real-against-real. Mixing one resolved side with one unresolved one produces
69
+ // a `../../../..` climb out of the filesystem — which reads as "outside the workspace" and
70
+ // would quietly hand every path the wrong verdict on a symlinked checkout.
71
+ return new GovernedPath(path.relative(tree.governedRoot, absolute), path.relative(tree.root, absolute), tree.root);
72
+ }
73
+ // The nearest ancestor directory of `targetPath` that EXISTS — the deepest directory git can be
74
+ // asked about — with symlinks resolved. Bounded by the filesystem root (path.dirname is its own
75
+ // fixed point at `/`), so the loop terminates without a counter.
76
+ nearestExistingDir(targetPath, governedRoot) {
77
+ if (targetPath === '')
78
+ return governedRoot;
79
+ let dir = path.dirname(path.resolve(targetPath));
80
+ for (;;) {
81
+ if (fs.existsSync(dir))
82
+ return fs.realpathSync(dir);
83
+ const parent = path.dirname(dir);
84
+ if (parent === dir)
85
+ return governedRoot;
86
+ dir = parent;
87
+ }
88
+ }
89
+ /**
90
+ * `targetPath` made absolute AND symlink-resolved, including when it does not exist yet.
91
+ *
92
+ * `fs.realpathSync` throws on a missing path, and a Write's target is missing by definition, so the
93
+ * resolvable HEAD of the path (its nearest existing ancestor) is resolved and the not-yet-existing
94
+ * TAIL is re-appended verbatim. That keeps it comparable with `tree.root`, which is git's answer and
95
+ * always real.
96
+ */
97
+ realAbsolute(targetPath) {
98
+ const absolute = path.resolve(targetPath);
99
+ const tail = [];
100
+ let dir = absolute;
101
+ for (;;) {
102
+ if (fs.existsSync(dir))
103
+ return path.join(fs.realpathSync(dir), ...tail);
104
+ const parent = path.dirname(dir);
105
+ if (parent === dir)
106
+ return absolute;
107
+ tail.unshift(path.basename(dir));
108
+ dir = parent;
109
+ }
110
+ }
111
+ // A directory made real when it is there, and merely absolute when it is not.
112
+ realDir(dir) {
113
+ return fs.existsSync(dir) ? fs.realpathSync(dir) : path.resolve(dir);
114
+ }
115
+ }
116
+ exports.TargetTreeResolver = TargetTreeResolver;
117
+ /**
118
+ * One target path, in the TWO relative spellings the hook path needs — data-only, per CLAUDE.md.
119
+ *
120
+ * They are different questions and conflating them is issue #851's secondary defect:
121
+ *
122
+ * `relativePath` — relative to the GOVERNED root. What `excludePaths`' globs are written against,
123
+ * and what the violation report prints.
124
+ * `treeRelativePath` — relative to the tree that OWNS the file. What `isWebpiecesStateDir` must be
125
+ * asked about, because `.webpieces/` is the tooling's state dir *of a tree*, and
126
+ * a worktree has its own. Governed-root-relative, a reviewer subagent's
127
+ * `<primary>/.claude/worktrees/agent-X/.webpieces/pr-review/<branch>/summary.json`
128
+ * reads as `.claude/...` and was NOT exempt — while
129
+ * `<primary>/.webpieces/worktrees/agent-X/pr-review/.../review-1.json`, the same
130
+ * kind of file one directory over, was. `wp-review-upsert-pr` REQUIRES that file
131
+ * before `wp-finish-upsert-pr` will open a PR, so the guard could forbid a file
132
+ * the gate demands.
133
+ *
134
+ * In the primary clone the two strings are identical, which is why the gap was invisible for so long.
135
+ */
136
+ class GovernedPath {
137
+ relativePath;
138
+ treeRelativePath;
139
+ /** The git tree that owns the target path; policy still belongs to the separately loaded governed root. */
140
+ treeRoot;
141
+ constructor(relativePath, treeRelativePath, treeRoot) {
142
+ this.relativePath = relativePath;
143
+ this.treeRelativePath = treeRelativePath;
144
+ this.treeRoot = treeRoot;
145
+ }
146
+ }
147
+ exports.GovernedPath = GovernedPath;
148
+ //# sourceMappingURL=target-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target-tree.js","sourceRoot":"","sources":["../../../../../../packages/tooling/hook-runtime/src/core/target-tree.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { EffectiveTree, EffectiveTreeResolver } from './effective-tree';\n\n/**\n * WHICH TREE owns the FILE a Write/Edit/Read is aimed at? The file-path counterpart to\n * EffectiveTreeResolver, which answers the same question for a Bash command's effective cwd.\n *\n * WHY IT HAS TO EXIST (issue #851). The bash half of this question has been answered by git since\n * effective-tree.ts was written — `isLinkedWorktree` is `--git-dir !== --git-common-dir`, and its header\n * states the principle in capitals: *PLACEMENT IS NOT IDENTITY*. The FILE half never consumed that\n * answer. Every file-scoped guard judged `ctx.workspaceRoot`, which is the walk-up from the SESSION's\n * cwd to whichever `webpieces.config.json` governs it — the PRIMARY clone, for a main-session edit into\n * an agent worktree. Because Claude Code checks a worktree out INSIDE the repo at\n * `<repo>/.claude/worktrees/agent-XXXX`, that walk-up answers \"primary\" for a path that is manifestly\n * not the primary's, and nothing downstream re-examined it.\n *\n * Measured live on 2026-09-03: an Edit of one import line inside a worktree on a CLEAN branch was\n * refused, citing `pnpm-lock.yaml` and `pnpm-workspace.yaml` — the PRIMARY clone's branch's conflict\n * files. The main-sync cache is already keyed by branch and already held the correct, non-conflicting\n * entry for the worktree's branch, one key over. Only the lookup key was wrong. So this class exists to\n * make the lookup ask the same authority the bash half asks, rather than to introduce a second one.\n *\n * IT ASKS EffectiveTreeResolver, and adds NOTHING of its own beyond finding a directory to ask about.\n * That is deliberate and it is the whole design: two resolvers WILL disagree about which tree a path is\n * in, and this module exists precisely because two layers already did. There is no `.claude/worktrees`\n * string match here and there must never be one — matching on placement is the bug, not the fix.\n *\n * The only thing worth naming is the walk-up: a Write legitimately names a file, and directories, that\n * do not exist yet, and git cannot be asked about a directory that is not there. So we climb to the\n * nearest EXISTING ancestor and ask about that. A parent directory is in the same worktree as the child\n * it is about to contain — that is a property of the filesystem, not a guess.\n */\nexport class TargetTreeResolver {\n constructor(private readonly trees: EffectiveTreeResolver = new EffectiveTreeResolver()) {}\n\n /**\n * The tree that owns `targetPath`, classified exactly as a Bash command's cwd would be.\n *\n * `governedRoot` is the fallback for every case with no better answer — an empty path, a path with\n * no existing ancestor at all — and is also what `EffectiveTree.governedRoot` keeps meaning: whose\n * config and excludePaths apply. It is NOT the tree to judge; `EffectiveTree.root` is.\n */\n resolve(targetPath: string, governedRoot: string): EffectiveTree {\n // REAL paths on BOTH sides, or git's answers cannot be compared with each other. Identity here\n // is `commonDir(target) === commonDir(governed)`, and git prints an ABSOLUTE, symlink-resolved\n // path from a linked worktree while printing a bare relative `.git` from the primary clone. So\n // on any repo reached through a symlink — `/var/folders/...` on macOS, which is `/private/var`,\n // and every `os.tmpdir()` path under it — the two strings differ for the same `.git` and the\n // repo's OWN worktree classifies as `foreign`, i.e. every guard silently off. Resolving both\n // sides first is what makes the comparison an answer about git rather than about spelling.\n const governed = this.realDir(governedRoot);\n return this.trees.resolve('', this.nearestExistingDir(targetPath, governed), governed);\n }\n\n /**\n * The two relative spellings of one target path, resolved together so a caller cannot accidentally\n * use the wrong one. See GovernedPath for why there are two.\n */\n governedPath(targetPath: string, governedRoot: string): GovernedPath {\n const absolute = this.realAbsolute(targetPath);\n const tree = this.resolve(targetPath, governedRoot);\n // `tree.root` and `tree.governedRoot` are already resolved by resolve() above, so both\n // subtractions are real-against-real. Mixing one resolved side with one unresolved one produces\n // a `../../../..` climb out of the filesystem — which reads as \"outside the workspace\" and\n // would quietly hand every path the wrong verdict on a symlinked checkout.\n return new GovernedPath(\n path.relative(tree.governedRoot, absolute),\n path.relative(tree.root, absolute),\n tree.root,\n );\n }\n\n // The nearest ancestor directory of `targetPath` that EXISTS — the deepest directory git can be\n // asked about — with symlinks resolved. Bounded by the filesystem root (path.dirname is its own\n // fixed point at `/`), so the loop terminates without a counter.\n private nearestExistingDir(targetPath: string, governedRoot: string): string {\n if (targetPath === '') return governedRoot;\n let dir = path.dirname(path.resolve(targetPath));\n for (;;) {\n if (fs.existsSync(dir)) return fs.realpathSync(dir);\n const parent = path.dirname(dir);\n if (parent === dir) return governedRoot;\n dir = parent;\n }\n }\n\n /**\n * `targetPath` made absolute AND symlink-resolved, including when it does not exist yet.\n *\n * `fs.realpathSync` throws on a missing path, and a Write's target is missing by definition, so the\n * resolvable HEAD of the path (its nearest existing ancestor) is resolved and the not-yet-existing\n * TAIL is re-appended verbatim. That keeps it comparable with `tree.root`, which is git's answer and\n * always real.\n */\n private realAbsolute(targetPath: string): string {\n const absolute = path.resolve(targetPath);\n const tail: string[] = [];\n let dir = absolute;\n for (;;) {\n if (fs.existsSync(dir)) return path.join(fs.realpathSync(dir), ...tail);\n const parent = path.dirname(dir);\n if (parent === dir) return absolute;\n tail.unshift(path.basename(dir));\n dir = parent;\n }\n }\n\n // A directory made real when it is there, and merely absolute when it is not.\n private realDir(dir: string): string {\n return fs.existsSync(dir) ? fs.realpathSync(dir) : path.resolve(dir);\n }\n}\n\n/**\n * One target path, in the TWO relative spellings the hook path needs — data-only, per CLAUDE.md.\n *\n * They are different questions and conflating them is issue #851's secondary defect:\n *\n * `relativePath` — relative to the GOVERNED root. What `excludePaths`' globs are written against,\n * and what the violation report prints.\n * `treeRelativePath` — relative to the tree that OWNS the file. What `isWebpiecesStateDir` must be\n * asked about, because `.webpieces/` is the tooling's state dir *of a tree*, and\n * a worktree has its own. Governed-root-relative, a reviewer subagent's\n * `<primary>/.claude/worktrees/agent-X/.webpieces/pr-review/<branch>/summary.json`\n * reads as `.claude/...` and was NOT exempt — while\n * `<primary>/.webpieces/worktrees/agent-X/pr-review/.../review-1.json`, the same\n * kind of file one directory over, was. `wp-review-upsert-pr` REQUIRES that file\n * before `wp-finish-upsert-pr` will open a PR, so the guard could forbid a file\n * the gate demands.\n *\n * In the primary clone the two strings are identical, which is why the gap was invisible for so long.\n */\nexport class GovernedPath {\n readonly relativePath: string;\n readonly treeRelativePath: string;\n /** The git tree that owns the target path; policy still belongs to the separately loaded governed root. */\n readonly treeRoot: string;\n\n constructor(relativePath: string, treeRelativePath: string, treeRoot: string) {\n this.relativePath = relativePath;\n this.treeRelativePath = treeRelativePath;\n this.treeRoot = treeRoot;\n }\n}\n"]}
@@ -0,0 +1,149 @@
1
+ import { RuleOptions } from '@webpieces/rules-config';
2
+ import { ToolKind } from '../protocol';
3
+ import { FixHint } from './fix-hint';
4
+ export type RuleScope = 'edit' | 'file' | 'bash';
5
+ export type IsLineDisabled = (lineNum: number, ruleName: string) => boolean;
6
+ export declare class Violation {
7
+ readonly line: number;
8
+ readonly snippet: string;
9
+ readonly message: string | undefined;
10
+ editIndex: number | undefined;
11
+ editCount: number | undefined;
12
+ constructor(line: number, snippet: string, message?: string);
13
+ }
14
+ export declare class EditContext {
15
+ readonly tool: ToolKind;
16
+ readonly editIndex: number;
17
+ readonly editCount: number;
18
+ readonly filePath: string;
19
+ readonly relativePath: string;
20
+ readonly workspaceRoot: string;
21
+ readonly addedContent: string;
22
+ readonly strippedContent: string;
23
+ readonly lines: readonly string[];
24
+ readonly strippedLines: readonly string[];
25
+ readonly removedContent: string;
26
+ readonly isLineDisabled: IsLineDisabled;
27
+ options: RuleOptions;
28
+ constructor(tool: ToolKind, editIndex: number, editCount: number, filePath: string, relativePath: string, workspaceRoot: string, addedContent: string, strippedContent: string, lines: readonly string[], strippedLines: readonly string[], removedContent: string, isLineDisabled: IsLineDisabled);
29
+ }
30
+ export declare class BashContext {
31
+ readonly tool: 'Bash';
32
+ readonly command: string;
33
+ /**
34
+ * `command` with heredoc bodies and prose-in-quotes removed — what a guard should MATCH on, while
35
+ * `command` stays raw for the violation message (a human needs to see what they actually typed).
36
+ *
37
+ * A commit message that merely MENTIONS a blocked command is not that command. This repo's whole
38
+ * subject matter is the git workflow, so its commit messages, docs and the guards' own fix-hint
39
+ * text are full of `git push` / `gh pr …` strings; matching raw text blocks writing about the
40
+ * tooling. A guard that cries wolf is a guard someone eventually turns OFF, which costs far more
41
+ * than the theoretical bypass this opens (`sh -c '<blocked command>'` stops matching). That trade
42
+ * is deliberate: these guards exist to catch a FORGETFUL agent, not an adversarial one.
43
+ *
44
+ * ONLY for blocklist-shaped guards ("if the command matches X, block"), where stripping can only
45
+ * ever block LESS. Never use it in an allowlist-shaped guard such as merged-branch-bash-guard,
46
+ * where stripping could turn a permitted recovery command into a blocked one and deadlock the
47
+ * session — there, match on the raw `command`.
48
+ */
49
+ readonly commandCode: string;
50
+ /**
51
+ * The tree this command is JUDGED against — the root of the worktree it actually acts on, which
52
+ * is NOT the shell's cwd whenever the command carries a leading `cd <path> &&` (see
53
+ * EffectiveTreeResolver). Guards read git state, the main-sync cache and the decision log from
54
+ * here, so a command aimed at a linked worktree is judged on that worktree's branch, never the
55
+ * primary clone's.
56
+ */
57
+ readonly workspaceRoot: string;
58
+ /** The directory the command really runs in (post-`cd`). Relative paths resolve against THIS. */
59
+ readonly effectiveCwd: string;
60
+ /** The root owning webpieces.config.json. Equals workspaceRoot except in a linked worktree. */
61
+ readonly governedRoot: string;
62
+ options: RuleOptions;
63
+ constructor(command: string, workspaceRoot: string, effectiveCwd?: string, governedRoot?: string);
64
+ /**
65
+ * A quoted span WITHOUT whitespace is kept: `git checkout "main"` is a real command with a quoted
66
+ * argument, not prose. One containing whitespace is a sentence, and becomes a single space.
67
+ *
68
+ * ONE exception, and it is a false positive that actually fired: a whitespace-free span carrying
69
+ * SHELL METACHARACTERS is dropped too. Keeping the content strips only the quotes, so a jq filter —
70
+ * `--jq '[.[]|select(.title|test("x"))]|length'` — was handed to the guards as bare shell syntax.
71
+ * Every guard then split it on its `|`, `(` and `;` and saw a segment reading exactly `test`, which
72
+ * whole-repo-build-guard classified as the workspace-wide test script and BLOCKED. There was no
73
+ * build anywhere in that command; it polled npm and GitHub.
74
+ *
75
+ * Quoting is precisely how a shell says "this is DATA, not syntax", so honouring the quotes is the
76
+ * correct reading. And because commandCode only ever feeds blocklist-shaped guards, dropping more
77
+ * can only ever block LESS — never turn an allowed command into a refused one.
78
+ */
79
+ private stripProse;
80
+ }
81
+ export declare class FileContext {
82
+ readonly tool: ToolKind;
83
+ readonly filePath: string;
84
+ readonly relativePath: string;
85
+ readonly workspaceRoot: string;
86
+ /** The git tree that owns filePath. workspaceRoot remains the root that owns policy/config. */
87
+ readonly targetRoot: string;
88
+ /** filePath relative to targetRoot, independent of its governed-root spelling. */
89
+ readonly targetRelativePath: string;
90
+ readonly currentFileLines: number;
91
+ readonly linesAdded: number;
92
+ readonly linesRemoved: number;
93
+ readonly projectedFileLines: number;
94
+ options: RuleOptions;
95
+ constructor(tool: ToolKind, filePath: string, relativePath: string, workspaceRoot: string, currentFileLines: number, linesAdded: number, linesRemoved: number, projectedFileLines: number, targetRoot?: string, targetRelativePath?: string);
96
+ }
97
+ /**
98
+ * The shape of a custom rule loaded from a `rulesDir` (a plain object returned by require()).
99
+ * It carries only metadata + a `check` method — no on/off logic of its own.
100
+ */
101
+ export interface PlainRule {
102
+ readonly name: string;
103
+ readonly description: string;
104
+ readonly scope: RuleScope;
105
+ readonly files: readonly string[];
106
+ readonly defaultOptions: RuleOptions;
107
+ readonly fixHint: FixHint;
108
+ check(ctx: EditContext | FileContext | BashContext): readonly Violation[];
109
+ }
110
+ /**
111
+ * The runtime contract the runner iterates. Both the built-in rule classes (which extend
112
+ * EditRuleBase/FileRuleBase/BashRuleBase) and the custom-rule adapter satisfy this: they add
113
+ * `shouldRun()` (mode + escape-hatch decision) on top of the PlainRule metadata.
114
+ */
115
+ export interface Rule extends PlainRule {
116
+ /**
117
+ * The webpieces.config.json key whose entry configures this rule — NOT necessarily `name`.
118
+ *
119
+ * `name` is the operator identity (what a decision-log line and a deny report carry); `configKey`
120
+ * is the switch. They differ wherever several classes implement one POLICY: all four branch-state
121
+ * guards read `branch-state-guard`, all four PR-lifecycle guards read `pr-lifecycle-guard`. Every
122
+ * consumer that asks "which section does this live in?" (filterByMode) or "does this have an
123
+ * entry?" (the fault-Y config-sync check) must read THIS field, because asking for `name` there
124
+ * demands entries under keys the validator rejects as retired.
125
+ */
126
+ readonly configKey: string;
127
+ shouldRun(): boolean;
128
+ }
129
+ export declare class RuleGroup {
130
+ readonly ruleName: string;
131
+ readonly ruleDescription: string;
132
+ readonly fixHint: FixHint;
133
+ readonly violations: readonly Violation[];
134
+ constructor(ruleName: string, ruleDescription: string, fixHint: FixHint, violations: readonly Violation[]);
135
+ }
136
+ export declare class BlockedResult {
137
+ readonly report: string;
138
+ /**
139
+ * The L0 fault this block IS, in the codebook's own letter (see core/l0-fault-codes.ts), or `-` for
140
+ * an ordinary rule block.
141
+ *
142
+ * It rides on the result rather than being re-derived from the report text because the block is
143
+ * decided deep in the runner (fault C in configMissingBlock, fault Y in checkConfigSync) and
144
+ * STAMPED by the adapter at the terminal boundary, several frames up. Scraping the report for a
145
+ * fault would be a second answer to a question the producer already knows.
146
+ */
147
+ readonly fault: string;
148
+ constructor(report: string, fault?: string);
149
+ }