@cluesmith/codev 3.1.2 → 3.1.4

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 (54) hide show
  1. package/bin/afx.js +8 -4
  2. package/dashboard-dist/assets/{index-CM6A5r59.js → index-DSENHqFj.js} +2 -2
  3. package/dashboard-dist/assets/{index-CM6A5r59.js.map → index-DSENHqFj.js.map} +1 -1
  4. package/dashboard-dist/index.html +1 -1
  5. package/dist/agent-farm/cli.d.ts.map +1 -1
  6. package/dist/agent-farm/cli.js +28 -0
  7. package/dist/agent-farm/cli.js.map +1 -1
  8. package/dist/agent-farm/commands/spawn-worktree.d.ts +8 -2
  9. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
  10. package/dist/agent-farm/commands/spawn-worktree.js +30 -7
  11. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
  12. package/dist/agent-farm/commands/spawn.d.ts +8 -0
  13. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  14. package/dist/agent-farm/commands/spawn.js +21 -3
  15. package/dist/agent-farm/commands/spawn.js.map +1 -1
  16. package/dist/agent-farm/commands/workspace-recover.d.ts +74 -0
  17. package/dist/agent-farm/commands/workspace-recover.d.ts.map +1 -0
  18. package/dist/agent-farm/commands/workspace-recover.js +307 -0
  19. package/dist/agent-farm/commands/workspace-recover.js.map +1 -0
  20. package/dist/agent-farm/servers/overview.d.ts +37 -0
  21. package/dist/agent-farm/servers/overview.d.ts.map +1 -1
  22. package/dist/agent-farm/servers/overview.js +38 -0
  23. package/dist/agent-farm/servers/overview.js.map +1 -1
  24. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  25. package/dist/agent-farm/servers/tower-instances.js +57 -1
  26. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  27. package/dist/agent-farm/utils/claude-session-discovery.d.ts +19 -0
  28. package/dist/agent-farm/utils/claude-session-discovery.d.ts.map +1 -0
  29. package/dist/agent-farm/utils/claude-session-discovery.js +64 -0
  30. package/dist/agent-farm/utils/claude-session-discovery.js.map +1 -0
  31. package/dist/cli.d.ts +2 -1
  32. package/dist/cli.d.ts.map +1 -1
  33. package/dist/cli.js +22 -32
  34. package/dist/cli.js.map +1 -1
  35. package/dist/commands/porch/index.d.ts.map +1 -1
  36. package/dist/commands/porch/index.js +29 -1
  37. package/dist/commands/porch/index.js.map +1 -1
  38. package/dist/commands/porch/next.d.ts.map +1 -1
  39. package/dist/commands/porch/next.js +59 -9
  40. package/dist/commands/porch/next.js.map +1 -1
  41. package/dist/commands/porch/protocol.d.ts +25 -1
  42. package/dist/commands/porch/protocol.d.ts.map +1 -1
  43. package/dist/commands/porch/protocol.js +43 -3
  44. package/dist/commands/porch/protocol.js.map +1 -1
  45. package/dist/commands/porch/state.d.ts +17 -0
  46. package/dist/commands/porch/state.d.ts.map +1 -1
  47. package/dist/commands/porch/state.js +47 -0
  48. package/dist/commands/porch/state.js.map +1 -1
  49. package/dist/commands/porch/types.d.ts +34 -0
  50. package/dist/commands/porch/types.d.ts.map +1 -1
  51. package/package.json +3 -4
  52. package/skeleton/protocols/aspir/protocol.json +5 -5
  53. package/skeleton/protocols/spir/protocol.json +5 -5
  54. package/bin/af.js +0 -8
