@webpieces/rules-config 0.4.709 → 0.4.711

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.
@@ -0,0 +1,152 @@
1
+ /**
2
+ * THE ONE PLACE THAT KNOWS ANYTHING ABOUT CLAUDE CODE'S ON-DISK STATE.
3
+ *
4
+ * ─── THE RULE THIS CLASS EXISTS UNDER ────────────────────────────────────────────────────────────
5
+ *
6
+ * HARNESS STATE MAY ONLY VETO A REAP. IT MAY NEVER LICENSE ONE.
7
+ *
8
+ * Read that before reading anything else here, because every answer below is shaped by it. The
9
+ * licence to remove a worktree comes SOLELY from evidence wp-cleanup already computes and already
10
+ * gets right: a merged PR, a snapshot of a ref that still holds the work, a ref identical to
11
+ * origin/main, plus a clean `git status --porcelain`. This class answers ONE question — "is somebody
12
+ * plausibly still in there?" — and its only power is to STOP a reap that the branch evidence had
13
+ * already authorised. No answer it can give turns a spared worktree into a reaped one.
14
+ *
15
+ * ─── WHY IT EXISTS ───────────────────────────────────────────────────────────────────────────────
16
+ * `git worktree lock` reasons written by the Claude Code harness carry a pid, and wp-cleanup used to
17
+ * ask the kernel whether that pid was alive. It always is. Subagents are NOT separate OS processes —
18
+ * every agent in a session records the SAME pid, the session process — so "is that pid running?"
19
+ * reduced to "is the editor still open?", which is true by construction for the whole life of a
20
+ * session. Observed in monorepo-nx2: nine worktrees against a cap of five, eight of them reported as
21
+ * "that agent is working in here" with one identical pid, exactly ONE agent actually running, and
22
+ * several of the others' PRs already merged.
23
+ *
24
+ * So a veto has to come from somewhere that knows about AGENTS rather than processes. The harness
25
+ * does, and it writes it down:
26
+ *
27
+ * <config>/projects/<project-slug>/<sessionId>/subagents/agent-<id>.meta.json
28
+ * <config>/projects/<project-slug>/<sessionId>/subagents/agent-<id>.jsonl
29
+ *
30
+ * `<config>` is `$CLAUDE_CONFIG_DIR` when set, else `~/.claude`. `agent-<id>` is ONE token, and it is
31
+ * the same token in three places: the stem of those two filenames, the basename of the `worktreePath`
32
+ * that meta records, and the name in the lock reason `claude agent agent-<id> (pid N …)`. So worktree
33
+ * → agent state is an exact lookup, not a heuristic — and nothing here re-derives or re-prefixes that
34
+ * token, because a filename built from half of it is how a lookup quietly becomes a miss.
35
+ *
36
+ * ─── THE SIGNAL: THE SHAPE OF THE LAST RECORD IN THE AGENT'S OWN TRANSCRIPT ──────────────────────
37
+ * An agent's loop pauses when the model emits text and asks for no tool. That is the definition of
38
+ * the loop, not a correlation with one:
39
+ *
40
+ * last record is `assistant` with NO `tool_use` block → RETURNED — not currently in a tool loop
41
+ * anything else (a `tool_use` block, a `user`/tool_result record) → MID-LOOP
42
+ *
43
+ * Verified on three agents known to have finished (all `assistant` / `['text']`) and on a live one
44
+ * (`assistant` / `['tool_use']`). `thinking` blocks appear alongside `text` and change nothing: the
45
+ * criterion is the ABSENCE of `tool_use`, never "exactly one block". A `user` record's content can be
46
+ * a plain STRING rather than a list of blocks; that is mid-loop too, and must not throw.
47
+ *
48
+ * ─── "RETURNED" IS NOT "DONE FOREVER", WHICH IS WHY IT CANNOT LICENSE A REAP ──────────────────────
49
+ * Measured: a throwaway agent returned with a text-only record at 17:04:20, and at 17:08:11 a `user`
50
+ * record appeared and it ran again, returning a second time at 17:08:13. The harness RE-INVOKES an
51
+ * agent when a background child it started completes, and when a parent sends it a message. It holds
52
+ * its worktree across that gap. So RETURNED means "not in a tool loop right now" and nothing
53
+ * stronger — which is survivable only because of the rule at the top of this file: the reap was
54
+ * already authorised by a merged PR and a clean tree, and an agent that briefly resumes into that
55
+ * situation has nothing to lose.
56
+ *
57
+ * ─── WHY MTIME IS ONLY A TIEBREAKER ──────────────────────────────────────────────────────────────
58
+ * Freshness of the transcript looks like the obvious liveness signal and is NOT one. Measured: one
59
+ * minute after that throwaway agent finished, its mtime was FRESHER than the genuinely live agent's
60
+ * had been moments earlier. And a live agent writes nothing at all while it sits inside one long Bash
61
+ * call — a `wp-build` is ten minutes of silence — so any threshold safe against that also covers
62
+ * every just-finished agent, which is precisely the population wp-cleanup meets. Mtime alone does not
63
+ * fix this bug; it re-times it. It earns its place on exactly one case: telling a mid-loop transcript
64
+ * that is being written RIGHT NOW (a live agent, veto) from one frozen mid-`tool_use` by a killed
65
+ * session (no veto — that is the original "looks live forever" defect).
66
+ *
67
+ * ─── NEGATIVE RESULT — DO NOT RE-TRY THIS ────────────────────────────────────────────────────────
68
+ * The meta records a `toolUseId`, and a `tool_result` for it in the SPAWNER's transcript looks like an
69
+ * exact completion marker. It is NOT one: a backgrounded Agent call gets its "launched successfully"
70
+ * tool_result IMMEDIATELY, so a running agent already has one, indistinguishable from a finished
71
+ * agent's. Measured on a live agent and rejected.
72
+ *
73
+ * ─── EVERY ANSWER FAILS SAFE ─────────────────────────────────────────────────────────────────────
74
+ * A missing config dir, an unreadable file, a meta describing a different worktree, a truncated last
75
+ * line — all of them are UNKNOWN. UNKNOWN withholds the veto, which is only ever safe because the
76
+ * branch evidence had to authorise the reap first.
77
+ *
78
+ * THIS IS UNDOCUMENTED HARNESS INTERNALS, deliberately quarantined in one file so a layout change
79
+ * breaks exactly here and degrades to "cannot tell" everywhere else.
80
+ */
81
+ /** Mid-loop and being written to right now. The ONE answer that vetoes a reap. */
82
+ export declare const AGENT_ACTIVITY_LIVE = "live";
83
+ /** Its transcript ends with the model returning — not in a tool loop. It may still resume. */
84
+ export declare const AGENT_ACTIVITY_RETURNED = "returned";
85
+ /** No usable evidence, or a mid-loop transcript nobody has touched for a long time. */
86
+ export declare const AGENT_ACTIVITY_UNKNOWN = "unknown";
87
+ /**
88
+ * How long a MID-LOOP transcript may sit untouched before its silence stops vetoing a reap.
89
+ *
90
+ * Consulted for a mid-loop transcript only — a returned one is recognised by shape, whatever its
91
+ * mtime — so this number decides exactly one thing: how long an agent that was KILLED inside a tool
92
+ * call goes on vetoing. Generous on purpose, because a live agent writes NOTHING while it is inside
93
+ * one long Bash call and a `wp-build` or `nx run … :ci` is routinely ten minutes of that. A threshold
94
+ * in seconds would drop the veto on a building agent, which is the one direction that costs work;
95
+ * being late costs a directory the next cleanup takes.
96
+ */
97
+ export declare const AGENT_TRANSCRIPT_QUIET_MS: number;
98
+ export declare class AgentActivity {
99
+ /** One of the AGENT_ACTIVITY_* constants. */
100
+ state: string;
101
+ /** Human-readable evidence, printed verbatim into wp-cleanup's spared/overridden reasons. */
102
+ detail: string;
103
+ constructor(state: string, detail: string);
104
+ }
105
+ export declare class HarnessAgentActivityReader {
106
+ /**
107
+ * What the harness says about `agentId`, cross-checked against the worktree we are judging.
108
+ *
109
+ * `worktreePath` is not decoration: it is what makes this a LOOKUP rather than a guess. If the
110
+ * meta we find records a different worktree we have matched the wrong thing, and the answer is
111
+ * UNKNOWN — never an answer about somebody else's directory.
112
+ */
113
+ activityOf(agentId: string, worktreePath: string, now?: number): AgentActivity;
114
+ /**
115
+ * Did the agent RETURN? True only for an `assistant` record whose content asks for no tool.
116
+ *
117
+ * A `user` record (a tool result coming back, or a message sent to the agent) is mid-loop by
118
+ * definition — and its content may be a plain string, which is why the array check comes first. So
119
+ * is an `assistant` record carrying a `tool_use` EVEN ALONGSIDE TEXT: the model narrating before
120
+ * it calls something is not a return.
121
+ */
122
+ private isReturn;
123
+ /**
124
+ * The agent's own transcript, or the reason there is none — each of the three exits phrased for
125
+ * what actually happened. `exists` is asked BEFORE `readMeta` precisely so "not there" and
126
+ * "there and unreadable" stay distinguishable; folding them together is what produced the flat
127
+ * over-claim this replaced.
128
+ */
129
+ private locate;
130
+ /** Every `<config>/projects/<slug>/<session>/subagents` directory that exists right now. */
131
+ private subagentDirs;
132
+ /**
133
+ * Seam: where the harness keeps its per-project state. `$CLAUDE_CONFIG_DIR` wins when set, which
134
+ * is how the harness itself resolves it; specs override this to point at a fixture tree.
135
+ */
136
+ protected projectsRoot(): string;
137
+ private minutesAgo;
138
+ private exists;
139
+ private readMeta;
140
+ /**
141
+ * The last complete JSONL record of `transcript`, or null.
142
+ *
143
+ * Reads only the tail, because these files grow without bound and only the final line matters. A
144
+ * window that starts mid-record yields an unparseable fragment and therefore null — "cannot tell"
145
+ * — which is exactly what a truncated or half-written last line should also produce.
146
+ */
147
+ private lastRecord;
148
+ private readTail;
149
+ private readDir;
150
+ private isDirectory;
151
+ private mtimeOf;
152
+ }
@@ -0,0 +1,345 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HarnessAgentActivityReader = exports.AgentActivity = exports.AGENT_TRANSCRIPT_QUIET_MS = exports.AGENT_ACTIVITY_UNKNOWN = exports.AGENT_ACTIVITY_RETURNED = exports.AGENT_ACTIVITY_LIVE = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const os = tslib_1.__importStar(require("os"));
7
+ const path = tslib_1.__importStar(require("path"));
8
+ const inversify_1 = require("inversify");
9
+ const to_error_1 = require("./to-error");
10
+ /**
11
+ * THE ONE PLACE THAT KNOWS ANYTHING ABOUT CLAUDE CODE'S ON-DISK STATE.
12
+ *
13
+ * ─── THE RULE THIS CLASS EXISTS UNDER ────────────────────────────────────────────────────────────
14
+ *
15
+ * HARNESS STATE MAY ONLY VETO A REAP. IT MAY NEVER LICENSE ONE.
16
+ *
17
+ * Read that before reading anything else here, because every answer below is shaped by it. The
18
+ * licence to remove a worktree comes SOLELY from evidence wp-cleanup already computes and already
19
+ * gets right: a merged PR, a snapshot of a ref that still holds the work, a ref identical to
20
+ * origin/main, plus a clean `git status --porcelain`. This class answers ONE question — "is somebody
21
+ * plausibly still in there?" — and its only power is to STOP a reap that the branch evidence had
22
+ * already authorised. No answer it can give turns a spared worktree into a reaped one.
23
+ *
24
+ * ─── WHY IT EXISTS ───────────────────────────────────────────────────────────────────────────────
25
+ * `git worktree lock` reasons written by the Claude Code harness carry a pid, and wp-cleanup used to
26
+ * ask the kernel whether that pid was alive. It always is. Subagents are NOT separate OS processes —
27
+ * every agent in a session records the SAME pid, the session process — so "is that pid running?"
28
+ * reduced to "is the editor still open?", which is true by construction for the whole life of a
29
+ * session. Observed in monorepo-nx2: nine worktrees against a cap of five, eight of them reported as
30
+ * "that agent is working in here" with one identical pid, exactly ONE agent actually running, and
31
+ * several of the others' PRs already merged.
32
+ *
33
+ * So a veto has to come from somewhere that knows about AGENTS rather than processes. The harness
34
+ * does, and it writes it down:
35
+ *
36
+ * <config>/projects/<project-slug>/<sessionId>/subagents/agent-<id>.meta.json
37
+ * <config>/projects/<project-slug>/<sessionId>/subagents/agent-<id>.jsonl
38
+ *
39
+ * `<config>` is `$CLAUDE_CONFIG_DIR` when set, else `~/.claude`. `agent-<id>` is ONE token, and it is
40
+ * the same token in three places: the stem of those two filenames, the basename of the `worktreePath`
41
+ * that meta records, and the name in the lock reason `claude agent agent-<id> (pid N …)`. So worktree
42
+ * → agent state is an exact lookup, not a heuristic — and nothing here re-derives or re-prefixes that
43
+ * token, because a filename built from half of it is how a lookup quietly becomes a miss.
44
+ *
45
+ * ─── THE SIGNAL: THE SHAPE OF THE LAST RECORD IN THE AGENT'S OWN TRANSCRIPT ──────────────────────
46
+ * An agent's loop pauses when the model emits text and asks for no tool. That is the definition of
47
+ * the loop, not a correlation with one:
48
+ *
49
+ * last record is `assistant` with NO `tool_use` block → RETURNED — not currently in a tool loop
50
+ * anything else (a `tool_use` block, a `user`/tool_result record) → MID-LOOP
51
+ *
52
+ * Verified on three agents known to have finished (all `assistant` / `['text']`) and on a live one
53
+ * (`assistant` / `['tool_use']`). `thinking` blocks appear alongside `text` and change nothing: the
54
+ * criterion is the ABSENCE of `tool_use`, never "exactly one block". A `user` record's content can be
55
+ * a plain STRING rather than a list of blocks; that is mid-loop too, and must not throw.
56
+ *
57
+ * ─── "RETURNED" IS NOT "DONE FOREVER", WHICH IS WHY IT CANNOT LICENSE A REAP ──────────────────────
58
+ * Measured: a throwaway agent returned with a text-only record at 17:04:20, and at 17:08:11 a `user`
59
+ * record appeared and it ran again, returning a second time at 17:08:13. The harness RE-INVOKES an
60
+ * agent when a background child it started completes, and when a parent sends it a message. It holds
61
+ * its worktree across that gap. So RETURNED means "not in a tool loop right now" and nothing
62
+ * stronger — which is survivable only because of the rule at the top of this file: the reap was
63
+ * already authorised by a merged PR and a clean tree, and an agent that briefly resumes into that
64
+ * situation has nothing to lose.
65
+ *
66
+ * ─── WHY MTIME IS ONLY A TIEBREAKER ──────────────────────────────────────────────────────────────
67
+ * Freshness of the transcript looks like the obvious liveness signal and is NOT one. Measured: one
68
+ * minute after that throwaway agent finished, its mtime was FRESHER than the genuinely live agent's
69
+ * had been moments earlier. And a live agent writes nothing at all while it sits inside one long Bash
70
+ * call — a `wp-build` is ten minutes of silence — so any threshold safe against that also covers
71
+ * every just-finished agent, which is precisely the population wp-cleanup meets. Mtime alone does not
72
+ * fix this bug; it re-times it. It earns its place on exactly one case: telling a mid-loop transcript
73
+ * that is being written RIGHT NOW (a live agent, veto) from one frozen mid-`tool_use` by a killed
74
+ * session (no veto — that is the original "looks live forever" defect).
75
+ *
76
+ * ─── NEGATIVE RESULT — DO NOT RE-TRY THIS ────────────────────────────────────────────────────────
77
+ * The meta records a `toolUseId`, and a `tool_result` for it in the SPAWNER's transcript looks like an
78
+ * exact completion marker. It is NOT one: a backgrounded Agent call gets its "launched successfully"
79
+ * tool_result IMMEDIATELY, so a running agent already has one, indistinguishable from a finished
80
+ * agent's. Measured on a live agent and rejected.
81
+ *
82
+ * ─── EVERY ANSWER FAILS SAFE ─────────────────────────────────────────────────────────────────────
83
+ * A missing config dir, an unreadable file, a meta describing a different worktree, a truncated last
84
+ * line — all of them are UNKNOWN. UNKNOWN withholds the veto, which is only ever safe because the
85
+ * branch evidence had to authorise the reap first.
86
+ *
87
+ * THIS IS UNDOCUMENTED HARNESS INTERNALS, deliberately quarantined in one file so a layout change
88
+ * breaks exactly here and degrades to "cannot tell" everywhere else.
89
+ */
90
+ /** Mid-loop and being written to right now. The ONE answer that vetoes a reap. */
91
+ exports.AGENT_ACTIVITY_LIVE = 'live';
92
+ /** Its transcript ends with the model returning — not in a tool loop. It may still resume. */
93
+ exports.AGENT_ACTIVITY_RETURNED = 'returned';
94
+ /** No usable evidence, or a mid-loop transcript nobody has touched for a long time. */
95
+ exports.AGENT_ACTIVITY_UNKNOWN = 'unknown';
96
+ /**
97
+ * How long a MID-LOOP transcript may sit untouched before its silence stops vetoing a reap.
98
+ *
99
+ * Consulted for a mid-loop transcript only — a returned one is recognised by shape, whatever its
100
+ * mtime — so this number decides exactly one thing: how long an agent that was KILLED inside a tool
101
+ * call goes on vetoing. Generous on purpose, because a live agent writes NOTHING while it is inside
102
+ * one long Bash call and a `wp-build` or `nx run … :ci` is routinely ten minutes of that. A threshold
103
+ * in seconds would drop the veto on a building agent, which is the one direction that costs work;
104
+ * being late costs a directory the next cleanup takes.
105
+ */
106
+ exports.AGENT_TRANSCRIPT_QUIET_MS = 45 * 60 * 1000;
107
+ /**
108
+ * How much of the tail of a transcript is read to find its last record.
109
+ *
110
+ * Transcripts are append-only and reach tens of megabytes; the answer lives in the final line. Large
111
+ * enough that a single fat record (a big tool result) still fits whole — and when it does not, the
112
+ * fragment fails to parse and the answer is UNKNOWN.
113
+ */
114
+ const TRANSCRIPT_TAIL_BYTES = 8 * 1024 * 1024;
115
+ // Data-only (per CLAUDE.md, classes for data). What the harness says about one agent, and why.
116
+ class AgentActivity {
117
+ /** One of the AGENT_ACTIVITY_* constants. */
118
+ state;
119
+ /** Human-readable evidence, printed verbatim into wp-cleanup's spared/overridden reasons. */
120
+ detail;
121
+ constructor(state, detail) {
122
+ this.state = state;
123
+ this.detail = detail;
124
+ }
125
+ }
126
+ exports.AgentActivity = AgentActivity;
127
+ /**
128
+ * Data-only. Where an agent's transcript is — or, when it is nowhere, WHICH of the three genuinely
129
+ * different silences we hit.
130
+ *
131
+ * One flat "no state file for that agent id" covered all three, and two of them were untrue: a meta
132
+ * that exists and cannot be PARSED, and a meta that exists and describes SOMEBODY ELSE'S worktree.
133
+ * That string is printed verbatim into wp-cleanup's reason, so it is a message asserting more than
134
+ * the evidence supports — the exact defect this whole file exists to remove, small and on the safe
135
+ * side but the same shape.
136
+ */
137
+ class AgentStateLookup {
138
+ /** The agent's own transcript, or '' when there is none to read. */
139
+ transcript;
140
+ /** When `transcript` is '': the honest reason, printed to a human as-is. */
141
+ detail;
142
+ constructor(transcript, detail) {
143
+ this.transcript = transcript;
144
+ this.detail = detail;
145
+ }
146
+ }
147
+ let HarnessAgentActivityReader = class HarnessAgentActivityReader {
148
+ /**
149
+ * What the harness says about `agentId`, cross-checked against the worktree we are judging.
150
+ *
151
+ * `worktreePath` is not decoration: it is what makes this a LOOKUP rather than a guess. If the
152
+ * meta we find records a different worktree we have matched the wrong thing, and the answer is
153
+ * UNKNOWN — never an answer about somebody else's directory.
154
+ */
155
+ activityOf(agentId, worktreePath, now = Date.now()) {
156
+ const found = this.locate(agentId, worktreePath);
157
+ if (found.transcript === '')
158
+ return new AgentActivity(exports.AGENT_ACTIVITY_UNKNOWN, found.detail);
159
+ const last = this.lastRecord(found.transcript);
160
+ if (last === null) {
161
+ return new AgentActivity(exports.AGENT_ACTIVITY_UNKNOWN, 'its transcript could not be read to the end');
162
+ }
163
+ if (this.isReturn(last)) {
164
+ return new AgentActivity(exports.AGENT_ACTIVITY_RETURNED, 'its transcript ends with that agent returning its answer, so it is not in a tool call');
165
+ }
166
+ const written = this.mtimeOf(found.transcript);
167
+ if (written > 0 && now - written < exports.AGENT_TRANSCRIPT_QUIET_MS) {
168
+ return new AgentActivity(exports.AGENT_ACTIVITY_LIVE, `its transcript ends mid-tool-call and was written ${this.minutesAgo(now, written)}`);
169
+ }
170
+ return new AgentActivity(exports.AGENT_ACTIVITY_UNKNOWN, 'its transcript ends mid-tool-call but nothing has written to it for a long time, so that '
171
+ + 'agent was more likely killed than working');
172
+ }
173
+ /**
174
+ * Did the agent RETURN? True only for an `assistant` record whose content asks for no tool.
175
+ *
176
+ * A `user` record (a tool result coming back, or a message sent to the agent) is mid-loop by
177
+ * definition — and its content may be a plain string, which is why the array check comes first. So
178
+ * is an `assistant` record carrying a `tool_use` EVEN ALONGSIDE TEXT: the model narrating before
179
+ * it calls something is not a return.
180
+ */
181
+ isReturn(record) {
182
+ if ((record.type ?? '') !== 'assistant')
183
+ return false;
184
+ const content = record.message === undefined ? undefined : record.message.content;
185
+ if (!Array.isArray(content) || content.length === 0)
186
+ return false;
187
+ for (const block of content) {
188
+ if ((block.type ?? '') === 'tool_use')
189
+ return false;
190
+ }
191
+ return true;
192
+ }
193
+ /**
194
+ * The agent's own transcript, or the reason there is none — each of the three exits phrased for
195
+ * what actually happened. `exists` is asked BEFORE `readMeta` precisely so "not there" and
196
+ * "there and unreadable" stay distinguishable; folding them together is what produced the flat
197
+ * over-claim this replaced.
198
+ */
199
+ locate(agentId, worktreePath) {
200
+ for (const subagents of this.subagentDirs()) {
201
+ const metaPath = path.join(subagents, `${agentId}.meta.json`);
202
+ if (!this.exists(metaPath))
203
+ continue;
204
+ const meta = this.readMeta(metaPath);
205
+ if (meta === null) {
206
+ return new AgentStateLookup('', `that agent's harness state file (${metaPath}) could not be read`);
207
+ }
208
+ const recorded = meta.worktreePath ?? '';
209
+ if (recorded !== '' && worktreePath !== '' && path.resolve(recorded) !== path.resolve(worktreePath)) {
210
+ return new AgentStateLookup('', `the harness records that agent against a different worktree (${recorded})`);
211
+ }
212
+ return new AgentStateLookup(path.join(subagents, `${agentId}.jsonl`), '');
213
+ }
214
+ return new AgentStateLookup('', 'the Claude Code harness has no state file for that agent id');
215
+ }
216
+ /** Every `<config>/projects/<slug>/<session>/subagents` directory that exists right now. */
217
+ subagentDirs() {
218
+ const out = [];
219
+ const root = this.projectsRoot();
220
+ for (const project of this.readDir(root)) {
221
+ const projectDir = path.join(root, project);
222
+ for (const session of this.readDir(projectDir)) {
223
+ const subagents = path.join(projectDir, session, 'subagents');
224
+ if (this.isDirectory(subagents))
225
+ out.push(subagents);
226
+ }
227
+ }
228
+ return out;
229
+ }
230
+ /**
231
+ * Seam: where the harness keeps its per-project state. `$CLAUDE_CONFIG_DIR` wins when set, which
232
+ * is how the harness itself resolves it; specs override this to point at a fixture tree.
233
+ */
234
+ projectsRoot() {
235
+ const configured = process.env['CLAUDE_CONFIG_DIR'] ?? '';
236
+ const root = configured !== '' ? configured : path.join(os.homedir(), '.claude');
237
+ return path.join(root, 'projects');
238
+ }
239
+ minutesAgo(now, written) {
240
+ const minutes = Math.max(0, Math.floor((now - written) / 60000));
241
+ return minutes === 1 ? '1 minute ago' : `${String(minutes)} minutes ago`;
242
+ }
243
+ // ── Everything below is a guarded filesystem read: any failure is silence, never a throw. ──
244
+ exists(filePath) {
245
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
246
+ try {
247
+ return fs.existsSync(filePath);
248
+ }
249
+ catch (err) {
250
+ const error = (0, to_error_1.toError)(err);
251
+ void error;
252
+ return false;
253
+ }
254
+ }
255
+ // null means "it is there and this release could not make sense of it" — NOT "it is absent".
256
+ readMeta(metaPath) {
257
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
258
+ try {
259
+ return JSON.parse(fs.readFileSync(metaPath, 'utf8'));
260
+ }
261
+ catch (err) {
262
+ const error = (0, to_error_1.toError)(err);
263
+ void error;
264
+ return null;
265
+ }
266
+ }
267
+ /**
268
+ * The last complete JSONL record of `transcript`, or null.
269
+ *
270
+ * Reads only the tail, because these files grow without bound and only the final line matters. A
271
+ * window that starts mid-record yields an unparseable fragment and therefore null — "cannot tell"
272
+ * — which is exactly what a truncated or half-written last line should also produce.
273
+ */
274
+ lastRecord(transcript) {
275
+ const lines = this.readTail(transcript).split('\n')
276
+ .filter((line) => line.trim() !== '');
277
+ if (lines.length === 0)
278
+ return null;
279
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
280
+ try {
281
+ return JSON.parse(lines[lines.length - 1]);
282
+ }
283
+ catch (err) {
284
+ const error = (0, to_error_1.toError)(err);
285
+ void error;
286
+ return null;
287
+ }
288
+ }
289
+ readTail(filePath) {
290
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
291
+ try {
292
+ const size = fs.statSync(filePath).size;
293
+ const length = Math.min(size, TRANSCRIPT_TAIL_BYTES);
294
+ const buffer = Buffer.alloc(length);
295
+ const fd = fs.openSync(filePath, 'r');
296
+ fs.readSync(fd, buffer, 0, length, size - length);
297
+ fs.closeSync(fd);
298
+ return buffer.toString('utf8');
299
+ }
300
+ catch (err) {
301
+ const error = (0, to_error_1.toError)(err);
302
+ void error;
303
+ return '';
304
+ }
305
+ }
306
+ readDir(dirPath) {
307
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
308
+ try {
309
+ return fs.readdirSync(dirPath);
310
+ }
311
+ catch (err) {
312
+ const error = (0, to_error_1.toError)(err);
313
+ void error;
314
+ return [];
315
+ }
316
+ }
317
+ isDirectory(dirPath) {
318
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
319
+ try {
320
+ return fs.statSync(dirPath).isDirectory();
321
+ }
322
+ catch (err) {
323
+ const error = (0, to_error_1.toError)(err);
324
+ void error;
325
+ return false;
326
+ }
327
+ }
328
+ // 0 when the file is absent or unreadable — which reads as "no evidence", not "written long ago".
329
+ mtimeOf(filePath) {
330
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
331
+ try {
332
+ return fs.statSync(filePath).mtimeMs;
333
+ }
334
+ catch (err) {
335
+ const error = (0, to_error_1.toError)(err);
336
+ void error;
337
+ return 0;
338
+ }
339
+ }
340
+ };
341
+ exports.HarnessAgentActivityReader = HarnessAgentActivityReader;
342
+ exports.HarnessAgentActivityReader = HarnessAgentActivityReader = tslib_1.__decorate([
343
+ (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton)
344
+ ], HarnessAgentActivityReader);
345
+ //# sourceMappingURL=harness-agent-activity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness-agent-activity.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/harness-agent-activity.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { toError } from './to-error';\n\n/**\n * THE ONE PLACE THAT KNOWS ANYTHING ABOUT CLAUDE CODE'S ON-DISK STATE.\n *\n * ─── THE RULE THIS CLASS EXISTS UNDER ────────────────────────────────────────────────────────────\n *\n * HARNESS STATE MAY ONLY VETO A REAP. IT MAY NEVER LICENSE ONE.\n *\n * Read that before reading anything else here, because every answer below is shaped by it. The\n * licence to remove a worktree comes SOLELY from evidence wp-cleanup already computes and already\n * gets right: a merged PR, a snapshot of a ref that still holds the work, a ref identical to\n * origin/main, plus a clean `git status --porcelain`. This class answers ONE question — \"is somebody\n * plausibly still in there?\" — and its only power is to STOP a reap that the branch evidence had\n * already authorised. No answer it can give turns a spared worktree into a reaped one.\n *\n * ─── WHY IT EXISTS ───────────────────────────────────────────────────────────────────────────────\n * `git worktree lock` reasons written by the Claude Code harness carry a pid, and wp-cleanup used to\n * ask the kernel whether that pid was alive. It always is. Subagents are NOT separate OS processes —\n * every agent in a session records the SAME pid, the session process — so \"is that pid running?\"\n * reduced to \"is the editor still open?\", which is true by construction for the whole life of a\n * session. Observed in monorepo-nx2: nine worktrees against a cap of five, eight of them reported as\n * \"that agent is working in here\" with one identical pid, exactly ONE agent actually running, and\n * several of the others' PRs already merged.\n *\n * So a veto has to come from somewhere that knows about AGENTS rather than processes. The harness\n * does, and it writes it down:\n *\n * <config>/projects/<project-slug>/<sessionId>/subagents/agent-<id>.meta.json\n * <config>/projects/<project-slug>/<sessionId>/subagents/agent-<id>.jsonl\n *\n * `<config>` is `$CLAUDE_CONFIG_DIR` when set, else `~/.claude`. `agent-<id>` is ONE token, and it is\n * the same token in three places: the stem of those two filenames, the basename of the `worktreePath`\n * that meta records, and the name in the lock reason `claude agent agent-<id> (pid N …)`. So worktree\n * → agent state is an exact lookup, not a heuristic — and nothing here re-derives or re-prefixes that\n * token, because a filename built from half of it is how a lookup quietly becomes a miss.\n *\n * ─── THE SIGNAL: THE SHAPE OF THE LAST RECORD IN THE AGENT'S OWN TRANSCRIPT ──────────────────────\n * An agent's loop pauses when the model emits text and asks for no tool. That is the definition of\n * the loop, not a correlation with one:\n *\n * last record is `assistant` with NO `tool_use` block → RETURNED — not currently in a tool loop\n * anything else (a `tool_use` block, a `user`/tool_result record) → MID-LOOP\n *\n * Verified on three agents known to have finished (all `assistant` / `['text']`) and on a live one\n * (`assistant` / `['tool_use']`). `thinking` blocks appear alongside `text` and change nothing: the\n * criterion is the ABSENCE of `tool_use`, never \"exactly one block\". A `user` record's content can be\n * a plain STRING rather than a list of blocks; that is mid-loop too, and must not throw.\n *\n * ─── \"RETURNED\" IS NOT \"DONE FOREVER\", WHICH IS WHY IT CANNOT LICENSE A REAP ──────────────────────\n * Measured: a throwaway agent returned with a text-only record at 17:04:20, and at 17:08:11 a `user`\n * record appeared and it ran again, returning a second time at 17:08:13. The harness RE-INVOKES an\n * agent when a background child it started completes, and when a parent sends it a message. It holds\n * its worktree across that gap. So RETURNED means \"not in a tool loop right now\" and nothing\n * stronger — which is survivable only because of the rule at the top of this file: the reap was\n * already authorised by a merged PR and a clean tree, and an agent that briefly resumes into that\n * situation has nothing to lose.\n *\n * ─── WHY MTIME IS ONLY A TIEBREAKER ──────────────────────────────────────────────────────────────\n * Freshness of the transcript looks like the obvious liveness signal and is NOT one. Measured: one\n * minute after that throwaway agent finished, its mtime was FRESHER than the genuinely live agent's\n * had been moments earlier. And a live agent writes nothing at all while it sits inside one long Bash\n * call — a `wp-build` is ten minutes of silence — so any threshold safe against that also covers\n * every just-finished agent, which is precisely the population wp-cleanup meets. Mtime alone does not\n * fix this bug; it re-times it. It earns its place on exactly one case: telling a mid-loop transcript\n * that is being written RIGHT NOW (a live agent, veto) from one frozen mid-`tool_use` by a killed\n * session (no veto — that is the original \"looks live forever\" defect).\n *\n * ─── NEGATIVE RESULT — DO NOT RE-TRY THIS ────────────────────────────────────────────────────────\n * The meta records a `toolUseId`, and a `tool_result` for it in the SPAWNER's transcript looks like an\n * exact completion marker. It is NOT one: a backgrounded Agent call gets its \"launched successfully\"\n * tool_result IMMEDIATELY, so a running agent already has one, indistinguishable from a finished\n * agent's. Measured on a live agent and rejected.\n *\n * ─── EVERY ANSWER FAILS SAFE ─────────────────────────────────────────────────────────────────────\n * A missing config dir, an unreadable file, a meta describing a different worktree, a truncated last\n * line — all of them are UNKNOWN. UNKNOWN withholds the veto, which is only ever safe because the\n * branch evidence had to authorise the reap first.\n *\n * THIS IS UNDOCUMENTED HARNESS INTERNALS, deliberately quarantined in one file so a layout change\n * breaks exactly here and degrades to \"cannot tell\" everywhere else.\n */\n\n/** Mid-loop and being written to right now. The ONE answer that vetoes a reap. */\nexport const AGENT_ACTIVITY_LIVE = 'live';\n/** Its transcript ends with the model returning — not in a tool loop. It may still resume. */\nexport const AGENT_ACTIVITY_RETURNED = 'returned';\n/** No usable evidence, or a mid-loop transcript nobody has touched for a long time. */\nexport const AGENT_ACTIVITY_UNKNOWN = 'unknown';\n\n/**\n * How long a MID-LOOP transcript may sit untouched before its silence stops vetoing a reap.\n *\n * Consulted for a mid-loop transcript only — a returned one is recognised by shape, whatever its\n * mtime — so this number decides exactly one thing: how long an agent that was KILLED inside a tool\n * call goes on vetoing. Generous on purpose, because a live agent writes NOTHING while it is inside\n * one long Bash call and a `wp-build` or `nx run … :ci` is routinely ten minutes of that. A threshold\n * in seconds would drop the veto on a building agent, which is the one direction that costs work;\n * being late costs a directory the next cleanup takes.\n */\nexport const AGENT_TRANSCRIPT_QUIET_MS = 45 * 60 * 1000;\n\n/**\n * How much of the tail of a transcript is read to find its last record.\n *\n * Transcripts are append-only and reach tens of megabytes; the answer lives in the final line. Large\n * enough that a single fat record (a big tool result) still fits whole — and when it does not, the\n * fragment fails to parse and the answer is UNKNOWN.\n */\nconst TRANSCRIPT_TAIL_BYTES = 8 * 1024 * 1024;\n\n// Data-only (per CLAUDE.md, classes for data). What the harness says about one agent, and why.\nexport class AgentActivity {\n /** One of the AGENT_ACTIVITY_* constants. */\n state: string;\n /** Human-readable evidence, printed verbatim into wp-cleanup's spared/overridden reasons. */\n detail: string;\n\n constructor(state: string, detail: string) {\n this.state = state;\n this.detail = detail;\n }\n}\n\n/**\n * Data-only. Where an agent's transcript is — or, when it is nowhere, WHICH of the three genuinely\n * different silences we hit.\n *\n * One flat \"no state file for that agent id\" covered all three, and two of them were untrue: a meta\n * that exists and cannot be PARSED, and a meta that exists and describes SOMEBODY ELSE'S worktree.\n * That string is printed verbatim into wp-cleanup's reason, so it is a message asserting more than\n * the evidence supports — the exact defect this whole file exists to remove, small and on the safe\n * side but the same shape.\n */\nclass AgentStateLookup {\n /** The agent's own transcript, or '' when there is none to read. */\n transcript: string;\n /** When `transcript` is '': the honest reason, printed to a human as-is. */\n detail: string;\n\n constructor(transcript: string, detail: string) {\n this.transcript = transcript;\n this.detail = detail;\n }\n}\n\n// Raw JSON shapes for the cast at the parse boundary — the convention merged-branches.ts already uses\n// for files it revives. Every field optional: this is somebody else's format.\ninterface RawAgentMeta {\n worktreePath?: string;\n}\n\ninterface RawContentBlock {\n type?: string;\n}\n\ninterface RawTranscriptMessage {\n // A `user` record's content is sometimes a plain string rather than a list of blocks.\n content?: RawContentBlock[] | string;\n}\n\ninterface RawTranscriptRecord {\n type?: string;\n message?: RawTranscriptMessage;\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class HarnessAgentActivityReader {\n /**\n * What the harness says about `agentId`, cross-checked against the worktree we are judging.\n *\n * `worktreePath` is not decoration: it is what makes this a LOOKUP rather than a guess. If the\n * meta we find records a different worktree we have matched the wrong thing, and the answer is\n * UNKNOWN — never an answer about somebody else's directory.\n */\n activityOf(agentId: string, worktreePath: string, now: number = Date.now()): AgentActivity {\n const found = this.locate(agentId, worktreePath);\n if (found.transcript === '') return new AgentActivity(AGENT_ACTIVITY_UNKNOWN, found.detail);\n const last = this.lastRecord(found.transcript);\n if (last === null) {\n return new AgentActivity(AGENT_ACTIVITY_UNKNOWN, 'its transcript could not be read to the end');\n }\n if (this.isReturn(last)) {\n return new AgentActivity(AGENT_ACTIVITY_RETURNED,\n 'its transcript ends with that agent returning its answer, so it is not in a tool call');\n }\n const written = this.mtimeOf(found.transcript);\n if (written > 0 && now - written < AGENT_TRANSCRIPT_QUIET_MS) {\n return new AgentActivity(AGENT_ACTIVITY_LIVE,\n `its transcript ends mid-tool-call and was written ${this.minutesAgo(now, written)}`);\n }\n return new AgentActivity(AGENT_ACTIVITY_UNKNOWN,\n 'its transcript ends mid-tool-call but nothing has written to it for a long time, so that '\n + 'agent was more likely killed than working');\n }\n\n /**\n * Did the agent RETURN? True only for an `assistant` record whose content asks for no tool.\n *\n * A `user` record (a tool result coming back, or a message sent to the agent) is mid-loop by\n * definition — and its content may be a plain string, which is why the array check comes first. So\n * is an `assistant` record carrying a `tool_use` EVEN ALONGSIDE TEXT: the model narrating before\n * it calls something is not a return.\n */\n private isReturn(record: RawTranscriptRecord): boolean {\n if ((record.type ?? '') !== 'assistant') return false;\n const content = record.message === undefined ? undefined : record.message.content;\n if (!Array.isArray(content) || content.length === 0) return false;\n for (const block of content) {\n if ((block.type ?? '') === 'tool_use') return false;\n }\n return true;\n }\n\n /**\n * The agent's own transcript, or the reason there is none — each of the three exits phrased for\n * what actually happened. `exists` is asked BEFORE `readMeta` precisely so \"not there\" and\n * \"there and unreadable\" stay distinguishable; folding them together is what produced the flat\n * over-claim this replaced.\n */\n private locate(agentId: string, worktreePath: string): AgentStateLookup {\n for (const subagents of this.subagentDirs()) {\n const metaPath = path.join(subagents, `${agentId}.meta.json`);\n if (!this.exists(metaPath)) continue;\n const meta = this.readMeta(metaPath);\n if (meta === null) {\n return new AgentStateLookup('',\n `that agent's harness state file (${metaPath}) could not be read`);\n }\n const recorded = meta.worktreePath ?? '';\n if (recorded !== '' && worktreePath !== '' && path.resolve(recorded) !== path.resolve(worktreePath)) {\n return new AgentStateLookup('',\n `the harness records that agent against a different worktree (${recorded})`);\n }\n return new AgentStateLookup(path.join(subagents, `${agentId}.jsonl`), '');\n }\n return new AgentStateLookup('', 'the Claude Code harness has no state file for that agent id');\n }\n\n /** Every `<config>/projects/<slug>/<session>/subagents` directory that exists right now. */\n private subagentDirs(): string[] {\n const out: string[] = [];\n const root = this.projectsRoot();\n for (const project of this.readDir(root)) {\n const projectDir = path.join(root, project);\n for (const session of this.readDir(projectDir)) {\n const subagents = path.join(projectDir, session, 'subagents');\n if (this.isDirectory(subagents)) out.push(subagents);\n }\n }\n return out;\n }\n\n /**\n * Seam: where the harness keeps its per-project state. `$CLAUDE_CONFIG_DIR` wins when set, which\n * is how the harness itself resolves it; specs override this to point at a fixture tree.\n */\n protected projectsRoot(): string {\n const configured = process.env['CLAUDE_CONFIG_DIR'] ?? '';\n const root = configured !== '' ? configured : path.join(os.homedir(), '.claude');\n return path.join(root, 'projects');\n }\n\n private minutesAgo(now: number, written: number): string {\n const minutes = Math.max(0, Math.floor((now - written) / 60000));\n return minutes === 1 ? '1 minute ago' : `${String(minutes)} minutes ago`;\n }\n\n // ── Everything below is a guarded filesystem read: any failure is silence, never a throw. ──\n\n private exists(filePath: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.existsSync(filePath);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return false;\n }\n }\n\n // null means \"it is there and this release could not make sense of it\" — NOT \"it is absent\".\n private readMeta(metaPath: string): RawAgentMeta | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(fs.readFileSync(metaPath, 'utf8')) as RawAgentMeta;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * The last complete JSONL record of `transcript`, or null.\n *\n * Reads only the tail, because these files grow without bound and only the final line matters. A\n * window that starts mid-record yields an unparseable fragment and therefore null — \"cannot tell\"\n * — which is exactly what a truncated or half-written last line should also produce.\n */\n private lastRecord(transcript: string): RawTranscriptRecord | null {\n const lines = this.readTail(transcript).split('\\n')\n .filter((line: string): boolean => line.trim() !== '');\n if (lines.length === 0) return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(lines[lines.length - 1]) as RawTranscriptRecord;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n private readTail(filePath: string): string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const size = fs.statSync(filePath).size;\n const length = Math.min(size, TRANSCRIPT_TAIL_BYTES);\n const buffer = Buffer.alloc(length);\n const fd = fs.openSync(filePath, 'r');\n fs.readSync(fd, buffer, 0, length, size - length);\n fs.closeSync(fd);\n return buffer.toString('utf8');\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return '';\n }\n }\n\n private readDir(dirPath: string): string[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.readdirSync(dirPath);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n }\n\n private isDirectory(dirPath: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.statSync(dirPath).isDirectory();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return false;\n }\n }\n\n // 0 when the file is absent or unreadable — which reads as \"no evidence\", not \"written long ago\".\n private mtimeOf(filePath: string): number {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.statSync(filePath).mtimeMs;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return 0;\n }\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -75,7 +75,8 @@ export { MergedBranch, DeletableBranch, DeletableWorktree, MergedBranchesCache,
75
75
  export { BranchArchiver, ArchiveResult, ARCHIVE_TAG_PREFIX, BRANCH_RETENTIONS, BRANCH_RETENTION_DELETE, BRANCH_RETENTION_ARCHIVE_TAG, BRANCH_RETENTION_KEEP, } from './branch-archiver';
