@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.
Files changed (106) hide show
  1. package/.claude/commands/agents/architect.md +1162 -0
  2. package/.claude/commands/agents/architect.meta.yaml +124 -0
  3. package/.claude/commands/agents/builder.md +1432 -0
  4. package/.claude/commands/agents/builder.meta.yaml +117 -0
  5. package/.claude/commands/agents/chronicler.md +633 -0
  6. package/.claude/commands/agents/chronicler.meta.yaml +217 -0
  7. package/.claude/commands/agents/guardian.md +456 -0
  8. package/.claude/commands/agents/guardian.meta.yaml +127 -0
  9. package/.claude/commands/agents/strategist.md +483 -0
  10. package/.claude/commands/agents/strategist.meta.yaml +158 -0
  11. package/.claude/commands/agents/system-designer.md +1137 -0
  12. package/.claude/commands/agents/system-designer.meta.yaml +156 -0
  13. package/.claude/commands/devflow-help.md +93 -0
  14. package/.claude/commands/devflow-status.md +60 -0
  15. package/.claude/commands/quick/create-adr.md +82 -0
  16. package/.claude/commands/quick/new-feature.md +57 -0
  17. package/.claude/commands/quick/security-check.md +54 -0
  18. package/.claude/commands/quick/system-design.md +58 -0
  19. package/.claude_project +52 -0
  20. package/.devflow/agents/architect.meta.yaml +122 -0
  21. package/.devflow/agents/builder.meta.yaml +116 -0
  22. package/.devflow/agents/chronicler.meta.yaml +222 -0
  23. package/.devflow/agents/guardian.meta.yaml +127 -0
  24. package/.devflow/agents/strategist.meta.yaml +158 -0
  25. package/.devflow/agents/system-designer.meta.yaml +265 -0
  26. package/.devflow/project.yaml +242 -0
  27. package/.gitignore-template +84 -0
  28. package/LICENSE +21 -0
  29. package/README.md +249 -0
  30. package/bin/devflow.js +54 -0
  31. package/lib/autopilot.js +235 -0
  32. package/lib/autopilotConstants.js +213 -0
  33. package/lib/constants.js +95 -0
  34. package/lib/init.js +200 -0
  35. package/lib/update.js +181 -0
  36. package/lib/utils.js +157 -0
  37. package/lib/web.js +119 -0
  38. package/package.json +57 -0
  39. package/web/CHANGELOG.md +192 -0
  40. package/web/README.md +156 -0
  41. package/web/app/api/autopilot/execute/route.ts +102 -0
  42. package/web/app/api/autopilot/terminal-execute/route.ts +124 -0
  43. package/web/app/api/files/route.ts +280 -0
  44. package/web/app/api/files/tree/route.ts +160 -0
  45. package/web/app/api/git/route.ts +201 -0
  46. package/web/app/api/health/route.ts +94 -0
  47. package/web/app/api/project/open/route.ts +134 -0
  48. package/web/app/api/search/route.ts +247 -0
  49. package/web/app/api/specs/route.ts +405 -0
  50. package/web/app/api/terminal/route.ts +222 -0
  51. package/web/app/globals.css +160 -0
  52. package/web/app/ide/layout.tsx +43 -0
  53. package/web/app/ide/page.tsx +216 -0
  54. package/web/app/layout.tsx +34 -0
  55. package/web/app/page.tsx +303 -0
  56. package/web/components/agents/AgentIcons.tsx +281 -0
  57. package/web/components/autopilot/AutopilotConfigModal.tsx +245 -0
  58. package/web/components/autopilot/AutopilotPanel.tsx +299 -0
  59. package/web/components/dashboard/DashboardPanel.tsx +393 -0
  60. package/web/components/editor/Breadcrumbs.tsx +134 -0
  61. package/web/components/editor/EditorPanel.tsx +120 -0
  62. package/web/components/editor/EditorTabs.tsx +229 -0
  63. package/web/components/editor/MarkdownPreview.tsx +154 -0
  64. package/web/components/editor/MermaidDiagram.tsx +113 -0
  65. package/web/components/editor/MonacoEditor.tsx +177 -0
  66. package/web/components/editor/TabContextMenu.tsx +207 -0
  67. package/web/components/git/GitPanel.tsx +534 -0
  68. package/web/components/layout/Shell.tsx +15 -0
  69. package/web/components/layout/StatusBar.tsx +100 -0
  70. package/web/components/modals/CommandPalette.tsx +393 -0
  71. package/web/components/modals/GlobalSearch.tsx +348 -0
  72. package/web/components/modals/QuickOpen.tsx +241 -0
  73. package/web/components/modals/RecentFiles.tsx +208 -0
  74. package/web/components/projects/ProjectSelector.tsx +147 -0
  75. package/web/components/settings/SettingItem.tsx +150 -0
  76. package/web/components/settings/SettingsPanel.tsx +323 -0
  77. package/web/components/specs/SpecsPanel.tsx +1091 -0
  78. package/web/components/terminal/TerminalPanel.tsx +683 -0
  79. package/web/components/ui/ContextMenu.tsx +182 -0
  80. package/web/components/ui/LoadingSpinner.tsx +66 -0
  81. package/web/components/ui/ResizeHandle.tsx +110 -0
  82. package/web/components/ui/Skeleton.tsx +108 -0
  83. package/web/components/ui/SkipLinks.tsx +37 -0
  84. package/web/components/ui/Toaster.tsx +57 -0
  85. package/web/hooks/useFocusTrap.ts +141 -0
  86. package/web/hooks/useKeyboardShortcuts.ts +169 -0
  87. package/web/hooks/useListNavigation.ts +237 -0
  88. package/web/lib/autopilotConstants.ts +213 -0
  89. package/web/lib/constants/agents.ts +67 -0
  90. package/web/lib/git.ts +339 -0
  91. package/web/lib/ptyManager.ts +191 -0
  92. package/web/lib/specsParser.ts +299 -0
  93. package/web/lib/stores/autopilotStore.ts +288 -0
  94. package/web/lib/stores/fileStore.ts +550 -0
  95. package/web/lib/stores/gitStore.ts +386 -0
  96. package/web/lib/stores/projectStore.ts +196 -0
  97. package/web/lib/stores/settingsStore.ts +126 -0
  98. package/web/lib/stores/specsStore.ts +297 -0
  99. package/web/lib/stores/uiStore.ts +175 -0
  100. package/web/lib/types/index.ts +177 -0
  101. package/web/lib/utils.ts +98 -0
  102. package/web/next.config.js +50 -0
  103. package/web/package.json +54 -0
  104. package/web/postcss.config.js +6 -0
  105. package/web/tailwind.config.ts +68 -0
  106. package/web/tsconfig.json +41 -0
