specsmd 0.0.0-dev.85 → 0.0.0-dev.87

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 (62) hide show
  1. package/README.md +15 -0
  2. package/bin/cli.js +15 -1
  3. package/flows/fire/agents/builder/agent.md +2 -2
  4. package/flows/fire/agents/builder/skills/code-review/SKILL.md +1 -1
  5. package/flows/fire/agents/builder/skills/run-execute/SKILL.md +16 -7
  6. package/flows/fire/agents/builder/skills/run-execute/scripts/complete-run.cjs +22 -3
  7. package/flows/fire/agents/builder/skills/run-execute/scripts/init-run.cjs +63 -20
  8. package/flows/fire/agents/builder/skills/run-execute/scripts/update-checkpoint.cjs +254 -0
  9. package/flows/fire/agents/builder/skills/run-execute/scripts/update-phase.cjs +17 -6
  10. package/flows/fire/agents/builder/skills/run-status/SKILL.md +1 -1
  11. package/flows/fire/agents/builder/skills/walkthrough-generate/SKILL.md +30 -27
  12. package/flows/fire/agents/orchestrator/agent.md +1 -1
  13. package/flows/fire/agents/orchestrator/skills/status/SKILL.md +2 -2
  14. package/flows/fire/memory-bank.yaml +4 -4
  15. package/flows/ideation/agents/orchestrator/agent.md +8 -7
  16. package/flows/ideation/agents/orchestrator/skills/flame/SKILL.md +1 -0
  17. package/flows/ideation/agents/orchestrator/skills/flame/references/evaluation-criteria.md +4 -0
  18. package/flows/ideation/agents/orchestrator/skills/flame/references/six-hats-method.md +12 -0
  19. package/flows/ideation/agents/orchestrator/skills/forge/SKILL.md +1 -0
  20. package/flows/ideation/agents/orchestrator/skills/forge/references/disney-method.md +8 -0
  21. package/flows/ideation/agents/orchestrator/skills/forge/references/pitch-framework.md +15 -0
  22. package/flows/ideation/agents/orchestrator/skills/spark/SKILL.md +1 -0
  23. package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/analogy.md +7 -0
  24. package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/first-principles.md +5 -0
  25. package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/inversion.md +6 -0
  26. package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/questorming.md +6 -0
  27. package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/random-word.md +1 -0
  28. package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/scamper.md +15 -0
  29. package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/what-if.md +6 -0
  30. package/flows/ideation/shared/protocols/anti-bias.md +7 -4
  31. package/flows/ideation/shared/protocols/deep-thinking.md +7 -0
  32. package/flows/ideation/shared/protocols/diverge-converge.md +2 -0
  33. package/flows/ideation/shared/protocols/interaction-adaptation.md +7 -0
  34. package/lib/dashboard/aidlc/parser.js +581 -0
  35. package/lib/dashboard/fire/model.js +382 -0
  36. package/lib/dashboard/fire/parser.js +470 -0
  37. package/lib/dashboard/flow-detect.js +86 -0
  38. package/lib/dashboard/git/changes.js +362 -0
  39. package/lib/dashboard/git/worktrees.js +248 -0
  40. package/lib/dashboard/index.js +709 -0
  41. package/lib/dashboard/runtime/watch-runtime.js +122 -0
  42. package/lib/dashboard/simple/parser.js +293 -0
  43. package/lib/dashboard/tui/app.js +1675 -0
  44. package/lib/dashboard/tui/components/error-banner.js +35 -0
  45. package/lib/dashboard/tui/components/header.js +60 -0
  46. package/lib/dashboard/tui/components/help-footer.js +15 -0
  47. package/lib/dashboard/tui/components/stats-strip.js +35 -0
  48. package/lib/dashboard/tui/file-entries.js +383 -0
  49. package/lib/dashboard/tui/flow-builders.js +991 -0
  50. package/lib/dashboard/tui/git-builders.js +218 -0
  51. package/lib/dashboard/tui/helpers.js +236 -0
  52. package/lib/dashboard/tui/overlays.js +242 -0
  53. package/lib/dashboard/tui/preview.js +220 -0
  54. package/lib/dashboard/tui/renderer.js +76 -0
  55. package/lib/dashboard/tui/row-builders.js +797 -0
  56. package/lib/dashboard/tui/sections.js +45 -0
  57. package/lib/dashboard/tui/store.js +44 -0
  58. package/lib/dashboard/tui/views/overview-view.js +61 -0
  59. package/lib/dashboard/tui/views/runs-view.js +93 -0
  60. package/lib/dashboard/tui/worktree-builders.js +229 -0
  61. package/lib/installers/CodexInstaller.js +72 -1
  62. package/package.json +7 -3