76
76
  export { Worktree, WorktreeWorkInFlight, WorktreeService, } from './worktrees';
77
77
  export { AgentWorktreeLock, AgentWorktreeLockReader, } from './agent-worktree-lock';
78
- export { WorktreeLockVerdicts } from './worktree-lock-verdicts';
78
+ export { HARNESS_NOT_CONSULTED, LOCK_LIVENESS_UNVERIFIABLE, LockDecision, LockEvidence, WorktreeLockVerdicts, } from './worktree-lock-verdicts';
79
+ export { AGENT_ACTIVITY_LIVE, AGENT_ACTIVITY_RETURNED, AGENT_ACTIVITY_UNKNOWN, AGENT_TRANSCRIPT_QUIET_MS, AgentActivity, HarnessAgentActivityReader, } from './harness-agent-activity';
79
80
  export { ReapedBranch, ReapResult, BranchReaper, } from './branch-reaper';
80
81
  export { ReapedWorktree, WorktreeReapResult, WorktreeReaper, } from './worktree-reaper';
81
82
  export type { MutationVerb, MutationPhase } from './branch-mutation-log';
package/src/index.js CHANGED
@@ -7,7 +7,7 @@ exports.NoSymbolDiTokensConfig = exports.AngularNoDirectApiInResolverConfig = ex
7
7
  exports.defaultLandPrConfig = exports.defaultPrGateConfig = exports.defaultGates = exports.ReviewContextEntry = exports.DEFAULT_DEV_BRANCH = exports.DEFAULT_DEV_BRANCH_NAMESPACE = exports.DevDeployConfig = exports.LandPrConfig = exports.DEFAULT_BUILD_COMMAND = exports.PrGateConfig = exports.GateDefinition = exports.BranchStateGuardConfig = exports.DEFAULT_BANNED_STATE_PATH_PREFIXES = exports.DEFAULT_TEMPLATE_DIRS = exports.NoStatePathsInTemplatesConfig = exports.CLIENT_CREATION_SEVERITIES = exports.NoClientCreationOutsideServerOrClientConfig = exports.VALIDATE_TS_MODES = exports.STRUCTURAL_MODES = exports.ON_OFF_MODES = exports.THROW_CAUSE_MODES = exports.DIRECT_API_RESOLVER_MODES = exports.PRISMA_CONVERTER_MODES = exports.PRISMA_DTOS_MODES = exports.PROJECT_MODES = exports.MODIFIED_CODE_MODES = exports.INLINE_TYPE_MODES = exports.RETURN_TYPE_MODES = exports.FILE_LIMIT_MODES = exports.METHOD_LIMIT_MODES = exports.BaseRuleConfig = exports.ValidateEslintSyncConfig = exports.ValidateVersionsLockedConfig = exports.ValidatePackageJsonConfig = exports.ValidateNoArchitectureCyclesConfig = exports.ValidateArchitectureUnchangedConfig = exports.ValidateTsInSrcConfig = exports.NoJsFilesConfig = exports.DiGraphConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.PrLifecycleGuardConfig = exports.BranchCreationGuardConfig = exports.RoleTagConfig = exports.FrameworkTagConfig = exports.InjectAnnotationNotNeededForConcreteClassConfig = exports.NoFunctionOutsideClassConfig = exports.NoProcessExitOutsideMainConfig = exports.NoCustomCssConfig = void 0;