@@ -0,0 +1,169 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+ import { useUIStore } from '@/lib/stores/uiStore';
5
+ import { useFileStore } from '@/lib/stores/fileStore';
6
+ import { useSettingsStore } from '@/lib/stores/settingsStore';
7
+
8
+ export function useKeyboardShortcuts() {
9
+ const { openModal, activeModal, closeModal, toggleSidebar, toggleTerminal, togglePreview } = useUIStore();
10
+ const {
11
+ activeFile,
12
+ saveFile,
13
+ closeFile,
14
+ navigateBack,
15
+ navigateForward,
16
+ canGoBack,
17
+ canGoForward,
18
+ reopenClosedTab,
19
+ } = useFileStore();
20
+ const { openSettings, isSettingsOpen, closeSettings } = useSettingsStore();
21
+
22
+ useEffect(() => {
23
+ const handleKeyDown = (e: KeyboardEvent) => {
24
+ const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
25
+ const modKey = isMac ? e.metaKey : e.ctrlKey;
26
+
27
+ // Don't trigger shortcuts when typing in inputs
28
+ const target = e.target as HTMLElement;
29
+ const isInput = target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
30
+
31
+ // Allow Escape to close modals/settings even in inputs
32
+ if (e.key === 'Escape') {
33
+ if (isSettingsOpen) {
34
+ e.preventDefault();
35
+ closeSettings();
36
+ return;
37
+ }
38
+ if (activeModal) {
39
+ e.preventDefault();
40
+ closeModal();
41
+ return;
42
+ }
43
+ }
44
+
45
+ // If in input and not using mod key, skip
46
+ if (isInput && !modKey) return;
47
+
48
+ // Cmd/Ctrl + P - Quick Open
49
+ if (modKey && e.key === 'p' && !e.shiftKey) {
50
+ e.preventDefault();
51
+ openModal('quickOpen');
52
+ return;
53
+ }
54
+
55
+ // Cmd/Ctrl + Shift + F - Global Search
56
+ if (modKey && e.shiftKey && e.key === 'f') {
57
+ e.preventDefault();
58
+ openModal('globalSearch');
59
+ return;
60
+ }
61
+
62
+ // Cmd/Ctrl + Shift + P - Command Palette
63
+ if (modKey && e.shiftKey && e.key === 'p') {
64
+ e.preventDefault();
65
+ openModal('commandPalette');
66
+ return;
67
+ }
68
+
69
+ // Cmd/Ctrl + B - Toggle Sidebar
70
+ if (modKey && e.key === 'b' && !e.shiftKey) {
71
+ e.preventDefault();
72
+ toggleSidebar();
73
+ return;
74
+ }
75
+
76
+ // Cmd/Ctrl + ` - Toggle Terminal
77
+ if (modKey && e.key === '`') {
78
+ e.preventDefault();
79
+ toggleTerminal();
80
+ return;
81
+ }
82
+
83
+ // Cmd/Ctrl + Shift + V - Toggle Preview
84
+ if (modKey && e.shiftKey && e.key === 'v') {
85
+ e.preventDefault();
86
+ togglePreview();
87
+ return;
88
+ }
89
+
90
+ // Cmd/Ctrl + S - Save File
91
+ if (modKey && e.key === 's' && !e.shiftKey) {
92
+ e.preventDefault();
93
+ if (activeFile) {
94
+ saveFile(activeFile);
95
+ }
96
+ return;
97
+ }
98
+
99
+ // Cmd/Ctrl + , - Open Settings
100
+ if (modKey && e.key === ',') {
101
+ e.preventDefault();
102
+ openSettings();
103
+ return;
104
+ }
105
+
106
+ // Cmd/Ctrl + Tab - Recent Files
107
+ if (modKey && e.key === 'Tab' && !e.shiftKey) {
108
+ e.preventDefault();
109
+ openModal('recentFiles');
110
+ return;
111
+ }
112
+
113
+ // Cmd/Ctrl + W - Close Tab
114
+ if (modKey && e.key === 'w' && !e.shiftKey) {
115
+ e.preventDefault();
116
+ if (activeFile) {
117
+ closeFile(activeFile);
118
+ }
119
+ return;
120
+ }
121
+
122
+ // Cmd/Ctrl + Shift + T - Reopen Closed Tab
123
+ if (modKey && e.shiftKey && e.key === 't') {
124
+ e.preventDefault();
125
+ reopenClosedTab();
126
+ return;
127
+ }
128
+
129
+ // Alt + Left - Navigate Back
130
+ if (e.altKey && e.key === 'ArrowLeft' && !modKey) {
131
+ e.preventDefault();
132
+ if (canGoBack()) {
133
+ navigateBack();
134
+ }
135
+ return;
136
+ }
137
+
138
+ // Alt + Right - Navigate Forward
139
+ if (e.altKey && e.key === 'ArrowRight' && !modKey) {
140
+ e.preventDefault();
141
+ if (canGoForward()) {
142
+ navigateForward();
143
+ }
144
+ return;
145
+ }
146
+ };
147
+
148
+ window.addEventListener('keydown', handleKeyDown);
149
+ return () => window.removeEventListener('keydown', handleKeyDown);
150
+ }, [
151
+ openModal,
152
+ activeModal,
153
+ closeModal,
154
+ toggleSidebar,
155
+ toggleTerminal,
156
+ togglePreview,
157
+ activeFile,
158
+ saveFile,
159
+ closeFile,
160
+ navigateBack,
161
+ navigateForward,
162
+ canGoBack,
163
+ canGoForward,
164
+ reopenClosedTab,
165
+ openSettings,
166
+ isSettingsOpen,
167
+ closeSettings,
168
+ ]);
169
+ }
@@ -0,0 +1,237 @@
1
+ 'use client';
2
+
3
+ import { useState, useCallback, useEffect, KeyboardEvent } from 'react';
4
+
5
+ /**
6
+ * Custom hook for keyboard navigation in lists.
7
+ * Implements accessible list navigation per WAI-ARIA guidelines.
8
+ *
9
+ * Features:
10
+ * - Arrow up/down navigation
11
+ * - Home/End jump to first/last
12
+ * - Enter to select
13
+ * - Escape to cancel
14
+ * - Type-ahead search (optional)
15
+ *
16
+ * @param options - Configuration options
17
+ * @returns Navigation state and handlers
18
+ */
19
+ interface UseListNavigationOptions<T> {
20
+ /** Array of items to navigate */
21
+ items: T[];
22
+ /** Called when an item is selected (Enter key) */
23
+ onSelect?: (item: T, index: number) => void;
24
+ /** Called when Escape is pressed */
25
+ onEscape?: () => void;
26
+ /** Navigation orientation: 'vertical' (default) or 'horizontal' */
27
+ orientation?: 'vertical' | 'horizontal';
28
+ /** Whether navigation wraps around (default: true) */
29
+ loop?: boolean;
30
+ /** Initial selected index (default: 0) */
31
+ initialIndex?: number;
32
+ /** Enable type-ahead search (default: false) */
33
+ typeAhead?: boolean;
34
+ /** Function to get searchable text from item for type-ahead */
35
+ getItemText?: (item: T) => string;
36
+ }
37
+
38
+ interface UseListNavigationReturn {
39
+ /** Current selected index */
40
+ selectedIndex: number;
41
+ /** Set selected index programmatically */
42
+ setSelectedIndex: (index: number) => void;
43
+ /** Handle keyboard events - attach to container */
44
+ handleKeyDown: (event: KeyboardEvent) => void;
45
+ /** Check if index is selected */
46
+ isSelected: (index: number) => boolean;
47
+ /** Move to next item */
48
+ moveNext: () => void;
49
+ /** Move to previous item */
50
+ movePrevious: () => void;
51
+ /** Move to first item */
52
+ moveFirst: () => void;
53
+ /** Move to last item */
54
+ moveLast: () => void;
55
+ /** Select current item */
56
+ selectCurrent: () => void;
57
+ /** Reset to initial state */
58
+ reset: () => void;
59
+ }
60
+
61
+ export function useListNavigation<T>({
62
+ items,
63
+ onSelect,
64
+ onEscape,
65
+ orientation = 'vertical',
66
+ loop = true,
67
+ initialIndex = 0,
68
+ typeAhead = false,
69
+ getItemText,
70
+ }: UseListNavigationOptions<T>): UseListNavigationReturn {
71
+ const [selectedIndex, setSelectedIndex] = useState(
72
+ Math.min(initialIndex, Math.max(0, items.length - 1))
73
+ );
74
+ const [searchString, setSearchString] = useState('');
75
+ const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout | null>(null);
76
+
77
+ // Reset index when items change
78
+ useEffect(() => {
79
+ if (items.length === 0) {
80
+ setSelectedIndex(-1);
81
+ } else if (selectedIndex >= items.length) {
82
+ setSelectedIndex(items.length - 1);
83
+ }
84
+ }, [items.length, selectedIndex]);
85
+
86
+ const moveNext = useCallback(() => {
87
+ setSelectedIndex((current) => {
88
+ if (items.length === 0) return -1;
89
+ if (current >= items.length - 1) {
90
+ return loop ? 0 : current;
91
+ }
92
+ return current + 1;
93
+ });
94
+ }, [items.length, loop]);
95
+
96
+ const movePrevious = useCallback(() => {
97
+ setSelectedIndex((current) => {
98
+ if (items.length === 0) return -1;
99
+ if (current <= 0) {
100
+ return loop ? items.length - 1 : 0;
101
+ }
102
+ return current - 1;
103
+ });
104
+ }, [items.length, loop]);
105
+
106
+ const moveFirst = useCallback(() => {
107
+ if (items.length > 0) {
108
+ setSelectedIndex(0);
109
+ }
110
+ }, [items.length]);
111
+
112
+ const moveLast = useCallback(() => {
113
+ if (items.length > 0) {
114
+ setSelectedIndex(items.length - 1);
115
+ }
116
+ }, [items.length]);
117
+
118
+ const selectCurrent = useCallback(() => {
119
+ if (selectedIndex >= 0 && selectedIndex < items.length && onSelect) {
120
+ onSelect(items[selectedIndex], selectedIndex);
121
+ }
122
+ }, [selectedIndex, items, onSelect]);
123
+
124
+ const reset = useCallback(() => {
125
+ setSelectedIndex(Math.min(initialIndex, Math.max(0, items.length - 1)));
126
+ setSearchString('');
127
+ if (searchTimeout) {
128
+ clearTimeout(searchTimeout);
129
+ setSearchTimeout(null);
130
+ }
131
+ }, [initialIndex, items.length, searchTimeout]);
132
+
133
+ // Type-ahead search
134
+ const handleTypeAhead = useCallback(
135
+ (char: string) => {
136
+ if (!typeAhead || !getItemText) return false;
137
+
138
+ const newSearchString = searchString + char.toLowerCase();
139
+ setSearchString(newSearchString);
140
+
141
+ // Clear previous timeout
142
+ if (searchTimeout) {
143
+ clearTimeout(searchTimeout);
144
+ }
145
+
146
+ // Set new timeout to reset search
147
+ setSearchTimeout(
148
+ setTimeout(() => {
149
+ setSearchString('');
150
+ }, 500)
151
+ );
152
+
153
+ // Find matching item
154
+ const matchIndex = items.findIndex((item) =>
155
+ getItemText(item).toLowerCase().startsWith(newSearchString)
156
+ );
157
+
158
+ if (matchIndex !== -1) {
159
+ setSelectedIndex(matchIndex);
160
+ return true;
161
+ }
162
+
163
+ return false;
164
+ },
165
+ [typeAhead, getItemText, searchString, searchTimeout, items]
166
+ );
167
+
168
+ const handleKeyDown = useCallback(
169
+ (event: KeyboardEvent) => {
170
+ const prevKey = orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft';
171
+ const nextKey = orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight';
172
+
173
+ switch (event.key) {
174
+ case prevKey:
175
+ event.preventDefault();
176
+ movePrevious();
177
+ break;
178
+
179
+ case nextKey:
180
+ event.preventDefault();
181
+ moveNext();
182
+ break;
183
+
184
+ case 'Home':
185
+ event.preventDefault();
186
+ moveFirst();
187
+ break;
188
+
189
+ case 'End':
190
+ event.preventDefault();
191
+ moveLast();
192
+ break;
193
+
194
+ case 'Enter':
195
+ case ' ':
196
+ event.preventDefault();
197
+ selectCurrent();
198
+ break;
199
+
200
+ case 'Escape':
201
+ event.preventDefault();
202
+ if (onEscape) {
203
+ onEscape();
204
+ }
205
+ break;
206
+
207
+ default:
208
+ // Type-ahead for printable characters
209
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
210
+ if (handleTypeAhead(event.key)) {
211
+ event.preventDefault();
212
+ }
213
+ }
214
+ break;
215
+ }
216
+ },
217
+ [orientation, movePrevious, moveNext, moveFirst, moveLast, selectCurrent, onEscape, handleTypeAhead]
218
+ );
219
+
220
+ const isSelected = useCallback(
221
+ (index: number) => index === selectedIndex,
222
+ [selectedIndex]
223
+ );
224
+
225
+ return {
226
+ selectedIndex,
227
+ setSelectedIndex,
228
+ handleKeyDown,
229
+ isSelected,
230
+ moveNext,
231
+ movePrevious,
232
+ moveFirst,
233
+ moveLast,
234
+ selectCurrent,
235
+ reset,
236
+ };
237
+ }
@@ -0,0 +1,213 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'node:path';
3
+
4
+ /**
5
+ * Shared autopilot constants and utilities.
6
+ * Used by both /api/autopilot/execute and /api/autopilot/terminal-execute routes.
7
+ */
8
+
9
+ export const VALID_AGENTS = [
10
+ 'strategist', 'architect', 'system-designer', 'builder', 'guardian', 'chronicler',
11
+ ] as const;
12
+
13
+ export type AgentName = typeof VALID_AGENTS[number];
14
+
15
+ export function isValidAgent(agent: string): agent is AgentName {
16
+ return VALID_AGENTS.includes(agent as AgentName);
17
+ }
18
+
19
+ export const AGENT_SKILLS: Record<AgentName, string> = {
20
+ strategist: '/agents:strategist',
21
+ architect: '/agents:architect',
22
+ 'system-designer': '/agents:system-designer',
23
+ builder: '/agents:builder',
24
+ guardian: '/agents:guardian',
25
+ chronicler: '/agents:chronicler',
26
+ };
27
+
28
+ export const AGENT_PROMPTS: Record<AgentName, string> = {
29
+ strategist: `Analise a spec e refine os requisitos:
30
+ {spec_content}
31
+
32
+ 1. Identificar requisitos implícitos
33
+ 2. Listar acceptance criteria
34
+ 3. Dependências e riscos
35
+ 4. Estimar complexidade`,
36
+
37
+ architect: `Defina a arquitetura com base na spec:
38
+ {spec_content}
39
+
40
+ Contexto anterior: {previous_output}
41
+
42
+ 1. Arquitetura da solução
43
+ 2. Padrões e tecnologias
44
+ 3. Componentes necessários
45
+ 4. Decisões importantes`,
46
+
47
+ 'system-designer': `Projete o system design com base na spec:
48
+ {spec_content}
49
+
50
+ Contexto anterior: {previous_output}
51
+
52
+ 1. Back-of-the-envelope estimation
53
+ 2. High-level design
54
+ 3. Data model e storage
55
+ 4. Scalability e reliability`,
56
+
57
+ builder: `Implemente a solução conforme spec e design:
58
+ {spec_content}
59
+
60
+ Contexto anterior: {previous_output}
61
+
62
+ 1. Criar/modificar arquivos necessários
63
+ 2. Implementar lógica principal
64
+ 3. Tratamento de erros`,
65
+
66
+ guardian: `Revise o código implementado:
67
+ {spec_content}
68
+
69
+ Implementação: {previous_output}
70
+
71
+ 1. Segurança
72
+ 2. Performance
73
+ 3. Edge cases
74
+ 4. Melhorias necessárias`,
75
+
76
+ chronicler: `Documente as mudanças realizadas:
77
+ {spec_content}
78
+
79
+ Implementação: {previous_output}
80
+
81
+ 1. Resumir o que foi implementado
82
+ 2. Arquivos criados/modificados
83
+ 3. Atualizar tasks na spec`,
84
+ };
85
+
86
+ export const AGENT_TIMEOUTS: Record<AgentName, number> = {
87
+ strategist: 300,
88
+ architect: 600,
89
+ 'system-designer': 600,
90
+ builder: 1200,
91
+ guardian: 600,
92
+ chronicler: 300,
93
+ };
94
+
95
+ // Agents that produce actionable output for task tracking
96
+ export const TASK_TRACKING_AGENTS: AgentName[] = ['builder', 'guardian', 'chronicler'];
97
+
98
+ /**
99
+ * Load the full agent definition from .claude/commands/agents/{agent}.md
100
+ * Returns the file content or null if not found.
101
+ */
102
+ export async function loadAgentDefinition(projectPath: string, agent: AgentName): Promise<string | null> {
103
+ const filePath = path.join(projectPath, '.claude', 'commands', 'agents', `${agent}.md`);
104
+ try {
105
+ return await fs.readFile(filePath, 'utf-8');
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Build the full prompt for a given agent phase.
113
+ * If agentDefinition is provided (from loadAgentDefinition), uses it as context.
114
+ * Otherwise falls back to the slash command text (won't work with --print mode).
115
+ */
116
+ export function buildPrompt(
117
+ agent: AgentName,
118
+ specContent: string,
119
+ previousOutputs: string[],
120
+ agentDefinition?: string | null,
121
+ ): string {
122
+ const prompt = AGENT_PROMPTS[agent]
123
+ .replace('{spec_content}', specContent)
124
+ .replace('{previous_output}', previousOutputs.join('\n---\n') || 'N/A');
125
+
126
+ // Use full agent definition if available, otherwise fallback to skill command
127
+ const agentContext = agentDefinition || AGENT_SKILLS[agent];
128
+
129
+ return `${agentContext}\n\n---\n\n${prompt}`;
130
+ }
131
+
132
+ /**
133
+ * Extract unchecked task titles from a markdown spec file.
134
+ * Matches lines like: - [ ] Task title
135
+ */
136
+ export function extractUncheckedTasks(content: string): string[] {
137
+ const tasks: string[] = [];
138
+ const regex = /^\s*[-*]\s*\[ \]\s*(?:\[[^\]]+\]\s*)?(.+)$/gm;
139
+ let match;
140
+ while ((match = regex.exec(content)) !== null) {
141
+ tasks.push(match[1].trim());
142
+ }
143
+ return tasks;
144
+ }
145
+
146
+ /**
147
+ * Check if the agent output mentions a task as completed.
148
+ * Uses case-insensitive substring matching with completion context.
149
+ */
150
+ export function isTaskMentionedAsCompleted(taskTitle: string, output: string): boolean {
151
+ const lower = output.toLowerCase();
152
+ const taskLower = taskTitle.toLowerCase();
153
+
154
+ if (!lower.includes(taskLower)) return false;
155
+
156
+ // For short task titles (< 10 chars), require a completion keyword nearby
157
+ if (taskLower.length < 10) {
158
+ const completionKeywords = [
159
+ 'completed', 'done', 'implemented', 'finished', 'created',
160
+ 'added', 'fixed', 'resolved', 'built', 'configured',
161
+ '✅', '✓', '[x]', 'complete',
162
+ ];
163
+ const idx = lower.indexOf(taskLower);
164
+ const context = lower.slice(Math.max(0, idx - 200), idx + taskLower.length + 200);
165
+ return completionKeywords.some(kw => context.includes(kw));
166
+ }
167
+
168
+ return true;
169
+ }
170
+
171
+ /**
172
+ * After a phase completes, auto-detect completed tasks and update the spec file.
173
+ */
174
+ export async function autoUpdateSpecTasks(
175
+ specFilePath: string,
176
+ agentOutput: string
177
+ ): Promise<string[]> {
178
+ const completedTasks: string[] = [];
179
+
180
+ try {
181
+ const content = await fs.readFile(specFilePath, 'utf-8');
182
+ const uncheckedTasks = extractUncheckedTasks(content);
183
+
184
+ if (uncheckedTasks.length === 0) return [];
185
+
186
+ let updatedContent = content;
187
+ for (const taskTitle of uncheckedTasks) {
188
+ if (isTaskMentionedAsCompleted(taskTitle, agentOutput)) {
189
+ const escapedTitle = taskTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
190
+ const taskRegex = new RegExp(
191
+ `^(\\s*[-*]\\s*)\\[ \\](\\s*(?:\\[[^\\]]+\\]\\s*)?)${escapedTitle}`,
192
+ 'gm'
193
+ );
194
+ updatedContent = updatedContent.replace(taskRegex, (match, prefix, middle) => {
195
+ completedTasks.push(taskTitle);
196
+ return `${prefix}[x]${middle}${taskTitle}`;
197
+ });
198
+ }
199
+ }
200
+
201
+ if (completedTasks.length > 0) {
202
+ await fs.writeFile(specFilePath, updatedContent, 'utf-8');
203
+ }
204
+ } catch (error) {
205
+ console.error('Error auto-updating spec tasks:', error);
206
+ }
207
+
208
+ return completedTasks;
209
+ }
210
+
211
+ /** Completion marker used to detect when an autopilot phase finishes in the terminal. */
212
+ export const PHASE_DONE_MARKER = '___DEVFLOW_PHASE_DONE_';
213
+ export const PHASE_DONE_REGEX = /___DEVFLOW_PHASE_DONE_(\d+)___/;
@@ -0,0 +1,67 @@
1
+ import type { Agent } from '@/lib/types';
2
+
3
+ // Agent definitions
4
+ export const AGENTS: Agent[] = [
5
+ {
6
+ id: 'strategist',
7
+ name: '@strategist',
8
+ displayName: 'Strategist',
9
+ icon: '📊',
10
+ color: '#3B82F6',
11
+ description: 'Product Manager & Analista - Transforma problemas em planos',
12
+ shortDescription: 'Planejamento & Produto',
13
+ },
14
+ {
15
+ id: 'architect',
16
+ name: '@architect',
17
+ displayName: 'Architect',
18
+ icon: '🏗️',
19
+ color: '#8B5CF6',
20
+ description: 'Solutions Architect - Design técnico e decisões',
21
+ shortDescription: 'Design & Arquitetura',
22
+ },
23
+ {
24
+ id: 'system-designer',
25
+ name: '@system-designer',
26
+ displayName: 'System Designer',
27
+ icon: '⚙️',
28
+ color: '#06B6D4',
29
+ description: 'System Design Specialist - Infraestrutura em escala',
30
+ shortDescription: 'System Design & Escala',
31
+ },
32
+ {
33
+ id: 'builder',
34
+ name: '@builder',
35
+ displayName: 'Builder',
36
+ icon: '🔨',
37
+ color: '#F59E0B',
38
+ description: 'Senior Developer - Implementação de código',
39
+ shortDescription: 'Implementação',
40
+ },
41
+ {
42
+ id: 'guardian',
43
+ name: '@guardian',
44
+ displayName: 'Guardian',
45
+ icon: '🛡️',
46
+ color: '#10B981',
47
+ description: 'QA Engineer - Qualidade e segurança',
48
+ shortDescription: 'Qualidade & Testes',
49
+ },
50
+ {
51
+ id: 'chronicler',
52
+ name: '@chronicler',
53
+ displayName: 'Chronicler',
54
+ icon: '📝',
55
+ color: '#EC4899',
56
+ description: 'Technical Writer - Documentação',
57
+ shortDescription: 'Documentação',
58
+ },
59
+ ];
60
+
61
+ export const getAgentById = (id: string): Agent | undefined => {
62
+ return AGENTS.find(agent => agent.id === id);
63
+ };
64
+
65
+ export const getAgentByName = (name: string): Agent | undefined => {
66
+ return AGENTS.find(agent => agent.name === name);
67
+ };