delimit-cli 4.0.2 → 4.0.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.
@@ -0,0 +1,655 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const readline = require('readline');
4
+ const { spawn } = require('child_process');
5
+ const {
6
+ resolveContinuityContext,
7
+ saveActiveVenture,
8
+ } = require('./continuity-resolver');
9
+ const { hookBootstrap } = require('./cross-model-hooks');
10
+
11
+ function readJson(filePath) {
12
+ try {
13
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+
19
+ function appendJsonl(filePath, payload) {
20
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
21
+ fs.appendFileSync(filePath, JSON.stringify(payload) + '\n');
22
+ }
23
+
24
+ function readJsonl(filePath) {
25
+ if (!fs.existsSync(filePath)) {
26
+ return [];
27
+ }
28
+ return fs.readFileSync(filePath, 'utf-8')
29
+ .split('\n')
30
+ .map(line => line.trim())
31
+ .filter(Boolean)
32
+ .map(line => {
33
+ try {
34
+ return JSON.parse(line);
35
+ } catch {
36
+ return null;
37
+ }
38
+ })
39
+ .filter(Boolean);
40
+ }
41
+
42
+ function readLatestSession(sessionDir) {
43
+ if (!fs.existsSync(sessionDir)) {
44
+ return null;
45
+ }
46
+ const files = fs.readdirSync(sessionDir)
47
+ .filter(name => name.endsWith('.json'))
48
+ .map(name => path.join(sessionDir, name))
49
+ .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
50
+ return files.length > 0 ? readJson(files[0]) : null;
51
+ }
52
+
53
+ function resolvePortfolioVenture(context, rawName) {
54
+ const normalized = String(rawName || '').trim().toLowerCase();
55
+ if (!normalized) {
56
+ return null;
57
+ }
58
+ const ventures = context.ventureLedgers || [];
59
+ return ventures.find((entry) => entry.scope === 'repo' && (
60
+ entry.venture.toLowerCase() === normalized
61
+ || path.basename(entry.repoRoot || '').toLowerCase() === normalized
62
+ || entry.venture.toLowerCase().includes(normalized)
63
+ || path.basename(entry.repoRoot || '').toLowerCase().includes(normalized)
64
+ )) || null;
65
+ }
66
+
67
+ function rememberActiveVenture(target) {
68
+ if (target?.repoRoot) {
69
+ saveActiveVenture({
70
+ venture: target.venture,
71
+ repoRoot: target.repoRoot,
72
+ });
73
+ }
74
+ }
75
+
76
+ function buildLedgerSnapshot(ledgerPath) {
77
+ const entries = readJsonl(ledgerPath);
78
+ const latestById = new Map();
79
+ for (const entry of entries) {
80
+ if (!entry || !entry.id) continue;
81
+ const current = latestById.get(entry.id) || {};
82
+ if (entry.type === 'update') {
83
+ latestById.set(entry.id, {
84
+ ...current,
85
+ ...entry,
86
+ id: current.id || entry.id,
87
+ title: current.title || entry.title,
88
+ description: current.description || entry.description,
89
+ priority: current.priority || entry.priority,
90
+ });
91
+ } else {
92
+ latestById.set(entry.id, {
93
+ ...entry,
94
+ ...current,
95
+ });
96
+ }
97
+ }
98
+ const priorityWeight = { P0: 0, P1: 1, P2: 2 };
99
+ const open = Array.from(latestById.values())
100
+ .filter(item => !['done', 'blocked'].includes(String(item.status || 'open')))
101
+ .sort((a, b) => (priorityWeight[a.priority] ?? 9) - (priorityWeight[b.priority] ?? 9));
102
+ return {
103
+ openCount: open.length,
104
+ nextItem: open[0] || null,
105
+ openItems: open.slice(0, 5),
106
+ };
107
+ }
108
+
109
+ function buildPortfolioSnapshot(context) {
110
+ const ledgers = (context.ventureLedgers || []).map((entry) => {
111
+ const snapshot = buildLedgerSnapshot(path.join(entry.ledgerRoot, 'operations.jsonl'));
112
+ return {
113
+ venture: entry.venture,
114
+ scope: entry.scope,
115
+ repoRoot: entry.repoRoot,
116
+ ledgerRoot: entry.ledgerRoot,
117
+ openCount: snapshot.openCount,
118
+ nextItem: snapshot.nextItem,
119
+ };
120
+ });
121
+ const active = ledgers
122
+ .filter(item => item.openCount > 0)
123
+ .sort((a, b) => {
124
+ const prio = { P0: 0, P1: 1, P2: 2 };
125
+ const aPrio = prio[a.nextItem?.priority] ?? 9;
126
+ const bPrio = prio[b.nextItem?.priority] ?? 9;
127
+ if (aPrio !== bPrio) return aPrio - bPrio;
128
+ return b.openCount - a.openCount;
129
+ });
130
+ return {
131
+ openCount: active.reduce((sum, item) => sum + item.openCount, 0),
132
+ nextItem: active[0]?.nextItem || null,
133
+ nextVenture: active[0]?.venture || null,
134
+ ventures: ledgers,
135
+ active,
136
+ };
137
+ }
138
+
139
+ function getBootstrapState(context) {
140
+ const statePath = path.join(context.continuityRoot, 'bootstrap-state.json');
141
+ return {
142
+ statePath,
143
+ state: readJson(statePath),
144
+ };
145
+ }
146
+
147
+ function getTaskBrief(context) {
148
+ const taskBriefPath = path.join(context.continuityRoot, 'task-brief.json');
149
+ return {
150
+ taskBriefPath,
151
+ brief: readJson(taskBriefPath),
152
+ };
153
+ }
154
+
155
+ function getExecutionPlan(context) {
156
+ const executionPlanPath = path.join(context.continuityRoot, 'execution-plan.json');
157
+ return {
158
+ executionPlanPath,
159
+ plan: readJson(executionPlanPath),
160
+ };
161
+ }
162
+
163
+ function getOwnerActions(context) {
164
+ const ownerActionsPath = path.join(context.continuityRoot, 'owner-actions.json');
165
+ return {
166
+ ownerActionsPath,
167
+ state: readJson(ownerActionsPath),
168
+ };
169
+ }
170
+
171
+ function getExecutionState(context) {
172
+ const statePath = path.join(context.continuityRoot, 'execution-state.json');
173
+ return {
174
+ statePath,
175
+ state: readJson(statePath),
176
+ };
177
+ }
178
+
179
+ function setExecutionState(context, nextState) {
180
+ const statePath = path.join(context.continuityRoot, 'execution-state.json');
181
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
182
+ fs.writeFileSync(statePath, JSON.stringify(nextState, null, 2) + '\n');
183
+ }
184
+
185
+ function getWorkerState(context) {
186
+ const pointerPath = path.join(context.continuityRoot, 'active-worker.json');
187
+ const pointer = readJson(pointerPath);
188
+ const statePath = pointer?.statePath || path.join(context.continuityRoot, 'worker-state.json');
189
+ const workerState = readJson(statePath);
190
+ const executionState = getExecutionState(context).state;
191
+ return {
192
+ statePath,
193
+ state: workerState ? {
194
+ ...workerState,
195
+ phase: executionState?.phase ?? workerState.phase ?? null,
196
+ currentItem: executionState?.currentItem ?? workerState.currentItem ?? null,
197
+ nextItem: executionState?.nextItem ?? workerState.nextItem ?? null,
198
+ } : null,
199
+ };
200
+ }
201
+
202
+ function pidIsAlive(pid) {
203
+ if (!pid || !Number.isInteger(pid)) {
204
+ return false;
205
+ }
206
+ try {
207
+ process.kill(pid, 0);
208
+ return true;
209
+ } catch {
210
+ return false;
211
+ }
212
+ }
213
+
214
+ function ensureWorker(context) {
215
+ const worker = getWorkerState(context);
216
+ const workerMatchesContext = worker.state
217
+ && worker.state.repo === (context.repoRoot || process.cwd())
218
+ && worker.state.ledgerRoot === context.ledgerRoot
219
+ && worker.state.venture === context.venture;
220
+ if (worker.state && pidIsAlive(worker.state.pid) && workerMatchesContext) {
221
+ return { started: false, worker, restarted: false };
222
+ }
223
+
224
+ fs.mkdirSync(context.continuityRoot, { recursive: true });
225
+ const workerPath = path.join(__dirname, 'session-worker.js');
226
+ const runId = String(Date.now());
227
+ const child = spawn(process.execPath, [workerPath], {
228
+ detached: true,
229
+ stdio: 'ignore',
230
+ cwd: context.repoRoot || process.cwd(),
231
+ env: {
232
+ ...process.env,
233
+ DELIMIT_WORKER_RUN_ID: runId,
234
+ DELIMIT_HOME: context.delimitHome,
235
+ DELIMIT_CONTINUITY_ROOT: context.continuityRoot,
236
+ DELIMIT_REPO_GOVERNANCE_ROOT: context.repoGovernanceRoot || '',
237
+ DELIMIT_RESOLVED_VENTURE: context.venture,
238
+ DELIMIT_RESOLVED_ACTOR: context.actor,
239
+ },
240
+ });
241
+ child.unref();
242
+ const startedAt = new Date().toISOString();
243
+ const statePath = path.join(context.continuityRoot, `worker-state-${runId}.json`);
244
+ fs.writeFileSync(statePath, JSON.stringify({
245
+ pid: child.pid,
246
+ status: 'starting',
247
+ phase: 'starting',
248
+ updatedAt: startedAt,
249
+ startedAt,
250
+ actor: context.actor,
251
+ venture: context.venture,
252
+ repo: context.repoRoot || process.cwd(),
253
+ ledgerRoot: context.ledgerRoot,
254
+ ledgerScope: context.ledgerScope,
255
+ continuityRoot: context.continuityRoot,
256
+ }, null, 2) + '\n');
257
+ fs.writeFileSync(path.join(context.continuityRoot, 'active-worker.json'), JSON.stringify({
258
+ statePath,
259
+ updatedAt: startedAt,
260
+ }, null, 2) + '\n');
261
+ return {
262
+ started: true,
263
+ restarted: Boolean(worker.state),
264
+ worker: {
265
+ statePath,
266
+ state: null,
267
+ },
268
+ };
269
+ }
270
+
271
+ async function waitForWorkerState(context, timeoutMs = 1500) {
272
+ const start = Date.now();
273
+ while (Date.now() - start < timeoutMs) {
274
+ const worker = getWorkerState(context);
275
+ if (worker.state && pidIsAlive(worker.state.pid)) {
276
+ return worker;
277
+ }
278
+ await new Promise(resolve => setTimeout(resolve, 100));
279
+ }
280
+ return getWorkerState(context);
281
+ }
282
+
283
+ function renderSummary(context) {
284
+ const sessionDir = path.join(context.delimitHome, 'sessions');
285
+ const ledger = context.ledgerScope === 'all'
286
+ ? buildPortfolioSnapshot(context)
287
+ : buildLedgerSnapshot(path.join(context.ledgerRoot, 'operations.jsonl'));
288
+ const latestSession = readLatestSession(sessionDir);
289
+ const bootstrap = getBootstrapState(context);
290
+ const worker = getWorkerState(context);
291
+
292
+ const workerStatus = worker.state
293
+ ? (pidIsAlive(worker.state.pid) ? worker.state.status || 'running' : 'stale')
294
+ : 'not running';
295
+ const workerPhase = context.ledgerScope === 'all'
296
+ ? null
297
+ : (worker.state?.phase || (worker.state ? 'ready' : 'idle'));
298
+ const lines = [];
299
+ lines.push('Delimit');
300
+ lines.push(`${context.venture} ${context.repoRoot || process.cwd()}`);
301
+ lines.push('');
302
+ const currentItem = context.ledgerScope !== 'all' ? worker.state?.currentItem : null;
303
+ if (currentItem) {
304
+ lines.push(`Current: ${currentItem.id} ${currentItem.title || '(untitled)'} [${currentItem.priority || 'P?'}]`);
305
+ }
306
+ if (ledger.nextItem) {
307
+ const prefix = context.ledgerScope === 'all' && ledger.nextVenture ? `${ledger.nextVenture} :: ` : '';
308
+ lines.push(`Next: ${prefix}${ledger.nextItem.id} ${ledger.nextItem.title || '(untitled)'} [${ledger.nextItem.priority || 'P?'}]`);
309
+ } else {
310
+ lines.push('Next: none');
311
+ }
312
+ lines.push(`Queue: ${ledger.openCount} open`);
313
+ lines.push(`Scope: ${context.ledgerScope}`);
314
+ lines.push(`Worker: ${context.ledgerScope === 'all' ? 'portfolio' : workerStatus}`);
315
+ if (workerPhase) {
316
+ lines.push(`State: ${workerPhase}`);
317
+ }
318
+ if (context.ledgerScope !== 'all' && worker.state?.ownerActionCount) {
319
+ lines.push(`Owner actions: ${worker.state.ownerActionCount} queued`);
320
+ }
321
+ lines.push(`Inbox: ${bootstrap.state?.daemons?.inbox?.active || 'unknown'}`);
322
+ lines.push(`Social: ${bootstrap.state?.daemons?.social?.active || 'unknown'}`);
323
+ if (context.ledgerScope === 'all' && ledger.active.length > 0) {
324
+ lines.push('');
325
+ lines.push('Active ventures:');
326
+ for (const item of ledger.active.slice(0, 3)) {
327
+ const next = item.nextItem ? `${item.nextItem.id} ${item.nextItem.title || '(untitled)'}` : 'no open items';
328
+ lines.push(`- ${item.venture}: ${item.openCount} open | ${next}`);
329
+ }
330
+ }
331
+ if (latestSession && latestSession.summary) {
332
+ lines.push('');
333
+ lines.push(`Recent: ${latestSession.summary}`);
334
+ }
335
+ return {
336
+ ledger,
337
+ latestSession,
338
+ bootstrap,
339
+ worker,
340
+ workerStatus,
341
+ text: lines.join('\n'),
342
+ };
343
+ }
344
+
345
+ async function refreshBootstrap(context, mode = 'inspect') {
346
+ return hookBootstrap(mode, {
347
+ silent: true,
348
+ scope: context.ledgerScope === 'all' ? 'all' : undefined,
349
+ cwd: context.repoRoot || process.cwd(),
350
+ });
351
+ }
352
+
353
+ function printHelp(context) {
354
+ const commands = ['home', 'next', 'recent', 'details', 'help', 'exit'];
355
+ if (context.ledgerScope === 'all') {
356
+ commands.splice(3, 0, 'ventures', 'open <venture>', 'build <venture>', 'worker');
357
+ } else {
358
+ commands.splice(3, 0, 'done', 'worker', 'switch <venture>', 'portfolio');
359
+ }
360
+ console.log(`Commands: ${commands.join(', ')}`);
361
+ }
362
+
363
+ function markCurrentItemDone(context, note = '') {
364
+ if (context.ledgerScope === 'all') {
365
+ return { ok: false, error: 'Portfolio mode cannot mark items done. Open a venture first.' };
366
+ }
367
+ const execution = getExecutionState(context).state || {};
368
+ const current = execution.currentItem;
369
+ if (!current?.id) {
370
+ return { ok: false, error: 'No current item selected.' };
371
+ }
372
+ const ledgerPath = path.join(context.ledgerRoot, 'operations.jsonl');
373
+ const timestamp = new Date().toISOString();
374
+ appendJsonl(ledgerPath, {
375
+ id: current.id,
376
+ type: 'update',
377
+ updated_at: timestamp,
378
+ status: 'done',
379
+ note: note || 'Marked done from native Delimit session.',
380
+ created_at: timestamp,
381
+ });
382
+ const refreshed = buildLedgerSnapshot(ledgerPath);
383
+ setExecutionState(context, {
384
+ ...execution,
385
+ phase: refreshed.nextItem ? 'ready' : 'idle',
386
+ currentItem: refreshed.nextItem ? {
387
+ id: refreshed.nextItem.id,
388
+ title: refreshed.nextItem.title || '',
389
+ priority: refreshed.nextItem.priority || '',
390
+ status: refreshed.nextItem.status || 'open',
391
+ description: refreshed.nextItem.description || '',
392
+ } : null,
393
+ nextItem: refreshed.nextItem ? {
394
+ id: refreshed.nextItem.id,
395
+ title: refreshed.nextItem.title || '',
396
+ priority: refreshed.nextItem.priority || '',
397
+ status: refreshed.nextItem.status || 'open',
398
+ } : null,
399
+ updatedAt: timestamp,
400
+ });
401
+ return {
402
+ ok: true,
403
+ completed: current,
404
+ next: refreshed.nextItem || null,
405
+ };
406
+ }
407
+
408
+ async function runInteractiveSession(options = {}) {
409
+ const mode = options.build ? 'execute' : 'inspect';
410
+ const context = resolveContinuityContext({ cwd: options.cwd || process.cwd(), scope: options.scope });
411
+ await refreshBootstrap(context, mode);
412
+
413
+ let workerAction = null;
414
+ if (options.build && context.ledgerScope !== 'all') {
415
+ workerAction = ensureWorker(context);
416
+ if (workerAction.started) {
417
+ await waitForWorkerState(context);
418
+ }
419
+ }
420
+ const summary = renderSummary(context);
421
+ console.log(summary.text);
422
+ if (workerAction && workerAction.started) {
423
+ console.log('');
424
+ console.log(workerAction.restarted ? 'Build session refreshed.' : 'Build session resumed.');
425
+ }
426
+ console.log('');
427
+ printHelp(context);
428
+
429
+ const rl = readline.createInterface({
430
+ input: process.stdin,
431
+ output: process.stdout,
432
+ prompt: `delimit:${context.venture}> `,
433
+ });
434
+ let transitioning = false;
435
+
436
+ rl.prompt();
437
+ rl.on('line', async (line) => {
438
+ const raw = line.trim();
439
+ const command = raw.toLowerCase();
440
+ if (!command || command === 'home' || command === 'status') {
441
+ await refreshBootstrap(context, mode);
442
+ console.log(renderSummary(context).text);
443
+ } else if (command === 'next') {
444
+ await refreshBootstrap(context, mode);
445
+ const refreshed = renderSummary(context);
446
+ const current = refreshed.ledger.nextItem;
447
+ if (current) {
448
+ const prefix = context.ledgerScope === 'all' && refreshed.ledger.nextVenture ? `${refreshed.ledger.nextVenture} :: ` : '';
449
+ console.log(`${prefix}${current.id} ${current.title || '(untitled)'} [${current.priority || 'P?'}]`);
450
+ if (current.description) {
451
+ console.log('');
452
+ console.log(current.description);
453
+ }
454
+ } else {
455
+ console.log('No open ledger items.');
456
+ }
457
+ } else if (command === 'ventures') {
458
+ if (context.ledgerScope !== 'all') {
459
+ console.log('Portfolio view: use `portfolio`.');
460
+ } else {
461
+ await refreshBootstrap(context, mode);
462
+ const refreshed = renderSummary(context);
463
+ const lines = refreshed.ledger.active.map(item => {
464
+ const next = item.nextItem ? `${item.nextItem.id} ${item.nextItem.title || '(untitled)'}` : 'no open items';
465
+ return `${item.venture}: ${item.openCount} open | ${next}`;
466
+ });
467
+ console.log(lines.length ? lines.join('\n') : 'No open items across ventures.');
468
+ }
469
+ } else if (command.startsWith('open ')) {
470
+ if (context.ledgerScope !== 'all') {
471
+ console.log('`open` is available from portfolio view.');
472
+ } else {
473
+ const target = resolvePortfolioVenture(context, raw.slice(5));
474
+ if (!target?.repoRoot) {
475
+ console.log(`Unknown venture: ${raw.slice(5).trim()}`);
476
+ } else {
477
+ transitioning = true;
478
+ rememberActiveVenture(target);
479
+ rl.close();
480
+ await runInteractiveSession({ cwd: target.repoRoot, build: false });
481
+ return;
482
+ }
483
+ }
484
+ } else if (command.startsWith('build ')) {
485
+ if (context.ledgerScope !== 'all') {
486
+ console.log('`build <venture>` is available from portfolio view.');
487
+ } else {
488
+ const target = resolvePortfolioVenture(context, raw.slice(6));
489
+ if (!target?.repoRoot) {
490
+ console.log(`Unknown venture: ${raw.slice(6).trim()}`);
491
+ } else {
492
+ transitioning = true;
493
+ rememberActiveVenture(target);
494
+ rl.close();
495
+ await runInteractiveSession({ cwd: target.repoRoot, build: true });
496
+ return;
497
+ }
498
+ }
499
+ } else if (command === 'portfolio') {
500
+ if (context.ledgerScope === 'all') {
501
+ console.log('Already in portfolio view.');
502
+ } else {
503
+ transitioning = true;
504
+ rl.close();
505
+ await runInteractiveSession({ cwd: process.cwd(), scope: 'all' });
506
+ return;
507
+ }
508
+ } else if (command.startsWith('switch ')) {
509
+ if (context.ledgerScope === 'all') {
510
+ console.log('Use `open <venture>` or `build <venture>` from portfolio view.');
511
+ } else {
512
+ const portfolio = resolveContinuityContext({ cwd: process.cwd(), scope: 'all' });
513
+ const target = resolvePortfolioVenture(portfolio, raw.slice(7));
514
+ if (!target?.repoRoot) {
515
+ console.log(`Unknown venture: ${raw.slice(7).trim()}`);
516
+ } else {
517
+ transitioning = true;
518
+ rememberActiveVenture(target);
519
+ rl.close();
520
+ await runInteractiveSession({ cwd: target.repoRoot, build: false });
521
+ return;
522
+ }
523
+ }
524
+ } else if (command === 'recent') {
525
+ await refreshBootstrap(context, 'inspect');
526
+ const latest = renderSummary(context).latestSession;
527
+ console.log(latest?.summary || 'No saved session state.');
528
+ if (latest?.blockers?.length) {
529
+ console.log('');
530
+ console.log(`Blockers: ${latest.blockers.join('; ')}`);
531
+ }
532
+ } else if (command === 'details' || command === 'bootstrap') {
533
+ await refreshBootstrap(context, mode);
534
+ const taskBrief = getTaskBrief(context);
535
+ const executionPlan = getExecutionPlan(context);
536
+ const ownerActions = getOwnerActions(context);
537
+ if (taskBrief.brief?.item) {
538
+ console.log(`Task: ${taskBrief.brief.summary}`);
539
+ if (taskBrief.brief.item.description) {
540
+ console.log('');
541
+ console.log(taskBrief.brief.item.description);
542
+ }
543
+ if (taskBrief.brief.recommendedAction) {
544
+ console.log('');
545
+ console.log(`Next action: ${taskBrief.brief.recommendedAction}`);
546
+ }
547
+ if (Array.isArray(taskBrief.brief.guardrails) && taskBrief.brief.guardrails.length > 0) {
548
+ console.log('');
549
+ console.log('Guardrails:');
550
+ for (const line of taskBrief.brief.guardrails) {
551
+ console.log(`- ${line}`);
552
+ }
553
+ }
554
+ if (executionPlan.plan?.targetAreas?.length) {
555
+ console.log('');
556
+ console.log(`Target areas: ${executionPlan.plan.targetAreas.join(', ')}`);
557
+ }
558
+ if (executionPlan.plan?.steps?.length) {
559
+ console.log('');
560
+ console.log('Plan:');
561
+ for (const step of executionPlan.plan.steps) {
562
+ console.log(`- ${step}`);
563
+ }
564
+ }
565
+ if (ownerActions.state?.actions?.length) {
566
+ console.log('');
567
+ console.log('Owner actions (non-blocking):');
568
+ for (const action of ownerActions.state.actions) {
569
+ console.log(`- ${action.title} [${(action.channels || []).join(', ')}]`);
570
+ }
571
+ }
572
+ } else {
573
+ const state = getBootstrapState(context);
574
+ console.log(state.state ? JSON.stringify(state.state, null, 2) : 'No bootstrap state written yet.');
575
+ }
576
+ } else if (command === 'done') {
577
+ const result = markCurrentItemDone(context);
578
+ if (!result.ok) {
579
+ console.log(result.error);
580
+ } else {
581
+ console.log(`Completed: ${result.completed.id} ${result.completed.title || ''}`);
582
+ if (result.next) {
583
+ console.log(`Next: ${result.next.id} ${result.next.title || ''}`);
584
+ } else {
585
+ console.log('Queue empty.');
586
+ }
587
+ }
588
+ } else if (command === 'worker') {
589
+ if (context.ledgerScope === 'all') {
590
+ console.log('Portfolio view does not run a single worker. Use a repo session to build.');
591
+ console.log('Try: build <venture>');
592
+ console.log('');
593
+ rl.prompt();
594
+ return;
595
+ }
596
+ await refreshBootstrap(context, mode);
597
+ const worker = getWorkerState(context);
598
+ if (worker.state) {
599
+ const status = pidIsAlive(worker.state.pid) ? worker.state.status || 'running' : 'stale';
600
+ console.log(`Worker: ${status}`);
601
+ if (worker.state.phase) {
602
+ console.log(`State: ${worker.state.phase}`);
603
+ }
604
+ console.log(`PID: ${worker.state.pid}`);
605
+ if (worker.state.currentItem) {
606
+ console.log(`Current: ${worker.state.currentItem.id} ${worker.state.currentItem.title || ''}`);
607
+ }
608
+ if (worker.state.nextItem) {
609
+ console.log(`Next: ${worker.state.nextItem.id} ${worker.state.nextItem.title || ''}`);
610
+ }
611
+ const taskBrief = getTaskBrief(context);
612
+ if (taskBrief.brief?.recommendedAction) {
613
+ console.log(`Action: ${taskBrief.brief.recommendedAction}`);
614
+ }
615
+ const executionPlan = getExecutionPlan(context);
616
+ if (executionPlan.plan?.targetAreas?.length) {
617
+ console.log(`Targets: ${executionPlan.plan.targetAreas.join(', ')}`);
618
+ }
619
+ const ownerActions = getOwnerActions(context);
620
+ if (ownerActions.state?.actions?.length) {
621
+ console.log(`Owner actions: ${ownerActions.state.actions.length} queued (non-blocking)`);
622
+ }
623
+ } else {
624
+ console.log('No worker state written yet.');
625
+ }
626
+ } else if (command === 'help') {
627
+ printHelp(context);
628
+ } else if (command === 'exit' || command === 'quit') {
629
+ rl.close();
630
+ return;
631
+ } else {
632
+ console.log('Unknown command. Use `help` for available commands.');
633
+ }
634
+ console.log('');
635
+ rl.prompt();
636
+ });
637
+
638
+ rl.on('close', () => {
639
+ if (!transitioning) {
640
+ console.log('Session closed.');
641
+ }
642
+ });
643
+ }
644
+
645
+ module.exports = {
646
+ runInteractiveSession,
647
+ renderSummary,
648
+ ensureWorker,
649
+ waitForWorkerState,
650
+ getWorkerState,
651
+ getTaskBrief,
652
+ getExecutionPlan,
653
+ getOwnerActions,
654
+ pidIsAlive,
655
+ };