8
8
  exports.CK_UNAUTHORIZED = exports.CK_BAD_FORMAT = exports.CK_MISSING = exports.CK_FAIL = exports.CK_OVERRIDDEN = exports.CK_WARN = exports.CK_PASS = exports.ChecklistVerdict = exports.ChecklistResult = exports.PrContext = exports.ReviewJson = exports.DEFAULT_RETENTION_DAYS = exports.ProvenanceWriteRequest = exports.OfferedContext = exports.ReviewerPaths = exports.ReviewerTranscript = exports.ReviewProvenance = exports.ReviewProvenanceService = exports.PROVENANCE_SKIPPED = exports.PROVENANCE_MISSING = exports.PROVENANCE_OK = exports.ProvenanceResult = exports.TranscriptScan = exports.ReviewerContext = exports.ReviewerEvidence = exports.SubagentProvenanceService = exports.verifyGateToken = exports.extractGateToken = exports.gateTokenMarker = exports.computeGateToken = exports.GateTokenService = exports.ALL_DIFF_ONE_READ_LINES = exports.READ_TRUNCATION_LINES = exports.ContextEntry = exports.BriefedFile = exports.ReviewerBriefing = exports.ReviewerInstructionsService = exports.ChecklistInstructionsService = exports.ChecklistValidator = exports.formatFileList = exports.normalizeChecklistDoc = exports.toChecklist = exports.ChecklistDefinition = exports.MERGE_MODES = exports.MERGE_MODE_NONE = exports.MERGE_MODE_AUTO = exports.buildDevDeployConfig = exports.buildLandPrConfig = exports.buildPrGateConfig = exports.defaultDevDeployConfig = void 0;