@@ -0,0 +1,74 @@
1
+ import type { ProjectState } from '../../commands/porch/types.js';
2
+ import type { DbTerminalSession } from '../servers/tower-types.js';
3
+ export interface WorkspaceRecoverOptions {
4
+ apply?: boolean;
5
+ maxAge?: number;
6
+ includeStale?: boolean;
7
+ yes?: boolean;
8
+ }
9
+ export type IneligibleReason = 'terminal' | 'unsupported_protocol' | 'worktree_missing' | 'shellper_alive' | 'stale';
10
+ export type EligibilityResult = {
11
+ eligible: true;
12
+ } | {
13
+ eligible: false;
14
+ reason: IneligibleReason;
15
+ };
16
+ export interface EligibilityInputs {
17
+ state: ProjectState;
18
+ builderInfo: BuilderInfo | null;
19
+ sessions: DbTerminalSession[];
20
+ worktreeExists: boolean;
21
+ ageDays: number;
22
+ maxAgeDays: number;
23
+ includeStale: boolean;
24
+ isProcessAlive: (pid: number) => boolean;
25
+ socketExists: (socket: string) => boolean;
26
+ }
27
+ /**
28
+ * Pure predicate — no I/O. All filesystem and process probes happen in the
29
+ * caller and are passed in via `isProcessAlive` and `socketExists`. This keeps
30
+ * the predicate trivially unit-testable.
31
+ *
32
+ * On terminal_sessions and the "no row" case:
33
+ * Tower's reconciliation deletes terminal_sessions rows whose shellper can't
34
+ * be reconnected (tower-terminals.ts:485-711). That runs on Tower startup,
35
+ * which is exactly what happens between a machine reboot and the user
36
+ * invoking `workspace recover`. By the time recovery runs, the rows for
37
+ * dead builders have already been pruned. So "no matching session row"
38
+ * is the COMMON case for any builder that needs revival — not a reason to
39
+ * skip. The row is only useful here as a positive "still alive" signal.
40
+ *
41
+ * Predicate order (cheap structural checks first):
42
+ * 1. terminal phase
43
+ * 2. unsupported protocol family
44
+ * 3. worktree missing on disk
45
+ * 4. any matching session row is alive → known still running, leave alone
46
+ * 5. stale (older than maxAge, unless includeStale)
47
+ * 6. otherwise → revive
48
+ */
49
+ export declare function evaluateEligibility(inputs: EligibilityInputs): EligibilityResult;
50
+ export interface BuilderInfo {
51
+ builderId: string;
52
+ issueArg: string;
53
+ cliProtocol: string;
54
+ }
55
+ /**
56
+ * Derive the inputs needed to invoke `afx spawn <issueArg> --resume --protocol <cliProtocol>`
57
+ * and the SQLite `role_id` to look up the builder's terminal session.
58
+ *
59
+ * Returns null for protocols that cannot be cleanly resumed via the spawn CLI
60
+ * (experiment/maintain/task/legacy spider/etc) — callers should skip those
61
+ * projects with an `unsupported_protocol` reason.
62
+ */
63
+ export declare function deriveBuilderInfo(state: ProjectState): BuilderInfo | null;
64
+ /**
65
+ * Resolve the builder's worktree path on disk, handling both the Spec-653
66
+ * ID-only layout and the legacy title-suffixed form.
67
+ *
68
+ * Returns null when the project's protocol isn't revivable (no defined worktree
69
+ * naming) or when no matching directory exists.
70
+ */
71
+ export declare function resolveWorktreePath(buildersDir: string, state: ProjectState): string | null;
72
+ export declare function formatRelativeAge(iso: string): string;
73
+ export declare function workspaceRecover(options?: WorkspaceRecoverOptions): Promise<void>;
74
+ //# sourceMappingURL=workspace-recover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-recover.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/commands/workspace-recover.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAcnE,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,MAAM,gBAAgB,GACxB,UAAU,GACV,sBAAsB,GACtB,kBAAkB,GAClB,gBAAgB,GAChB,OAAO,CAAC;AAEZ,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAClB;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3C;AAuBD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,CAyBhF;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,WAAW,GAAG,IAAI,CAiBzE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAkB3F;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAcrD;AA6DD,wBAAsB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAAC,IAAI,CAAC,CAoH3F"}
@@ -0,0 +1,307 @@
1
+ // workspace recover — revive builders whose shellper died (e.g. machine reboot).
2
+ // Issue #829. Dry-run by default; --apply actually respawns.
3
+ import { spawn } from 'node:child_process';
4
+ import { existsSync, readdirSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import chalk from 'chalk';
7
+ import { getConfig } from '../utils/index.js';
8
+ import { logger } from '../utils/logger.js';
9
+ import { buildAgentName, stripLeadingZeros } from '../utils/agent-names.js';
10
+ import { processExists, getTerminalSessionsForWorkspace } from '../servers/tower-terminals.js';
11
+ import { closeGlobalDb } from '../db/index.js';
12
+ import { listAllProjects } from '../../commands/porch/state.js';
13
+ import { confirm } from '../../lib/cli-prompts.js';
14
+ const TERMINAL_PHASES = new Set(['verified', 'complete']);
15
+ const DEFAULT_MAX_AGE_DAYS = 7;
16
+ // Protocols that `afx spawn <id> --resume --protocol <name>` can revive cleanly:
17
+ // issue-driven families with a positional issue arg and a stable worktree layout.
18
+ // `experiment` / `maintain` / `task` / bare protocol-mode builders can't be
19
+ // resumed by ID through spawn.ts and are skipped with `unsupported_protocol`.
20
+ // (Legacy `spider` is also excluded — it was retired long ago, and any stray
21
+ // project still carrying that protocol value should be treated as unsupported.)
22
+ const REVIVABLE_PROTOCOLS = new Set(['spir', 'aspir', 'air', 'pir', 'bugfix']);
23
+ /**
24
+ * Liveness rule per row (Gemini cmap finding):
25
+ * - shellper_pid known + alive → ALIVE
26
+ * - shellper_pid known + dead → DEAD (socket is ignored; the OS doesn't clean
27
+ * ~/.codev/run/ on reboot, so a stale socket
28
+ * would otherwise falsely mark the row alive
29
+ * and defeat the primary recovery use case)
30
+ * - shellper_pid null + socket file exists → ALIVE (legacy / pre-PID rows)
31
+ * - shellper_pid null + no socket → DEAD
32
+ */
33
+ function isSessionAlive(session, isProcessAlive, socketExists) {
34
+ if (session.shellper_pid !== null) {
35
+ return isProcessAlive(session.shellper_pid);
36
+ }
37
+ return session.shellper_socket !== null && socketExists(session.shellper_socket);
38
+ }
39
+ /**
40
+ * Pure predicate — no I/O. All filesystem and process probes happen in the
41
+ * caller and are passed in via `isProcessAlive` and `socketExists`. This keeps
42
+ * the predicate trivially unit-testable.
43
+ *
44
+ * On terminal_sessions and the "no row" case:
45
+ * Tower's reconciliation deletes terminal_sessions rows whose shellper can't
46
+ * be reconnected (tower-terminals.ts:485-711). That runs on Tower startup,
47
+ * which is exactly what happens between a machine reboot and the user
48
+ * invoking `workspace recover`. By the time recovery runs, the rows for
49
+ * dead builders have already been pruned. So "no matching session row"
50
+ * is the COMMON case for any builder that needs revival — not a reason to
51
+ * skip. The row is only useful here as a positive "still alive" signal.
52
+ *
53
+ * Predicate order (cheap structural checks first):
54
+ * 1. terminal phase
55
+ * 2. unsupported protocol family
56
+ * 3. worktree missing on disk
57
+ * 4. any matching session row is alive → known still running, leave alone
58
+ * 5. stale (older than maxAge, unless includeStale)
59
+ * 6. otherwise → revive
60
+ */
61
+ export function evaluateEligibility(inputs) {
62
+ const { state, builderInfo, sessions, worktreeExists, ageDays, maxAgeDays, includeStale, isProcessAlive, socketExists, } = inputs;
63
+ if (TERMINAL_PHASES.has(state.phase)) {
64
+ return { eligible: false, reason: 'terminal' };
65
+ }
66
+ if (builderInfo === null) {
67
+ return { eligible: false, reason: 'unsupported_protocol' };
68
+ }
69
+ if (!worktreeExists) {
70
+ return { eligible: false, reason: 'worktree_missing' };
71
+ }
72
+ // If ANY matching session looks alive, treat the builder as alive. Duplicates
73
+ // can occur when a prior recovery left a dead row behind (terminal_sessions
74
+ // has no UNIQUE constraint on role_id) — the cautious read is "alive."
75
+ if (sessions.some(s => isSessionAlive(s, isProcessAlive, socketExists))) {
76
+ return { eligible: false, reason: 'shellper_alive' };
77
+ }
78
+ if (!includeStale && ageDays > maxAgeDays) {
79
+ return { eligible: false, reason: 'stale' };
80
+ }
81
+ return { eligible: true };
82
+ }
83
+ /**
84
+ * Derive the inputs needed to invoke `afx spawn <issueArg> --resume --protocol <cliProtocol>`
85
+ * and the SQLite `role_id` to look up the builder's terminal session.
86
+ *
87
+ * Returns null for protocols that cannot be cleanly resumed via the spawn CLI
88
+ * (experiment/maintain/task/legacy spider/etc) — callers should skip those
89
+ * projects with an `unsupported_protocol` reason.
90
+ */
91
+ export function deriveBuilderInfo(state) {
92
+ if (!REVIVABLE_PROTOCOLS.has(state.protocol)) {
93
+ return null;
94
+ }
95
+ if (state.protocol === 'bugfix') {
96
+ const numericId = state.id.replace(/^bugfix-/, '');
97
+ return {
98
+ builderId: buildAgentName('bugfix', numericId),
99
+ issueArg: numericId,
100
+ cliProtocol: 'bugfix',
101
+ };
102
+ }
103
+ return {
104
+ builderId: buildAgentName('spec', state.id, state.protocol),
105
+ issueArg: stripLeadingZeros(state.id),
106
+ cliProtocol: state.protocol,
107
+ };
108
+ }
109
+ /**
110
+ * Resolve the builder's worktree path on disk, handling both the Spec-653
111
+ * ID-only layout and the legacy title-suffixed form.
112
+ *
113
+ * Returns null when the project's protocol isn't revivable (no defined worktree
114
+ * naming) or when no matching directory exists.
115
+ */
116
+ export function resolveWorktreePath(buildersDir, state) {
117
+ const info = deriveBuilderInfo(state);
118
+ if (info === null)
119
+ return null;
120
+ const idOnlyName = `${info.cliProtocol}-${info.issueArg}`;
121
+ const idOnlyPath = join(buildersDir, idOnlyName);
122
+ if (existsSync(idOnlyPath) && existsSync(join(idOnlyPath, '.git'))) {
123
+ return idOnlyPath;
124
+ }
125
+ if (!existsSync(buildersDir))
126
+ return null;
127
+ const prefix = `${info.cliProtocol}-${info.issueArg}-`;
128
+ for (const entry of readdirSync(buildersDir, { withFileTypes: true })) {
129
+ if (!entry.isDirectory() || !entry.name.startsWith(prefix))
130
+ continue;
131
+ const candidate = join(buildersDir, entry.name);
132
+ if (existsSync(join(candidate, '.git')))
133
+ return candidate;
134
+ }
135
+ return null;
136
+ }
137
+ export function formatRelativeAge(iso) {
138
+ const ms = Date.now() - Date.parse(iso);
139
+ if (Number.isNaN(ms) || ms < 0)
140
+ return '—';
141
+ const minutes = Math.floor(ms / 60_000);
142
+ if (minutes < 60)
143
+ return `${minutes}m ago`;
144
+ const hours = Math.floor(minutes / 60);
145
+ if (hours < 24)
146
+ return `${hours}h ago`;
147
+ // Round UP rather than down so the label aligns with --max-age semantics:
148
+ // anything strictly older than 24h prints as "2d ago" (not "1d ago"), so
149
+ // a row labelled "1d ago" is actually within --max-age 1 (≤ 24h exact).
150
+ // Use ms rather than the floored `hours` to preserve sub-hour precision —
151
+ // 24h + 1s must ceil to 2 days, not 1.
152
+ const days = Math.ceil(ms / 86_400_000);
153
+ return `${days}d ago`;
154
+ }
155
+ function reasonLabel(reason) {
156
+ switch (reason) {
157
+ case 'terminal': return 'terminal';
158
+ case 'unsupported_protocol': return 'unsupported protocol';
159
+ case 'worktree_missing': return 'worktree missing';
160
+ case 'shellper_alive': return 'shellper alive';
161
+ case 'stale': return 'stale';
162
+ }
163
+ }
164
+ function printPreview(rows) {
165
+ const widths = [6, 9, 12, 14, 10, 20];
166
+ logger.row(['ID', 'PROTOCOL', 'PHASE', 'UPDATED', 'STATUS', 'REASON'], widths);
167
+ logger.row(['─'.repeat(6), '─'.repeat(9), '─'.repeat(12), '─'.repeat(14), '─'.repeat(10), '─'.repeat(20)], widths);
168
+ for (const row of rows) {
169
+ const status = row.eligibility.eligible
170
+ ? chalk.green('revive')
171
+ : chalk.gray('skip');
172
+ const reason = row.eligibility.eligible ? '—' : reasonLabel(row.eligibility.reason);
173
+ logger.row([
174
+ row.state.id,
175
+ row.state.protocol,
176
+ row.state.phase,
177
+ formatRelativeAge(row.state.updated_at),
178
+ status,
179
+ reason,
180
+ ], widths);
181
+ }
182
+ }
183
+ async function respawnBuilder(info) {
184
+ // Use the current node binary and CLI entry point so the respawn invocation
185
+ // matches the install method this command was started under (npm global,
186
+ // npx, dev script, etc.) instead of relying on PATH lookup of 'afx'.
187
+ await new Promise((resolvePromise, rejectPromise) => {
188
+ const child = spawn(process.execPath, [process.argv[1], 'spawn', info.issueArg, '--resume', '--protocol', info.cliProtocol], { stdio: 'inherit' });
189
+ child.on('error', rejectPromise);
190
+ child.on('exit', (code) => {
191
+ if (code === 0)
192
+ resolvePromise();
193
+ else
194
+ rejectPromise(new Error(`afx spawn exited with code ${code}`));
195
+ });
196
+ });
197
+ }
198
+ export async function workspaceRecover(options = {}) {
199
+ const config = getConfig();
200
+ const maxAgeDays = options.maxAge ?? DEFAULT_MAX_AGE_DAYS;
201
+ const includeStale = options.includeStale ?? false;
202
+ const apply = options.apply ?? false;
203
+ logger.header(`Workspace Recover${apply ? '' : ' (dry-run)'}`);
204
+ logger.kv('Workspace', config.workspaceRoot);
205
+ if (!includeStale)
206
+ logger.kv('Max age', `${maxAgeDays} day(s)`);
207
+ logger.blank();
208
+ const projects = listAllProjects(config.workspaceRoot, {
209
+ onParseError: (statusPath, err) => {
210
+ logger.debug(`Skipped unparseable ${statusPath}: ${err instanceof Error ? err.message : String(err)}`);
211
+ },
212
+ });
213
+ if (projects.length === 0) {
214
+ logger.info('No porch projects found.');
215
+ return;
216
+ }
217
+ let sessions;
218
+ try {
219
+ sessions = getTerminalSessionsForWorkspace(config.workspaceRoot);
220
+ }
221
+ finally {
222
+ closeGlobalDb();
223
+ }
224
+ // role_id has no UNIQUE constraint in the schema, so collect every matching
225
+ // row per builder id rather than collapsing to last-write-wins.
226
+ const sessionsByRoleId = new Map();
227
+ for (const s of sessions) {
228
+ if (s.type !== 'builder' || !s.role_id)
229
+ continue;
230
+ const bucket = sessionsByRoleId.get(s.role_id);
231
+ if (bucket)
232
+ bucket.push(s);
233
+ else
234
+ sessionsByRoleId.set(s.role_id, [s]);
235
+ }
236
+ const allRows = projects.map(({ state }) => {
237
+ const builderInfo = deriveBuilderInfo(state);
238
+ const matchingSessions = builderInfo ? sessionsByRoleId.get(builderInfo.builderId) ?? [] : [];
239
+ const worktreePath = resolveWorktreePath(config.buildersDir, state);
240
+ const ageDays = (Date.now() - Date.parse(state.updated_at)) / 86_400_000;
241
+ const eligibility = evaluateEligibility({
242
+ state, builderInfo,
243
+ sessions: matchingSessions,
244
+ worktreeExists: worktreePath !== null,
245
+ ageDays, maxAgeDays, includeStale,
246
+ isProcessAlive: processExists,
247
+ socketExists: existsSync,
248
+ });
249
+ return { state, builderInfo, worktreePath, eligibility, ageDays };
250
+ });
251
+ // By default the preview hides projects beyond the recency window — for a
252
+ // large workspace the stale tail dominates the table and obscures the few
253
+ // rows the operator actually cares about. --include-stale shows everything.
254
+ const visibleRows = includeStale
255
+ ? allRows
256
+ : allRows.filter(r => r.ageDays <= maxAgeDays);
257
+ const hiddenStaleCount = allRows.length - visibleRows.length;
258
+ printPreview(visibleRows);
259
+ if (hiddenStaleCount > 0) {
260
+ logger.blank();
261
+ logger.info(`${hiddenStaleCount} project(s) older than ${maxAgeDays} day(s) hidden. Pass --include-stale to show them.`);
262
+ }
263
+ const eligible = allRows.filter((r) => r.eligibility.eligible && r.builderInfo !== null);
264
+ logger.blank();
265
+ if (hiddenStaleCount > 0) {
266
+ logger.kv('Eligible', `${eligible.length} / ${visibleRows.length} visible (${allRows.length} scanned)`);
267
+ }
268
+ else {
269
+ logger.kv('Eligible', `${eligible.length} / ${allRows.length}`);
270
+ }
271
+ if (eligible.length === 0) {
272
+ logger.info(apply ? 'Nothing to revive.' : 'Nothing would be revived.');
273
+ return;
274
+ }
275
+ if (!apply) {
276
+ logger.info(`Run with --apply to respawn ${eligible.length} builder(s).`);
277
+ return;
278
+ }
279
+ if (!options.yes) {
280
+ const proceed = await confirm(`Proceed to respawn ${eligible.length} builder(s)?`, false);
281
+ if (!proceed) {
282
+ logger.info('Aborted.');
283
+ return;
284
+ }
285
+ }
286
+ let succeeded = 0;
287
+ let failed = 0;
288
+ for (const row of eligible) {
289
+ logger.blank();
290
+ logger.info(`Respawning ${row.builderInfo.builderId} (issue ${row.builderInfo.issueArg}, ${row.builderInfo.cliProtocol})...`);
291
+ try {
292
+ await respawnBuilder(row.builderInfo);
293
+ succeeded++;
294
+ }
295
+ catch (err) {
296
+ failed++;
297
+ logger.error(`Failed to respawn ${row.builderInfo.builderId}: ${err instanceof Error ? err.message : String(err)}`);
298
+ }
299
+ }
300
+ logger.blank();
301
+ logger.kv('Respawned', String(succeeded));
302
+ if (failed > 0) {
303
+ logger.kv('Failed', String(failed));
304
+ process.exit(1);
305
+ }
306
+ }
307
+ //# sourceMappingURL=workspace-recover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-recover.js","sourceRoot":"","sources":["../../../src/agent-farm/commands/workspace-recover.ts"],"names":[],"mappings":"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"}
@@ -60,6 +60,18 @@ export interface BuilderOverview {
60
60
  * an inline attribution tag when the workspace hosts more than one architect.
61
61
  */
62
62
  spawnedByArchitect: string | null;
63
+ /**
64
+ * Canonical "PR is waiting on a human reviewer" signal (Issue #872). True the
65
+ * moment porch transitions out of the CMAP-emitting state for the PR-creating
66
+ * phase, for ALL bundled protocols. Computed from `pr_ready_for_human` in
67
+ * status.yaml when present; otherwise falls back to the v3.1.3 derived logic
68
+ * (`blocked === 'PR review'` || BUGFIX `phase === 'verified'`) so in-flight
69
+ * projects from before this field shipped continue to surface correctly.
70
+ *
71
+ * Consumers (dashboard NeedsAttentionList, VSCode tree) gate on this flag
72
+ * instead of deriving from the protocol-specific gate shape themselves.
73
+ */
74
+ prReady: boolean;
63
75
  }
64
76
  export interface PROverview {
65
77
  id: string;
@@ -121,6 +133,13 @@ interface ParsedStatus {
121
133
  gateApprovedAt: Record<string, string>;
122
134
  planPhases: PlanPhase[];
123
135
  startedAt: string;
136
+ /**
137
+ * Mirror of porch's `pr_ready_for_human` status.yaml field (Issue #872).
138
+ * `null` when the field is absent in the yaml (legacy state from before
139
+ * the field shipped) so `derivePrReady` can choose between the explicit
140
+ * value and the v3.1.3 fallback derivation.
141
+ */
142
+ prReadyForHuman: boolean | null;
124
143
  }
125
144
  /**
126
145
  * Parse a porch status.yaml file into structured data.
@@ -153,6 +172,24 @@ export declare function detectBlockedGate(parsed: ParsedStatus): string | null;
153
172
  * Keep this list in sync with `detectBlocked`'s `gateLabels` keys.
154
173
  */
155
174
  export declare function detectBlockedSince(parsed: ParsedStatus): string | null;
175
+ /**
176
+ * Compute the canonical "PR ready for human review" signal for a builder
177
+ * (Issue #872). Prefers porch's explicit `pr_ready_for_human` field in
178
+ * status.yaml when present; otherwise falls back to the v3.1.3 derivation so
179
+ * builders whose state.yaml pre-dates the field continue to surface.
180
+ *
181
+ * Fallback derivation:
182
+ * - `blocked === 'PR review'` — protocols with a `pr` gate (AIR/SPIR/ASPIR/PIR)
183
+ * that reached the human-bottleneck state under v3.1.3 logic.
184
+ * - BUGFIX `phase === 'verified'` — the v3.1.3 gap this canonical signal
185
+ * closes. BUGFIX has no `pr` gate, so its post-CMAP state is `verified`
186
+ * with no gate-pending signal; the fallback adds this case so in-flight
187
+ * BUGFIX builders aren't dropped from Needs Attention.
188
+ *
189
+ * Once porch has written `pr_ready_for_human` to status.yaml the fallback no
190
+ * longer fires for that project — the explicit field is authoritative.
191
+ */
192
+ export declare function derivePrReady(parsed: ParsedStatus): boolean;
156
193
  /**
157
194
  * Compute total idle time (ms) from gate wait periods.
158
195
  * Includes completed gate waits (requested_at → approved_at) and
@@ -1 +1 @@
1
- {"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/overview.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,OAAO,KAAK,EAAW,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAQvE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;;;;OAIG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;;;OAOG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,cAAc,EAAE,kBAAkB,EAAE,CAAC;IACrC,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5C;AAMD,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAkH7D;AAgBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CActF;AA6BD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAK7E;AAwCD,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAOjE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAOrE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAQtE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAqB1D;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoCnE;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkC/E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,eAAe,EAAE,CAwIzE;AAyBD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,kBAAkB,EAAE,EAC5B,aAAa,EAAE,MAAM,EACrB,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,EAChC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAC1B,WAAW,EAAE,CAiCf;AAMD,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAA6D;IAC5E,OAAO,CAAC,UAAU,CAAwE;IAC1F,OAAO,CAAC,WAAW,CAAwE;IAC3F,OAAO,CAAC,aAAa,CAA6D;IAClF,OAAO,CAAC,gBAAgB,CAA0D;IAClF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAU;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IAEtC;;;;;;;OAOG;IACG,WAAW,CAAC,aAAa,EAAE,MAAM,EAAE,oBAAoB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;IAiJnG;;OAEG;IACH,UAAU,IAAI,IAAI;YAYJ,cAAc;YAcd,iBAAiB;IAc/B;;;;;OAKG;YACW,sBAAsB;YActB,yBAAyB;YAczB,oBAAoB;CAanC"}
1
+ {"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/overview.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAaH,OAAO,KAAK,EAAW,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAQvE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;;;;OAIG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;;;OAOG;IACH,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;;;;;;;;OAUG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,cAAc,EAAE,kBAAkB,EAAE,CAAC;IACrC,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5C;AAMD,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,eAAe,EAAE,OAAO,GAAG,IAAI,CAAC;CACjC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CA0H7D;AAgBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CActF;AA6BD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAK7E;AAwCD,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAOjE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAOrE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAQtE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAQ3D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAqB1D;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoCnE;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkC/E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,eAAe,EAAE,CA2IzE;AAyBD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,kBAAkB,EAAE,EAC5B,aAAa,EAAE,MAAM,EACrB,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,EAChC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAC1B,WAAW,EAAE,CAiCf;AAMD,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAA6D;IAC5E,OAAO,CAAC,UAAU,CAAwE;IAC1F,OAAO,CAAC,WAAW,CAAwE;IAC3F,OAAO,CAAC,aAAa,CAA6D;IAClF,OAAO,CAAC,gBAAgB,CAA0D;IAClF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAU;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IAEtC;;;;;;;OAOG;IACG,WAAW,CAAC,aAAa,EAAE,MAAM,EAAE,oBAAoB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;IAiJnG;;OAEG;IACH,UAAU,IAAI,IAAI;YAYJ,cAAc;YAcd,iBAAiB;IAc/B;;;;;OAKG;YACW,sBAAsB;YActB,yBAAyB;YAczB,oBAAoB;CAanC"}
@@ -27,6 +27,7 @@ export function parseStatusYaml(content) {
27
27
  gateApprovedAt: {},
28
28
  planPhases: [],
29
29
  startedAt: '',
30
+ prReadyForHuman: null,
30
31
  };
31
32
  const lines = content.split('\n');
32
33
  let section = 'none';
@@ -70,6 +71,12 @@ export function parseStatusYaml(content) {
70
71
  section = 'none';
71
72
  continue;
72
73
  }
74
+ const prReadyMatch = line.match(/^pr_ready_for_human:\s*(true|false)\s*$/);
75
+ if (prReadyMatch) {
76
+ result.prReadyForHuman = prReadyMatch[1] === 'true';
77
+ section = 'none';
78
+ continue;
79
+ }
73
80
  // Section headers
74
81
  if (/^gates:\s*$/.test(line)) {
75
82
  if (currentPlanPhase) {
@@ -285,6 +292,34 @@ export function detectBlockedSince(parsed) {
285
292
  }
286
293
  return null;
287
294
  }
295
+ /**
296
+ * Compute the canonical "PR ready for human review" signal for a builder
297
+ * (Issue #872). Prefers porch's explicit `pr_ready_for_human` field in
298
+ * status.yaml when present; otherwise falls back to the v3.1.3 derivation so
299
+ * builders whose state.yaml pre-dates the field continue to surface.
300
+ *
301
+ * Fallback derivation:
302
+ * - `blocked === 'PR review'` — protocols with a `pr` gate (AIR/SPIR/ASPIR/PIR)
303
+ * that reached the human-bottleneck state under v3.1.3 logic.
304
+ * - BUGFIX `phase === 'verified'` — the v3.1.3 gap this canonical signal
305
+ * closes. BUGFIX has no `pr` gate, so its post-CMAP state is `verified`
306
+ * with no gate-pending signal; the fallback adds this case so in-flight
307
+ * BUGFIX builders aren't dropped from Needs Attention.
308
+ *
309
+ * Once porch has written `pr_ready_for_human` to status.yaml the fallback no
310
+ * longer fires for that project — the explicit field is authoritative.
311
+ */
312
+ export function derivePrReady(parsed) {
313
+ if (parsed.prReadyForHuman !== null)
314
+ return parsed.prReadyForHuman;
315
+ // Fallback: v3.1.3 logic + BUGFIX case.
316
+ const prGatePending = parsed.gates['pr'] === 'pending' && !!parsed.gateRequestedAt['pr'];
317
+ if (prGatePending)
318
+ return true;
319
+ if (parsed.protocol === 'bugfix' && parsed.phase === 'verified')
320
+ return true;
321
+ return false;
322
+ }
288
323
  /**
289
324
  * Compute total idle time (ms) from gate wait periods.
290
325
  * Includes completed gate waits (requested_at → approved_at) and
@@ -450,6 +485,7 @@ export function discoverBuilders(workspaceRoot) {
450
485
  idleMs: 0,
451
486
  lastDataAt: null,
452
487
  spawnedByArchitect: null,
488
+ prReady: false,
453
489
  });
454
490
  continue;
455
491
  }
@@ -503,6 +539,7 @@ export function discoverBuilders(workspaceRoot) {
503
539
  idleMs: computeIdleMs(parsed),
504
540
  lastDataAt: null,
505
541
  spawnedByArchitect: null,
542
+ prReady: derivePrReady(parsed),
506
543
  });
507
544
  found = true;
508
545
  break;
@@ -535,6 +572,7 @@ export function discoverBuilders(workspaceRoot) {
535
572
  idleMs: 0,
536
573
  lastDataAt: null,
537
574
  spawnedByArchitect: null,
575
+ prReady: false,
538
576
  });
539
577
  }
540
578
  }