@arvorco/relentless 0.1.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/relentless.analyze.md +20 -0
- package/.claude/commands/relentless.checklist.md +15 -0
- package/.claude/commands/relentless.clarify.md +19 -0
- package/.claude/commands/relentless.constitution.md +78 -0
- package/.claude/commands/relentless.implement.md +15 -0
- package/.claude/commands/relentless.plan.md +22 -0
- package/.claude/commands/relentless.plan.old.md +89 -0
- package/.claude/commands/relentless.specify.md +254 -0
- package/.claude/commands/relentless.tasks.md +25 -0
- package/.claude/commands/relentless.taskstoissues.md +15 -0
- package/.claude/settings.local.json +23 -0
- package/.claude/skills/analyze/SKILL.md +149 -0
- package/.claude/skills/checklist/SKILL.md +173 -0
- package/.claude/skills/checklist/templates/checklist-template.md +40 -0
- package/.claude/skills/clarify/SKILL.md +174 -0
- package/.claude/skills/constitution/SKILL.md +150 -0
- package/.claude/skills/constitution/templates/constitution-template.md +228 -0
- package/.claude/skills/implement/SKILL.md +141 -0
- package/.claude/skills/plan/SKILL.md +179 -0
- package/.claude/skills/plan/templates/plan-template.md +104 -0
- package/.claude/skills/prd/SKILL.md +242 -0
- package/.claude/skills/relentless/SKILL.md +265 -0
- package/.claude/skills/specify/SKILL.md +220 -0
- package/.claude/skills/specify/scripts/bash/check-prerequisites.sh +166 -0
- package/.claude/skills/specify/scripts/bash/common.sh +156 -0
- package/.claude/skills/specify/scripts/bash/create-new-feature.sh +305 -0
- package/.claude/skills/specify/scripts/bash/setup-plan.sh +61 -0
- package/.claude/skills/specify/scripts/bash/update-agent-context.sh +799 -0
- package/.claude/skills/specify/templates/spec-template.md +115 -0
- package/.claude/skills/tasks/SKILL.md +202 -0
- package/.claude/skills/tasks/templates/tasks-template.md +251 -0
- package/.claude/skills/taskstoissues/SKILL.md +97 -0
- package/.specify/memory/constitution.md +50 -0
- package/.specify/scripts/bash/check-prerequisites.sh +166 -0
- package/.specify/scripts/bash/common.sh +156 -0
- package/.specify/scripts/bash/create-new-feature.sh +297 -0
- package/.specify/scripts/bash/setup-plan.sh +61 -0
- package/.specify/scripts/bash/update-agent-context.sh +799 -0
- package/.specify/templates/agent-file-template.md +28 -0
- package/.specify/templates/checklist-template.md +40 -0
- package/.specify/templates/plan-template.md +104 -0
- package/.specify/templates/spec-template.md +115 -0
- package/.specify/templates/tasks-template.md +251 -0
- package/CHANGES_SUMMARY.md +255 -0
- package/CLAUDE.md +92 -0
- package/GEMINI_SETUP.md +256 -0
- package/LICENSE +21 -0
- package/README.md +1171 -0
- package/REFACTOR_SUMMARY.md +267 -0
- package/bin/relentless.ts +536 -0
- package/bun.lock +352 -0
- package/eslint.config.js +37 -0
- package/package.json +61 -0
- package/prd.json.example +64 -0
- package/prompt.md +108 -0
- package/ralph.sh +80 -0
- package/relentless/config.json +38 -0
- package/relentless/features/.gitkeep +0 -0
- package/relentless/features/ghsk-ideas/prd.json +229 -0
- package/relentless/features/ghsk-ideas/prd.md +191 -0
- package/relentless/features/ghsk-ideas/progress.txt +408 -0
- package/relentless/prompt.md +79 -0
- package/skills/checklist/SKILL.md +349 -0
- package/skills/clarify/SKILL.md +476 -0
- package/skills/prd/SKILL.md +242 -0
- package/skills/relentless/SKILL.md +268 -0
- package/skills/tasks/SKILL.md +577 -0
- package/src/agents/amp.ts +115 -0
- package/src/agents/claude.ts +185 -0
- package/src/agents/codex.ts +89 -0
- package/src/agents/droid.ts +90 -0
- package/src/agents/gemini.ts +109 -0
- package/src/agents/index.ts +16 -0
- package/src/agents/opencode.ts +88 -0
- package/src/agents/registry.ts +95 -0
- package/src/agents/types.ts +101 -0
- package/src/config/index.ts +8 -0
- package/src/config/loader.ts +237 -0
- package/src/config/schema.ts +115 -0
- package/src/execution/index.ts +8 -0
- package/src/execution/router.ts +49 -0
- package/src/execution/runner.ts +512 -0
- package/src/index.ts +11 -0
- package/src/init/index.ts +7 -0
- package/src/init/scaffolder.ts +377 -0
- package/src/prd/analyzer.ts +512 -0
- package/src/prd/index.ts +11 -0
- package/src/prd/issues.ts +249 -0
- package/src/prd/parser.ts +281 -0
- package/src/prd/progress.ts +198 -0
- package/src/prd/types.ts +170 -0
- package/src/tui/App.tsx +85 -0
- package/src/tui/TUIRunner.tsx +400 -0
- package/src/tui/components/AgentOutput.tsx +45 -0
- package/src/tui/components/AgentStatus.tsx +64 -0
- package/src/tui/components/CurrentStory.tsx +66 -0
- package/src/tui/components/Header.tsx +49 -0
- package/src/tui/components/ProgressBar.tsx +39 -0
- package/src/tui/components/StoryGrid.tsx +86 -0
- package/src/tui/hooks/useTUI.ts +147 -0
- package/src/tui/hooks/useTimer.ts +51 -0
- package/src/tui/index.tsx +17 -0
- package/src/tui/theme.ts +41 -0
- package/src/tui/types.ts +77 -0
- package/templates/constitution.md +228 -0
- package/templates/plan.md +273 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress Tracking with Metadata
|
|
3
|
+
*
|
|
4
|
+
* Manages progress.txt with YAML frontmatter for machine-readable context
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import yaml from "js-yaml";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Progress metadata stored in YAML frontmatter
|
|
12
|
+
*/
|
|
13
|
+
export interface ProgressMetadata {
|
|
14
|
+
/** Feature name */
|
|
15
|
+
feature: string;
|
|
16
|
+
/** When the feature was started */
|
|
17
|
+
started: string;
|
|
18
|
+
/** Last update timestamp */
|
|
19
|
+
last_updated: string;
|
|
20
|
+
/** Number of stories completed */
|
|
21
|
+
stories_completed: number;
|
|
22
|
+
/** Patterns learned during development */
|
|
23
|
+
patterns: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Progress file with frontmatter and content
|
|
28
|
+
*/
|
|
29
|
+
export interface ProgressFile {
|
|
30
|
+
/** Parsed frontmatter metadata */
|
|
31
|
+
metadata: ProgressMetadata;
|
|
32
|
+
/** Content after frontmatter (logs and patterns) */
|
|
33
|
+
content: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse progress.txt with YAML frontmatter
|
|
38
|
+
*/
|
|
39
|
+
export function parseProgress(content: string): ProgressFile {
|
|
40
|
+
// Check for frontmatter delimiter
|
|
41
|
+
if (!content.startsWith("---\n")) {
|
|
42
|
+
// Legacy format without frontmatter - create default metadata
|
|
43
|
+
const lines = content.split("\n");
|
|
44
|
+
const featureLine = lines.find((l) => l.startsWith("# Progress Log:"));
|
|
45
|
+
const feature = featureLine ? featureLine.replace("# Progress Log:", "").trim() : "unknown";
|
|
46
|
+
const startedLine = lines.find((l) => l.startsWith("Started:"));
|
|
47
|
+
const started = startedLine ? startedLine.replace("Started:", "").trim() : new Date().toISOString();
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
metadata: {
|
|
51
|
+
feature,
|
|
52
|
+
started,
|
|
53
|
+
last_updated: new Date().toISOString(),
|
|
54
|
+
stories_completed: 0,
|
|
55
|
+
patterns: [],
|
|
56
|
+
},
|
|
57
|
+
content,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Find closing delimiter
|
|
62
|
+
const endIndex = content.indexOf("\n---\n", 4);
|
|
63
|
+
if (endIndex === -1) {
|
|
64
|
+
throw new Error("Progress file has opening --- but no closing --- delimiter");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Extract frontmatter
|
|
68
|
+
const frontmatterText = content.substring(4, endIndex);
|
|
69
|
+
const metadata = yaml.load(frontmatterText) as ProgressMetadata;
|
|
70
|
+
|
|
71
|
+
// Extract content after frontmatter
|
|
72
|
+
const contentAfterFrontmatter = content.substring(endIndex + 5); // Skip "\n---\n"
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
metadata,
|
|
76
|
+
content: contentAfterFrontmatter,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Serialize progress file back to string with frontmatter
|
|
82
|
+
*/
|
|
83
|
+
export function serializeProgress(progress: ProgressFile): string {
|
|
84
|
+
const frontmatter = yaml.dump(progress.metadata, {
|
|
85
|
+
indent: 2,
|
|
86
|
+
lineWidth: -1, // Disable line wrapping
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return `---\n${frontmatter}---\n${progress.content}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Load progress file
|
|
94
|
+
*/
|
|
95
|
+
export async function loadProgress(progressPath: string): Promise<ProgressFile | null> {
|
|
96
|
+
if (!existsSync(progressPath)) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const content = await Bun.file(progressPath).text();
|
|
101
|
+
return parseProgress(content);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Update progress metadata
|
|
106
|
+
*/
|
|
107
|
+
export async function updateProgressMetadata(
|
|
108
|
+
progressPath: string,
|
|
109
|
+
updates: Partial<ProgressMetadata>
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
const progress = await loadProgress(progressPath);
|
|
112
|
+
if (!progress) {
|
|
113
|
+
throw new Error(`Progress file not found: ${progressPath}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Merge updates
|
|
117
|
+
progress.metadata = {
|
|
118
|
+
...progress.metadata,
|
|
119
|
+
...updates,
|
|
120
|
+
last_updated: new Date().toISOString(),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Write back
|
|
124
|
+
await Bun.write(progressPath, serializeProgress(progress));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Append entry to progress log
|
|
129
|
+
*/
|
|
130
|
+
export async function appendProgress(progressPath: string, entry: string): Promise<void> {
|
|
131
|
+
const progress = await loadProgress(progressPath);
|
|
132
|
+
if (!progress) {
|
|
133
|
+
throw new Error(`Progress file not found: ${progressPath}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Append entry
|
|
137
|
+
progress.content += `\n${entry}`;
|
|
138
|
+
|
|
139
|
+
// Update last_updated
|
|
140
|
+
progress.metadata.last_updated = new Date().toISOString();
|
|
141
|
+
|
|
142
|
+
// Write back
|
|
143
|
+
await Bun.write(progressPath, serializeProgress(progress));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Extract patterns from progress content
|
|
148
|
+
*/
|
|
149
|
+
export function extractPatterns(content: string): string[] {
|
|
150
|
+
const patterns: string[] = [];
|
|
151
|
+
const lines = content.split("\n");
|
|
152
|
+
|
|
153
|
+
let inLearningsSection = false;
|
|
154
|
+
for (const line of lines) {
|
|
155
|
+
// Check for "Learnings for future iterations:" header
|
|
156
|
+
if (line.includes("Learnings for future iterations:")) {
|
|
157
|
+
inLearningsSection = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check for section end
|
|
162
|
+
if (line.trim() === "---" || line.startsWith("##")) {
|
|
163
|
+
inLearningsSection = false;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Extract patterns from learnings sections
|
|
168
|
+
if (inLearningsSection && line.trim().startsWith("-")) {
|
|
169
|
+
const pattern = line.trim().substring(1).trim(); // Remove leading "- "
|
|
170
|
+
if (pattern && pattern.startsWith("**")) {
|
|
171
|
+
// Pattern format: **Key**: Description
|
|
172
|
+
patterns.push(pattern);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return patterns;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Update patterns in frontmatter from content
|
|
182
|
+
*/
|
|
183
|
+
export async function syncPatternsFromContent(progressPath: string): Promise<void> {
|
|
184
|
+
const progress = await loadProgress(progressPath);
|
|
185
|
+
if (!progress) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Extract patterns from content
|
|
190
|
+
const patterns = extractPatterns(progress.content);
|
|
191
|
+
|
|
192
|
+
// Update metadata
|
|
193
|
+
progress.metadata.patterns = patterns;
|
|
194
|
+
progress.metadata.last_updated = new Date().toISOString();
|
|
195
|
+
|
|
196
|
+
// Write back
|
|
197
|
+
await Bun.write(progressPath, serializeProgress(progress));
|
|
198
|
+
}
|
package/src/prd/types.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRD Types
|
|
3
|
+
*
|
|
4
|
+
* Defines the structure of prd.json for user stories
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* User story schema
|
|
11
|
+
*/
|
|
12
|
+
export const UserStorySchema = z.object({
|
|
13
|
+
id: z.string(), // e.g., "US-001"
|
|
14
|
+
title: z.string(),
|
|
15
|
+
description: z.string(),
|
|
16
|
+
acceptanceCriteria: z.array(z.string()),
|
|
17
|
+
priority: z.number().int().positive(),
|
|
18
|
+
passes: z.boolean().default(false),
|
|
19
|
+
notes: z.string().default(""),
|
|
20
|
+
dependencies: z.array(z.string()).optional(), // Array of story IDs this story depends on
|
|
21
|
+
parallel: z.boolean().optional(), // Can be executed in parallel with other stories
|
|
22
|
+
phase: z.string().optional(), // Phase marker (e.g., "Setup", "Foundation", "Stories", "Polish")
|
|
23
|
+
research: z.boolean().optional(), // Requires research phase before implementation
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export type UserStory = z.infer<typeof UserStorySchema>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Complete PRD schema
|
|
30
|
+
*/
|
|
31
|
+
export const PRDSchema = z.object({
|
|
32
|
+
project: z.string(),
|
|
33
|
+
branchName: z.string(),
|
|
34
|
+
description: z.string(),
|
|
35
|
+
userStories: z.array(UserStorySchema),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export type PRD = z.infer<typeof PRDSchema>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validate dependencies and detect circular dependencies
|
|
42
|
+
*/
|
|
43
|
+
export function validateDependencies(prd: PRD): void {
|
|
44
|
+
const storyMap = new Map<string, UserStory>();
|
|
45
|
+
for (const story of prd.userStories) {
|
|
46
|
+
storyMap.set(story.id, story);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check for invalid dependencies (references to non-existent stories)
|
|
50
|
+
for (const story of prd.userStories) {
|
|
51
|
+
if (story.dependencies) {
|
|
52
|
+
for (const depId of story.dependencies) {
|
|
53
|
+
if (!storyMap.has(depId)) {
|
|
54
|
+
throw new Error(`Story ${story.id} depends on non-existent story ${depId}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Detect circular dependencies using DFS
|
|
61
|
+
const visited = new Set<string>();
|
|
62
|
+
const recursionStack = new Set<string>();
|
|
63
|
+
|
|
64
|
+
function hasCycle(storyId: string, path: string[] = []): boolean {
|
|
65
|
+
if (recursionStack.has(storyId)) {
|
|
66
|
+
const cycle = [...path, storyId].join(" -> ");
|
|
67
|
+
throw new Error(`Circular dependency detected: ${cycle}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (visited.has(storyId)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
visited.add(storyId);
|
|
75
|
+
recursionStack.add(storyId);
|
|
76
|
+
|
|
77
|
+
const story = storyMap.get(storyId);
|
|
78
|
+
if (story?.dependencies) {
|
|
79
|
+
for (const depId of story.dependencies) {
|
|
80
|
+
hasCycle(depId, [...path, storyId]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
recursionStack.delete(storyId);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const story of prd.userStories) {
|
|
89
|
+
hasCycle(story.id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if a story's dependencies are all completed
|
|
95
|
+
*/
|
|
96
|
+
function areDependenciesMet(story: UserStory, prd: PRD): boolean {
|
|
97
|
+
if (!story.dependencies || story.dependencies.length === 0) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const storyMap = new Map<string, UserStory>();
|
|
102
|
+
for (const s of prd.userStories) {
|
|
103
|
+
storyMap.set(s.id, s);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return story.dependencies.every((depId) => {
|
|
107
|
+
const depStory = storyMap.get(depId);
|
|
108
|
+
return depStory?.passes ?? false;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get the next story to work on
|
|
114
|
+
*/
|
|
115
|
+
export function getNextStory(prd: PRD): UserStory | null {
|
|
116
|
+
// Validate dependencies first
|
|
117
|
+
validateDependencies(prd);
|
|
118
|
+
|
|
119
|
+
// Find highest priority story where passes is false and dependencies are met
|
|
120
|
+
const pendingStories = prd.userStories
|
|
121
|
+
.filter((s) => !s.passes && areDependenciesMet(s, prd))
|
|
122
|
+
.sort((a, b) => a.priority - b.priority);
|
|
123
|
+
|
|
124
|
+
return pendingStories[0] ?? null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if all stories are complete
|
|
129
|
+
*/
|
|
130
|
+
export function isComplete(prd: PRD): boolean {
|
|
131
|
+
return prd.userStories.every((s) => s.passes);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Count stories by status
|
|
136
|
+
*/
|
|
137
|
+
export function countStories(prd: PRD): { total: number; completed: number; pending: number } {
|
|
138
|
+
const total = prd.userStories.length;
|
|
139
|
+
const completed = prd.userStories.filter((s) => s.passes).length;
|
|
140
|
+
const pending = total - completed;
|
|
141
|
+
return { total, completed, pending };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Infer the story type from title and description
|
|
146
|
+
*/
|
|
147
|
+
export function inferStoryType(story: UserStory): string {
|
|
148
|
+
const text = `${story.title} ${story.description}`.toLowerCase();
|
|
149
|
+
|
|
150
|
+
if (text.includes("database") || text.includes("migration") || text.includes("schema") || text.includes("table")) {
|
|
151
|
+
return "database";
|
|
152
|
+
}
|
|
153
|
+
if (text.includes("ui") || text.includes("component") || text.includes("browser") || text.includes("frontend") || text.includes("page")) {
|
|
154
|
+
return "ui";
|
|
155
|
+
}
|
|
156
|
+
if (text.includes("api") || text.includes("endpoint") || text.includes("server") || text.includes("route")) {
|
|
157
|
+
return "api";
|
|
158
|
+
}
|
|
159
|
+
if (text.includes("test") || text.includes("spec") || text.includes("coverage")) {
|
|
160
|
+
return "test";
|
|
161
|
+
}
|
|
162
|
+
if (text.includes("refactor") || text.includes("cleanup") || text.includes("reorganize")) {
|
|
163
|
+
return "refactor";
|
|
164
|
+
}
|
|
165
|
+
if (text.includes("doc") || text.includes("readme") || text.includes("comment")) {
|
|
166
|
+
return "docs";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return "general";
|
|
170
|
+
}
|
package/src/tui/App.tsx
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relentless TUI App
|
|
3
|
+
*
|
|
4
|
+
* Main application component that renders the full interface
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { Box, Text } from "ink";
|
|
9
|
+
import { Header } from "./components/Header.js";
|
|
10
|
+
import { ProgressBar } from "./components/ProgressBar.js";
|
|
11
|
+
import { CurrentStory } from "./components/CurrentStory.js";
|
|
12
|
+
import { AgentOutput } from "./components/AgentOutput.js";
|
|
13
|
+
import { StoryGrid } from "./components/StoryGrid.js";
|
|
14
|
+
import { AgentStatus } from "./components/AgentStatus.js";
|
|
15
|
+
import { colors } from "./theme.js";
|
|
16
|
+
import type { TUIState } from "./types.js";
|
|
17
|
+
|
|
18
|
+
interface AppProps {
|
|
19
|
+
state: TUIState;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function App({ state }: AppProps): React.ReactElement {
|
|
23
|
+
const completedCount = state.stories.filter((s) => s.passes).length;
|
|
24
|
+
const totalCount = state.stories.length;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Box flexDirection="column" width="100%">
|
|
28
|
+
{/* Header */}
|
|
29
|
+
<Header agent={state.currentAgent} />
|
|
30
|
+
|
|
31
|
+
{/* Feature info and progress */}
|
|
32
|
+
<Box flexDirection="column" paddingX={1} paddingY={1}>
|
|
33
|
+
<Box>
|
|
34
|
+
<Text color={colors.dim}>Feature: </Text>
|
|
35
|
+
<Text bold>{state.feature}</Text>
|
|
36
|
+
</Box>
|
|
37
|
+
<Box marginTop={1}>
|
|
38
|
+
<Text color={colors.dim}>Progress: </Text>
|
|
39
|
+
<ProgressBar completed={completedCount} total={totalCount} />
|
|
40
|
+
</Box>
|
|
41
|
+
</Box>
|
|
42
|
+
|
|
43
|
+
{/* Current story */}
|
|
44
|
+
<Box paddingX={1}>
|
|
45
|
+
<CurrentStory
|
|
46
|
+
story={state.currentStory}
|
|
47
|
+
elapsedSeconds={state.elapsedSeconds}
|
|
48
|
+
isRunning={state.isRunning}
|
|
49
|
+
/>
|
|
50
|
+
</Box>
|
|
51
|
+
|
|
52
|
+
{/* Agent output */}
|
|
53
|
+
<AgentOutput lines={state.outputLines} />
|
|
54
|
+
|
|
55
|
+
{/* Story grid */}
|
|
56
|
+
<StoryGrid
|
|
57
|
+
stories={state.stories}
|
|
58
|
+
currentStoryId={state.currentStory?.id}
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
{/* Agent status footer */}
|
|
62
|
+
<AgentStatus
|
|
63
|
+
agents={state.agents}
|
|
64
|
+
iteration={state.iteration}
|
|
65
|
+
maxIterations={state.maxIterations}
|
|
66
|
+
/>
|
|
67
|
+
|
|
68
|
+
{/* Error display */}
|
|
69
|
+
{state.error && (
|
|
70
|
+
<Box paddingX={1}>
|
|
71
|
+
<Text color={colors.error}>Error: {state.error}</Text>
|
|
72
|
+
</Box>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{/* Completion message */}
|
|
76
|
+
{state.isComplete && (
|
|
77
|
+
<Box paddingX={1} paddingY={1}>
|
|
78
|
+
<Text color={colors.success} bold>
|
|
79
|
+
🎉 All stories complete!
|
|
80
|
+
</Text>
|
|
81
|
+
</Box>
|
|
82
|
+
)}
|
|
83
|
+
</Box>
|
|
84
|
+
);
|
|
85
|
+
}
|