9
9
  exports.CACHE_STALE_AFTER_MS = exports.CacheFreshness = exports.MergedBranchesService = exports.MergedBranchesCache = exports.DeletableWorktree = exports.DeletableBranch = exports.MergedBranch = exports.squashRecoverySteps = exports.stampCleanMainSyncStatus = exports.computeMainSyncStatus = exports.finishedLock = exports.inProcessLock = exports.tryAcquireMainSyncLock = exports.isRefreshInProgress = exports.isLockStale = exports.writeMainSyncLock = exports.readMainSyncLock = exports.computeAllMainSyncStatuses = exports.writeMainSyncStatusFile = exports.writeMainSyncStatus = exports.readMainSyncStatusFile = exports.readMainSyncStatus = exports.mainSyncLockPath = exports.mainSyncStatusPath = exports.DEFAULT_HANG_TIMEOUT_MINUTES = exports.MainSyncStatusService = exports.MainSyncLock = exports.MAIN_SYNC_STATUS_VERSION = exports.PullRequestIndex = exports.MainSyncFileStore = exports.MainSyncStatusFile = exports.MainSyncStatus = exports.DEFAULT_APPROVAL_HOURS = exports.AUTHORIZATIONS_DIR = exports.HumanAuthorizationService = exports.AuthorizedOverrides = exports.AuthorizationCheck = exports.AuthorizationContext = exports.AuthorizationFile = exports.HumanApproval = exports.reviewJsonSchemaHint = exports.reviewJsonPath = exports.prDirFor = exports.ReviewJsonService = exports.ChecklistReviewContext = exports.RequiredChecklist = exports.VERDICT_STATUSES = exports.VERDICT_RED = exports.VERDICT_YELLOW = exports.VERDICT_GREEN = void 0;