@@ -0,0 +1,991 @@
1
+ const path = require('path');
2
+ const { truncate, formatTime, fileExists, readFileTextSafe, normalizeToken } = require('./helpers');
3
+
4
+ function buildShortStats(snapshot, flow) {
5
+ if (!snapshot?.initialized) {
6
+ if (flow === 'aidlc') {
7
+ return 'init: waiting for memory-bank scan';
8
+ }
9
+ if (flow === 'simple') {
10
+ return 'init: waiting for specs scan';
11
+ }
12
+ return 'init: waiting for state.yaml';
13
+ }
14
+
15
+ const stats = snapshot?.stats || {};
16
+
17
+ if (flow === 'aidlc') {
18
+ return `bolts ${stats.activeBoltsCount || 0}/${stats.completedBolts || 0} | intents ${stats.completedIntents || 0}/${stats.totalIntents || 0} | stories ${stats.completedStories || 0}/${stats.totalStories || 0}`;
19
+ }
20
+
21
+ if (flow === 'simple') {
22
+ return `specs ${stats.completedSpecs || 0}/${stats.totalSpecs || 0} | tasks ${stats.completedTasks || 0}/${stats.totalTasks || 0} | active ${stats.activeSpecsCount || 0}`;
23
+ }
24
+
25
+ return `runs ${stats.activeRunsCount || 0}/${stats.completedRuns || 0} | intents ${stats.completedIntents || 0}/${stats.totalIntents || 0} | work ${stats.completedWorkItems || 0}/${stats.totalWorkItems || 0}`;
26
+ }
27
+
28
+ function buildHeaderLine(snapshot, flow, watchEnabled, watchStatus, lastRefreshAt, view, width, worktreeLabel = null) {
29
+ const projectName = snapshot?.project?.name || 'Unnamed project';
30
+ const shortStats = buildShortStats(snapshot, flow);
31
+ const worktreeSegment = worktreeLabel ? ` | wt:${worktreeLabel}` : '';
32
+ const line = `${flow.toUpperCase()} | ${projectName} | ${shortStats} | watch:${watchEnabled ? watchStatus : 'off'}${worktreeSegment} | ${view} | ${formatTime(lastRefreshAt)}`;
33
+
34
+ return truncate(line, width);
35
+ }
36
+
37
+ function buildErrorLines(error, width) {
38
+ if (!error) {
39
+ return [];
40
+ }
41
+
42
+ const lines = [`[${error.code || 'ERROR'}] ${error.message || 'Unknown error'}`];
43
+
44
+ if (error.details) {
45
+ lines.push(`details: ${error.details}`);
46
+ }
47
+ if (error.path) {
48
+ lines.push(`path: ${error.path}`);
49
+ }
50
+ if (error.hint) {
51
+ lines.push(`hint: ${error.hint}`);
52
+ }
53
+
54
+ return lines.map((line) => truncate(line, width));
55
+ }
56
+
57
+ function getCurrentRun(snapshot) {
58
+ const activeRuns = Array.isArray(snapshot?.activeRuns) ? [...snapshot.activeRuns] : [];
59
+ if (activeRuns.length === 0) {
60
+ return null;
61
+ }
62
+
63
+ activeRuns.sort((a, b) => {
64
+ const aTime = a?.startedAt ? Date.parse(a.startedAt) : 0;
65
+ const bTime = b?.startedAt ? Date.parse(b.startedAt) : 0;
66
+ if (bTime !== aTime) {
67
+ return bTime - aTime;
68
+ }
69
+ return String(a?.id || '').localeCompare(String(b?.id || ''));
70
+ });
71
+
72
+ return activeRuns[0] || null;
73
+ }
74
+
75
+ function getCurrentFireWorkItem(run) {
76
+ const workItems = Array.isArray(run?.workItems) ? run.workItems : [];
77
+ if (workItems.length === 0) {
78
+ return null;
79
+ }
80
+ return workItems.find((item) => item.id === run.currentItem)
81
+ || workItems.find((item) => normalizeToken(item?.status) === 'in_progress')
82
+ || workItems[0]
83
+ || null;
84
+ }
85
+
86
+ function extractFrontmatterBlock(content) {
87
+ if (typeof content !== 'string') {
88
+ return null;
89
+ }
90
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
91
+ return match ? match[1] : null;
92
+ }
93
+
94
+ function extractFrontmatterValue(frontmatterBlock, key) {
95
+ if (typeof frontmatterBlock !== 'string' || typeof key !== 'string' || key === '') {
96
+ return null;
97
+ }
98
+
99
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
100
+ const expression = new RegExp(`^${escapedKey}\\s*:\\s*(.+)$`, 'mi');
101
+ const match = frontmatterBlock.match(expression);
102
+ if (!match) {
103
+ return null;
104
+ }
105
+
106
+ const raw = String(match[1] || '').trim();
107
+ if (raw === '') {
108
+ return '';
109
+ }
110
+
111
+ return raw
112
+ .replace(/^["']/, '')
113
+ .replace(/["']$/, '')
114
+ .trim();
115
+ }
116
+
117
+ const FIRE_AWAITING_APPROVAL_STATES = new Set([
118
+ 'awaiting_approval',
119
+ 'waiting',
120
+ 'pending_approval',
121
+ 'approval_needed',
122
+ 'approval_required',
123
+ 'checkpoint_pending'
124
+ ]);
125
+
126
+ const FIRE_APPROVED_STATES = new Set([
127
+ 'approved',
128
+ 'confirmed',
129
+ 'accepted',
130
+ 'resumed',
131
+ 'done',
132
+ 'completed',
133
+ 'cleared',
134
+ 'none',
135
+ 'not_required',
136
+ 'skipped'
137
+ ]);
138
+
139
+ function parseFirePlanCheckpointMetadata(run) {
140
+ if (!run || typeof run.folderPath !== 'string' || run.folderPath.trim() === '') {
141
+ return { hasPlan: false, checkpointState: null, checkpoint: null };
142
+ }
143
+
144
+ const planPath = path.join(run.folderPath, 'plan.md');
145
+ if (!fileExists(planPath)) {
146
+ return { hasPlan: false, checkpointState: null, checkpoint: null };
147
+ }
148
+
149
+ const content = readFileTextSafe(planPath);
150
+ const frontmatter = extractFrontmatterBlock(content);
151
+ if (!frontmatter) {
152
+ return { hasPlan: true, checkpointState: null, checkpoint: null };
153
+ }
154
+
155
+ const checkpointState = normalizeToken(
156
+ extractFrontmatterValue(frontmatter, 'checkpoint_state')
157
+ || extractFrontmatterValue(frontmatter, 'checkpointState')
158
+ || extractFrontmatterValue(frontmatter, 'approval_state')
159
+ || extractFrontmatterValue(frontmatter, 'approvalState')
160
+ || ''
161
+ ) || null;
162
+ const checkpoint = extractFrontmatterValue(frontmatter, 'current_checkpoint')
163
+ || extractFrontmatterValue(frontmatter, 'currentCheckpoint')
164
+ || extractFrontmatterValue(frontmatter, 'checkpoint')
165
+ || null;
166
+
167
+ return {
168
+ hasPlan: true,
169
+ checkpointState,
170
+ checkpoint
171
+ };
172
+ }
173
+
174
+ function resolveFireApprovalState(run, currentWorkItem) {
175
+ const itemState = normalizeToken(
176
+ currentWorkItem?.checkpointState
177
+ || currentWorkItem?.checkpoint_state
178
+ || currentWorkItem?.approvalState
179
+ || currentWorkItem?.approval_state
180
+ || ''
181
+ );
182
+ const runState = normalizeToken(
183
+ run?.checkpointState
184
+ || run?.checkpoint_state
185
+ || run?.approvalState
186
+ || run?.approval_state
187
+ || ''
188
+ );
189
+ const planState = parseFirePlanCheckpointMetadata(run);
190
+ const state = itemState || runState || planState.checkpointState || null;
191
+ const checkpointValue = currentWorkItem?.currentCheckpoint
192
+ || currentWorkItem?.current_checkpoint
193
+ || run?.currentCheckpoint
194
+ || run?.current_checkpoint
195
+ || planState.checkpoint
196
+ || null;
197
+
198
+ return {
199
+ state,
200
+ checkpoint: checkpointValue,
201
+ source: itemState
202
+ ? 'item-state'
203
+ : (runState
204
+ ? 'run-state'
205
+ : (planState.checkpointState ? 'plan-frontmatter' : null))
206
+ };
207
+ }
208
+
209
+ function getCurrentPhaseLabel(run, currentWorkItem) {
210
+ const phase = currentWorkItem?.currentPhase || '';
211
+ if (typeof phase === 'string' && phase !== '') {
212
+ return phase.toLowerCase();
213
+ }
214
+
215
+ if (run?.hasTestReport) {
216
+ return 'review';
217
+ }
218
+ if (run?.hasPlan) {
219
+ return 'execute';
220
+ }
221
+ return 'plan';
222
+ }
223
+
224
+ function getFireRunApprovalGate(run, currentWorkItem) {
225
+ const mode = normalizeToken(currentWorkItem?.mode);
226
+ const status = normalizeToken(currentWorkItem?.status);
227
+ if (!['confirm', 'validate'].includes(mode) || status !== 'in_progress') {
228
+ return null;
229
+ }
230
+
231
+ const phase = normalizeToken(getCurrentPhaseLabel(run, currentWorkItem));
232
+ if (phase !== 'plan') {
233
+ return null;
234
+ }
235
+
236
+ const resolvedApproval = resolveFireApprovalState(run, currentWorkItem);
237
+ if (!resolvedApproval.state) {
238
+ return null;
239
+ }
240
+
241
+ if (FIRE_APPROVED_STATES.has(resolvedApproval.state)) {
242
+ return null;
243
+ }
244
+
245
+ if (!FIRE_AWAITING_APPROVAL_STATES.has(resolvedApproval.state)) {
246
+ return null;
247
+ }
248
+
249
+ const modeLabel = String(currentWorkItem?.mode || 'confirm').toUpperCase();
250
+ const itemId = String(currentWorkItem?.id || run.currentItem || 'unknown-item');
251
+ const checkpointLabel = String(resolvedApproval.checkpoint || 'plan').replace(/[_\s]+/g, '-');
252
+
253
+ return {
254
+ flow: 'fire',
255
+ title: 'Approval Needed',
256
+ message: `${run.id}: ${itemId} (${modeLabel}) is waiting at ${checkpointLabel} checkpoint`,
257
+ checkpoint: checkpointLabel,
258
+ source: resolvedApproval.source
259
+ };
260
+ }
261
+
262
+ function isFireRunAwaitingApproval(run, currentWorkItem) {
263
+ return Boolean(getFireRunApprovalGate(run, currentWorkItem));
264
+ }
265
+
266
+ function detectFireRunApprovalGate(snapshot) {
267
+ const run = getCurrentRun(snapshot);
268
+ if (!run) {
269
+ return null;
270
+ }
271
+
272
+ const currentWorkItem = getCurrentFireWorkItem(run);
273
+ if (!currentWorkItem) {
274
+ return null;
275
+ }
276
+
277
+ return getFireRunApprovalGate(run, currentWorkItem);
278
+ }
279
+
280
+ function normalizeStageName(stage) {
281
+ return normalizeToken(stage).replace(/_/g, '-');
282
+ }
283
+
284
+ function getAidlcCheckpointSignalFiles(boltType, stageName) {
285
+ const normalizedType = normalizeToken(boltType).replace(/_/g, '-');
286
+ const normalizedStage = normalizeStageName(stageName);
287
+
288
+ if (normalizedType === 'simple-construction-bolt') {
289
+ if (normalizedStage === 'plan') return ['implementation-plan.md'];
290
+ if (normalizedStage === 'implement') return ['implementation-walkthrough.md'];
291
+ if (normalizedStage === 'test') return ['test-walkthrough.md'];
292
+ return [];
293
+ }
294
+
295
+ if (normalizedType === 'ddd-construction-bolt') {
296
+ if (normalizedStage === 'model') return ['ddd-01-domain-model.md'];
297
+ if (normalizedStage === 'design') return ['ddd-02-technical-design.md'];
298
+ if (normalizedStage === 'implement') return ['implementation-walkthrough.md'];
299
+ if (normalizedStage === 'test') return ['ddd-03-test-report.md'];
300
+ return [];
301
+ }
302
+
303
+ if (normalizedType === 'spike-bolt') {
304
+ if (normalizedStage === 'explore') return ['spike-exploration.md'];
305
+ if (normalizedStage === 'document') return ['spike-report.md'];
306
+ return [];
307
+ }
308
+
309
+ return [];
310
+ }
311
+
312
+ function hasAidlcCheckpointSignal(bolt, stageName) {
313
+ const fileNames = Array.isArray(bolt?.files) ? bolt.files : [];
314
+ const lowerNames = new Set(fileNames.map((name) => String(name || '').toLowerCase()));
315
+ const expectedFiles = getAidlcCheckpointSignalFiles(bolt?.type, stageName)
316
+ .map((name) => String(name).toLowerCase());
317
+
318
+ for (const expectedFile of expectedFiles) {
319
+ if (lowerNames.has(expectedFile)) {
320
+ return true;
321
+ }
322
+ }
323
+
324
+ if (normalizeStageName(stageName) === 'adr') {
325
+ for (const name of lowerNames) {
326
+ if (/^adr-[\w-]+\.md$/.test(name)) {
327
+ return true;
328
+ }
329
+ }
330
+ }
331
+
332
+ return false;
333
+ }
334
+
335
+ function isAidlcBoltAwaitingApproval(bolt) {
336
+ if (!bolt || normalizeToken(bolt.status) !== 'in_progress') {
337
+ return false;
338
+ }
339
+
340
+ const currentStage = normalizeStageName(bolt.currentStage);
341
+ if (!currentStage) {
342
+ return false;
343
+ }
344
+
345
+ const stages = Array.isArray(bolt.stages) ? bolt.stages : [];
346
+ const stageMeta = stages.find((stage) => normalizeStageName(stage?.name) === currentStage);
347
+ if (normalizeToken(stageMeta?.status) === 'completed') {
348
+ return false;
349
+ }
350
+
351
+ return hasAidlcCheckpointSignal(bolt, currentStage);
352
+ }
353
+
354
+ function getCurrentBolt(snapshot) {
355
+ const activeBolts = Array.isArray(snapshot?.activeBolts) ? [...snapshot.activeBolts] : [];
356
+ if (activeBolts.length === 0) {
357
+ return null;
358
+ }
359
+
360
+ activeBolts.sort((a, b) => {
361
+ const aTime = a?.startedAt ? Date.parse(a.startedAt) : 0;
362
+ const bTime = b?.startedAt ? Date.parse(b.startedAt) : 0;
363
+ if (bTime !== aTime) {
364
+ return bTime - aTime;
365
+ }
366
+ return String(a?.id || '').localeCompare(String(b?.id || ''));
367
+ });
368
+
369
+ return activeBolts[0] || null;
370
+ }
371
+
372
+ function detectAidlcBoltApprovalGate(snapshot) {
373
+ const bolt = getCurrentBolt(snapshot);
374
+ if (!bolt) {
375
+ return null;
376
+ }
377
+
378
+ if (!isAidlcBoltAwaitingApproval(bolt)) {
379
+ return null;
380
+ }
381
+
382
+ return {
383
+ flow: 'aidlc',
384
+ title: 'Approval Needed',
385
+ message: `${bolt.id}: ${bolt.currentStage || 'current'} stage is waiting for confirmation`
386
+ };
387
+ }
388
+
389
+ function getEffectiveFlow(flow, snapshot) {
390
+ const explicitFlow = typeof flow === 'string' && flow !== '' ? flow : null;
391
+ const snapshotFlow = typeof snapshot?.flow === 'string' && snapshot.flow !== '' ? snapshot.flow : null;
392
+ return (snapshotFlow || explicitFlow || 'fire').toLowerCase();
393
+ }
394
+
395
+ function detectDashboardApprovalGate(snapshot, flow) {
396
+ const effectiveFlow = getEffectiveFlow(flow, snapshot);
397
+ if (effectiveFlow === 'fire') {
398
+ return detectFireRunApprovalGate(snapshot);
399
+ }
400
+ if (effectiveFlow === 'aidlc') {
401
+ return detectAidlcBoltApprovalGate(snapshot);
402
+ }
403
+ return null;
404
+ }
405
+
406
+ function buildPhaseTrack(currentPhase) {
407
+ const order = ['plan', 'execute', 'test', 'review'];
408
+ const labels = ['P', 'E', 'T', 'R'];
409
+ const currentIndex = Math.max(0, order.indexOf(currentPhase));
410
+ return labels.map((label, index) => (index === currentIndex ? `[${label}]` : ` ${label} `)).join(' - ');
411
+ }
412
+
413
+ function buildFireCurrentRunLines(snapshot, width) {
414
+ const run = getCurrentRun(snapshot);
415
+ if (!run) {
416
+ return [truncate('No active run', width)];
417
+ }
418
+
419
+ const workItems = Array.isArray(run.workItems) ? run.workItems : [];
420
+ const completed = workItems.filter((item) => item.status === 'completed').length;
421
+ const currentWorkItem = workItems.find((item) => item.id === run.currentItem) || workItems.find((item) => item.status === 'in_progress') || workItems[0];
422
+
423
+ const itemId = currentWorkItem?.id || run.currentItem || 'n/a';
424
+ const mode = String(currentWorkItem?.mode || 'confirm').toUpperCase();
425
+ const status = currentWorkItem?.status || 'pending';
426
+ const currentPhase = getCurrentPhaseLabel(run, currentWorkItem);
427
+ const phaseTrack = buildPhaseTrack(currentPhase);
428
+
429
+ const lines = [
430
+ `${run.id} [${run.scope}] ${completed}/${workItems.length} items done`,
431
+ `work item: ${itemId}`,
432
+ `mode: ${mode} | status: ${status}`,
433
+ `phase: ${phaseTrack}`
434
+ ];
435
+
436
+ return lines.map((line) => truncate(line, width));
437
+ }
438
+
439
+ function buildFirePendingLines(snapshot, width) {
440
+ const pending = snapshot?.pendingItems || [];
441
+ if (pending.length === 0) {
442
+ return [truncate('No pending work items', width)];
443
+ }
444
+
445
+ return pending.map((item) => {
446
+ const deps = item.dependencies && item.dependencies.length > 0 ? ` deps:${item.dependencies.join(',')}` : '';
447
+ return truncate(`${item.id} (${item.mode}/${item.complexity}) in ${item.intentTitle}${deps}`, width);
448
+ });
449
+ }
450
+
451
+ function buildFireCompletedLines(snapshot, width) {
452
+ const completedRuns = snapshot?.completedRuns || [];
453
+ if (completedRuns.length === 0) {
454
+ return [truncate('No completed runs yet', width)];
455
+ }
456
+
457
+ return completedRuns.map((run) => {
458
+ const workItems = Array.isArray(run.workItems) ? run.workItems : [];
459
+ const completed = workItems.filter((item) => item.status === 'completed').length;
460
+ return truncate(`${run.id} [${run.scope}] ${completed}/${workItems.length} done at ${run.completedAt || 'unknown'}`, width);
461
+ });
462
+ }
463
+
464
+ function buildFireStatsLines(snapshot, width) {
465
+ if (!snapshot?.initialized) {
466
+ return [truncate('Waiting for .specs-fire/state.yaml initialization.', width)];
467
+ }
468
+
469
+ const stats = snapshot.stats;
470
+ return [
471
+ `intents: ${stats.completedIntents}/${stats.totalIntents} done | in_progress: ${stats.inProgressIntents} | blocked: ${stats.blockedIntents}`,
472
+ `work items: ${stats.completedWorkItems}/${stats.totalWorkItems} done | in_progress: ${stats.inProgressWorkItems} | pending: ${stats.pendingWorkItems} | blocked: ${stats.blockedWorkItems}`,
473
+ `runs: ${stats.activeRunsCount} active | ${stats.completedRuns} completed | ${stats.totalRuns} total`
474
+ ].map((line) => truncate(line, width));
475
+ }
476
+
477
+ function buildWarningsLines(snapshot, width) {
478
+ const warnings = snapshot?.warnings || [];
479
+ if (warnings.length === 0) {
480
+ return [truncate('No warnings', width)];
481
+ }
482
+
483
+ return warnings.map((warning) => truncate(warning, width));
484
+ }
485
+
486
+ function buildFireOverviewProjectLines(snapshot, width) {
487
+ if (!snapshot?.initialized) {
488
+ return [
489
+ truncate('FIRE folder detected, but state.yaml is missing.', width),
490
+ truncate('Initialize project context and this view will populate.', width)
491
+ ];
492
+ }
493
+
494
+ const project = snapshot.project || {};
495
+ const workspace = snapshot.workspace || {};
496
+
497
+ return [
498
+ `project: ${project.name || 'unknown'} | fire_version: ${project.fireVersion || snapshot.version || '0.0.0'}`,
499
+ `workspace: ${workspace.type || 'unknown'} / ${workspace.structure || 'unknown'}`,
500
+ `autonomy: ${workspace.autonomyBias || 'unknown'} | run scope pref: ${workspace.runScopePreference || 'unknown'}`
501
+ ].map((line) => truncate(line, width));
502
+ }
503
+
504
+ function buildFireOverviewIntentLines(snapshot, width) {
505
+ const intents = snapshot?.intents || [];
506
+ if (intents.length === 0) {
507
+ return [truncate('No intents found', width)];
508
+ }
509
+
510
+ return intents.map((intent) => {
511
+ const workItems = Array.isArray(intent.workItems) ? intent.workItems : [];
512
+ const done = workItems.filter((item) => item.status === 'completed').length;
513
+ return truncate(`${intent.id}: ${intent.status} (${done}/${workItems.length} work items)`, width);
514
+ });
515
+ }
516
+
517
+ function buildFireOverviewStandardsLines(snapshot, width) {
518
+ const expected = ['constitution', 'tech-stack', 'coding-standards', 'testing-standards', 'system-architecture'];
519
+ const actual = new Set((snapshot?.standards || []).map((item) => item.type));
520
+
521
+ return expected.map((name) => {
522
+ const marker = actual.has(name) ? '[x]' : '[ ]';
523
+ return truncate(`${marker} ${name}.md`, width);
524
+ });
525
+ }
526
+
527
+ function buildAidlcStageTrack(bolt) {
528
+ const stages = Array.isArray(bolt?.stages) ? bolt.stages : [];
529
+ if (stages.length === 0) {
530
+ return 'n/a';
531
+ }
532
+
533
+ return stages.map((stage) => {
534
+ const label = String(stage?.name || '?').charAt(0).toUpperCase();
535
+ if (stage?.status === 'completed') {
536
+ return `[${label}]`;
537
+ }
538
+ if (stage?.status === 'in_progress') {
539
+ return `<${label}>`;
540
+ }
541
+ return ` ${label} `;
542
+ }).join('-');
543
+ }
544
+
545
+ function buildAidlcCurrentRunLines(snapshot, width) {
546
+ const bolt = getCurrentBolt(snapshot);
547
+ if (!bolt) {
548
+ return [truncate('No active bolt', width)];
549
+ }
550
+
551
+ const stages = Array.isArray(bolt.stages) ? bolt.stages : [];
552
+ const completedStages = stages.filter((stage) => stage.status === 'completed').length;
553
+ const phaseTrack = buildAidlcStageTrack(bolt);
554
+ const location = `${bolt.intent || 'unknown-intent'} / ${bolt.unit || 'unknown-unit'}`;
555
+
556
+ const lines = [
557
+ `${bolt.id} [${bolt.type}] ${completedStages}/${stages.length} stages done`,
558
+ `scope: ${location}`,
559
+ `stage: ${bolt.currentStage || 'n/a'} | status: ${bolt.status}`,
560
+ `phase: ${phaseTrack}`
561
+ ];
562
+
563
+ return lines.map((line) => truncate(line, width));
564
+ }
565
+
566
+ function buildAidlcPendingLines(snapshot, width) {
567
+ const pendingBolts = Array.isArray(snapshot?.pendingBolts) ? snapshot.pendingBolts : [];
568
+ if (pendingBolts.length === 0) {
569
+ return [truncate('No queued bolts', width)];
570
+ }
571
+
572
+ return pendingBolts.map((bolt) => {
573
+ const deps = Array.isArray(bolt.blockedBy) && bolt.blockedBy.length > 0
574
+ ? ` blocked_by:${bolt.blockedBy.join(',')}`
575
+ : '';
576
+ const location = `${bolt.intent || 'unknown'}/${bolt.unit || 'unknown'}`;
577
+ return truncate(`${bolt.id} (${bolt.status}) in ${location}${deps}`, width);
578
+ });
579
+ }
580
+
581
+ function buildAidlcCompletedLines(snapshot, width) {
582
+ const completedBolts = Array.isArray(snapshot?.completedBolts) ? snapshot.completedBolts : [];
583
+ if (completedBolts.length === 0) {
584
+ return [truncate('No completed bolts yet', width)];
585
+ }
586
+
587
+ return completedBolts.map((bolt) =>
588
+ truncate(`${bolt.id} [${bolt.type}] done at ${bolt.completedAt || 'unknown'}`, width)
589
+ );
590
+ }
591
+
592
+ function buildAidlcStatsLines(snapshot, width) {
593
+ const stats = snapshot?.stats || {};
594
+
595
+ return [
596
+ `intents: ${stats.completedIntents || 0}/${stats.totalIntents || 0} done | in_progress: ${stats.inProgressIntents || 0} | blocked: ${stats.blockedIntents || 0}`,
597
+ `stories: ${stats.completedStories || 0}/${stats.totalStories || 0} done | in_progress: ${stats.inProgressStories || 0} | pending: ${stats.pendingStories || 0} | blocked: ${stats.blockedStories || 0}`,
598
+ `bolts: ${stats.activeBoltsCount || 0} active | ${stats.queuedBolts || 0} queued | ${stats.blockedBolts || 0} blocked | ${stats.completedBolts || 0} done`
599
+ ].map((line) => truncate(line, width));
600
+ }
601
+
602
+ function buildAidlcOverviewProjectLines(snapshot, width) {
603
+ const project = snapshot?.project || {};
604
+ const stats = snapshot?.stats || {};
605
+
606
+ return [
607
+ `project: ${project.name || 'unknown'} | project_type: ${project.projectType || 'unknown'}`,
608
+ `memory-bank: intents ${stats.totalIntents || 0} | units ${stats.totalUnits || 0} | stories ${stats.totalStories || 0}`,
609
+ `progress: ${stats.progressPercent || 0}% stories complete | standards: ${(snapshot?.standards || []).length}`
610
+ ].map((line) => truncate(line, width));
611
+ }
612
+
613
+ function buildAidlcOverviewIntentLines(snapshot, width) {
614
+ const intents = Array.isArray(snapshot?.intents) ? snapshot.intents : [];
615
+ if (intents.length === 0) {
616
+ return [truncate('No intents found', width)];
617
+ }
618
+
619
+ return intents.map((intent) => {
620
+ return truncate(
621
+ `${intent.id}: ${intent.status} (${intent.completedStories || 0}/${intent.storyCount || 0} stories, ${intent.completedUnits || 0}/${intent.unitCount || 0} units)`,
622
+ width
623
+ );
624
+ });
625
+ }
626
+
627
+ function buildAidlcOverviewStandardsLines(snapshot, width) {
628
+ const standards = Array.isArray(snapshot?.standards) ? snapshot.standards : [];
629
+ if (standards.length === 0) {
630
+ return [truncate('No standards found under memory-bank/standards', width)];
631
+ }
632
+
633
+ return standards.map((standard) =>
634
+ truncate(`[x] ${standard.name || standard.type || 'unknown'}.md`, width)
635
+ );
636
+ }
637
+
638
+ function getCurrentSpec(snapshot) {
639
+ const specs = Array.isArray(snapshot?.activeSpecs) ? snapshot.activeSpecs : [];
640
+ if (specs.length === 0) {
641
+ return null;
642
+ }
643
+ return specs[0] || null;
644
+ }
645
+
646
+ function simplePhaseIndex(state) {
647
+ if (state === 'requirements_pending') {
648
+ return 0;
649
+ }
650
+ if (state === 'design_pending') {
651
+ return 1;
652
+ }
653
+ return 2;
654
+ }
655
+
656
+ function buildSimplePhaseTrack(spec) {
657
+ if (spec?.state === 'completed') {
658
+ return '[R] - [D] - [T]';
659
+ }
660
+
661
+ const labels = ['R', 'D', 'T'];
662
+ const current = simplePhaseIndex(spec?.state);
663
+ return labels.map((label, index) => (index === current ? `[${label}]` : ` ${label} `)).join(' - ');
664
+ }
665
+
666
+ function buildSimpleCurrentRunLines(snapshot, width) {
667
+ const spec = getCurrentSpec(snapshot);
668
+ if (!spec) {
669
+ return [truncate('No active spec', width)];
670
+ }
671
+
672
+ const files = [
673
+ spec.hasRequirements ? 'req' : '-',
674
+ spec.hasDesign ? 'design' : '-',
675
+ spec.hasTasks ? 'tasks' : '-'
676
+ ].join('/');
677
+
678
+ const lines = [
679
+ `${spec.name} [${spec.state}] ${spec.tasksCompleted}/${spec.tasksTotal} tasks done`,
680
+ `phase: ${spec.phase}`,
681
+ `files: ${files}`,
682
+ `track: ${buildSimplePhaseTrack(spec)}`
683
+ ];
684
+
685
+ return lines.map((line) => truncate(line, width));
686
+ }
687
+
688
+ function buildSimplePendingLines(snapshot, width) {
689
+ const pendingSpecs = Array.isArray(snapshot?.pendingSpecs) ? snapshot.pendingSpecs : [];
690
+ if (pendingSpecs.length === 0) {
691
+ return [truncate('No pending specs', width)];
692
+ }
693
+
694
+ return pendingSpecs.map((spec) =>
695
+ truncate(`${spec.name} (${spec.state}) ${spec.tasksCompleted}/${spec.tasksTotal} tasks`, width)
696
+ );
697
+ }
698
+
699
+ function buildSimpleCompletedLines(snapshot, width) {
700
+ const completedSpecs = Array.isArray(snapshot?.completedSpecs) ? snapshot.completedSpecs : [];
701
+ if (completedSpecs.length === 0) {
702
+ return [truncate('No completed specs yet', width)];
703
+ }
704
+
705
+ return completedSpecs.map((spec) =>
706
+ truncate(`${spec.name} done at ${spec.updatedAt || 'unknown'} (${spec.tasksCompleted}/${spec.tasksTotal})`, width)
707
+ );
708
+ }
709
+
710
+ function buildSimpleStatsLines(snapshot, width) {
711
+ const stats = snapshot?.stats || {};
712
+
713
+ return [
714
+ `specs: ${stats.completedSpecs || 0}/${stats.totalSpecs || 0} complete | in_progress: ${stats.inProgressSpecs || 0} | pending: ${stats.pendingSpecs || 0}`,
715
+ `pipeline: ready ${stats.readySpecs || 0} | design_pending ${stats.designPendingSpecs || 0} | tasks_pending ${stats.tasksPendingSpecs || 0}`,
716
+ `tasks: ${stats.completedTasks || 0}/${stats.totalTasks || 0} complete | pending: ${stats.pendingTasks || 0} | optional: ${stats.optionalTasks || 0}`
717
+ ].map((line) => truncate(line, width));
718
+ }
719
+
720
+ function buildSimpleOverviewProjectLines(snapshot, width) {
721
+ const project = snapshot?.project || {};
722
+ const stats = snapshot?.stats || {};
723
+
724
+ return [
725
+ `project: ${project.name || 'unknown'} | simple flow`,
726
+ `specs: ${stats.totalSpecs || 0} total | active: ${stats.activeSpecsCount || 0} | completed: ${stats.completedSpecs || 0}`,
727
+ `tasks: ${stats.completedTasks || 0}/${stats.totalTasks || 0} complete (${stats.progressPercent || 0}%)`
728
+ ].map((line) => truncate(line, width));
729
+ }
730
+
731
+ function buildSimpleOverviewIntentLines(snapshot, width) {
732
+ const specs = Array.isArray(snapshot?.specs) ? snapshot.specs : [];
733
+ if (specs.length === 0) {
734
+ return [truncate('No specs found', width)];
735
+ }
736
+
737
+ return specs.map((spec) =>
738
+ truncate(`${spec.name}: ${spec.state} (${spec.tasksCompleted}/${spec.tasksTotal} tasks)`, width)
739
+ );
740
+ }
741
+
742
+ function buildSimpleOverviewStandardsLines(snapshot, width) {
743
+ const specs = Array.isArray(snapshot?.specs) ? snapshot.specs : [];
744
+ if (specs.length === 0) {
745
+ return [truncate('No spec artifacts found', width)];
746
+ }
747
+
748
+ const reqCount = specs.filter((spec) => spec.hasRequirements).length;
749
+ const designCount = specs.filter((spec) => spec.hasDesign).length;
750
+ const tasksCount = specs.filter((spec) => spec.hasTasks).length;
751
+ const total = specs.length;
752
+
753
+ return [
754
+ `[x] requirements.md coverage ${reqCount}/${total}`,
755
+ `[x] design.md coverage ${designCount}/${total}`,
756
+ `[x] tasks.md coverage ${tasksCount}/${total}`
757
+ ].map((line) => truncate(line, width));
758
+ }
759
+
760
+ function buildCurrentRunLines(snapshot, width, flow) {
761
+ const effectiveFlow = getEffectiveFlow(flow, snapshot);
762
+ if (effectiveFlow === 'aidlc') {
763
+ return buildAidlcCurrentRunLines(snapshot, width);
764
+ }
765
+ if (effectiveFlow === 'simple') {
766
+ return buildSimpleCurrentRunLines(snapshot, width);
767
+ }
768
+ return buildFireCurrentRunLines(snapshot, width);
769
+ }
770
+
771
+ function buildPendingLines(snapshot, width, flow) {
772
+ const effectiveFlow = getEffectiveFlow(flow, snapshot);
773
+ if (effectiveFlow === 'aidlc') {
774
+ return buildAidlcPendingLines(snapshot, width);
775
+ }
776
+ if (effectiveFlow === 'simple') {
777
+ return buildSimplePendingLines(snapshot, width);
778
+ }
779
+ return buildFirePendingLines(snapshot, width);
780
+ }
781
+
782
+ function buildCompletedLines(snapshot, width, flow) {
783
+ const effectiveFlow = getEffectiveFlow(flow, snapshot);
784
+ if (effectiveFlow === 'aidlc') {
785
+ return buildAidlcCompletedLines(snapshot, width);
786
+ }
787
+ if (effectiveFlow === 'simple') {
788
+ return buildSimpleCompletedLines(snapshot, width);
789
+ }
790
+ return buildFireCompletedLines(snapshot, width);
791
+ }
792
+
793
+ function buildStatsLines(snapshot, width, flow) {
794
+ const effectiveFlow = getEffectiveFlow(flow, snapshot);
795
+ if (!snapshot?.initialized) {
796
+ if (effectiveFlow === 'aidlc') {
797
+ return [truncate('Waiting for memory-bank initialization.', width)];
798
+ }
799
+ if (effectiveFlow === 'simple') {
800
+ return [truncate('Waiting for specs/ initialization.', width)];
801
+ }
802
+ return [truncate('Waiting for .specs-fire/state.yaml initialization.', width)];
803
+ }
804
+
805
+ if (effectiveFlow === 'aidlc') {
806
+ return buildAidlcStatsLines(snapshot, width);
807
+ }
808
+ if (effectiveFlow === 'simple') {
809
+ return buildSimpleStatsLines(snapshot, width);
810
+ }
811
+ return buildFireStatsLines(snapshot, width);
812
+ }
813
+
814
+ function buildOverviewProjectLines(snapshot, width, flow) {
815
+ const effectiveFlow = getEffectiveFlow(flow, snapshot);
816
+ if (effectiveFlow === 'aidlc') {
817
+ return buildAidlcOverviewProjectLines(snapshot, width);
818
+ }
819
+ if (effectiveFlow === 'simple') {
820
+ return buildSimpleOverviewProjectLines(snapshot, width);
821
+ }
822
+ return buildFireOverviewProjectLines(snapshot, width);
823
+ }
824
+
825
+ function listOverviewIntentEntries(snapshot, flow) {
826
+ const effectiveFlow = getEffectiveFlow(flow, snapshot);
827
+ if (effectiveFlow === 'aidlc') {
828
+ const intents = Array.isArray(snapshot?.intents) ? snapshot.intents : [];
829
+ return intents.map((intent) => ({
830
+ id: intent?.id || 'unknown',
831
+ status: intent?.status || 'pending',
832
+ line: `${intent?.id || 'unknown'}: ${intent?.status || 'pending'} (${intent?.completedStories || 0}/${intent?.storyCount || 0} stories, ${intent?.completedUnits || 0}/${intent?.unitCount || 0} units)`
833
+ }));
834
+ }
835
+ if (effectiveFlow === 'simple') {
836
+ const specs = Array.isArray(snapshot?.specs) ? snapshot.specs : [];
837
+ return specs.map((spec) => ({
838
+ id: spec?.name || 'unknown',
839
+ status: spec?.state || 'pending',
840
+ line: `${spec?.name || 'unknown'}: ${spec?.state || 'pending'} (${spec?.tasksCompleted || 0}/${spec?.tasksTotal || 0} tasks)`
841
+ }));
842
+ }
843
+ const intents = Array.isArray(snapshot?.intents) ? snapshot.intents : [];
844
+ return intents.map((intent) => {
845
+ const workItems = Array.isArray(intent?.workItems) ? intent.workItems : [];
846
+ const done = workItems.filter((item) => item.status === 'completed').length;
847
+ return {
848
+ id: intent?.id || 'unknown',
849
+ status: intent?.status || 'pending',
850
+ line: `${intent?.id || 'unknown'}: ${intent?.status || 'pending'} (${done}/${workItems.length} work items)`
851
+ };
852
+ });
853
+ }
854
+
855
+ function buildOverviewIntentLines(snapshot, width, flow, filter = 'next') {
856
+ const entries = listOverviewIntentEntries(snapshot, flow);
857
+ const normalizedFilter = filter === 'completed' ? 'completed' : 'next';
858
+ const isNextFilter = normalizedFilter === 'next';
859
+ const nextLabel = isNextFilter ? '[NEXT]' : ' next ';
860
+ const completedLabel = !isNextFilter ? '[COMPLETED]' : ' completed ';
861
+
862
+ const filtered = entries.filter((entry) => {
863
+ if (normalizedFilter === 'completed') {
864
+ return entry.status === 'completed';
865
+ }
866
+ return entry.status !== 'completed';
867
+ });
868
+
869
+ const lines = [{
870
+ text: truncate(`filter ${nextLabel} | ${completedLabel} (←/→ or n/x)`, width),
871
+ color: 'cyan',
872
+ bold: true
873
+ }];
874
+
875
+ if (filtered.length === 0) {
876
+ lines.push({
877
+ text: truncate(
878
+ normalizedFilter === 'completed' ? 'No completed intents yet' : 'No upcoming intents',
879
+ width
880
+ ),
881
+ color: 'gray',
882
+ bold: false
883
+ });
884
+ return lines;
885
+ }
886
+
887
+ lines.push(...filtered.map((entry) => truncate(entry.line, width)));
888
+ return lines;
889
+ }
890
+
891
+ function buildOverviewStandardsLines(snapshot, width, flow) {
892
+ const effectiveFlow = getEffectiveFlow(flow, snapshot);
893
+ if (effectiveFlow === 'aidlc') {
894
+ return buildAidlcOverviewStandardsLines(snapshot, width);
895
+ }
896
+ if (effectiveFlow === 'simple') {
897
+ return buildSimpleOverviewStandardsLines(snapshot, width);
898
+ }
899
+ return buildFireOverviewStandardsLines(snapshot, width);
900
+ }
901
+
902
+ function getPanelTitles(flow, snapshot) {
903
+ const effectiveFlow = getEffectiveFlow(flow, snapshot);
904
+ if (effectiveFlow === 'aidlc') {
905
+ return {
906
+ current: 'Current Bolt',
907
+ files: 'Bolt Files',
908
+ pending: 'Queued Bolts',
909
+ completed: 'Recent Completed Bolts',
910
+ otherWorktrees: 'Other Worktrees: Active Bolts'
911
+ };
912
+ }
913
+ if (effectiveFlow === 'simple') {
914
+ return {
915
+ current: 'Current Spec',
916
+ files: 'Spec Files',
917
+ pending: 'Pending Specs',
918
+ completed: 'Recent Completed Specs',
919
+ otherWorktrees: 'Other Worktrees: Active Specs'
920
+ };
921
+ }
922
+ return {
923
+ current: 'Current Run',
924
+ files: 'Run Files',
925
+ pending: 'Pending Queue',
926
+ completed: 'Recent Completed Runs',
927
+ otherWorktrees: 'Other Worktrees: Active Runs',
928
+ git: 'Git Changes'
929
+ };
930
+ }
931
+
932
+ module.exports = {
933
+ buildShortStats,
934
+ buildHeaderLine,
935
+ buildErrorLines,
936
+ getCurrentRun,
937
+ getCurrentFireWorkItem,
938
+ extractFrontmatterBlock,
939
+ extractFrontmatterValue,
940
+ FIRE_AWAITING_APPROVAL_STATES,
941
+ FIRE_APPROVED_STATES,
942
+ parseFirePlanCheckpointMetadata,
943
+ resolveFireApprovalState,
944
+ getCurrentPhaseLabel,
945
+ getFireRunApprovalGate,
946
+ isFireRunAwaitingApproval,
947
+ detectFireRunApprovalGate,
948
+ normalizeStageName,
949
+ getAidlcCheckpointSignalFiles,
950
+ hasAidlcCheckpointSignal,
951
+ isAidlcBoltAwaitingApproval,
952
+ getCurrentBolt,
953
+ detectAidlcBoltApprovalGate,
954
+ getEffectiveFlow,
955
+ detectDashboardApprovalGate,
956
+ buildPhaseTrack,
957
+ buildFireCurrentRunLines,
958
+ buildFirePendingLines,
959
+ buildFireCompletedLines,
960
+ buildFireStatsLines,
961
+ buildWarningsLines,
962
+ buildFireOverviewProjectLines,
963
+ buildFireOverviewIntentLines,
964
+ buildFireOverviewStandardsLines,
965
+ buildAidlcStageTrack,
966
+ buildAidlcCurrentRunLines,
967
+ buildAidlcPendingLines,
968
+ buildAidlcCompletedLines,
969
+ buildAidlcStatsLines,
970
+ buildAidlcOverviewProjectLines,
971
+ buildAidlcOverviewIntentLines,
972
+ buildAidlcOverviewStandardsLines,
973
+ getCurrentSpec,
974
+ buildSimplePhaseTrack,
975
+ buildSimpleCurrentRunLines,
976
+ buildSimplePendingLines,
977
+ buildSimpleCompletedLines,
978
+ buildSimpleStatsLines,
979
+ buildSimpleOverviewProjectLines,
980
+ buildSimpleOverviewIntentLines,
981
+ buildSimpleOverviewStandardsLines,
982
+ buildCurrentRunLines,
983
+ buildPendingLines,
984
+ buildCompletedLines,
985
+ buildStatsLines,
986
+ buildOverviewProjectLines,
987
+ listOverviewIntentEntries,
988
+ buildOverviewIntentLines,
989
+ buildOverviewStandardsLines,
990
+ getPanelTitles
991
+ };