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.
- package/README.md +15 -0
- package/bin/cli.js +15 -1
- package/flows/fire/agents/builder/agent.md +2 -2
- package/flows/fire/agents/builder/skills/code-review/SKILL.md +1 -1
- package/flows/fire/agents/builder/skills/run-execute/SKILL.md +16 -7
- package/flows/fire/agents/builder/skills/run-execute/scripts/complete-run.cjs +22 -3
- package/flows/fire/agents/builder/skills/run-execute/scripts/init-run.cjs +63 -20
- package/flows/fire/agents/builder/skills/run-execute/scripts/update-checkpoint.cjs +254 -0
- package/flows/fire/agents/builder/skills/run-execute/scripts/update-phase.cjs +17 -6
- package/flows/fire/agents/builder/skills/run-status/SKILL.md +1 -1
- package/flows/fire/agents/builder/skills/walkthrough-generate/SKILL.md +30 -27
- package/flows/fire/agents/orchestrator/agent.md +1 -1
- package/flows/fire/agents/orchestrator/skills/status/SKILL.md +2 -2
- package/flows/fire/memory-bank.yaml +4 -4
- package/flows/ideation/agents/orchestrator/agent.md +8 -7
- package/flows/ideation/agents/orchestrator/skills/flame/SKILL.md +1 -0
- package/flows/ideation/agents/orchestrator/skills/flame/references/evaluation-criteria.md +4 -0
- package/flows/ideation/agents/orchestrator/skills/flame/references/six-hats-method.md +12 -0
- package/flows/ideation/agents/orchestrator/skills/forge/SKILL.md +1 -0
- package/flows/ideation/agents/orchestrator/skills/forge/references/disney-method.md +8 -0
- package/flows/ideation/agents/orchestrator/skills/forge/references/pitch-framework.md +15 -0
- package/flows/ideation/agents/orchestrator/skills/spark/SKILL.md +1 -0
- package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/analogy.md +7 -0
- package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/first-principles.md +5 -0
- package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/inversion.md +6 -0
- package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/questorming.md +6 -0
- package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/random-word.md +1 -0
- package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/scamper.md +15 -0
- package/flows/ideation/agents/orchestrator/skills/spark/references/techniques/what-if.md +6 -0
- package/flows/ideation/shared/protocols/anti-bias.md +7 -4
- package/flows/ideation/shared/protocols/deep-thinking.md +7 -0
- package/flows/ideation/shared/protocols/diverge-converge.md +2 -0
- package/flows/ideation/shared/protocols/interaction-adaptation.md +7 -0
- package/lib/dashboard/aidlc/parser.js +581 -0
- package/lib/dashboard/fire/model.js +382 -0
- package/lib/dashboard/fire/parser.js +470 -0
- package/lib/dashboard/flow-detect.js +86 -0
- package/lib/dashboard/git/changes.js +362 -0
- package/lib/dashboard/git/worktrees.js +248 -0
- package/lib/dashboard/index.js +709 -0
- package/lib/dashboard/runtime/watch-runtime.js +122 -0
- package/lib/dashboard/simple/parser.js +293 -0
- package/lib/dashboard/tui/app.js +1675 -0
- package/lib/dashboard/tui/components/error-banner.js +35 -0
- package/lib/dashboard/tui/components/header.js +60 -0
- package/lib/dashboard/tui/components/help-footer.js +15 -0
- package/lib/dashboard/tui/components/stats-strip.js +35 -0
- package/lib/dashboard/tui/file-entries.js +383 -0
- package/lib/dashboard/tui/flow-builders.js +991 -0
- package/lib/dashboard/tui/git-builders.js +218 -0
- package/lib/dashboard/tui/helpers.js +236 -0
- package/lib/dashboard/tui/overlays.js +242 -0
- package/lib/dashboard/tui/preview.js +220 -0
- package/lib/dashboard/tui/renderer.js +76 -0
- package/lib/dashboard/tui/row-builders.js +797 -0
- package/lib/dashboard/tui/sections.js +45 -0
- package/lib/dashboard/tui/store.js +44 -0
- package/lib/dashboard/tui/views/overview-view.js +61 -0
- package/lib/dashboard/tui/views/runs-view.js +93 -0
- package/lib/dashboard/tui/worktree-builders.js +229 -0
- package/lib/installers/CodexInstaller.js +72 -1
- package/package.json +7 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { truncate } = require('./header');
|
|
2
|
+
|
|
3
|
+
function renderErrorLines(error, width, watchEnabled = true) {
|
|
4
|
+
if (!error) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const lines = [
|
|
9
|
+
`[error:${error.code || 'UNKNOWN'}] ${error.message || 'Unknown error'}`
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
if (error.details) {
|
|
13
|
+
lines.push(`details: ${error.details}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (error.path) {
|
|
17
|
+
lines.push(`path: ${error.path}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (error.hint) {
|
|
21
|
+
lines.push(`hint: ${error.hint}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (watchEnabled) {
|
|
25
|
+
lines.push('Dashboard keeps running and will recover after the next valid update.');
|
|
26
|
+
} else {
|
|
27
|
+
lines.push('Fix the error and rerun dashboard.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return lines.map((line) => truncate(line, width));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
renderErrorLines
|
|
35
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
function truncate(value, width) {
|
|
2
|
+
const text = String(value);
|
|
3
|
+
if (!Number.isFinite(width) || width <= 0 || text.length <= width) {
|
|
4
|
+
return text;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (width <= 3) {
|
|
8
|
+
return text.slice(0, width);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return `${text.slice(0, width - 3)}...`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function formatTime(value) {
|
|
15
|
+
if (!value) {
|
|
16
|
+
return 'n/a';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const date = new Date(value);
|
|
20
|
+
if (Number.isNaN(date.getTime())) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return date.toLocaleTimeString();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function renderHeaderLines(params) {
|
|
28
|
+
const {
|
|
29
|
+
snapshot,
|
|
30
|
+
flow,
|
|
31
|
+
workspacePath,
|
|
32
|
+
view,
|
|
33
|
+
watchEnabled,
|
|
34
|
+
watchStatus,
|
|
35
|
+
lastRefreshAt,
|
|
36
|
+
width
|
|
37
|
+
} = params;
|
|
38
|
+
|
|
39
|
+
const projectName = snapshot?.project?.name || 'Unnamed FIRE project';
|
|
40
|
+
const topLine = `specsmd dashboard | ${flow.toUpperCase()} | ${projectName}`;
|
|
41
|
+
const subLine = [
|
|
42
|
+
`path: ${workspacePath}`,
|
|
43
|
+
`updated: ${formatTime(lastRefreshAt)}`,
|
|
44
|
+
`watch: ${watchEnabled ? watchStatus : 'off'}`,
|
|
45
|
+
`view: ${view}`
|
|
46
|
+
].join(' | ');
|
|
47
|
+
|
|
48
|
+
const horizontal = '-'.repeat(Math.max(20, Math.min(width || 120, 120)));
|
|
49
|
+
|
|
50
|
+
return [
|
|
51
|
+
truncate(topLine, width),
|
|
52
|
+
truncate(subLine, width),
|
|
53
|
+
truncate(horizontal, width)
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
renderHeaderLines,
|
|
59
|
+
truncate
|
|
60
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { truncate } = require('./header');
|
|
2
|
+
|
|
3
|
+
function renderHelpLines(showHelp, width) {
|
|
4
|
+
if (!showHelp) {
|
|
5
|
+
return [truncate('Press h to show keyboard shortcuts.', width)];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return [
|
|
9
|
+
truncate('Keys: q quit | r refresh | h/? toggle help | tab cycle view | 1 runs | 2 overview', width)
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
renderHelpLines
|
|
15
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { truncate } = require('./header');
|
|
2
|
+
|
|
3
|
+
function safePercent(part, total) {
|
|
4
|
+
if (!total || total <= 0) {
|
|
5
|
+
return 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return Math.round((part / total) * 100);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function renderStatsLines(snapshot, width) {
|
|
12
|
+
if (!snapshot?.initialized) {
|
|
13
|
+
return [truncate('stats: waiting for .specs-fire/state.yaml initialization', width)];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const stats = snapshot.stats;
|
|
17
|
+
const workItemProgress = `${stats.completedWorkItems}/${stats.totalWorkItems}`;
|
|
18
|
+
const workItemPct = safePercent(stats.completedWorkItems, stats.totalWorkItems);
|
|
19
|
+
|
|
20
|
+
const intentProgress = `${stats.completedIntents}/${stats.totalIntents}`;
|
|
21
|
+
const intentPct = safePercent(stats.completedIntents, stats.totalIntents);
|
|
22
|
+
|
|
23
|
+
const line = [
|
|
24
|
+
`Intents ${intentProgress} (${intentPct}%)`,
|
|
25
|
+
`Work items ${workItemProgress} (${workItemPct}%)`,
|
|
26
|
+
`Runs ${stats.activeRunsCount} active / ${stats.completedRuns} completed`,
|
|
27
|
+
`Blocked ${stats.blockedWorkItems}`
|
|
28
|
+
].join(' | ');
|
|
29
|
+
|
|
30
|
+
return [truncate(line, width)];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
renderStatsLines
|
|
35
|
+
};
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { fileExists, clampIndex } = require('./helpers');
|
|
4
|
+
const { getEffectiveFlow, getCurrentRun, getCurrentBolt, getCurrentSpec } = require('./flow-builders');
|
|
5
|
+
|
|
6
|
+
function listMarkdownFiles(dirPath) {
|
|
7
|
+
try {
|
|
8
|
+
return fs.readdirSync(dirPath, { withFileTypes: true })
|
|
9
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
10
|
+
.map((entry) => entry.name)
|
|
11
|
+
.sort((a, b) => a.localeCompare(b));
|
|
12
|
+
} catch {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function pushFileEntry(entries, seenPaths, candidate) {
|
|
18
|
+
if (!candidate || typeof candidate.path !== 'string' || typeof candidate.label !== 'string') {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!fileExists(candidate.path)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (seenPaths.has(candidate.path)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
seenPaths.add(candidate.path);
|
|
31
|
+
entries.push({
|
|
32
|
+
path: candidate.path,
|
|
33
|
+
label: candidate.label,
|
|
34
|
+
scope: candidate.scope || 'other'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildIntentScopedLabel(snapshot, intentId, filePath, fallbackName = 'file.md') {
|
|
39
|
+
const safeIntentId = typeof intentId === 'string' && intentId.trim() !== ''
|
|
40
|
+
? intentId
|
|
41
|
+
: '';
|
|
42
|
+
const safeFallback = typeof fallbackName === 'string' && fallbackName.trim() !== ''
|
|
43
|
+
? fallbackName
|
|
44
|
+
: 'file.md';
|
|
45
|
+
|
|
46
|
+
if (typeof filePath === 'string' && filePath.trim() !== '') {
|
|
47
|
+
if (safeIntentId && typeof snapshot?.rootPath === 'string' && snapshot.rootPath.trim() !== '') {
|
|
48
|
+
const intentPath = path.join(snapshot.rootPath, 'intents', safeIntentId);
|
|
49
|
+
const relativePath = path.relative(intentPath, filePath);
|
|
50
|
+
if (relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath)) {
|
|
51
|
+
return `${safeIntentId}/${relativePath.split(path.sep).join('/')}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const basename = path.basename(filePath);
|
|
56
|
+
return safeIntentId ? `${safeIntentId}/${basename}` : basename;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return safeIntentId ? `${safeIntentId}/${safeFallback}` : safeFallback;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function findIntentIdForWorkItem(snapshot, workItemId) {
|
|
63
|
+
if (typeof workItemId !== 'string' || workItemId.trim() === '') {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const intents = Array.isArray(snapshot?.intents) ? snapshot.intents : [];
|
|
68
|
+
for (const intent of intents) {
|
|
69
|
+
const items = Array.isArray(intent?.workItems) ? intent.workItems : [];
|
|
70
|
+
if (items.some((item) => item?.id === workItemId)) {
|
|
71
|
+
return intent?.id || '';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveFireWorkItemPath(snapshot, intentId, workItemId, explicitPath) {
|
|
79
|
+
if (typeof explicitPath === 'string' && explicitPath.trim() !== '') {
|
|
80
|
+
return explicitPath;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (typeof snapshot?.rootPath !== 'string' || snapshot.rootPath.trim() === '') {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeof workItemId !== 'string' || workItemId.trim() === '') {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const safeIntentId = typeof intentId === 'string' && intentId.trim() !== ''
|
|
92
|
+
? intentId
|
|
93
|
+
: findIntentIdForWorkItem(snapshot, workItemId);
|
|
94
|
+
|
|
95
|
+
if (!safeIntentId) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return path.join(snapshot.rootPath, 'intents', safeIntentId, 'work-items', `${workItemId}.md`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function collectFireRunFiles(run) {
|
|
103
|
+
if (!run || typeof run.folderPath !== 'string') {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const names = listMarkdownFiles(run.folderPath).sort((a, b) => {
|
|
108
|
+
if (a === 'run.md') return -1;
|
|
109
|
+
if (b === 'run.md') return 1;
|
|
110
|
+
return a.localeCompare(b);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return names.map((fileName) => ({
|
|
114
|
+
label: `${run.id}/${fileName}`,
|
|
115
|
+
path: path.join(run.folderPath, fileName)
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function collectAidlcBoltFiles(bolt) {
|
|
120
|
+
if (!bolt || typeof bolt.path !== 'string') {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const fileNames = Array.isArray(bolt.files) && bolt.files.length > 0
|
|
125
|
+
? bolt.files
|
|
126
|
+
: listMarkdownFiles(bolt.path);
|
|
127
|
+
|
|
128
|
+
return fileNames.map((fileName) => ({
|
|
129
|
+
label: `${bolt.id}/${fileName}`,
|
|
130
|
+
path: path.join(bolt.path, fileName)
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function collectSimpleSpecFiles(spec) {
|
|
135
|
+
if (!spec || typeof spec.path !== 'string') {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const names = [];
|
|
140
|
+
if (spec.hasRequirements) names.push('requirements.md');
|
|
141
|
+
if (spec.hasDesign) names.push('design.md');
|
|
142
|
+
if (spec.hasTasks) names.push('tasks.md');
|
|
143
|
+
|
|
144
|
+
return names.map((fileName) => ({
|
|
145
|
+
label: `${spec.name}/${fileName}`,
|
|
146
|
+
path: path.join(spec.path, fileName)
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function collectAidlcIntentContextFiles(snapshot, intentId) {
|
|
151
|
+
if (!snapshot || typeof intentId !== 'string' || intentId.trim() === '') {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const intentPath = path.join(snapshot.rootPath || '', 'intents', intentId);
|
|
156
|
+
return [
|
|
157
|
+
{
|
|
158
|
+
label: `${intentId}/requirements.md`,
|
|
159
|
+
path: path.join(intentPath, 'requirements.md'),
|
|
160
|
+
scope: 'intent'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
label: `${intentId}/system-context.md`,
|
|
164
|
+
path: path.join(intentPath, 'system-context.md'),
|
|
165
|
+
scope: 'intent'
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
label: `${intentId}/units.md`,
|
|
169
|
+
path: path.join(intentPath, 'units.md'),
|
|
170
|
+
scope: 'intent'
|
|
171
|
+
}
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function filterExistingFiles(files) {
|
|
176
|
+
return (Array.isArray(files) ? files : []).filter((file) => {
|
|
177
|
+
if (!file || typeof file.path !== 'string' || typeof file.label !== 'string') {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
if (file.allowMissing === true) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return fileExists(file.path);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getRunFileEntries(snapshot, flow, options = {}) {
|
|
188
|
+
const includeBacklog = options.includeBacklog !== false;
|
|
189
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
190
|
+
const entries = [];
|
|
191
|
+
const seenPaths = new Set();
|
|
192
|
+
|
|
193
|
+
if (effectiveFlow === 'aidlc') {
|
|
194
|
+
const bolt = getCurrentBolt(snapshot);
|
|
195
|
+
for (const file of collectAidlcBoltFiles(bolt)) {
|
|
196
|
+
pushFileEntry(entries, seenPaths, { ...file, scope: 'active' });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!includeBacklog) {
|
|
200
|
+
return entries;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const pendingBolts = Array.isArray(snapshot?.pendingBolts) ? snapshot.pendingBolts : [];
|
|
204
|
+
for (const pendingBolt of pendingBolts) {
|
|
205
|
+
for (const file of collectAidlcBoltFiles(pendingBolt)) {
|
|
206
|
+
pushFileEntry(entries, seenPaths, { ...file, scope: 'upcoming' });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const completedBolts = Array.isArray(snapshot?.completedBolts) ? snapshot.completedBolts : [];
|
|
211
|
+
for (const completedBolt of completedBolts) {
|
|
212
|
+
for (const file of collectAidlcBoltFiles(completedBolt)) {
|
|
213
|
+
pushFileEntry(entries, seenPaths, { ...file, scope: 'completed' });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const intentIds = new Set([
|
|
218
|
+
...pendingBolts.map((item) => item?.intent).filter(Boolean),
|
|
219
|
+
...completedBolts.map((item) => item?.intent).filter(Boolean)
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
for (const intentId of intentIds) {
|
|
223
|
+
const intentPath = path.join(snapshot?.rootPath || '', 'intents', intentId);
|
|
224
|
+
pushFileEntry(entries, seenPaths, {
|
|
225
|
+
label: `${intentId}/requirements.md`,
|
|
226
|
+
path: path.join(intentPath, 'requirements.md'),
|
|
227
|
+
scope: 'intent'
|
|
228
|
+
});
|
|
229
|
+
pushFileEntry(entries, seenPaths, {
|
|
230
|
+
label: `${intentId}/system-context.md`,
|
|
231
|
+
path: path.join(intentPath, 'system-context.md'),
|
|
232
|
+
scope: 'intent'
|
|
233
|
+
});
|
|
234
|
+
pushFileEntry(entries, seenPaths, {
|
|
235
|
+
label: `${intentId}/units.md`,
|
|
236
|
+
path: path.join(intentPath, 'units.md'),
|
|
237
|
+
scope: 'intent'
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return entries;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (effectiveFlow === 'simple') {
|
|
244
|
+
const spec = getCurrentSpec(snapshot);
|
|
245
|
+
for (const file of collectSimpleSpecFiles(spec)) {
|
|
246
|
+
pushFileEntry(entries, seenPaths, { ...file, scope: 'active' });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!includeBacklog) {
|
|
250
|
+
return entries;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const pendingSpecs = Array.isArray(snapshot?.pendingSpecs) ? snapshot.pendingSpecs : [];
|
|
254
|
+
for (const pendingSpec of pendingSpecs) {
|
|
255
|
+
for (const file of collectSimpleSpecFiles(pendingSpec)) {
|
|
256
|
+
pushFileEntry(entries, seenPaths, { ...file, scope: 'upcoming' });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const completedSpecs = Array.isArray(snapshot?.completedSpecs) ? snapshot.completedSpecs : [];
|
|
261
|
+
for (const completedSpec of completedSpecs) {
|
|
262
|
+
for (const file of collectSimpleSpecFiles(completedSpec)) {
|
|
263
|
+
pushFileEntry(entries, seenPaths, { ...file, scope: 'completed' });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return entries;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const run = getCurrentRun(snapshot);
|
|
271
|
+
for (const file of collectFireRunFiles(run)) {
|
|
272
|
+
pushFileEntry(entries, seenPaths, { ...file, scope: 'active' });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!includeBacklog) {
|
|
276
|
+
return entries;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const pendingItems = Array.isArray(snapshot?.pendingItems) ? snapshot.pendingItems : [];
|
|
280
|
+
for (const pendingItem of pendingItems) {
|
|
281
|
+
pushFileEntry(entries, seenPaths, {
|
|
282
|
+
label: buildIntentScopedLabel(
|
|
283
|
+
snapshot,
|
|
284
|
+
pendingItem?.intentId,
|
|
285
|
+
pendingItem?.filePath,
|
|
286
|
+
`${pendingItem?.id || 'work-item'}.md`
|
|
287
|
+
),
|
|
288
|
+
path: pendingItem?.filePath,
|
|
289
|
+
scope: 'upcoming'
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
if (pendingItem?.intentId) {
|
|
293
|
+
pushFileEntry(entries, seenPaths, {
|
|
294
|
+
label: buildIntentScopedLabel(
|
|
295
|
+
snapshot,
|
|
296
|
+
pendingItem.intentId,
|
|
297
|
+
path.join(snapshot?.rootPath || '', 'intents', pendingItem.intentId, 'brief.md'),
|
|
298
|
+
'brief.md'
|
|
299
|
+
),
|
|
300
|
+
path: path.join(snapshot?.rootPath || '', 'intents', pendingItem.intentId, 'brief.md'),
|
|
301
|
+
scope: 'intent'
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const completedRuns = Array.isArray(snapshot?.completedRuns) ? snapshot.completedRuns : [];
|
|
307
|
+
for (const completedRun of completedRuns) {
|
|
308
|
+
for (const file of collectFireRunFiles(completedRun)) {
|
|
309
|
+
pushFileEntry(entries, seenPaths, { ...file, scope: 'completed' });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const completedIntents = Array.isArray(snapshot?.intents)
|
|
314
|
+
? snapshot.intents.filter((intent) => intent?.status === 'completed')
|
|
315
|
+
: [];
|
|
316
|
+
for (const intent of completedIntents) {
|
|
317
|
+
pushFileEntry(entries, seenPaths, {
|
|
318
|
+
label: buildIntentScopedLabel(
|
|
319
|
+
snapshot,
|
|
320
|
+
intent?.id,
|
|
321
|
+
path.join(snapshot?.rootPath || '', 'intents', intent?.id || '', 'brief.md'),
|
|
322
|
+
'brief.md'
|
|
323
|
+
),
|
|
324
|
+
path: path.join(snapshot?.rootPath || '', 'intents', intent.id, 'brief.md'),
|
|
325
|
+
scope: 'intent'
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return entries;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function getNoFileMessage(flow) {
|
|
333
|
+
return `No selectable files for ${String(flow || 'flow').toUpperCase()}`;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function formatScope(scope) {
|
|
337
|
+
if (scope === 'active') return 'ACTIVE';
|
|
338
|
+
if (scope === 'upcoming') return 'UPNEXT';
|
|
339
|
+
if (scope === 'completed') return 'DONE';
|
|
340
|
+
if (scope === 'intent') return 'INTENT';
|
|
341
|
+
if (scope === 'staged') return 'STAGED';
|
|
342
|
+
if (scope === 'unstaged') return 'UNSTAGED';
|
|
343
|
+
if (scope === 'untracked') return 'UNTRACKED';
|
|
344
|
+
if (scope === 'conflicted') return 'CONFLICT';
|
|
345
|
+
return 'FILE';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function getNoPendingMessage(flow) {
|
|
349
|
+
if (flow === 'aidlc') return 'No queued bolts';
|
|
350
|
+
if (flow === 'simple') return 'No pending specs';
|
|
351
|
+
return 'No pending work items';
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function getNoCompletedMessage(flow) {
|
|
355
|
+
if (flow === 'aidlc') return 'No completed bolts yet';
|
|
356
|
+
if (flow === 'simple') return 'No completed specs yet';
|
|
357
|
+
return 'No completed runs yet';
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function getNoCurrentMessage(flow) {
|
|
361
|
+
if (flow === 'aidlc') return 'No active bolt';
|
|
362
|
+
if (flow === 'simple') return 'No active spec';
|
|
363
|
+
return 'No active run';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
module.exports = {
|
|
367
|
+
listMarkdownFiles,
|
|
368
|
+
pushFileEntry,
|
|
369
|
+
buildIntentScopedLabel,
|
|
370
|
+
findIntentIdForWorkItem,
|
|
371
|
+
resolveFireWorkItemPath,
|
|
372
|
+
collectFireRunFiles,
|
|
373
|
+
collectAidlcBoltFiles,
|
|
374
|
+
collectSimpleSpecFiles,
|
|
375
|
+
collectAidlcIntentContextFiles,
|
|
376
|
+
filterExistingFiles,
|
|
377
|
+
getRunFileEntries,
|
|
378
|
+
getNoFileMessage,
|
|
379
|
+
formatScope,
|
|
380
|
+
getNoPendingMessage,
|
|
381
|
+
getNoCompletedMessage,
|
|
382
|
+
getNoCurrentMessage
|
|
383
|
+
};
|