10
- exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.logBranchMutation = exports.branchMutationLogPath = exports.BranchMutationLog = exports.BranchMutationEvent = exports.WorktreeReaper = exports.WorktreeReapResult = exports.ReapedWorktree = exports.BranchReaper = exports.ReapResult = exports.ReapedBranch = exports.WorktreeLockVerdicts = exports.AgentWorktreeLockReader = exports.AgentWorktreeLock = exports.WorktreeService = exports.WorktreeWorkInFlight = exports.Worktree = exports.BRANCH_RETENTION_KEEP = exports.BRANCH_RETENTION_ARCHIVE_TAG = exports.BRANCH_RETENTION_DELETE = exports.BRANCH_RETENTIONS = exports.ARCHIVE_TAG_PREFIX = exports.ArchiveResult = exports.BranchArchiver = exports.ADJUDICATED_CLASSIFICATIONS = exports.CLASSIFICATION_DETACHED = exports.CLASSIFICATION_CURRENT = exports.CLASSIFICATION_LOCKED = exports.CLASSIFICATION_PRUNABLE = exports.CLASSIFICATION_IN_USE = exports.CLASSIFICATION_NEVER_PROPOSED = exports.CLASSIFICATION_CONTENT_IN_MAIN = exports.CLASSIFICATION_SUPERSEDED = exports.CLASSIFICATION_NO_COMMITS = exports.CLASSIFICATION_BACKUP_OF_LIVE = exports.CLASSIFICATION_BACKUP_OF_MERGED = exports.CLASSIFICATION_MERGED_PR = void 0;
10
+ exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.logBranchMutation = exports.branchMutationLogPath = exports.BranchMutationLog = exports.BranchMutationEvent = exports.WorktreeReaper = exports.WorktreeReapResult = exports.ReapedWorktree = exports.BranchReaper = exports.ReapResult = exports.ReapedBranch = exports.HarnessAgentActivityReader = exports.AgentActivity = exports.AGENT_TRANSCRIPT_QUIET_MS = exports.AGENT_ACTIVITY_UNKNOWN = exports.AGENT_ACTIVITY_RETURNED = exports.AGENT_ACTIVITY_LIVE = exports.WorktreeLockVerdicts = exports.LockEvidence = exports.LockDecision = exports.LOCK_LIVENESS_UNVERIFIABLE = exports.HARNESS_NOT_CONSULTED = exports.AgentWorktreeLockReader = exports.AgentWorktreeLock = exports.WorktreeService = exports.WorktreeWorkInFlight = exports.Worktree = exports.BRANCH_RETENTION_KEEP = exports.BRANCH_RETENTION_ARCHIVE_TAG = exports.BRANCH_RETENTION_DELETE = exports.BRANCH_RETENTIONS = exports.ARCHIVE_TAG_PREFIX = exports.ArchiveResult = exports.BranchArchiver = exports.ADJUDICATED_CLASSIFICATIONS = exports.CLASSIFICATION_DETACHED = exports.CLASSIFICATION_CURRENT = exports.CLASSIFICATION_LOCKED = exports.CLASSIFICATION_PRUNABLE = exports.CLASSIFICATION_IN_USE = exports.CLASSIFICATION_NEVER_PROPOSED = exports.CLASSIFICATION_CONTENT_IN_MAIN = exports.CLASSIFICATION_SUPERSEDED = exports.CLASSIFICATION_NO_COMMITS = exports.CLASSIFICATION_BACKUP_OF_LIVE = exports.CLASSIFICATION_BACKUP_OF_MERGED = exports.CLASSIFICATION_MERGED_PR = void 0;
11
11
  var types_1 = require("./types");
