red64-cli 0.1.0 → 0.3.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/README.md +1 -2
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +5 -0
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/components/init/CompleteStep.d.ts.map +1 -1
- package/dist/components/init/CompleteStep.js +2 -2
- package/dist/components/init/CompleteStep.js.map +1 -1
- package/dist/components/init/TestCheckStep.d.ts +16 -0
- package/dist/components/init/TestCheckStep.d.ts.map +1 -0
- package/dist/components/init/TestCheckStep.js +120 -0
- package/dist/components/init/TestCheckStep.js.map +1 -0
- package/dist/components/init/index.d.ts +1 -0
- package/dist/components/init/index.d.ts.map +1 -1
- package/dist/components/init/index.js +1 -0
- package/dist/components/init/index.js.map +1 -1
- package/dist/components/init/types.d.ts +9 -0
- package/dist/components/init/types.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.js +69 -6
- package/dist/components/screens/InitScreen.js.map +1 -1
- package/dist/components/screens/ListScreen.d.ts.map +1 -1
- package/dist/components/screens/ListScreen.js +28 -3
- package/dist/components/screens/ListScreen.js.map +1 -1
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +212 -13
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/components/ui/ArtifactsSidebar.d.ts +19 -0
- package/dist/components/ui/ArtifactsSidebar.d.ts.map +1 -0
- package/dist/components/ui/ArtifactsSidebar.js +51 -0
- package/dist/components/ui/ArtifactsSidebar.js.map +1 -0
- package/dist/components/ui/FeatureSidebar.d.ts.map +1 -1
- package/dist/components/ui/FeatureSidebar.js +1 -1
- package/dist/components/ui/FeatureSidebar.js.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +1 -0
- package/dist/components/ui/index.js.map +1 -1
- package/dist/services/ClaudeErrorDetector.js +3 -3
- package/dist/services/ClaudeErrorDetector.js.map +1 -1
- package/dist/services/ConfigService.d.ts +1 -0
- package/dist/services/ConfigService.d.ts.map +1 -1
- package/dist/services/ConfigService.js.map +1 -1
- package/dist/services/ProjectDetector.d.ts +28 -0
- package/dist/services/ProjectDetector.d.ts.map +1 -0
- package/dist/services/ProjectDetector.js +236 -0
- package/dist/services/ProjectDetector.js.map +1 -0
- package/dist/services/TestRunner.d.ts +46 -0
- package/dist/services/TestRunner.d.ts.map +1 -0
- package/dist/services/TestRunner.js +85 -0
- package/dist/services/TestRunner.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -0
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/framework/.red64/settings/templates/specs/gap-analysis.md +163 -0
- package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
- package/framework/agents/claude/.claude/agents/red64/validate-gap.md +13 -7
- package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
- package/framework/agents/claude/.claude/commands/red64/validate-gap.md +4 -0
- package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
- package/framework/agents/codex/.codex/agents/red64/validate-gap.md +13 -7
- package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
- package/framework/agents/codex/.codex/commands/red64/validate-gap.md +4 -0
- package/framework/stacks/generic/feedback.md +80 -0
- package/framework/stacks/nextjs/accessibility.md +437 -0
- package/framework/stacks/nextjs/api.md +431 -0
- package/framework/stacks/nextjs/coding-style.md +282 -0
- package/framework/stacks/nextjs/commenting.md +226 -0
- package/framework/stacks/nextjs/components.md +411 -0
- package/framework/stacks/nextjs/conventions.md +333 -0
- package/framework/stacks/nextjs/css.md +310 -0
- package/framework/stacks/nextjs/error-handling.md +442 -0
- package/framework/stacks/nextjs/feedback.md +124 -0
- package/framework/stacks/nextjs/migrations.md +332 -0
- package/framework/stacks/nextjs/models.md +362 -0
- package/framework/stacks/nextjs/queries.md +410 -0
- package/framework/stacks/nextjs/responsive.md +338 -0
- package/framework/stacks/nextjs/tech-stack.md +177 -0
- package/framework/stacks/nextjs/test-writing.md +475 -0
- package/framework/stacks/nextjs/validation.md +467 -0
- package/framework/stacks/python/api.md +468 -0
- package/framework/stacks/python/authentication.md +342 -0
- package/framework/stacks/python/code-quality.md +283 -0
- package/framework/stacks/python/code-refactoring.md +315 -0
- package/framework/stacks/python/coding-style.md +462 -0
- package/framework/stacks/python/conventions.md +399 -0
- package/framework/stacks/python/error-handling.md +512 -0
- package/framework/stacks/python/feedback.md +92 -0
- package/framework/stacks/python/implement-ai-llm.md +468 -0
- package/framework/stacks/python/migrations.md +388 -0
- package/framework/stacks/python/models.md +399 -0
- package/framework/stacks/python/python.md +232 -0
- package/framework/stacks/python/queries.md +451 -0
- package/framework/stacks/python/structure.md +245 -58
- package/framework/stacks/python/tech.md +92 -35
- package/framework/stacks/python/testing.md +380 -0
- package/framework/stacks/python/validation.md +471 -0
- package/framework/stacks/rails/authentication.md +176 -0
- package/framework/stacks/rails/code-quality.md +287 -0
- package/framework/stacks/rails/code-refactoring.md +299 -0
- package/framework/stacks/rails/feedback.md +130 -0
- package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
- package/framework/stacks/rails/rails.md +301 -0
- package/framework/stacks/rails/rails8-best-practices.md +498 -0
- package/framework/stacks/rails/rails8-css.md +573 -0
- package/framework/stacks/rails/structure.md +140 -0
- package/framework/stacks/rails/tech.md +108 -0
- package/framework/stacks/react/code-quality.md +521 -0
- package/framework/stacks/react/components.md +625 -0
- package/framework/stacks/react/data-fetching.md +586 -0
- package/framework/stacks/react/feedback.md +110 -0
- package/framework/stacks/react/forms.md +694 -0
- package/framework/stacks/react/performance.md +640 -0
- package/framework/stacks/react/product.md +22 -9
- package/framework/stacks/react/state-management.md +472 -0
- package/framework/stacks/react/structure.md +351 -44
- package/framework/stacks/react/tech.md +219 -30
- package/framework/stacks/react/testing.md +690 -0
- package/package.json +1 -1
- package/framework/stacks/node/product.md +0 -27
- package/framework/stacks/node/structure.md +0 -82
- package/framework/stacks/node/tech.md +0 -63
|
@@ -7,6 +7,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
import { useState, useEffect } from 'react';
|
|
8
8
|
import { Box, Text } from 'ink';
|
|
9
9
|
import { createStateStore } from '../../services/StateStore.js';
|
|
10
|
+
import { createWorktreeService } from '../../services/WorktreeService.js';
|
|
10
11
|
import { Spinner, FlowTable } from '../ui/index.js';
|
|
11
12
|
/**
|
|
12
13
|
* Get base directory from environment or default
|
|
@@ -22,13 +23,37 @@ function getBaseDir() {
|
|
|
22
23
|
export const ListScreen = () => {
|
|
23
24
|
const [state, setState] = useState({ step: 'loading' });
|
|
24
25
|
const [stateStore] = useState(() => createStateStore(getBaseDir()));
|
|
26
|
+
const [worktreeService] = useState(() => createWorktreeService());
|
|
25
27
|
// Load all flows on mount
|
|
26
28
|
useEffect(() => {
|
|
27
29
|
const loadFlows = async () => {
|
|
28
30
|
try {
|
|
31
|
+
const baseDir = getBaseDir();
|
|
29
32
|
// Requirements 3.1, 3.2 - Scan and load all flow states
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
// First load from main repo
|
|
34
|
+
const mainFlows = await stateStore.list();
|
|
35
|
+
// Also scan worktrees for flow states
|
|
36
|
+
const worktrees = await worktreeService.list(baseDir);
|
|
37
|
+
const worktreeFlows = [];
|
|
38
|
+
const seenFeatures = new Set(mainFlows.map(f => f.feature));
|
|
39
|
+
for (const worktree of worktrees) {
|
|
40
|
+
// Skip main worktree (already scanned)
|
|
41
|
+
if (worktree.path === baseDir)
|
|
42
|
+
continue;
|
|
43
|
+
if (!worktree.path)
|
|
44
|
+
continue;
|
|
45
|
+
const worktreeStateStore = createStateStore(worktree.path);
|
|
46
|
+
const flows = await worktreeStateStore.list();
|
|
47
|
+
for (const flow of flows) {
|
|
48
|
+
// Avoid duplicates - prefer worktree state as it's more current
|
|
49
|
+
if (!seenFeatures.has(flow.feature)) {
|
|
50
|
+
worktreeFlows.push(flow);
|
|
51
|
+
seenFeatures.add(flow.feature);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const allFlows = [...mainFlows, ...worktreeFlows];
|
|
56
|
+
setState({ step: 'loaded', flows: allFlows });
|
|
32
57
|
}
|
|
33
58
|
catch (error) {
|
|
34
59
|
setState({
|
|
@@ -38,7 +63,7 @@ export const ListScreen = () => {
|
|
|
38
63
|
}
|
|
39
64
|
};
|
|
40
65
|
loadFlows();
|
|
41
|
-
}, [stateStore]);
|
|
66
|
+
}, [stateStore, worktreeService]);
|
|
42
67
|
// Render based on state
|
|
43
68
|
switch (state.step) {
|
|
44
69
|
case 'loading':
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListScreen.js","sourceRoot":"","sources":["../../../src/components/screens/ListScreen.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AAEH,OAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAGhC,OAAO,EAAE,gBAAgB,EAA0B,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAWpD;;GAEG;AACH,SAAS,UAAU;IACjB,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC;AACvB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAA0B,GAAG,EAAE;IACpD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IACzE,MAAM,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAoB,GAAG,EAAE,CAAC,gBAAgB,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ListScreen.js","sourceRoot":"","sources":["../../../src/components/screens/ListScreen.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartScreen.d.ts","sourceRoot":"","sources":["../../../src/components/screens/StartScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAGxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"StartScreen.d.ts","sourceRoot":"","sources":["../../../src/components/screens/StartScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAGxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA0IpD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CA26C7C,CAAC"}
|
|
@@ -18,10 +18,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
18
18
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
19
19
|
import { Box, Text, useApp } from 'ink';
|
|
20
20
|
import { Spinner, Select } from '@inkjs/ui';
|
|
21
|
-
import { createStateStore, createAgentInvoker, createExtendedFlowMachine, createWorktreeService, createCommitService, createTaskParser, createSpecInitService, createClaudeHealthCheck, createGitStatusChecker, createConfigService, sanitizeFeatureName } from '../../services/index.js';
|
|
22
|
-
import { FeatureSidebar } from '../ui/index.js';
|
|
21
|
+
import { createStateStore, createAgentInvoker, createExtendedFlowMachine, createWorktreeService, createCommitService, createTaskParser, createSpecInitService, createClaudeHealthCheck, createGitStatusChecker, createConfigService, createProjectDetector, createTestRunner, sanitizeFeatureName } from '../../services/index.js';
|
|
22
|
+
import { FeatureSidebar, ArtifactsSidebar } from '../ui/index.js';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
|
-
import { appendFile, mkdir } from 'node:fs/promises';
|
|
24
|
+
import { appendFile, mkdir, stat } from 'node:fs/promises';
|
|
25
25
|
/**
|
|
26
26
|
* Phase display information
|
|
27
27
|
*/
|
|
@@ -47,6 +47,17 @@ const PHASE_LABELS = {
|
|
|
47
47
|
'aborted': { label: 'Aborted', description: 'Flow was aborted' },
|
|
48
48
|
'error': { label: 'Error', description: 'An error occurred' }
|
|
49
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* Map approval phases to the specific file(s) to review
|
|
52
|
+
*/
|
|
53
|
+
const PHASE_REVIEW_FILES = {
|
|
54
|
+
'requirements-approval': 'requirements.md',
|
|
55
|
+
'gap-review': 'gap-analysis.md',
|
|
56
|
+
'design-approval': 'design.md',
|
|
57
|
+
'design-validation-review': 'design.md',
|
|
58
|
+
'tasks-approval': 'tasks.md',
|
|
59
|
+
'merge-decision': ''
|
|
60
|
+
};
|
|
50
61
|
/**
|
|
51
62
|
* Approval options for review phases
|
|
52
63
|
*/
|
|
@@ -113,6 +124,8 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
113
124
|
const healthCheck = createClaudeHealthCheck();
|
|
114
125
|
const gitStatusChecker = createGitStatusChecker();
|
|
115
126
|
const configService = createConfigService();
|
|
127
|
+
const projectDetector = createProjectDetector();
|
|
128
|
+
const testRunner = createTestRunner();
|
|
116
129
|
servicesRef.current = {
|
|
117
130
|
stateStore,
|
|
118
131
|
agentInvoker,
|
|
@@ -123,7 +136,9 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
123
136
|
specInitService,
|
|
124
137
|
healthCheck,
|
|
125
138
|
gitStatusChecker,
|
|
126
|
-
configService
|
|
139
|
+
configService,
|
|
140
|
+
projectDetector,
|
|
141
|
+
testRunner
|
|
127
142
|
};
|
|
128
143
|
}
|
|
129
144
|
const services = servicesRef.current;
|
|
@@ -165,10 +180,15 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
165
180
|
completedTasks: [], // Orchestrator-tracked completed task IDs
|
|
166
181
|
phaseMetrics: {}, // Phase timing metrics
|
|
167
182
|
commitCount: 0, // Number of commits for this feature
|
|
168
|
-
agent: 'claude' // Default, will be loaded from config
|
|
183
|
+
agent: 'claude', // Default, will be loaded from config
|
|
184
|
+
artifacts: [] // Generated artifacts
|
|
169
185
|
});
|
|
170
186
|
// Track if flow has been started
|
|
171
187
|
const flowStartedRef = useRef(false);
|
|
188
|
+
// Ref to hold existingFlowState for resumeExistingFlow (avoids stale closure)
|
|
189
|
+
const existingFlowStateRef = useRef(null);
|
|
190
|
+
// Keep ref in sync with state
|
|
191
|
+
existingFlowStateRef.current = flowState.existingFlowState;
|
|
172
192
|
// Add output line (to screen and log file)
|
|
173
193
|
const addOutput = useCallback((line) => {
|
|
174
194
|
setFlowState(prev => ({
|
|
@@ -178,6 +198,19 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
178
198
|
// Also log to file
|
|
179
199
|
logToFile(line);
|
|
180
200
|
}, [logToFile]);
|
|
201
|
+
// Add artifact to the list
|
|
202
|
+
const addArtifact = useCallback((artifact) => {
|
|
203
|
+
setFlowState(prev => {
|
|
204
|
+
// Avoid duplicates
|
|
205
|
+
if (prev.artifacts.some(a => a.path === artifact.path)) {
|
|
206
|
+
return prev;
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
...prev,
|
|
210
|
+
artifacts: [...prev.artifacts, artifact]
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
}, []);
|
|
181
214
|
// Get working directory (worktree or repo)
|
|
182
215
|
const getWorkingDir = useCallback(() => {
|
|
183
216
|
return flowState.worktreePath ?? repoPath;
|
|
@@ -329,6 +362,10 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
329
362
|
const existingState = await stateStore.load(featureName);
|
|
330
363
|
// Use override if provided, otherwise use current state, otherwise preserve existing
|
|
331
364
|
const completedTasks = completedTasksOverride ?? flowState.completedTasks;
|
|
365
|
+
// Use dir (resolved workDir) as worktreePath if it's different from repoPath
|
|
366
|
+
// This fixes the race condition where flowState.worktreePath is still null due to async setFlowState
|
|
367
|
+
// Also preserve worktreePath from existing state on resume to prevent losing it
|
|
368
|
+
const effectiveWorktreePath = dir !== repoPath ? dir : (flowState.worktreePath ?? existingState?.metadata.worktreePath ?? undefined);
|
|
332
369
|
const state = {
|
|
333
370
|
feature: featureName,
|
|
334
371
|
phase: convertToFlowPhase(phase),
|
|
@@ -339,7 +376,7 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
339
376
|
description,
|
|
340
377
|
mode,
|
|
341
378
|
tier: flags.tier,
|
|
342
|
-
worktreePath:
|
|
379
|
+
worktreePath: effectiveWorktreePath,
|
|
343
380
|
resolvedFeatureName: flowState.resolvedFeatureName ?? undefined
|
|
344
381
|
},
|
|
345
382
|
// Orchestrator-controlled task progress
|
|
@@ -350,10 +387,14 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
350
387
|
// Phase timing metrics
|
|
351
388
|
phaseMetrics: Object.keys(flowState.phaseMetrics).length > 0
|
|
352
389
|
? { ...existingState?.phaseMetrics, ...flowState.phaseMetrics }
|
|
353
|
-
: existingState?.phaseMetrics
|
|
390
|
+
: existingState?.phaseMetrics,
|
|
391
|
+
// Persist artifacts to disk so they survive resume
|
|
392
|
+
artifacts: flowState.artifacts.length > 0
|
|
393
|
+
? flowState.artifacts
|
|
394
|
+
: existingState?.artifacts
|
|
354
395
|
};
|
|
355
396
|
await stateStore.save(state);
|
|
356
|
-
}, [featureName, description, mode, flags.tier, flowState.worktreePath, flowState.resolvedFeatureName, flowState.completedTasks, flowState.totalTasks, flowState.phaseMetrics, getWorkingDir]);
|
|
397
|
+
}, [featureName, description, mode, flags.tier, flowState.worktreePath, flowState.resolvedFeatureName, flowState.completedTasks, flowState.totalTasks, flowState.phaseMetrics, getWorkingDir, repoPath]);
|
|
357
398
|
// Transition to next phase
|
|
358
399
|
const transitionPhase = useCallback((event) => {
|
|
359
400
|
const nextPhase = services.flowMachine.send(event);
|
|
@@ -401,6 +442,7 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
401
442
|
existingFlowState: existingState,
|
|
402
443
|
worktreePath: existingState.metadata.worktreePath ?? null,
|
|
403
444
|
resolvedFeatureName: existingState.metadata.resolvedFeatureName ?? null,
|
|
445
|
+
artifacts: existingState.artifacts ? [...existingState.artifacts] : prev.artifacts,
|
|
404
446
|
completedTasks: [...completedTasks],
|
|
405
447
|
totalTasks
|
|
406
448
|
}));
|
|
@@ -418,6 +460,7 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
418
460
|
existingFlowState: existingState,
|
|
419
461
|
worktreePath: existingState.metadata.worktreePath ?? null,
|
|
420
462
|
resolvedFeatureName: existingState.metadata.resolvedFeatureName ?? null,
|
|
463
|
+
artifacts: existingState.artifacts ? [...existingState.artifacts] : prev.artifacts,
|
|
421
464
|
completedTasks: [...completedTasks],
|
|
422
465
|
totalTasks
|
|
423
466
|
}));
|
|
@@ -496,7 +539,8 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
496
539
|
}, [flowState.existingFlowState, services.commitService, services.gitStatusChecker, featureName, repoPath, exit, addOutput]);
|
|
497
540
|
// Resume from existing flow state
|
|
498
541
|
const resumeExistingFlow = useCallback(async () => {
|
|
499
|
-
|
|
542
|
+
// Read from ref to avoid stale closure issue
|
|
543
|
+
const existingState = existingFlowStateRef.current;
|
|
500
544
|
if (!existingState) {
|
|
501
545
|
addOutput('Error: No existing flow state to resume');
|
|
502
546
|
return;
|
|
@@ -522,6 +566,10 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
522
566
|
timeoutMs: 30000
|
|
523
567
|
});
|
|
524
568
|
setFlowState(prev => ({ ...prev, isHealthChecking: false }));
|
|
569
|
+
// Guard against undefined result (can happen if component unmounts during check)
|
|
570
|
+
if (!healthResult) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
525
573
|
if (!healthResult.healthy) {
|
|
526
574
|
const errorMsg = healthResult.error
|
|
527
575
|
? `${getClaudeErrorLabel(healthResult.error.code)}: ${healthResult.error.suggestion}`
|
|
@@ -535,17 +583,55 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
535
583
|
return;
|
|
536
584
|
}
|
|
537
585
|
addOutput(`API ready (${healthResult.durationMs}ms)`);
|
|
586
|
+
// Run project tests (unless skipped)
|
|
587
|
+
if (!flags['skip-tests']) {
|
|
588
|
+
addOutput('');
|
|
589
|
+
addOutput('Checking project tests...');
|
|
590
|
+
// Load config to get test command
|
|
591
|
+
const config = await services.configService.load(workDir);
|
|
592
|
+
let testCommand = config?.testCommand;
|
|
593
|
+
// If no stored command, try to detect
|
|
594
|
+
if (!testCommand) {
|
|
595
|
+
const detection = await services.projectDetector.detect(workDir);
|
|
596
|
+
testCommand = detection.testCommand ?? undefined;
|
|
597
|
+
}
|
|
598
|
+
if (testCommand) {
|
|
599
|
+
addOutput(`Running: ${testCommand}`);
|
|
600
|
+
const testResult = await services.testRunner.run({
|
|
601
|
+
testCommand,
|
|
602
|
+
workingDir: workDir,
|
|
603
|
+
timeoutMs: 300000
|
|
604
|
+
});
|
|
605
|
+
if (!testResult.success) {
|
|
606
|
+
const errorMsg = testResult.timedOut
|
|
607
|
+
? 'Tests timed out'
|
|
608
|
+
: `Tests failed (exit code: ${testResult.exitCode})`;
|
|
609
|
+
await logToFile(`Test check failed: ${errorMsg}`);
|
|
610
|
+
setFlowState(prev => ({
|
|
611
|
+
...prev,
|
|
612
|
+
error: errorMsg,
|
|
613
|
+
phase: { type: 'error', feature: featureName, error: `${errorMsg}. Use --skip-tests to bypass.` }
|
|
614
|
+
}));
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
addOutput(`Tests passed (${(testResult.durationMs / 1000).toFixed(1)}s)`);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
addOutput('No test command configured, skipping tests');
|
|
621
|
+
}
|
|
622
|
+
}
|
|
538
623
|
// Set up flow machine to the current phase
|
|
539
624
|
const effectiveName = existingState.metadata.resolvedFeatureName ?? sanitizeFeatureName(featureName);
|
|
540
|
-
// Update state with existing flow info
|
|
625
|
+
// Update state with existing flow info, restoring persisted artifacts
|
|
541
626
|
setFlowState(prev => ({
|
|
542
627
|
...prev,
|
|
543
628
|
worktreePath: existingState.metadata.worktreePath ?? null,
|
|
544
|
-
resolvedFeatureName: effectiveName
|
|
629
|
+
resolvedFeatureName: effectiveName,
|
|
630
|
+
artifacts: existingState.artifacts ? [...existingState.artifacts] : prev.artifacts
|
|
545
631
|
}));
|
|
546
632
|
// Resume based on current phase
|
|
547
633
|
await resumeFromPhase(existingState.phase.type, workDir, effectiveName);
|
|
548
|
-
}, [
|
|
634
|
+
}, [services.healthCheck, services.configService, services.projectDetector, services.testRunner, services.commitService, flags, featureName, repoPath, initLogFile, addOutput]);
|
|
549
635
|
// Resume from a specific phase
|
|
550
636
|
const resumeFromPhase = async (phaseType, workDir, effectiveName) => {
|
|
551
637
|
addOutput(`Continuing from ${phaseType}...`);
|
|
@@ -667,6 +753,10 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
667
753
|
timeoutMs: 30000
|
|
668
754
|
});
|
|
669
755
|
setFlowState(prev => ({ ...prev, isHealthChecking: false }));
|
|
756
|
+
// Guard against undefined result (can happen if component unmounts during check)
|
|
757
|
+
if (!healthResult) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
670
760
|
if (!healthResult.healthy) {
|
|
671
761
|
await logToFile(`Health check failed: ${healthResult.message}`);
|
|
672
762
|
if (healthResult.error) {
|
|
@@ -686,6 +776,43 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
686
776
|
return;
|
|
687
777
|
}
|
|
688
778
|
addOutput(`API ready (${healthResult.durationMs}ms)`);
|
|
779
|
+
// Run project tests (unless skipped)
|
|
780
|
+
if (!flags['skip-tests']) {
|
|
781
|
+
addOutput('');
|
|
782
|
+
addOutput('Checking project tests...');
|
|
783
|
+
// Load config to get test command
|
|
784
|
+
const config = await services.configService.load(repoPath);
|
|
785
|
+
let testCommand = config?.testCommand;
|
|
786
|
+
// If no stored command, try to detect
|
|
787
|
+
if (!testCommand) {
|
|
788
|
+
const detection = await services.projectDetector.detect(repoPath);
|
|
789
|
+
testCommand = detection.testCommand ?? undefined;
|
|
790
|
+
}
|
|
791
|
+
if (testCommand) {
|
|
792
|
+
addOutput(`Running: ${testCommand}`);
|
|
793
|
+
const testResult = await services.testRunner.run({
|
|
794
|
+
testCommand,
|
|
795
|
+
workingDir: repoPath,
|
|
796
|
+
timeoutMs: 300000
|
|
797
|
+
});
|
|
798
|
+
if (!testResult.success) {
|
|
799
|
+
const errorMsg = testResult.timedOut
|
|
800
|
+
? 'Tests timed out'
|
|
801
|
+
: `Tests failed (exit code: ${testResult.exitCode})`;
|
|
802
|
+
await logToFile(`Test check failed: ${errorMsg}`);
|
|
803
|
+
setFlowState(prev => ({
|
|
804
|
+
...prev,
|
|
805
|
+
error: errorMsg,
|
|
806
|
+
phase: { type: 'error', feature: featureName, error: `${errorMsg}. Use --skip-tests to bypass.` }
|
|
807
|
+
}));
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
addOutput(`Tests passed (${(testResult.durationMs / 1000).toFixed(1)}s)`);
|
|
811
|
+
}
|
|
812
|
+
else {
|
|
813
|
+
addOutput('No test command configured, skipping tests');
|
|
814
|
+
}
|
|
815
|
+
}
|
|
689
816
|
addOutput('');
|
|
690
817
|
addOutput(`Starting flow: ${featureName}`);
|
|
691
818
|
addOutput(`Mode: ${mode}`);
|
|
@@ -739,6 +866,14 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
739
866
|
setFlowState(prev => ({ ...prev, resolvedFeatureName: initResult.featureName }));
|
|
740
867
|
addOutput(`Spec directory: ${initResult.specDir}`);
|
|
741
868
|
addOutput(`Feature name: ${initResult.featureName}`);
|
|
869
|
+
// Track spec.json artifact
|
|
870
|
+
addArtifact({
|
|
871
|
+
name: 'Spec Config',
|
|
872
|
+
filename: 'spec.json',
|
|
873
|
+
path: `.red64/specs/${initResult.featureName}/spec.json`,
|
|
874
|
+
phase: 'initializing',
|
|
875
|
+
createdAt: new Date().toISOString()
|
|
876
|
+
});
|
|
742
877
|
// Commit init
|
|
743
878
|
await commitChanges(`initialize spec directory`, workDir);
|
|
744
879
|
// Step 3: Generate requirements - pass the resolved feature name
|
|
@@ -757,8 +892,30 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
757
892
|
transitionPhase({ type: 'ERROR', error: result.error ?? 'Requirements generation failed' });
|
|
758
893
|
return;
|
|
759
894
|
}
|
|
895
|
+
// Validate that requirements were actually written (not just template)
|
|
896
|
+
const reqPath = join(workDir, `.red64/specs/${effectiveName}/requirements.md`);
|
|
897
|
+
try {
|
|
898
|
+
const reqStat = await stat(reqPath);
|
|
899
|
+
if (reqStat.size < 500) {
|
|
900
|
+
// Template is ~160 bytes; real requirements are much larger
|
|
901
|
+
transitionPhase({ type: 'ERROR', error: 'Requirements agent did not generate content. Ensure a project description is provided.' });
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
transitionPhase({ type: 'ERROR', error: 'Requirements file was not created by agent.' });
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
760
909
|
// Commit requirements
|
|
761
910
|
await commitChanges(`generate requirements`, workDir);
|
|
911
|
+
// Track requirements artifact
|
|
912
|
+
addArtifact({
|
|
913
|
+
name: 'Requirements',
|
|
914
|
+
filename: 'requirements.md',
|
|
915
|
+
path: `.red64/specs/${effectiveName}/requirements.md`,
|
|
916
|
+
phase: 'requirements-generating',
|
|
917
|
+
createdAt: new Date().toISOString()
|
|
918
|
+
});
|
|
762
919
|
// Transition to approval
|
|
763
920
|
const approvalPhase = transitionPhase({ type: 'PHASE_COMPLETE' });
|
|
764
921
|
await saveFlowState(approvalPhase, workDir);
|
|
@@ -773,8 +930,25 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
773
930
|
transitionPhase({ type: 'ERROR', error: result.error ?? 'Design generation failed' });
|
|
774
931
|
return;
|
|
775
932
|
}
|
|
933
|
+
// Validate that design.md was actually created
|
|
934
|
+
const designPath = join(workDir, `.red64/specs/${effectiveName}/design.md`);
|
|
935
|
+
try {
|
|
936
|
+
await stat(designPath);
|
|
937
|
+
}
|
|
938
|
+
catch {
|
|
939
|
+
transitionPhase({ type: 'ERROR', error: 'Design file was not created by agent. Check that requirements were properly generated.' });
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
776
942
|
// Commit design
|
|
777
943
|
await commitChanges(`generate technical design`, workDir);
|
|
944
|
+
// Track design artifact
|
|
945
|
+
addArtifact({
|
|
946
|
+
name: 'Design',
|
|
947
|
+
filename: 'design.md',
|
|
948
|
+
path: `.red64/specs/${effectiveName}/design.md`,
|
|
949
|
+
phase: 'design-generating',
|
|
950
|
+
createdAt: new Date().toISOString()
|
|
951
|
+
});
|
|
778
952
|
// Transition to approval
|
|
779
953
|
const approvalPhase = transitionPhase({ type: 'PHASE_COMPLETE' });
|
|
780
954
|
await saveFlowState(approvalPhase, workDir);
|
|
@@ -789,8 +963,25 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
789
963
|
transitionPhase({ type: 'ERROR', error: result.error ?? 'Tasks generation failed' });
|
|
790
964
|
return;
|
|
791
965
|
}
|
|
966
|
+
// Validate that tasks.md was actually created
|
|
967
|
+
const tasksPath = join(workDir, `.red64/specs/${effectiveName}/tasks.md`);
|
|
968
|
+
try {
|
|
969
|
+
await stat(tasksPath);
|
|
970
|
+
}
|
|
971
|
+
catch {
|
|
972
|
+
transitionPhase({ type: 'ERROR', error: 'Tasks file was not created by agent. Check that design was properly generated.' });
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
792
975
|
// Commit tasks
|
|
793
976
|
await commitChanges(`generate implementation tasks`, workDir);
|
|
977
|
+
// Track tasks artifact
|
|
978
|
+
addArtifact({
|
|
979
|
+
name: 'Tasks',
|
|
980
|
+
filename: 'tasks.md',
|
|
981
|
+
path: `.red64/specs/${effectiveName}/tasks.md`,
|
|
982
|
+
phase: 'tasks-generating',
|
|
983
|
+
createdAt: new Date().toISOString()
|
|
984
|
+
});
|
|
794
985
|
// Parse tasks for implementation phase - use effective name for spec directory
|
|
795
986
|
const specDir = join(workDir, '.red64', 'specs', effectiveName);
|
|
796
987
|
const tasks = await services.taskParser.parse(specDir);
|
|
@@ -905,6 +1096,14 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
905
1096
|
const gapResult = await executeCommand(`/red64:validate-gap ${effectiveName} -y`, workDir);
|
|
906
1097
|
if (gapResult.success) {
|
|
907
1098
|
await commitChanges(`gap analysis`, workDir);
|
|
1099
|
+
// Track gap analysis artifact
|
|
1100
|
+
addArtifact({
|
|
1101
|
+
name: 'Gap Analysis',
|
|
1102
|
+
filename: 'gap-analysis.md',
|
|
1103
|
+
path: `.red64/specs/${effectiveName}/gap-analysis.md`,
|
|
1104
|
+
phase: 'gap-analysis',
|
|
1105
|
+
createdAt: new Date().toISOString()
|
|
1106
|
+
});
|
|
908
1107
|
}
|
|
909
1108
|
transitionPhase({ type: 'PHASE_COMPLETE' });
|
|
910
1109
|
break;
|
|
@@ -975,7 +1174,7 @@ export const StartScreen = ({ args, flags }) => {
|
|
|
975
1174
|
}
|
|
976
1175
|
// Should sidebar be shown?
|
|
977
1176
|
const showSidebar = flowState.worktreePath !== null || flowState.phase.type !== 'idle';
|
|
978
|
-
return (_jsxs(Box, { flexDirection: "row", paddingX: 1, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "red64 start" }), _jsxs(Text, { dimColor: true, children: [" - ", featureName] })] }), flowState.worktreePath && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Worktree: ", flowState.worktreePath] }) })), renderPhaseIndicator(), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: flowState.output.slice(-10).map((line, i) => (_jsx(Text, { dimColor: i < flowState.output.length - 1, children: line }, i))) }), flowState.isHealthChecking && (_jsx(Box, { marginBottom: 1, children: _jsx(Spinner, { label: "Checking Claude API status..." }) })), flowState.isExecuting && !flowState.isHealthChecking && (_jsx(Box, { marginBottom: 1, children: _jsx(Spinner, { label: "Processing..." }) })), flowState.error && !flowState.isExecuting && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "red", children: flowState.error }) })), flowState.preStartStep.type === 'existing-flow-detected' && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", paddingX: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "Existing Flow Detected" }), _jsxs(Text, { dimColor: true, children: ["Phase: ", flowState.preStartStep.existingState.phase.type] }), _jsx(Box, { marginTop: 1, children: _jsx(Select, { options: EXISTING_FLOW_OPTIONS, onChange: handleExistingFlowDecision }) })] })), flowState.preStartStep.type === 'uncommitted-changes' && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", paddingX: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "Uncommitted Changes Detected" }), _jsxs(Text, { dimColor: true, children: [flowState.preStartStep.gitStatus.staged, " staged, ", flowState.preStartStep.gitStatus.unstaged, " unstaged, ", flowState.preStartStep.gitStatus.untracked, " untracked"] }), _jsx(Box, { marginTop: 1, children: _jsx(Select, { options: UNCOMMITTED_CHANGES_OPTIONS, onChange: handleUncommittedChangesDecision }) })] })), isApprovalPhase && !flowState.isExecuting && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Review Required" }), _jsxs(Text, { dimColor: true, children: ["Review output in .red64/specs/", sanitizeFeatureName(featureName), "/"] }), _jsx(Box, { marginTop: 1, children: _jsx(Select, { options: APPROVAL_OPTIONS, onChange: handleApproval }) })] }))] }), showSidebar && (_jsx(
|
|
1177
|
+
return (_jsxs(Box, { flexDirection: "row", paddingX: 1, children: [showSidebar && (_jsx(FeatureSidebar, { featureName: flowState.resolvedFeatureName ?? sanitizeFeatureName(featureName), sandboxMode: flags.sandbox ?? false, currentPhase: flowState.phase.type, mode: mode, currentTask: flowState.currentTask, totalTasks: flowState.totalTasks, commitCount: flowState.commitCount, agent: flowState.agent, model: flags.model })), _jsxs(Box, { flexDirection: "column", flexGrow: 1, marginLeft: showSidebar ? 1 : 0, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "red64 start" }), _jsxs(Text, { dimColor: true, children: [" - ", featureName] })] }), flowState.worktreePath && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Worktree: ", flowState.worktreePath] }) })), renderPhaseIndicator(), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: flowState.output.slice(-10).map((line, i) => (_jsx(Text, { dimColor: i < flowState.output.length - 1, children: line }, i))) }), flowState.isHealthChecking && (_jsx(Box, { marginBottom: 1, children: _jsx(Spinner, { label: "Checking Claude API status..." }) })), flowState.isExecuting && !flowState.isHealthChecking && (_jsx(Box, { marginBottom: 1, children: _jsx(Spinner, { label: "Processing..." }) })), flowState.error && !flowState.isExecuting && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "red", children: flowState.error }) })), flowState.preStartStep.type === 'existing-flow-detected' && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", paddingX: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "Existing Flow Detected" }), _jsxs(Text, { dimColor: true, children: ["Phase: ", flowState.preStartStep.existingState.phase.type] }), _jsx(Box, { marginTop: 1, children: _jsx(Select, { options: EXISTING_FLOW_OPTIONS, onChange: handleExistingFlowDecision }) })] })), flowState.preStartStep.type === 'uncommitted-changes' && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", paddingX: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "Uncommitted Changes Detected" }), _jsxs(Text, { dimColor: true, children: [flowState.preStartStep.gitStatus.staged, " staged, ", flowState.preStartStep.gitStatus.unstaged, " unstaged, ", flowState.preStartStep.gitStatus.untracked, " untracked"] }), _jsx(Box, { marginTop: 1, children: _jsx(Select, { options: UNCOMMITTED_CHANGES_OPTIONS, onChange: handleUncommittedChangesDecision }) })] })), isApprovalPhase && !flowState.isExecuting && (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Review Required" }), _jsxs(Text, { dimColor: true, children: ["Review output in .red64/specs/", flowState.resolvedFeatureName ?? sanitizeFeatureName(featureName), "/", PHASE_REVIEW_FILES[flowState.phase.type] ?? ''] }), _jsx(Box, { marginTop: 1, children: _jsx(Select, { options: APPROVAL_OPTIONS, onChange: handleApproval }) })] }))] }), showSidebar && (_jsx(ArtifactsSidebar, { artifacts: flowState.artifacts, worktreePath: flowState.worktreePath }))] }));
|
|
979
1178
|
};
|
|
980
1179
|
/**
|
|
981
1180
|
* Convert ExtendedFlowPhase to FlowPhase for state persistence
|