@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.
Files changed (107) hide show
  1. package/.claude/commands/relentless.analyze.md +20 -0
  2. package/.claude/commands/relentless.checklist.md +15 -0
  3. package/.claude/commands/relentless.clarify.md +19 -0
  4. package/.claude/commands/relentless.constitution.md +78 -0
  5. package/.claude/commands/relentless.implement.md +15 -0
  6. package/.claude/commands/relentless.plan.md +22 -0
  7. package/.claude/commands/relentless.plan.old.md +89 -0
  8. package/.claude/commands/relentless.specify.md +254 -0
  9. package/.claude/commands/relentless.tasks.md +25 -0
  10. package/.claude/commands/relentless.taskstoissues.md +15 -0
  11. package/.claude/settings.local.json +23 -0
  12. package/.claude/skills/analyze/SKILL.md +149 -0
  13. package/.claude/skills/checklist/SKILL.md +173 -0
  14. package/.claude/skills/checklist/templates/checklist-template.md +40 -0
  15. package/.claude/skills/clarify/SKILL.md +174 -0
  16. package/.claude/skills/constitution/SKILL.md +150 -0
  17. package/.claude/skills/constitution/templates/constitution-template.md +228 -0
  18. package/.claude/skills/implement/SKILL.md +141 -0
  19. package/.claude/skills/plan/SKILL.md +179 -0
  20. package/.claude/skills/plan/templates/plan-template.md +104 -0
  21. package/.claude/skills/prd/SKILL.md +242 -0
  22. package/.claude/skills/relentless/SKILL.md +265 -0
  23. package/.claude/skills/specify/SKILL.md +220 -0
  24. package/.claude/skills/specify/scripts/bash/check-prerequisites.sh +166 -0
  25. package/.claude/skills/specify/scripts/bash/common.sh +156 -0
  26. package/.claude/skills/specify/scripts/bash/create-new-feature.sh +305 -0
  27. package/.claude/skills/specify/scripts/bash/setup-plan.sh +61 -0
  28. package/.claude/skills/specify/scripts/bash/update-agent-context.sh +799 -0
  29. package/.claude/skills/specify/templates/spec-template.md +115 -0
  30. package/.claude/skills/tasks/SKILL.md +202 -0
  31. package/.claude/skills/tasks/templates/tasks-template.md +251 -0
  32. package/.claude/skills/taskstoissues/SKILL.md +97 -0
  33. package/.specify/memory/constitution.md +50 -0
  34. package/.specify/scripts/bash/check-prerequisites.sh +166 -0
  35. package/.specify/scripts/bash/common.sh +156 -0
  36. package/.specify/scripts/bash/create-new-feature.sh +297 -0
  37. package/.specify/scripts/bash/setup-plan.sh +61 -0
  38. package/.specify/scripts/bash/update-agent-context.sh +799 -0
  39. package/.specify/templates/agent-file-template.md +28 -0
  40. package/.specify/templates/checklist-template.md +40 -0
  41. package/.specify/templates/plan-template.md +104 -0
  42. package/.specify/templates/spec-template.md +115 -0
  43. package/.specify/templates/tasks-template.md +251 -0
  44. package/CHANGES_SUMMARY.md +255 -0
  45. package/CLAUDE.md +92 -0
  46. package/GEMINI_SETUP.md +256 -0
  47. package/LICENSE +21 -0
  48. package/README.md +1171 -0
  49. package/REFACTOR_SUMMARY.md +267 -0
  50. package/bin/relentless.ts +536 -0
  51. package/bun.lock +352 -0
  52. package/eslint.config.js +37 -0
  53. package/package.json +61 -0
  54. package/prd.json.example +64 -0
  55. package/prompt.md +108 -0
  56. package/ralph.sh +80 -0
  57. package/relentless/config.json +38 -0
  58. package/relentless/features/.gitkeep +0 -0
  59. package/relentless/features/ghsk-ideas/prd.json +229 -0
  60. package/relentless/features/ghsk-ideas/prd.md +191 -0
  61. package/relentless/features/ghsk-ideas/progress.txt +408 -0
  62. package/relentless/prompt.md +79 -0
  63. package/skills/checklist/SKILL.md +349 -0
  64. package/skills/clarify/SKILL.md +476 -0
  65. package/skills/prd/SKILL.md +242 -0
  66. package/skills/relentless/SKILL.md +268 -0
  67. package/skills/tasks/SKILL.md +577 -0
  68. package/src/agents/amp.ts +115 -0
  69. package/src/agents/claude.ts +185 -0
  70. package/src/agents/codex.ts +89 -0
  71. package/src/agents/droid.ts +90 -0
  72. package/src/agents/gemini.ts +109 -0
  73. package/src/agents/index.ts +16 -0
  74. package/src/agents/opencode.ts +88 -0
  75. package/src/agents/registry.ts +95 -0
  76. package/src/agents/types.ts +101 -0
  77. package/src/config/index.ts +8 -0
  78. package/src/config/loader.ts +237 -0
  79. package/src/config/schema.ts +115 -0
  80. package/src/execution/index.ts +8 -0
  81. package/src/execution/router.ts +49 -0
  82. package/src/execution/runner.ts +512 -0
  83. package/src/index.ts +11 -0
  84. package/src/init/index.ts +7 -0
  85. package/src/init/scaffolder.ts +377 -0
  86. package/src/prd/analyzer.ts +512 -0
  87. package/src/prd/index.ts +11 -0
  88. package/src/prd/issues.ts +249 -0
  89. package/src/prd/parser.ts +281 -0
  90. package/src/prd/progress.ts +198 -0
  91. package/src/prd/types.ts +170 -0
  92. package/src/tui/App.tsx +85 -0
  93. package/src/tui/TUIRunner.tsx +400 -0
  94. package/src/tui/components/AgentOutput.tsx +45 -0
  95. package/src/tui/components/AgentStatus.tsx +64 -0
  96. package/src/tui/components/CurrentStory.tsx +66 -0
  97. package/src/tui/components/Header.tsx +49 -0
  98. package/src/tui/components/ProgressBar.tsx +39 -0
  99. package/src/tui/components/StoryGrid.tsx +86 -0
  100. package/src/tui/hooks/useTUI.ts +147 -0
  101. package/src/tui/hooks/useTimer.ts +51 -0
  102. package/src/tui/index.tsx +17 -0
  103. package/src/tui/theme.ts +41 -0
  104. package/src/tui/types.ts +77 -0
  105. package/templates/constitution.md +228 -0
  106. package/templates/plan.md +273 -0
  107. 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
+ }
@@ -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
+ }
@@ -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
+ }