12
12
  Object.defineProperty(exports, "ResolvedConfig", { enumerable: true, get: function () { return types_1.ResolvedConfig; } });
13
13
  Object.defineProperty(exports, "ResolvedRuleConfig", { enumerable: true, get: function () { return types_1.ResolvedRuleConfig; } });
@@ -498,7 +498,18 @@ var agent_worktree_lock_1 = require("./agent-worktree-lock");
498
498
  Object.defineProperty(exports, "AgentWorktreeLock", { enumerable: true, get: function () { return agent_worktree_lock_1.AgentWorktreeLock; } });
499
499
  Object.defineProperty(exports, "AgentWorktreeLockReader", { enumerable: true, get: function () { return agent_worktree_lock_1.AgentWorktreeLockReader; } });
500
500
  var worktree_lock_verdicts_1 = require("./worktree-lock-verdicts");
501
+ Object.defineProperty(exports, "HARNESS_NOT_CONSULTED", { enumerable: true, get: function () { return worktree_lock_verdicts_1.HARNESS_NOT_CONSULTED; } });
502
+ Object.defineProperty(exports, "LOCK_LIVENESS_UNVERIFIABLE", { enumerable: true, get: function () { return worktree_lock_verdicts_1.LOCK_LIVENESS_UNVERIFIABLE; } });
503
+ Object.defineProperty(exports, "LockDecision", { enumerable: true, get: function () { return worktree_lock_verdicts_1.LockDecision; } });
504
+ Object.defineProperty(exports, "LockEvidence", { enumerable: true, get: function () { return worktree_lock_verdicts_1.LockEvidence; } });
501
505
  Object.defineProperty(exports, "WorktreeLockVerdicts", { enumerable: true, get: function () { return worktree_lock_verdicts_1.WorktreeLockVerdicts; } });
