@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,377 @@
1
+ /**
2
+ * Project Scaffolder
3
+ *
4
+ * Creates Relentless files in a project's relentless/ directory
5
+ *
6
+ * Structure:
7
+ * relentless/
8
+ * ├── config.json
9
+ * ├── prompt.md
10
+ * └── features/
11
+ * └── <feature-name>/
12
+ * ├── prd.md
13
+ * ├── prd.json
14
+ * └── progress.txt
15
+ *
16
+ * Best practices:
17
+ * - All relentless files go in relentless/ subdirectory
18
+ * - Each feature gets its own folder with prd.md, prd.json, progress.txt
19
+ * - Only skills are installed to .claude/skills/ (expected by Claude Code)
20
+ * - Does not modify project root files (CLAUDE.md, AGENTS.md, etc.)
21
+ */
22
+
23
+ import { existsSync, mkdirSync } from "node:fs";
24
+ import { join } from "node:path";
25
+ import chalk from "chalk";
26
+ import { checkAgentHealth } from "../agents/registry";
27
+ import { DEFAULT_CONFIG } from "../config/schema";
28
+
29
+ /**
30
+ * Get the relentless root directory
31
+ */
32
+ const relentlessRoot = import.meta.dir.replace("/src/init", "");
33
+
34
+ /**
35
+ * Files to create in the relentless/ directory
36
+ */
37
+ const RELENTLESS_FILES: Record<string, () => string> = {
38
+ "config.json": () => JSON.stringify(DEFAULT_CONFIG, null, 2),
39
+ "prompt.md": () => PROMPT_TEMPLATE,
40
+ };
41
+
42
+ const PROMPT_TEMPLATE = `# Relentless Agent Instructions
43
+
44
+ You are an autonomous coding agent working on a software project.
45
+
46
+ ## Before You Start
47
+
48
+ 1. **Review the codebase** - Understand the current state, architecture, and patterns
49
+ 2. **Read progress.txt** - Check the Codebase Patterns section for learnings from previous iterations
50
+ 3. **Read the PRD** - Understand what needs to be done
51
+
52
+ ## Your Task
53
+
54
+ 1. Read the PRD at \`relentless/features/<feature>/prd.json\`
55
+ 2. Read the progress log at \`relentless/features/<feature>/progress.txt\`
56
+ 3. Check you're on the correct branch from PRD \`branchName\`. If not, check it out or create from main.
57
+ 4. Pick the **highest priority** user story where \`passes: false\`
58
+ 5. **Review relevant code** before implementing - understand existing patterns
59
+ 6. Implement that single user story
60
+ 7. Run quality checks (typecheck, lint, test - whatever your project requires)
61
+ 8. If checks pass, commit ALL changes with message: \`feat: [Story ID] - [Story Title]\`
62
+ 9. Update the PRD to set \`passes: true\` for the completed story
63
+ 10. Append your progress to \`relentless/features/<feature>/progress.txt\`
64
+
65
+ ## Progress Report Format
66
+
67
+ APPEND to progress.txt (never replace, always append):
68
+ \`\`\`
69
+ ## [Date/Time] - [Story ID]
70
+ - What was implemented
71
+ - Files changed
72
+ - **Learnings for future iterations:**
73
+ - Patterns discovered
74
+ - Gotchas encountered
75
+ - Useful context
76
+ ---
77
+ \`\`\`
78
+
79
+ ## Quality Requirements
80
+
81
+ - ALL commits must pass your project's quality checks (typecheck, lint, test)
82
+ - Do NOT commit broken code
83
+ - Keep changes focused and minimal
84
+ - Follow existing code patterns
85
+ - Review code before modifying it
86
+
87
+ ## Stop Condition
88
+
89
+ After completing a user story, check if ALL stories have \`passes: true\`.
90
+
91
+ If ALL stories are complete and passing, reply with:
92
+ <promise>COMPLETE</promise>
93
+
94
+ If there are still stories with \`passes: false\`, end your response normally (another iteration will pick up the next story).
95
+
96
+ ## Important
97
+
98
+ - Work on ONE story per iteration
99
+ - Review existing code before implementing
100
+ - Commit frequently
101
+ - Keep CI green
102
+ `;
103
+
104
+ /**
105
+ * Default progress.txt content for a new feature with YAML frontmatter
106
+ */
107
+ export function createProgressTemplate(featureName: string): string {
108
+ const started = new Date().toISOString();
109
+ return `---
110
+ feature: ${featureName}
111
+ started: ${started}
112
+ last_updated: ${started}
113
+ stories_completed: 0
114
+ patterns: []
115
+ ---
116
+
117
+ # Progress Log: ${featureName}
118
+
119
+ ## Codebase Patterns
120
+
121
+ <!-- Patterns discovered during development will be added here -->
122
+
123
+ ---
124
+ `;
125
+ }
126
+
127
+ /**
128
+ * Initialize Relentless in a project
129
+ */
130
+ export async function initProject(projectDir: string = process.cwd()): Promise<void> {
131
+ console.log(chalk.bold.blue("\n🚀 Initializing Relentless\n"));
132
+
133
+ // Check installed agents
134
+ console.log(chalk.dim("Detecting installed agents..."));
135
+ const health = await checkAgentHealth();
136
+ const installed = health.filter((h) => h.installed);
137
+
138
+ console.log(`\nFound ${chalk.green(installed.length)} installed agents:`);
139
+ for (const agent of installed) {
140
+ console.log(` ${chalk.green("✓")} ${agent.displayName}`);
141
+ }
142
+
143
+ const notInstalled = health.filter((h) => !h.installed);
144
+ if (notInstalled.length > 0) {
145
+ console.log(chalk.dim(`\nNot installed: ${notInstalled.map((a) => a.displayName).join(", ")}`));
146
+ }
147
+
148
+ // Create relentless directory structure
149
+ const relentlessDir = join(projectDir, "relentless");
150
+ const featuresDir = join(relentlessDir, "features");
151
+
152
+ for (const dir of [relentlessDir, featuresDir]) {
153
+ if (!existsSync(dir)) {
154
+ mkdirSync(dir, { recursive: true });
155
+ }
156
+ }
157
+
158
+ // Create relentless files
159
+ console.log(chalk.dim("\nCreating relentless files..."));
160
+
161
+ for (const [filename, contentFn] of Object.entries(RELENTLESS_FILES)) {
162
+ const path = join(relentlessDir, filename);
163
+
164
+ if (existsSync(path)) {
165
+ console.log(` ${chalk.yellow("⚠")} relentless/${filename} already exists, skipping`);
166
+ continue;
167
+ }
168
+
169
+ await Bun.write(path, contentFn());
170
+ console.log(` ${chalk.green("✓")} relentless/${filename}`);
171
+ }
172
+
173
+ // Note: constitution.md is NOT copied - it should be created by /relentless.constitution command
174
+ // This ensures each project gets a personalized constitution
175
+
176
+ // Create features directory with .gitkeep
177
+ const gitkeepPath = join(featuresDir, ".gitkeep");
178
+ if (!existsSync(gitkeepPath)) {
179
+ await Bun.write(gitkeepPath, "");
180
+ console.log(` ${chalk.green("✓")} relentless/features/.gitkeep`);
181
+ }
182
+
183
+ // Copy skills to .claude/skills/ (this is expected by Claude Code)
184
+ console.log(chalk.dim("\nInstalling skills..."));
185
+ const skillsDir = join(projectDir, ".claude", "skills");
186
+ if (!existsSync(skillsDir)) {
187
+ mkdirSync(skillsDir, { recursive: true });
188
+ }
189
+
190
+ const sourceSkillsDir = join(relentlessRoot, ".claude", "skills");
191
+
192
+ if (existsSync(sourceSkillsDir)) {
193
+ const skills = [
194
+ "prd",
195
+ "relentless",
196
+ "constitution",
197
+ "specify",
198
+ "plan",
199
+ "tasks",
200
+ "checklist",
201
+ "clarify",
202
+ "analyze",
203
+ "implement",
204
+ "taskstoissues",
205
+ ];
206
+
207
+ for (const skill of skills) {
208
+ const sourcePath = join(sourceSkillsDir, skill);
209
+ const destPath = join(skillsDir, skill);
210
+
211
+ if (existsSync(sourcePath) && !existsSync(destPath)) {
212
+ await Bun.spawn(["cp", "-r", sourcePath, destPath]).exited;
213
+ console.log(` ${chalk.green("✓")} .claude/skills/${skill}`);
214
+ } else if (existsSync(destPath)) {
215
+ console.log(` ${chalk.yellow("⚠")} .claude/skills/${skill} already exists, skipping`);
216
+ }
217
+ }
218
+ }
219
+
220
+ // Copy commands to .claude/commands/ (for Claude Code)
221
+ console.log(chalk.dim("\nInstalling commands..."));
222
+ const commandsDir = join(projectDir, ".claude", "commands");
223
+ if (!existsSync(commandsDir)) {
224
+ mkdirSync(commandsDir, { recursive: true });
225
+ }
226
+
227
+ const sourceCommandsDir = join(relentlessRoot, ".claude", "commands");
228
+
229
+ if (existsSync(sourceCommandsDir)) {
230
+ const commands = [
231
+ "relentless.analyze.md",
232
+ "relentless.checklist.md",
233
+ "relentless.clarify.md",
234
+ "relentless.constitution.md",
235
+ "relentless.implement.md",
236
+ "relentless.plan.md",
237
+ "relentless.specify.md",
238
+ "relentless.tasks.md",
239
+ "relentless.taskstoissues.md",
240
+ ];
241
+
242
+ for (const command of commands) {
243
+ const sourcePath = join(sourceCommandsDir, command);
244
+ const destPath = join(commandsDir, command);
245
+
246
+ if (existsSync(sourcePath) && !existsSync(destPath)) {
247
+ const content = await Bun.file(sourcePath).text();
248
+ await Bun.write(destPath, content);
249
+ console.log(` ${chalk.green("✓")} .claude/commands/${command}`);
250
+ } else if (existsSync(destPath)) {
251
+ console.log(` ${chalk.yellow("⚠")} .claude/commands/${command} already exists, skipping`);
252
+ }
253
+ }
254
+ }
255
+
256
+ // Print next steps
257
+ console.log(chalk.bold.green("\n✅ Relentless initialized!\n"));
258
+ console.log(chalk.dim("Structure:"));
259
+ console.log(chalk.dim(" relentless/"));
260
+ console.log(chalk.dim(" ├── config.json # Configuration"));
261
+ console.log(chalk.dim(" ├── constitution.md # Project governance"));
262
+ console.log(chalk.dim(" ├── prompt.md # Base prompt template"));
263
+ console.log(chalk.dim(" └── features/ # Feature folders"));
264
+ console.log(chalk.dim(" └── <feature>/ # Each feature has:"));
265
+ console.log(chalk.dim(" ├── prd.md # PRD markdown"));
266
+ console.log(chalk.dim(" ├── prd.json # PRD JSON"));
267
+ console.log(chalk.dim(" └── progress.txt # Progress log\n"));
268
+
269
+ console.log("Next steps:");
270
+ console.log(chalk.dim("1. Create project constitution (recommended):"));
271
+ console.log(` ${chalk.cyan("/relentless.constitution")}`);
272
+ console.log(chalk.dim("\n2. Create a feature specification:"));
273
+ console.log(` ${chalk.cyan("/relentless.specify Add user authentication")}`);
274
+ console.log(chalk.dim("\n3. Generate plan, tasks, and checklist:"));
275
+ console.log(` ${chalk.cyan("/relentless.plan")}`);
276
+ console.log(` ${chalk.cyan("/relentless.tasks")}`);
277
+ console.log(` ${chalk.cyan("/relentless.checklist")}`);
278
+ console.log(chalk.dim("\n4. Convert to JSON and run:"));
279
+ console.log(` ${chalk.cyan("relentless convert relentless/features/NNN-feature/tasks.md --feature NNN-feature")}`);
280
+ console.log(` ${chalk.cyan("relentless run --feature NNN-feature --tui")}`);
281
+ console.log("");
282
+ }
283
+
284
+ /**
285
+ * Options for creating a feature
286
+ */
287
+ export interface CreateFeatureOptions {
288
+ /** Include plan.md template */
289
+ withPlan?: boolean;
290
+ /** Auto-number the feature directory (e.g., 001-feature-name) */
291
+ autoNumber?: boolean;
292
+ }
293
+
294
+ /**
295
+ * Get the next feature number by finding the highest existing number
296
+ */
297
+ function getNextFeatureNumber(projectDir: string): number {
298
+ const featuresDir = join(projectDir, "relentless", "features");
299
+
300
+ if (!existsSync(featuresDir)) {
301
+ return 1;
302
+ }
303
+
304
+ const features = listFeatures(projectDir);
305
+
306
+ // Extract numbers from features with format NNN-name
307
+ const numbers = features
308
+ .map((feature) => {
309
+ const match = feature.match(/^(\d{3})-/);
310
+ return match ? parseInt(match[1], 10) : 0;
311
+ })
312
+ .filter((n) => n > 0);
313
+
314
+ // Return next number (or 1 if no numbered features exist)
315
+ return numbers.length > 0 ? Math.max(...numbers) + 1 : 1;
316
+ }
317
+
318
+ /**
319
+ * Create a new feature folder
320
+ */
321
+ export async function createFeature(
322
+ projectDir: string,
323
+ featureName: string,
324
+ options: CreateFeatureOptions = {}
325
+ ): Promise<string> {
326
+ // Generate numbered directory name if autoNumber is enabled
327
+ let finalFeatureName = featureName;
328
+ if (options.autoNumber) {
329
+ const nextNumber = getNextFeatureNumber(projectDir);
330
+ const numberPrefix = nextNumber.toString().padStart(3, "0");
331
+ finalFeatureName = `${numberPrefix}-${featureName}`;
332
+ }
333
+
334
+ const featureDir = join(projectDir, "relentless", "features", finalFeatureName);
335
+
336
+ if (existsSync(featureDir)) {
337
+ throw new Error(`Feature '${finalFeatureName}' already exists`);
338
+ }
339
+
340
+ mkdirSync(featureDir, { recursive: true });
341
+
342
+ // Create progress.txt
343
+ const progressPath = join(featureDir, "progress.txt");
344
+ await Bun.write(progressPath, createProgressTemplate(finalFeatureName));
345
+
346
+ // Copy plan.md template if requested
347
+ if (options.withPlan) {
348
+ const planSourcePath = join(relentlessRoot, "templates", "plan.md");
349
+ const planDestPath = join(featureDir, "plan.md");
350
+
351
+ if (existsSync(planSourcePath)) {
352
+ const planContent = await Bun.file(planSourcePath).text();
353
+ await Bun.write(planDestPath, planContent);
354
+ }
355
+ }
356
+
357
+ return featureDir;
358
+ }
359
+
360
+ /**
361
+ * List all features
362
+ */
363
+ export function listFeatures(projectDir: string): string[] {
364
+ const featuresDir = join(projectDir, "relentless", "features");
365
+
366
+ if (!existsSync(featuresDir)) {
367
+ return [];
368
+ }
369
+
370
+ const entries = Bun.spawnSync(["ls", "-1", featuresDir]);
371
+ const output = new TextDecoder().decode(entries.stdout);
372
+
373
+ return output
374
+ .split("\n")
375
+ .map((s) => s.trim())
376
+ .filter((s) => s && s !== ".gitkeep");
377
+ }