@evolve.labs/devflow 0.8.0
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/.claude/commands/agents/architect.md +1162 -0
- package/.claude/commands/agents/architect.meta.yaml +124 -0
- package/.claude/commands/agents/builder.md +1432 -0
- package/.claude/commands/agents/builder.meta.yaml +117 -0
- package/.claude/commands/agents/chronicler.md +633 -0
- package/.claude/commands/agents/chronicler.meta.yaml +217 -0
- package/.claude/commands/agents/guardian.md +456 -0
- package/.claude/commands/agents/guardian.meta.yaml +127 -0
- package/.claude/commands/agents/strategist.md +483 -0
- package/.claude/commands/agents/strategist.meta.yaml +158 -0
- package/.claude/commands/agents/system-designer.md +1137 -0
- package/.claude/commands/agents/system-designer.meta.yaml +156 -0
- package/.claude/commands/devflow-help.md +93 -0
- package/.claude/commands/devflow-status.md +60 -0
- package/.claude/commands/quick/create-adr.md +82 -0
- package/.claude/commands/quick/new-feature.md +57 -0
- package/.claude/commands/quick/security-check.md +54 -0
- package/.claude/commands/quick/system-design.md +58 -0
- package/.claude_project +52 -0
- package/.devflow/agents/architect.meta.yaml +122 -0
- package/.devflow/agents/builder.meta.yaml +116 -0
- package/.devflow/agents/chronicler.meta.yaml +222 -0
- package/.devflow/agents/guardian.meta.yaml +127 -0
- package/.devflow/agents/strategist.meta.yaml +158 -0
- package/.devflow/agents/system-designer.meta.yaml +265 -0
- package/.devflow/project.yaml +242 -0
- package/.gitignore-template +84 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/bin/devflow.js +54 -0
- package/lib/autopilot.js +235 -0
- package/lib/autopilotConstants.js +213 -0
- package/lib/constants.js +95 -0
- package/lib/init.js +200 -0
- package/lib/update.js +181 -0
- package/lib/utils.js +157 -0
- package/lib/web.js +119 -0
- package/package.json +57 -0
- package/web/CHANGELOG.md +192 -0
- package/web/README.md +156 -0
- package/web/app/api/autopilot/execute/route.ts +102 -0
- package/web/app/api/autopilot/terminal-execute/route.ts +124 -0
- package/web/app/api/files/route.ts +280 -0
- package/web/app/api/files/tree/route.ts +160 -0
- package/web/app/api/git/route.ts +201 -0
- package/web/app/api/health/route.ts +94 -0
- package/web/app/api/project/open/route.ts +134 -0
- package/web/app/api/search/route.ts +247 -0
- package/web/app/api/specs/route.ts +405 -0
- package/web/app/api/terminal/route.ts +222 -0
- package/web/app/globals.css +160 -0
- package/web/app/ide/layout.tsx +43 -0
- package/web/app/ide/page.tsx +216 -0
- package/web/app/layout.tsx +34 -0
- package/web/app/page.tsx +303 -0
- package/web/components/agents/AgentIcons.tsx +281 -0
- package/web/components/autopilot/AutopilotConfigModal.tsx +245 -0
- package/web/components/autopilot/AutopilotPanel.tsx +299 -0
- package/web/components/dashboard/DashboardPanel.tsx +393 -0
- package/web/components/editor/Breadcrumbs.tsx +134 -0
- package/web/components/editor/EditorPanel.tsx +120 -0
- package/web/components/editor/EditorTabs.tsx +229 -0
- package/web/components/editor/MarkdownPreview.tsx +154 -0
- package/web/components/editor/MermaidDiagram.tsx +113 -0
- package/web/components/editor/MonacoEditor.tsx +177 -0
- package/web/components/editor/TabContextMenu.tsx +207 -0
- package/web/components/git/GitPanel.tsx +534 -0
- package/web/components/layout/Shell.tsx +15 -0
- package/web/components/layout/StatusBar.tsx +100 -0
- package/web/components/modals/CommandPalette.tsx +393 -0
- package/web/components/modals/GlobalSearch.tsx +348 -0
- package/web/components/modals/QuickOpen.tsx +241 -0
- package/web/components/modals/RecentFiles.tsx +208 -0
- package/web/components/projects/ProjectSelector.tsx +147 -0
- package/web/components/settings/SettingItem.tsx +150 -0
- package/web/components/settings/SettingsPanel.tsx +323 -0
- package/web/components/specs/SpecsPanel.tsx +1091 -0
- package/web/components/terminal/TerminalPanel.tsx +683 -0
- package/web/components/ui/ContextMenu.tsx +182 -0
- package/web/components/ui/LoadingSpinner.tsx +66 -0
- package/web/components/ui/ResizeHandle.tsx +110 -0
- package/web/components/ui/Skeleton.tsx +108 -0
- package/web/components/ui/SkipLinks.tsx +37 -0
- package/web/components/ui/Toaster.tsx +57 -0
- package/web/hooks/useFocusTrap.ts +141 -0
- package/web/hooks/useKeyboardShortcuts.ts +169 -0
- package/web/hooks/useListNavigation.ts +237 -0
- package/web/lib/autopilotConstants.ts +213 -0
- package/web/lib/constants/agents.ts +67 -0
- package/web/lib/git.ts +339 -0
- package/web/lib/ptyManager.ts +191 -0
- package/web/lib/specsParser.ts +299 -0
- package/web/lib/stores/autopilotStore.ts +288 -0
- package/web/lib/stores/fileStore.ts +550 -0
- package/web/lib/stores/gitStore.ts +386 -0
- package/web/lib/stores/projectStore.ts +196 -0
- package/web/lib/stores/settingsStore.ts +126 -0
- package/web/lib/stores/specsStore.ts +297 -0
- package/web/lib/stores/uiStore.ts +175 -0
- package/web/lib/types/index.ts +177 -0
- package/web/lib/utils.ts +98 -0
- package/web/next.config.js +50 -0
- package/web/package.json +54 -0
- package/web/postcss.config.js +6 -0
- package/web/tailwind.config.ts +68 -0
- package/web/tsconfig.json +41 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared autopilot constants for CLI usage.
|
|
3
|
+
* Mirrors web/lib/autopilotConstants.ts for Node.js CLI.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const VALID_AGENTS = [
|
|
7
|
+
'strategist', 'architect', 'system-designer', 'builder', 'guardian', 'chronicler',
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const AGENT_SKILLS = {
|
|
11
|
+
strategist: '/agents:strategist',
|
|
12
|
+
architect: '/agents:architect',
|
|
13
|
+
'system-designer': '/agents:system-designer',
|
|
14
|
+
builder: '/agents:builder',
|
|
15
|
+
guardian: '/agents:guardian',
|
|
16
|
+
chronicler: '/agents:chronicler',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const AGENT_PROMPTS = {
|
|
20
|
+
strategist: `Analise a spec e refine os requisitos:
|
|
21
|
+
{spec_content}
|
|
22
|
+
|
|
23
|
+
1. Identificar requisitos implícitos
|
|
24
|
+
2. Listar acceptance criteria
|
|
25
|
+
3. Dependências e riscos
|
|
26
|
+
4. Estimar complexidade`,
|
|
27
|
+
|
|
28
|
+
architect: `Defina a arquitetura com base na spec:
|
|
29
|
+
{spec_content}
|
|
30
|
+
|
|
31
|
+
Contexto anterior: {previous_output}
|
|
32
|
+
|
|
33
|
+
1. Arquitetura da solução
|
|
34
|
+
2. Padrões e tecnologias
|
|
35
|
+
3. Componentes necessários
|
|
36
|
+
4. Decisões importantes`,
|
|
37
|
+
|
|
38
|
+
'system-designer': `Projete o system design com base na spec:
|
|
39
|
+
{spec_content}
|
|
40
|
+
|
|
41
|
+
Contexto anterior: {previous_output}
|
|
42
|
+
|
|
43
|
+
1. Back-of-the-envelope estimation
|
|
44
|
+
2. High-level design
|
|
45
|
+
3. Data model e storage
|
|
46
|
+
4. Scalability e reliability`,
|
|
47
|
+
|
|
48
|
+
builder: `Implemente a solução conforme spec e design:
|
|
49
|
+
{spec_content}
|
|
50
|
+
|
|
51
|
+
Contexto anterior: {previous_output}
|
|
52
|
+
|
|
53
|
+
1. Criar/modificar arquivos necessários
|
|
54
|
+
2. Implementar lógica principal
|
|
55
|
+
3. Tratamento de erros`,
|
|
56
|
+
|
|
57
|
+
guardian: `Revise o código implementado:
|
|
58
|
+
{spec_content}
|
|
59
|
+
|
|
60
|
+
Implementação: {previous_output}
|
|
61
|
+
|
|
62
|
+
1. Segurança
|
|
63
|
+
2. Performance
|
|
64
|
+
3. Edge cases
|
|
65
|
+
4. Melhorias necessárias`,
|
|
66
|
+
|
|
67
|
+
chronicler: `Documente as mudanças realizadas:
|
|
68
|
+
{spec_content}
|
|
69
|
+
|
|
70
|
+
Implementação: {previous_output}
|
|
71
|
+
|
|
72
|
+
1. Resumir o que foi implementado
|
|
73
|
+
2. Arquivos criados/modificados
|
|
74
|
+
3. Atualizar tasks na spec`,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const AGENT_TIMEOUTS = {
|
|
78
|
+
strategist: 300,
|
|
79
|
+
architect: 600,
|
|
80
|
+
'system-designer': 600,
|
|
81
|
+
builder: 1200,
|
|
82
|
+
guardian: 600,
|
|
83
|
+
chronicler: 300,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const DEFAULT_PHASES = [
|
|
87
|
+
{ id: 'strategist', name: 'Planning' },
|
|
88
|
+
{ id: 'architect', name: 'Design' },
|
|
89
|
+
{ id: 'system-designer', name: 'System Design' },
|
|
90
|
+
{ id: 'builder', name: 'Implementation' },
|
|
91
|
+
{ id: 'guardian', name: 'Validation' },
|
|
92
|
+
{ id: 'chronicler', name: 'Documentation' },
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const TASK_TRACKING_AGENTS = ['builder', 'guardian', 'chronicler'];
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Load the full agent definition from .claude/commands/agents/{agent}.md
|
|
99
|
+
* Returns the file content or null if not found.
|
|
100
|
+
*/
|
|
101
|
+
function loadAgentDefinitionSync(projectPath, agent) {
|
|
102
|
+
const path = require('node:path');
|
|
103
|
+
const fs = require('node:fs');
|
|
104
|
+
const filePath = path.join(projectPath, '.claude', 'commands', 'agents', `${agent}.md`);
|
|
105
|
+
try {
|
|
106
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Build the full prompt for a given agent phase.
|
|
114
|
+
* If agentDefinition is provided, uses it as context instead of the slash command.
|
|
115
|
+
*/
|
|
116
|
+
function buildPrompt(agent, specContent, previousOutputs, agentDefinition) {
|
|
117
|
+
const prompt = AGENT_PROMPTS[agent]
|
|
118
|
+
.replace('{spec_content}', specContent)
|
|
119
|
+
.replace('{previous_output}', (previousOutputs || []).join('\n---\n') || 'N/A');
|
|
120
|
+
|
|
121
|
+
// Use full agent definition if available, otherwise fallback to skill command
|
|
122
|
+
const agentContext = agentDefinition || AGENT_SKILLS[agent];
|
|
123
|
+
|
|
124
|
+
return `${agentContext}\n\n---\n\n${prompt}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Extract unchecked task titles from markdown content.
|
|
129
|
+
*/
|
|
130
|
+
function extractUncheckedTasks(content) {
|
|
131
|
+
const tasks = [];
|
|
132
|
+
const regex = /^\s*[-*]\s*\[ \]\s*(?:\[[^\]]+\]\s*)?(.+)$/gm;
|
|
133
|
+
let match;
|
|
134
|
+
while ((match = regex.exec(content)) !== null) {
|
|
135
|
+
tasks.push(match[1].trim());
|
|
136
|
+
}
|
|
137
|
+
return tasks;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check if the agent output mentions a task as completed.
|
|
142
|
+
*/
|
|
143
|
+
function isTaskMentionedAsCompleted(taskTitle, output) {
|
|
144
|
+
const lower = output.toLowerCase();
|
|
145
|
+
const taskLower = taskTitle.toLowerCase();
|
|
146
|
+
|
|
147
|
+
if (!lower.includes(taskLower)) return false;
|
|
148
|
+
|
|
149
|
+
if (taskLower.length < 10) {
|
|
150
|
+
const completionKeywords = [
|
|
151
|
+
'completed', 'done', 'implemented', 'finished', 'created',
|
|
152
|
+
'added', 'fixed', 'resolved', 'built', 'configured',
|
|
153
|
+
'✅', '✓', '[x]', 'complete',
|
|
154
|
+
];
|
|
155
|
+
const idx = lower.indexOf(taskLower);
|
|
156
|
+
const context = lower.slice(Math.max(0, idx - 200), idx + taskLower.length + 200);
|
|
157
|
+
return completionKeywords.some(kw => context.includes(kw));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Auto-detect completed tasks and update the spec file.
|
|
165
|
+
*/
|
|
166
|
+
async function autoUpdateSpecTasks(specFilePath, agentOutput) {
|
|
167
|
+
const fs = require('node:fs').promises;
|
|
168
|
+
const completedTasks = [];
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const content = await fs.readFile(specFilePath, 'utf-8');
|
|
172
|
+
const uncheckedTasks = extractUncheckedTasks(content);
|
|
173
|
+
|
|
174
|
+
if (uncheckedTasks.length === 0) return [];
|
|
175
|
+
|
|
176
|
+
let updatedContent = content;
|
|
177
|
+
for (const taskTitle of uncheckedTasks) {
|
|
178
|
+
if (isTaskMentionedAsCompleted(taskTitle, agentOutput)) {
|
|
179
|
+
const escapedTitle = taskTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
180
|
+
const taskRegex = new RegExp(
|
|
181
|
+
`^(\\s*[-*]\\s*)\\[ \\](\\s*(?:\\[[^\\]]+\\]\\s*)?)${escapedTitle}`,
|
|
182
|
+
'gm'
|
|
183
|
+
);
|
|
184
|
+
updatedContent = updatedContent.replace(taskRegex, (match, prefix, middle) => {
|
|
185
|
+
completedTasks.push(taskTitle);
|
|
186
|
+
return `${prefix}[x]${middle}${taskTitle}`;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (completedTasks.length > 0) {
|
|
192
|
+
await fs.writeFile(specFilePath, updatedContent, 'utf-8');
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error('Error auto-updating spec tasks:', error.message);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return completedTasks;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
VALID_AGENTS,
|
|
203
|
+
AGENT_SKILLS,
|
|
204
|
+
AGENT_PROMPTS,
|
|
205
|
+
AGENT_TIMEOUTS,
|
|
206
|
+
DEFAULT_PHASES,
|
|
207
|
+
TASK_TRACKING_AGENTS,
|
|
208
|
+
loadAgentDefinitionSync,
|
|
209
|
+
buildPrompt,
|
|
210
|
+
extractUncheckedTasks,
|
|
211
|
+
isTaskMentionedAsCompleted,
|
|
212
|
+
autoUpdateSpecTasks,
|
|
213
|
+
};
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
|
|
3
|
+
const VERSION = '0.8.0';
|
|
4
|
+
|
|
5
|
+
// Root of the installed npm package (where source files live)
|
|
6
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
7
|
+
|
|
8
|
+
// Directories to create in all install modes
|
|
9
|
+
const BASE_DIRS = [
|
|
10
|
+
'.claude/commands',
|
|
11
|
+
'.devflow/agents',
|
|
12
|
+
'.devflow/memory',
|
|
13
|
+
'.devflow/sessions',
|
|
14
|
+
'docs/snapshots',
|
|
15
|
+
'docs/system-design/sdd',
|
|
16
|
+
'docs/system-design/rfc',
|
|
17
|
+
'docs/system-design/capacity',
|
|
18
|
+
'docs/system-design/trade-offs',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// Additional directories for default/full modes
|
|
22
|
+
const DOCS_DIRS = [
|
|
23
|
+
'docs/planning/stories',
|
|
24
|
+
'docs/architecture/diagrams',
|
|
25
|
+
'docs/decisions',
|
|
26
|
+
'docs/security',
|
|
27
|
+
'docs/performance',
|
|
28
|
+
'docs/api',
|
|
29
|
+
'docs/migration',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// Directories that should have .gitkeep files
|
|
33
|
+
const GITKEEP_DIRS = [
|
|
34
|
+
'docs/planning',
|
|
35
|
+
'docs/planning/stories',
|
|
36
|
+
'docs/architecture',
|
|
37
|
+
'docs/architecture/diagrams',
|
|
38
|
+
'docs/decisions',
|
|
39
|
+
'docs/security',
|
|
40
|
+
'docs/performance',
|
|
41
|
+
'docs/api',
|
|
42
|
+
'docs/migration',
|
|
43
|
+
'docs/snapshots',
|
|
44
|
+
'docs/system-design/sdd',
|
|
45
|
+
'docs/system-design/rfc',
|
|
46
|
+
'docs/system-design/capacity',
|
|
47
|
+
'docs/system-design/trade-offs',
|
|
48
|
+
'.devflow/sessions',
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// Files and directories to copy during init
|
|
52
|
+
const AGENTS_COPY = [
|
|
53
|
+
{ src: '.claude/commands/agents', dest: '.claude/commands/agents', type: 'dir' },
|
|
54
|
+
{ src: '.claude/commands/quick', dest: '.claude/commands/quick', type: 'dir' },
|
|
55
|
+
{ src: '.claude/commands/devflow-help.md', dest: '.claude/commands/devflow-help.md', type: 'file' },
|
|
56
|
+
{ src: '.claude/commands/devflow-status.md', dest: '.claude/commands/devflow-status.md', type: 'file' },
|
|
57
|
+
{ src: '.claude_project', dest: '.claude_project', type: 'file' },
|
|
58
|
+
{ src: '.devflow/project.yaml', dest: '.devflow/project.yaml', type: 'file' },
|
|
59
|
+
{ src: '.devflow/agents', dest: '.devflow/agents', type: 'dir' },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// Doc files to copy in default/full modes
|
|
63
|
+
const DOC_FILES = [
|
|
64
|
+
'docs/decisions/000-template.md',
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
// Web IDE source directories to copy (excludes node_modules, .next, etc.)
|
|
68
|
+
const WEB_COPY_DIRS = [
|
|
69
|
+
'web/app',
|
|
70
|
+
'web/components',
|
|
71
|
+
'web/hooks',
|
|
72
|
+
'web/lib',
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const WEB_COPY_FILES = [
|
|
76
|
+
'web/package.json',
|
|
77
|
+
'web/next.config.js',
|
|
78
|
+
'web/tsconfig.json',
|
|
79
|
+
'web/tailwind.config.ts',
|
|
80
|
+
'web/postcss.config.js',
|
|
81
|
+
'web/README.md',
|
|
82
|
+
'web/CHANGELOG.md',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
VERSION,
|
|
87
|
+
PACKAGE_ROOT,
|
|
88
|
+
BASE_DIRS,
|
|
89
|
+
DOCS_DIRS,
|
|
90
|
+
GITKEEP_DIRS,
|
|
91
|
+
AGENTS_COPY,
|
|
92
|
+
DOC_FILES,
|
|
93
|
+
WEB_COPY_DIRS,
|
|
94
|
+
WEB_COPY_FILES,
|
|
95
|
+
};
|
package/lib/init.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const {
|
|
4
|
+
VERSION,
|
|
5
|
+
PACKAGE_ROOT,
|
|
6
|
+
BASE_DIRS,
|
|
7
|
+
DOCS_DIRS,
|
|
8
|
+
GITKEEP_DIRS,
|
|
9
|
+
AGENTS_COPY,
|
|
10
|
+
DOC_FILES,
|
|
11
|
+
WEB_COPY_DIRS,
|
|
12
|
+
WEB_COPY_FILES,
|
|
13
|
+
} = require('./constants');
|
|
14
|
+
const {
|
|
15
|
+
printHeader,
|
|
16
|
+
success,
|
|
17
|
+
error,
|
|
18
|
+
warn,
|
|
19
|
+
info,
|
|
20
|
+
printSuccess,
|
|
21
|
+
confirm,
|
|
22
|
+
checkDependencies,
|
|
23
|
+
copyDir,
|
|
24
|
+
copyFile,
|
|
25
|
+
ensureDirs,
|
|
26
|
+
createGitkeep,
|
|
27
|
+
pathExists,
|
|
28
|
+
resolveTarget,
|
|
29
|
+
} = require('./utils');
|
|
30
|
+
|
|
31
|
+
async function initCommand(targetArg, options) {
|
|
32
|
+
printHeader(VERSION);
|
|
33
|
+
|
|
34
|
+
const targetDir = resolveTarget(targetArg);
|
|
35
|
+
|
|
36
|
+
// 1. Check dependencies
|
|
37
|
+
if (!options.skipDeps) {
|
|
38
|
+
checkDependencies();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2. Validate/create target directory
|
|
42
|
+
if (!(await pathExists(targetDir))) {
|
|
43
|
+
info(`Directory does not exist: ${targetDir}`);
|
|
44
|
+
const shouldCreate = options.force || await confirm('Create this directory?');
|
|
45
|
+
if (!shouldCreate) {
|
|
46
|
+
error('Installation cancelled.');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
await fs.promises.mkdir(targetDir, { recursive: true });
|
|
50
|
+
success(`Directory created: ${targetDir}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
info(`Installing DevFlow in: ${targetDir}`);
|
|
54
|
+
console.log();
|
|
55
|
+
|
|
56
|
+
// 3. Check for existing installation
|
|
57
|
+
if (await pathExists(path.join(targetDir, '.devflow'))) {
|
|
58
|
+
warn('.devflow/ already exists in target directory!');
|
|
59
|
+
if (!options.force) {
|
|
60
|
+
const shouldOverwrite = await confirm('Overwrite existing installation?');
|
|
61
|
+
if (!shouldOverwrite) {
|
|
62
|
+
error('Installation cancelled.');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
console.log();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 4. Determine install mode
|
|
70
|
+
let mode = 'default';
|
|
71
|
+
if (options.agentsOnly) mode = 'agents-only';
|
|
72
|
+
if (options.full) mode = 'full';
|
|
73
|
+
|
|
74
|
+
const modeLabels = {
|
|
75
|
+
'agents-only': 'Agents only (minimal)',
|
|
76
|
+
'default': 'Agents + documentation structure',
|
|
77
|
+
'full': 'Full installation',
|
|
78
|
+
};
|
|
79
|
+
info(`Install mode: ${modeLabels[mode]}`);
|
|
80
|
+
console.log();
|
|
81
|
+
|
|
82
|
+
// 5. Create base directory structure
|
|
83
|
+
await ensureDirs(targetDir, BASE_DIRS);
|
|
84
|
+
|
|
85
|
+
// 6. Copy agent files and core config
|
|
86
|
+
for (const item of AGENTS_COPY) {
|
|
87
|
+
const src = path.join(PACKAGE_ROOT, item.src);
|
|
88
|
+
const dest = path.join(targetDir, item.dest);
|
|
89
|
+
try {
|
|
90
|
+
if (item.type === 'dir') {
|
|
91
|
+
await copyDir(src, dest);
|
|
92
|
+
} else {
|
|
93
|
+
await copyFile(src, dest);
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
warn(`Could not copy ${item.src}: ${err.message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
success('Agents installed (.claude/commands/agents/)');
|
|
101
|
+
success('Quick commands installed (.claude/commands/quick/)');
|
|
102
|
+
success('.claude_project orchestration rules installed');
|
|
103
|
+
success('.devflow/ structure created');
|
|
104
|
+
|
|
105
|
+
// 7. Default/full: create docs structure with gitkeep files
|
|
106
|
+
if (mode === 'default' || mode === 'full') {
|
|
107
|
+
await ensureDirs(targetDir, DOCS_DIRS);
|
|
108
|
+
await createGitkeep(targetDir, GITKEEP_DIRS);
|
|
109
|
+
|
|
110
|
+
// Copy doc template files
|
|
111
|
+
for (const file of DOC_FILES) {
|
|
112
|
+
const src = path.join(PACKAGE_ROOT, file);
|
|
113
|
+
const dest = path.join(targetDir, file);
|
|
114
|
+
try {
|
|
115
|
+
if (!(await pathExists(dest))) {
|
|
116
|
+
await copyFile(src, dest);
|
|
117
|
+
}
|
|
118
|
+
} catch { /* skip if source missing */ }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
success('Documentation structure created (docs/)');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 8. Full: copy .gitignore
|
|
125
|
+
if (mode === 'full') {
|
|
126
|
+
const gitignoreSrc = path.join(PACKAGE_ROOT, '.gitignore-template');
|
|
127
|
+
const gitignoreDest = path.join(targetDir, '.gitignore');
|
|
128
|
+
|
|
129
|
+
if (await pathExists(gitignoreSrc)) {
|
|
130
|
+
if (await pathExists(gitignoreDest)) {
|
|
131
|
+
warn('.gitignore already exists - appending DevFlow entries');
|
|
132
|
+
const existing = await fs.promises.readFile(gitignoreDest, 'utf8');
|
|
133
|
+
if (!existing.includes('# DevFlow')) {
|
|
134
|
+
const devflowIgnore = await fs.promises.readFile(gitignoreSrc, 'utf8');
|
|
135
|
+
await fs.promises.appendFile(
|
|
136
|
+
gitignoreDest,
|
|
137
|
+
`\n\n# --- DevFlow entries ---\n${devflowIgnore}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
success('.gitignore updated with DevFlow entries');
|
|
141
|
+
} else {
|
|
142
|
+
await copyFile(gitignoreSrc, gitignoreDest);
|
|
143
|
+
success('.gitignore created');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 9. Optional: copy Web IDE
|
|
149
|
+
if (options.web) {
|
|
150
|
+
info('Installing Web IDE source files...');
|
|
151
|
+
|
|
152
|
+
// Copy web directories
|
|
153
|
+
for (const dir of WEB_COPY_DIRS) {
|
|
154
|
+
const src = path.join(PACKAGE_ROOT, dir);
|
|
155
|
+
const dest = path.join(targetDir, dir);
|
|
156
|
+
try {
|
|
157
|
+
await copyDir(src, dest);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
warn(`Could not copy ${dir}: ${err.message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Copy web files
|
|
164
|
+
for (const file of WEB_COPY_FILES) {
|
|
165
|
+
const src = path.join(PACKAGE_ROOT, file);
|
|
166
|
+
const dest = path.join(targetDir, file);
|
|
167
|
+
try {
|
|
168
|
+
await copyFile(src, dest);
|
|
169
|
+
} catch { /* skip if source missing */ }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
success('Web IDE source files installed (web/)');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 10. Success output
|
|
176
|
+
printSuccess(VERSION);
|
|
177
|
+
info('Next steps:');
|
|
178
|
+
console.log();
|
|
179
|
+
console.log('1. Open the project in Claude Code:');
|
|
180
|
+
console.log(` cd ${targetDir}`);
|
|
181
|
+
console.log(' claude');
|
|
182
|
+
console.log();
|
|
183
|
+
console.log('2. In Claude Code, test an agent:');
|
|
184
|
+
console.log(' /agents:strategist Hello! Introduce yourself');
|
|
185
|
+
console.log();
|
|
186
|
+
console.log('3. Create your first feature:');
|
|
187
|
+
console.log(' /agents:strategist I want to create [your feature]');
|
|
188
|
+
|
|
189
|
+
if (options.web) {
|
|
190
|
+
console.log();
|
|
191
|
+
console.log('4. Start the Web IDE:');
|
|
192
|
+
console.log(` cd ${path.join(targetDir, 'web')}`);
|
|
193
|
+
console.log(' npm install');
|
|
194
|
+
console.log(' npm run dev');
|
|
195
|
+
console.log(' # Open http://localhost:3000');
|
|
196
|
+
}
|
|
197
|
+
console.log();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = { initCommand };
|
package/lib/update.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const {
|
|
4
|
+
VERSION,
|
|
5
|
+
PACKAGE_ROOT,
|
|
6
|
+
BASE_DIRS,
|
|
7
|
+
DOCS_DIRS,
|
|
8
|
+
GITKEEP_DIRS,
|
|
9
|
+
} = require('./constants');
|
|
10
|
+
const {
|
|
11
|
+
printHeader,
|
|
12
|
+
success,
|
|
13
|
+
error,
|
|
14
|
+
warn,
|
|
15
|
+
info,
|
|
16
|
+
printUpdateSuccess,
|
|
17
|
+
confirm,
|
|
18
|
+
copyDir,
|
|
19
|
+
copyFile,
|
|
20
|
+
ensureDirs,
|
|
21
|
+
createGitkeep,
|
|
22
|
+
pathExists,
|
|
23
|
+
resolveTarget,
|
|
24
|
+
} = require('./utils');
|
|
25
|
+
|
|
26
|
+
async function updateCommand(targetArg, options) {
|
|
27
|
+
printHeader(VERSION);
|
|
28
|
+
|
|
29
|
+
const targetDir = resolveTarget(targetArg);
|
|
30
|
+
|
|
31
|
+
// 1. Verify DevFlow is installed
|
|
32
|
+
const hasAgents = await pathExists(path.join(targetDir, '.claude', 'commands', 'agents'));
|
|
33
|
+
const hasDevflow = await pathExists(path.join(targetDir, '.devflow'));
|
|
34
|
+
|
|
35
|
+
if (!hasAgents && !hasDevflow) {
|
|
36
|
+
error(`DevFlow not found in: ${targetDir}`);
|
|
37
|
+
info('Use "devflow init" to install for the first time.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2. Read installed version
|
|
42
|
+
let installedVersion = 'unknown';
|
|
43
|
+
const projectYamlPath = path.join(targetDir, '.devflow', 'project.yaml');
|
|
44
|
+
try {
|
|
45
|
+
const content = await fs.promises.readFile(projectYamlPath, 'utf8');
|
|
46
|
+
const match = content.match(/^\s*version:\s*"?([^"\s]+)"?/m);
|
|
47
|
+
if (match) installedVersion = match[1];
|
|
48
|
+
} catch { /* file may not exist */ }
|
|
49
|
+
|
|
50
|
+
info(`Project: ${targetDir}`);
|
|
51
|
+
info(`Installed version: ${installedVersion}`);
|
|
52
|
+
info(`New version: ${VERSION}`);
|
|
53
|
+
console.log();
|
|
54
|
+
|
|
55
|
+
// 3. Check if already up to date
|
|
56
|
+
if (installedVersion === VERSION) {
|
|
57
|
+
success('DevFlow is already up to date!');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 4. Confirm update
|
|
62
|
+
if (!options.force) {
|
|
63
|
+
warn('The update will overwrite agent files.');
|
|
64
|
+
warn('Customizations in .claude/commands/agents/ will be lost.');
|
|
65
|
+
console.log();
|
|
66
|
+
const shouldContinue = await confirm('Continue with update?');
|
|
67
|
+
if (!shouldContinue) {
|
|
68
|
+
warn('Update cancelled.');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log();
|
|
74
|
+
info('Updating DevFlow...');
|
|
75
|
+
console.log();
|
|
76
|
+
|
|
77
|
+
// 5. Backup existing agents
|
|
78
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
79
|
+
const backupDir = path.join(targetDir, '.devflow', `backup-${timestamp}`);
|
|
80
|
+
await fs.promises.mkdir(backupDir, { recursive: true });
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await fs.promises.cp(
|
|
84
|
+
path.join(targetDir, '.claude', 'commands', 'agents'),
|
|
85
|
+
path.join(backupDir, 'agents'),
|
|
86
|
+
{ recursive: true }
|
|
87
|
+
);
|
|
88
|
+
} catch { /* may not exist */ }
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await fs.promises.copyFile(
|
|
92
|
+
projectYamlPath,
|
|
93
|
+
path.join(backupDir, 'project.yaml')
|
|
94
|
+
);
|
|
95
|
+
} catch { /* may not exist */ }
|
|
96
|
+
|
|
97
|
+
success(`Backup created: .devflow/${path.basename(backupDir)}/`);
|
|
98
|
+
|
|
99
|
+
// 6. Update agents
|
|
100
|
+
console.log(' \u2192 Updating agents...');
|
|
101
|
+
await copyDir(
|
|
102
|
+
path.join(PACKAGE_ROOT, '.claude', 'commands', 'agents'),
|
|
103
|
+
path.join(targetDir, '.claude', 'commands', 'agents')
|
|
104
|
+
);
|
|
105
|
+
success('Agents updated (.claude/commands/agents/)');
|
|
106
|
+
|
|
107
|
+
// 7. Update quick commands
|
|
108
|
+
console.log(' \u2192 Updating quick commands...');
|
|
109
|
+
await copyDir(
|
|
110
|
+
path.join(PACKAGE_ROOT, '.claude', 'commands', 'quick'),
|
|
111
|
+
path.join(targetDir, '.claude', 'commands', 'quick')
|
|
112
|
+
);
|
|
113
|
+
success('Quick commands updated');
|
|
114
|
+
|
|
115
|
+
// 8. Update help & status commands
|
|
116
|
+
console.log(' \u2192 Updating help & status commands...');
|
|
117
|
+
for (const file of ['devflow-help.md', 'devflow-status.md']) {
|
|
118
|
+
try {
|
|
119
|
+
await copyFile(
|
|
120
|
+
path.join(PACKAGE_ROOT, '.claude', 'commands', file),
|
|
121
|
+
path.join(targetDir, '.claude', 'commands', file)
|
|
122
|
+
);
|
|
123
|
+
} catch { /* skip if missing */ }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 9. Update .claude_project
|
|
127
|
+
console.log(' \u2192 Updating .claude_project...');
|
|
128
|
+
await copyFile(
|
|
129
|
+
path.join(PACKAGE_ROOT, '.claude_project'),
|
|
130
|
+
path.join(targetDir, '.claude_project')
|
|
131
|
+
);
|
|
132
|
+
success('.claude_project updated');
|
|
133
|
+
|
|
134
|
+
// 10. Update .devflow/agents/ meta.yaml files
|
|
135
|
+
console.log(' \u2192 Updating .devflow/agents/ meta.yaml files...');
|
|
136
|
+
await copyDir(
|
|
137
|
+
path.join(PACKAGE_ROOT, '.devflow', 'agents'),
|
|
138
|
+
path.join(targetDir, '.devflow', 'agents')
|
|
139
|
+
);
|
|
140
|
+
success('.devflow/agents/ updated');
|
|
141
|
+
|
|
142
|
+
// 11. Update project.yaml version
|
|
143
|
+
console.log(' \u2192 Updating project.yaml version...');
|
|
144
|
+
try {
|
|
145
|
+
let content = await fs.promises.readFile(projectYamlPath, 'utf8');
|
|
146
|
+
content = content.replace(
|
|
147
|
+
/version:\s*"?[^"\s]+"?/,
|
|
148
|
+
`version: "${VERSION}"`
|
|
149
|
+
);
|
|
150
|
+
await fs.promises.writeFile(projectYamlPath, content, 'utf8');
|
|
151
|
+
success(`project.yaml version updated to ${VERSION}`);
|
|
152
|
+
} catch {
|
|
153
|
+
await copyFile(
|
|
154
|
+
path.join(PACKAGE_ROOT, '.devflow', 'project.yaml'),
|
|
155
|
+
projectYamlPath
|
|
156
|
+
);
|
|
157
|
+
success('project.yaml created');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 12. Ensure directory structure is complete
|
|
161
|
+
console.log(' \u2192 Verifying directory structure...');
|
|
162
|
+
await ensureDirs(targetDir, [...BASE_DIRS, ...DOCS_DIRS]);
|
|
163
|
+
await createGitkeep(targetDir, GITKEEP_DIRS);
|
|
164
|
+
success('Directory structure verified');
|
|
165
|
+
|
|
166
|
+
// 13. Success output
|
|
167
|
+
printUpdateSuccess(VERSION);
|
|
168
|
+
info('What was updated:');
|
|
169
|
+
console.log(' \u2022 Agents in .claude/commands/agents/');
|
|
170
|
+
console.log(' \u2022 Quick commands in .claude/commands/quick/');
|
|
171
|
+
console.log(' \u2022 .claude_project orchestration rules');
|
|
172
|
+
console.log(' \u2022 .devflow/agents/ meta.yaml files');
|
|
173
|
+
console.log(' \u2022 Directory structure');
|
|
174
|
+
console.log();
|
|
175
|
+
info(`Backup saved in: .devflow/${path.basename(backupDir)}/`);
|
|
176
|
+
console.log();
|
|
177
|
+
info('Tip: Use /agents:strategist to start a new feature');
|
|
178
|
+
console.log();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = { updateCommand };
|