506
+ var harness_agent_activity_1 = require("./harness-agent-activity");
507
+ Object.defineProperty(exports, "AGENT_ACTIVITY_LIVE", { enumerable: true, get: function () { return harness_agent_activity_1.AGENT_ACTIVITY_LIVE; } });
508
+ Object.defineProperty(exports, "AGENT_ACTIVITY_RETURNED", { enumerable: true, get: function () { return harness_agent_activity_1.AGENT_ACTIVITY_RETURNED; } });
509
+ Object.defineProperty(exports, "AGENT_ACTIVITY_UNKNOWN", { enumerable: true, get: function () { return harness_agent_activity_1.AGENT_ACTIVITY_UNKNOWN; } });
510
+ Object.defineProperty(exports, "AGENT_TRANSCRIPT_QUIET_MS", { enumerable: true, get: function () { return harness_agent_activity_1.AGENT_TRANSCRIPT_QUIET_MS; } });
511
+ Object.defineProperty(exports, "AgentActivity", { enumerable: true, get: function () { return harness_agent_activity_1.AgentActivity; } });
512
+ Object.defineProperty(exports, "HarnessAgentActivityReader", { enumerable: true, get: function () { return harness_agent_activity_1.HarnessAgentActivityReader; } });
502
513
  var branch_reaper_1 = require("./branch-reaper");
503
514
  Object.defineProperty(exports, "ReapedBranch", { enumerable: true, get: function () { return branch_reaper_1.ReapedBranch; } });
504
515
  Object.defineProperty(exports, "ReapResult", { enumerable: true, get: function () { return branch_reaper_1.ReapResult; } });