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,45 @@
|
|
|
1
|
+
function getSectionOrderForView(view, options = {}) {
|
|
2
|
+
const includeWorktrees = options.includeWorktrees === true;
|
|
3
|
+
const includeOtherWorktrees = options.includeOtherWorktrees === true;
|
|
4
|
+
|
|
5
|
+
if (view === 'intents') {
|
|
6
|
+
return ['intent-status'];
|
|
7
|
+
}
|
|
8
|
+
if (view === 'completed') {
|
|
9
|
+
return ['completed-runs'];
|
|
10
|
+
}
|
|
11
|
+
if (view === 'health') {
|
|
12
|
+
return ['standards', 'stats', 'warnings', 'error-details'];
|
|
13
|
+
}
|
|
14
|
+
if (view === 'git') {
|
|
15
|
+
return ['git-status', 'git-changes', 'git-commits', 'git-diff'];
|
|
16
|
+
}
|
|
17
|
+
const sections = [];
|
|
18
|
+
if (includeWorktrees) {
|
|
19
|
+
sections.push('worktrees');
|
|
20
|
+
}
|
|
21
|
+
sections.push('current-run', 'run-files');
|
|
22
|
+
if (includeOtherWorktrees) {
|
|
23
|
+
sections.push('other-worktrees-active');
|
|
24
|
+
}
|
|
25
|
+
return sections;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function cycleSection(view, currentSectionKey, direction = 1, availableSections = null) {
|
|
29
|
+
const order = Array.isArray(availableSections) && availableSections.length > 0
|
|
30
|
+
? availableSections
|
|
31
|
+
: getSectionOrderForView(view);
|
|
32
|
+
if (order.length === 0) {
|
|
33
|
+
return currentSectionKey;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const currentIndex = order.indexOf(currentSectionKey);
|
|
37
|
+
const safeIndex = currentIndex >= 0 ? currentIndex : 0;
|
|
38
|
+
const nextIndex = (safeIndex + direction + order.length) % order.length;
|
|
39
|
+
return order[nextIndex];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
getSectionOrderForView,
|
|
44
|
+
cycleSection
|
|
45
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function createInitialUIState() {
|
|
2
|
+
return {
|
|
3
|
+
view: 'runs',
|
|
4
|
+
showHelp: false
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function cycleView(current) {
|
|
9
|
+
if (current === 'runs') {
|
|
10
|
+
return 'intents';
|
|
11
|
+
}
|
|
12
|
+
if (current === 'intents') {
|
|
13
|
+
return 'completed';
|
|
14
|
+
}
|
|
15
|
+
if (current === 'completed') {
|
|
16
|
+
return 'health';
|
|
17
|
+
}
|
|
18
|
+
if (current === 'health') {
|
|
19
|
+
return 'git';
|
|
20
|
+
}
|
|
21
|
+
return 'runs';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function cycleViewBackward(current) {
|
|
25
|
+
if (current === 'runs') {
|
|
26
|
+
return 'git';
|
|
27
|
+
}
|
|
28
|
+
if (current === 'intents') {
|
|
29
|
+
return 'runs';
|
|
30
|
+
}
|
|
31
|
+
if (current === 'completed') {
|
|
32
|
+
return 'intents';
|
|
33
|
+
}
|
|
34
|
+
if (current === 'health') {
|
|
35
|
+
return 'completed';
|
|
36
|
+
}
|
|
37
|
+
return 'health';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
createInitialUIState,
|
|
42
|
+
cycleView,
|
|
43
|
+
cycleViewBackward
|
|
44
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const { truncate } = require('../components/header');
|
|
2
|
+
|
|
3
|
+
const STANDARD_TYPES = [
|
|
4
|
+
'constitution',
|
|
5
|
+
'tech-stack',
|
|
6
|
+
'coding-standards',
|
|
7
|
+
'testing-standards',
|
|
8
|
+
'system-architecture'
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
function renderOverviewViewLines(snapshot, width) {
|
|
12
|
+
const lines = ['Overview'];
|
|
13
|
+
|
|
14
|
+
if (!snapshot?.initialized) {
|
|
15
|
+
lines.push('FIRE project folder exists but state.yaml is missing.');
|
|
16
|
+
lines.push('Run initialization and the overview will appear automatically.');
|
|
17
|
+
return lines.map((line) => truncate(line, width));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const project = snapshot.project || {};
|
|
21
|
+
const workspace = snapshot.workspace || {};
|
|
22
|
+
|
|
23
|
+
lines.push(`Project: ${project.name || 'Unknown'} | FIRE version: ${project.fireVersion || snapshot.version || '0.0.0'}`);
|
|
24
|
+
lines.push(`Workspace: ${workspace.type || 'unknown'} / ${workspace.structure || 'unknown'} | autonomy: ${workspace.autonomyBias || 'unknown'} | run scope pref: ${workspace.runScopePreference || 'unknown'}`);
|
|
25
|
+
lines.push('');
|
|
26
|
+
|
|
27
|
+
lines.push('Intent Summary');
|
|
28
|
+
lines.push(` total: ${snapshot.stats.totalIntents} | completed: ${snapshot.stats.completedIntents} | in_progress: ${snapshot.stats.inProgressIntents} | pending: ${snapshot.stats.pendingIntents} | blocked: ${snapshot.stats.blockedIntents}`);
|
|
29
|
+
lines.push('');
|
|
30
|
+
|
|
31
|
+
lines.push('Work Item Summary');
|
|
32
|
+
lines.push(` total: ${snapshot.stats.totalWorkItems} | completed: ${snapshot.stats.completedWorkItems} | in_progress: ${snapshot.stats.inProgressWorkItems} | pending: ${snapshot.stats.pendingWorkItems} | blocked: ${snapshot.stats.blockedWorkItems}`);
|
|
33
|
+
lines.push('');
|
|
34
|
+
|
|
35
|
+
const standardSet = new Set((snapshot.standards || []).map((item) => item.type));
|
|
36
|
+
lines.push('Standards');
|
|
37
|
+
for (const type of STANDARD_TYPES) {
|
|
38
|
+
const marker = standardSet.has(type) ? '[x]' : '[ ]';
|
|
39
|
+
lines.push(` ${marker} ${type}.md`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
lines.push('');
|
|
43
|
+
lines.push('Top Intents');
|
|
44
|
+
|
|
45
|
+
const intents = (snapshot.intents || []).slice(0, 6);
|
|
46
|
+
if (intents.length === 0) {
|
|
47
|
+
lines.push(' - none');
|
|
48
|
+
} else {
|
|
49
|
+
for (const intent of intents) {
|
|
50
|
+
const totalWorkItems = (intent.workItems || []).length;
|
|
51
|
+
const completedWorkItems = (intent.workItems || []).filter((item) => item.status === 'completed').length;
|
|
52
|
+
lines.push(` - ${intent.id}: ${intent.status} (${completedWorkItems}/${totalWorkItems} work items completed)`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return lines.map((line) => truncate(line, width));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
renderOverviewViewLines
|
|
61
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const { truncate } = require('../components/header');
|
|
2
|
+
|
|
3
|
+
function safeArray(value) {
|
|
4
|
+
return Array.isArray(value) ? value : [];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function formatRunProgress(run) {
|
|
8
|
+
const workItems = safeArray(run.workItems);
|
|
9
|
+
const total = workItems.length;
|
|
10
|
+
const completed = workItems.filter((item) => item.status === 'completed').length;
|
|
11
|
+
const inProgress = workItems.filter((item) => item.status === 'in_progress').length;
|
|
12
|
+
return `${completed}/${total} completed${inProgress > 0 ? `, ${inProgress} in_progress` : ''}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function renderActiveRunLines(activeRuns, width) {
|
|
16
|
+
const lines = ['Active Runs'];
|
|
17
|
+
if (!activeRuns || activeRuns.length === 0) {
|
|
18
|
+
lines.push(' - none');
|
|
19
|
+
return lines.map((line) => truncate(line, width));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const run of activeRuns) {
|
|
23
|
+
const currentItem = run.currentItem || 'n/a';
|
|
24
|
+
const artifacts = [
|
|
25
|
+
run.hasPlan ? 'plan' : null,
|
|
26
|
+
run.hasWalkthrough ? 'walkthrough' : null,
|
|
27
|
+
run.hasTestReport ? 'test-report' : null
|
|
28
|
+
].filter(Boolean).join(', ') || 'no artifacts yet';
|
|
29
|
+
|
|
30
|
+
lines.push(` - ${run.id} [${run.scope}] current: ${currentItem}`);
|
|
31
|
+
lines.push(` progress: ${formatRunProgress(run)} | artifacts: ${artifacts}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return lines.map((line) => truncate(line, width));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderPendingQueueLines(pendingItems, width) {
|
|
38
|
+
const lines = ['Pending Queue'];
|
|
39
|
+
if (!pendingItems || pendingItems.length === 0) {
|
|
40
|
+
lines.push(' - none');
|
|
41
|
+
return lines.map((line) => truncate(line, width));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const item of pendingItems.slice(0, 12)) {
|
|
45
|
+
const deps = item.dependencies && item.dependencies.length > 0
|
|
46
|
+
? ` deps:${item.dependencies.join(',')}`
|
|
47
|
+
: '';
|
|
48
|
+
lines.push(` - ${item.id} (${item.mode}/${item.complexity}) in ${item.intentTitle}${deps}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (pendingItems.length > 12) {
|
|
52
|
+
lines.push(` ... ${pendingItems.length - 12} more pending items`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return lines.map((line) => truncate(line, width));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderCompletedRunLines(completedRuns, width) {
|
|
59
|
+
const lines = ['Recent Completed Runs'];
|
|
60
|
+
if (!completedRuns || completedRuns.length === 0) {
|
|
61
|
+
lines.push(' - none');
|
|
62
|
+
return lines.map((line) => truncate(line, width));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const run of completedRuns.slice(0, 5)) {
|
|
66
|
+
const completedAt = run.completedAt || 'unknown';
|
|
67
|
+
lines.push(` - ${run.id} [${run.scope}] completed: ${completedAt} | ${formatRunProgress(run)}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return lines.map((line) => truncate(line, width));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function renderRunsViewLines(snapshot, width) {
|
|
74
|
+
const lines = [];
|
|
75
|
+
|
|
76
|
+
if (!snapshot?.initialized) {
|
|
77
|
+
lines.push('FIRE detected, but state.yaml is not initialized yet.');
|
|
78
|
+
lines.push('Initialize FIRE project context, then the dashboard will auto-populate.');
|
|
79
|
+
return lines.map((line) => truncate(line, width));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
lines.push(...renderActiveRunLines(snapshot.activeRuns, width));
|
|
83
|
+
lines.push('');
|
|
84
|
+
lines.push(...renderPendingQueueLines(snapshot.pendingItems, width));
|
|
85
|
+
lines.push('');
|
|
86
|
+
lines.push(...renderCompletedRunLines(snapshot.completedRuns, width));
|
|
87
|
+
|
|
88
|
+
return lines.map((line) => truncate(line, width));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
renderRunsViewLines
|
|
93
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { clampIndex } = require('./helpers');
|
|
3
|
+
const { truncate } = require('./helpers');
|
|
4
|
+
const { getEffectiveFlow, getCurrentBolt } = require('./flow-builders');
|
|
5
|
+
const { collectAidlcBoltFiles } = require('./file-entries');
|
|
6
|
+
const { collectFireRunFiles } = require('./file-entries');
|
|
7
|
+
|
|
8
|
+
function getDashboardWorktreeMeta(snapshot) {
|
|
9
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const meta = snapshot.dashboardWorktrees;
|
|
13
|
+
if (!meta || typeof meta !== 'object') {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const items = Array.isArray(meta.items) ? meta.items : [];
|
|
17
|
+
if (items.length === 0) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
...meta,
|
|
22
|
+
items
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getWorktreeItems(snapshot) {
|
|
27
|
+
return getDashboardWorktreeMeta(snapshot)?.items || [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getSelectedWorktree(snapshot) {
|
|
31
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
32
|
+
if (!meta) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return meta.items.find((item) => item.id === meta.selectedWorktreeId) || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hasMultipleWorktrees(snapshot) {
|
|
39
|
+
return getWorktreeItems(snapshot).length > 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isSelectedWorktreeMain(snapshot) {
|
|
43
|
+
const selected = getSelectedWorktree(snapshot);
|
|
44
|
+
return Boolean(selected?.isMainBranch);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getWorktreeDisplayName(worktree) {
|
|
48
|
+
if (!worktree || typeof worktree !== 'object') {
|
|
49
|
+
return 'unknown';
|
|
50
|
+
}
|
|
51
|
+
if (typeof worktree.displayBranch === 'string' && worktree.displayBranch.trim() !== '') {
|
|
52
|
+
return worktree.displayBranch;
|
|
53
|
+
}
|
|
54
|
+
if (typeof worktree.branch === 'string' && worktree.branch.trim() !== '') {
|
|
55
|
+
return worktree.branch;
|
|
56
|
+
}
|
|
57
|
+
if (typeof worktree.name === 'string' && worktree.name.trim() !== '') {
|
|
58
|
+
return worktree.name;
|
|
59
|
+
}
|
|
60
|
+
return path.basename(worktree.path || '') || 'unknown';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildWorktreeRows(snapshot, flow) {
|
|
64
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
65
|
+
if (!meta) {
|
|
66
|
+
return [{
|
|
67
|
+
kind: 'info',
|
|
68
|
+
key: 'worktrees:none',
|
|
69
|
+
label: 'No git worktrees detected',
|
|
70
|
+
selectable: false
|
|
71
|
+
}];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
75
|
+
const entityLabel = effectiveFlow === 'aidlc'
|
|
76
|
+
? 'active bolts'
|
|
77
|
+
: (effectiveFlow === 'simple' ? 'active specs' : 'active runs');
|
|
78
|
+
|
|
79
|
+
const rows = [];
|
|
80
|
+
for (const item of meta.items) {
|
|
81
|
+
const currentLabel = item.isSelected ? '[CURRENT] ' : '';
|
|
82
|
+
const mainLabel = item.isMainBranch && !item.detached ? '[MAIN] ' : '';
|
|
83
|
+
const availabilityLabel = item.flowAvailable ? '' : ' (flow unavailable)';
|
|
84
|
+
const statusLabel = item.status === 'loading'
|
|
85
|
+
? ' loading...'
|
|
86
|
+
: (item.status === 'error' ? ' error' : ` ${item.activeCount || 0} ${entityLabel}`);
|
|
87
|
+
const scopeLabel = item.name ? ` (${item.name})` : '';
|
|
88
|
+
|
|
89
|
+
rows.push({
|
|
90
|
+
kind: 'info',
|
|
91
|
+
key: `worktree:item:${item.id}`,
|
|
92
|
+
label: `${currentLabel}${mainLabel}${getWorktreeDisplayName(item)}${scopeLabel}${availabilityLabel}${statusLabel}`,
|
|
93
|
+
color: item.isSelected ? 'green' : (item.flowAvailable ? 'gray' : 'red'),
|
|
94
|
+
bold: item.isSelected,
|
|
95
|
+
selectable: true
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return rows;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function buildOtherWorktreeActiveGroups(snapshot, flow) {
|
|
103
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
104
|
+
if (effectiveFlow === 'simple') {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
109
|
+
if (!meta) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const selectedWorktree = getSelectedWorktree(snapshot);
|
|
114
|
+
if (!selectedWorktree || !selectedWorktree.isMainBranch) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const groups = [];
|
|
119
|
+
const otherItems = meta.items.filter((item) => item.id !== meta.selectedWorktreeId);
|
|
120
|
+
for (const item of otherItems) {
|
|
121
|
+
if (!item.flowAvailable || item.status === 'unavailable' || item.status === 'error') {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (effectiveFlow === 'aidlc') {
|
|
126
|
+
const activeBolts = Array.isArray(item.activity?.activeBolts) ? item.activity.activeBolts : [];
|
|
127
|
+
for (const bolt of activeBolts) {
|
|
128
|
+
const stages = Array.isArray(bolt?.stages) ? bolt.stages : [];
|
|
129
|
+
const completedStages = stages.filter((stage) => stage?.status === 'completed').length;
|
|
130
|
+
groups.push({
|
|
131
|
+
key: `other:wt:${item.id}:bolt:${bolt.id}`,
|
|
132
|
+
label: `[WT ${getWorktreeDisplayName(item)}] ${bolt.id} [${bolt.type || 'bolt'}] ${completedStages}/${stages.length || 0} stages`,
|
|
133
|
+
files: collectAidlcBoltFiles(bolt).map((file) => ({ ...file, scope: 'active' }))
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const activeRuns = Array.isArray(item.activity?.activeRuns) ? item.activity.activeRuns : [];
|
|
140
|
+
for (const run of activeRuns) {
|
|
141
|
+
const workItems = Array.isArray(run?.workItems) ? run.workItems : [];
|
|
142
|
+
const completed = workItems.filter((workItem) => workItem?.status === 'completed').length;
|
|
143
|
+
groups.push({
|
|
144
|
+
key: `other:wt:${item.id}:run:${run.id}`,
|
|
145
|
+
label: `[WT ${getWorktreeDisplayName(item)}] ${run.id} [${run.scope || 'single'}] ${completed}/${workItems.length} items`,
|
|
146
|
+
files: collectFireRunFiles(run).map((file) => ({ ...file, scope: 'active' }))
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return groups;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getOtherWorktreeEmptyMessage(snapshot, flow) {
|
|
155
|
+
const effectiveFlow = getEffectiveFlow(flow, snapshot);
|
|
156
|
+
if (!hasMultipleWorktrees(snapshot)) {
|
|
157
|
+
return 'No additional worktrees';
|
|
158
|
+
}
|
|
159
|
+
if (!isSelectedWorktreeMain(snapshot)) {
|
|
160
|
+
return 'Switch to main worktree to view active items from other worktrees';
|
|
161
|
+
}
|
|
162
|
+
if (effectiveFlow === 'aidlc') {
|
|
163
|
+
return 'No active bolts in other worktrees';
|
|
164
|
+
}
|
|
165
|
+
if (effectiveFlow === 'simple') {
|
|
166
|
+
return 'No active specs in other worktrees';
|
|
167
|
+
}
|
|
168
|
+
return 'No active runs in other worktrees';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function buildWorktreeOverlayLines(snapshot, selectedIndex, width) {
|
|
172
|
+
const meta = getDashboardWorktreeMeta(snapshot);
|
|
173
|
+
if (!meta) {
|
|
174
|
+
return [{
|
|
175
|
+
text: truncate('No worktrees available', width),
|
|
176
|
+
color: 'gray',
|
|
177
|
+
bold: false
|
|
178
|
+
}];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const items = Array.isArray(meta.items) ? meta.items : [];
|
|
182
|
+
const clampedIndex = clampIndex(selectedIndex, items.length || 1);
|
|
183
|
+
const lines = [{
|
|
184
|
+
text: truncate('Use ↑/↓ and Enter to switch. Esc closes.', width),
|
|
185
|
+
color: 'gray',
|
|
186
|
+
bold: false
|
|
187
|
+
}];
|
|
188
|
+
|
|
189
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
190
|
+
const item = items[index];
|
|
191
|
+
const marker = index === clampedIndex ? '>' : ' ';
|
|
192
|
+
const current = item.isSelected ? '[CURRENT] ' : '';
|
|
193
|
+
const main = item.isMainBranch && !item.detached ? '[MAIN] ' : '';
|
|
194
|
+
const status = item.status === 'loading'
|
|
195
|
+
? 'loading'
|
|
196
|
+
: (item.status === 'error'
|
|
197
|
+
? 'error'
|
|
198
|
+
: (item.flowAvailable ? `${item.activeCount || 0} active` : 'flow unavailable'));
|
|
199
|
+
const pathLabel = item.path ? path.basename(item.path) : 'unknown';
|
|
200
|
+
lines.push({
|
|
201
|
+
text: truncate(`${marker} ${current}${main}${getWorktreeDisplayName(item)} (${pathLabel}) | ${status}`, width),
|
|
202
|
+
color: index === clampedIndex ? 'green' : (item.isSelected ? 'cyan' : 'gray'),
|
|
203
|
+
bold: index === clampedIndex || item.isSelected
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (meta.hasPendingScans) {
|
|
208
|
+
lines.push({
|
|
209
|
+
text: truncate('Background scan in progress for additional worktrees...', width),
|
|
210
|
+
color: 'yellow',
|
|
211
|
+
bold: false
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return lines;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
getDashboardWorktreeMeta,
|
|
220
|
+
getWorktreeItems,
|
|
221
|
+
getSelectedWorktree,
|
|
222
|
+
hasMultipleWorktrees,
|
|
223
|
+
isSelectedWorktreeMain,
|
|
224
|
+
getWorktreeDisplayName,
|
|
225
|
+
buildWorktreeRows,
|
|
226
|
+
buildOtherWorktreeActiveGroups,
|
|
227
|
+
getOtherWorktreeEmptyMessage,
|
|
228
|
+
buildWorktreeOverlayLines
|
|
229
|
+
};
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
const ToolInstaller = require('./ToolInstaller');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const CLIUtils = require('../cli-utils');
|
|
5
|
+
const { theme } = CLIUtils;
|
|
2
6
|
|
|
3
7
|
class CodexInstaller extends ToolInstaller {
|
|
4
8
|
get key() {
|
|
@@ -10,12 +14,79 @@ class CodexInstaller extends ToolInstaller {
|
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
get commandsDir() {
|
|
13
|
-
return '.codex';
|
|
17
|
+
return path.join('.codex', 'skills');
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
get detectPath() {
|
|
17
21
|
return '.codex';
|
|
18
22
|
}
|
|
23
|
+
|
|
24
|
+
async installCommands(flowPath, config) {
|
|
25
|
+
const targetSkillsDir = this.commandsDir;
|
|
26
|
+
console.log(theme.dim(` Installing skills to ${targetSkillsDir}/...`));
|
|
27
|
+
await fs.ensureDir(targetSkillsDir);
|
|
28
|
+
|
|
29
|
+
const commandsSourceDir = path.join(flowPath, 'commands');
|
|
30
|
+
if (!await fs.pathExists(commandsSourceDir)) {
|
|
31
|
+
console.log(theme.warning(` No commands folder found at ${commandsSourceDir}`));
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const commandFiles = await fs.readdir(commandsSourceDir);
|
|
36
|
+
const installedFiles = [];
|
|
37
|
+
|
|
38
|
+
for (const cmdFile of commandFiles) {
|
|
39
|
+
if (!cmdFile.endsWith('.md')) continue;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const sourcePath = path.join(commandsSourceDir, cmdFile);
|
|
43
|
+
const content = await fs.readFile(sourcePath, 'utf8');
|
|
44
|
+
const commandName = cmdFile.replace('.md', '');
|
|
45
|
+
const prefix = (config && config.command && config.command.prefix) ? `${config.command.prefix}-` : '';
|
|
46
|
+
const skillName = `specsmd-${prefix}${commandName}`;
|
|
47
|
+
|
|
48
|
+
const { description, body } = this.parseFrontmatter(content);
|
|
49
|
+
|
|
50
|
+
// Build SKILL.md with Codex frontmatter
|
|
51
|
+
const skillContent = [
|
|
52
|
+
'---',
|
|
53
|
+
`name: ${skillName}`,
|
|
54
|
+
`description: "${description || 'specsmd agent'}"`,
|
|
55
|
+
'---',
|
|
56
|
+
'',
|
|
57
|
+
body
|
|
58
|
+
].join('\n');
|
|
59
|
+
|
|
60
|
+
// Write SKILL.md
|
|
61
|
+
const skillDir = path.join(targetSkillsDir, skillName);
|
|
62
|
+
await fs.ensureDir(skillDir);
|
|
63
|
+
await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillContent, 'utf8');
|
|
64
|
+
|
|
65
|
+
installedFiles.push(skillName);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.log(theme.warning(` Failed to install ${cmdFile}: ${err.message}`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
CLIUtils.displayStatus('', `Installed ${installedFiles.length} skills for ${this.name}`, 'success');
|
|
72
|
+
return installedFiles;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Parse YAML frontmatter from a markdown file
|
|
77
|
+
*/
|
|
78
|
+
parseFrontmatter(content) {
|
|
79
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
80
|
+
if (!match) return { description: '', body: content };
|
|
81
|
+
|
|
82
|
+
const frontmatter = match[1];
|
|
83
|
+
const body = match[2];
|
|
84
|
+
const descMatch = frontmatter.match(/description:\s*["']?(.+?)["']?\s*$/m);
|
|
85
|
+
return {
|
|
86
|
+
description: descMatch ? descMatch[1] : '',
|
|
87
|
+
body: body.trim()
|
|
88
|
+
};
|
|
89
|
+
}
|
|
19
90
|
}
|
|
20
91
|
|
|
21
92
|
module.exports = CodexInstaller;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.87",
|
|
4
4
|
"description": "Multi-agent orchestration system for AI-native software development. Delivers AI-DLC, Agile, and custom SDLC flows as markdown-based agent systems.",
|
|
5
5
|
"main": "lib/installer.js",
|
|
6
6
|
"bin": {
|
|
@@ -40,18 +40,22 @@
|
|
|
40
40
|
"README.md"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@inkjs/ui": "^2.0.0",
|
|
43
44
|
"chalk": "^4.1.2",
|
|
45
|
+
"chokidar": "^4.0.3",
|
|
44
46
|
"commander": "^11.1.0",
|
|
45
47
|
"figlet": "^1.9.4",
|
|
46
48
|
"fs-extra": "^11.1.1",
|
|
47
49
|
"gradient-string": "^2.0.2",
|
|
50
|
+
"ink": "^5.2.1",
|
|
48
51
|
"js-yaml": "^4.1.0",
|
|
49
52
|
"mixpanel": "^0.18.0",
|
|
50
53
|
"oh-my-logo": "^0.4.0",
|
|
51
|
-
"prompts": "^2.4.2"
|
|
54
|
+
"prompts": "^2.4.2",
|
|
55
|
+
"react": "^18.3.1"
|
|
52
56
|
},
|
|
53
57
|
"engines": {
|
|
54
|
-
"node": ">=
|
|
58
|
+
"node": ">=20.0.0"
|
|
55
59
|
},
|
|
56
60
|
"devDependencies": {
|
|
57
61
|
"@types/js-yaml": "^4.0.9",
|