@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,512 @@
1
+ /**
2
+ * Cross-Artifact Consistency Analysis
3
+ *
4
+ * Checks consistency across PRD, JSON, and code to catch errors early
5
+ */
6
+
7
+ import type { PRD, UserStory } from "./types";
8
+ import { join, dirname } from "node:path";
9
+ import { existsSync } from "node:fs";
10
+
11
+ export interface ConsistencyIssue {
12
+ category: string;
13
+ severity: "critical" | "warning" | "info";
14
+ message: string;
15
+ recommendation?: string;
16
+ storyId?: string;
17
+ }
18
+
19
+ export interface AnalysisReport {
20
+ feature: string;
21
+ timestamp: string;
22
+ issues: ConsistencyIssue[];
23
+ summary: {
24
+ total: number;
25
+ completed: number;
26
+ pending: number;
27
+ critical: number;
28
+ warnings: number;
29
+ info: number;
30
+ };
31
+ coverage: {
32
+ [key: string]: {
33
+ completed: number;
34
+ total: number;
35
+ percentage: number;
36
+ };
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Analyze cross-artifact consistency
42
+ */
43
+ export async function analyzeConsistency(
44
+ prdPath: string,
45
+ prd: PRD
46
+ ): Promise<AnalysisReport> {
47
+ const issues: ConsistencyIssue[] = [];
48
+ const featureDir = dirname(prdPath);
49
+ const featureName = featureDir.split("/").pop() || "unknown";
50
+
51
+ // Category 1: PRD Schema Validation
52
+ await checkSchemaValidation(prd, issues);
53
+
54
+ // Category 2: Dependency Consistency
55
+ await checkDependencyConsistency(prd, issues);
56
+
57
+ // Category 3: File Existence
58
+ await checkFileExistence(featureDir, issues);
59
+
60
+ // Category 4: Story Completeness
61
+ await checkStoryCompleteness(prd, issues);
62
+
63
+ // Category 5: Progress Log Sync
64
+ await checkProgressLogSync(featureDir, prd, issues);
65
+
66
+ // Calculate summary
67
+ const total = prd.userStories.length;
68
+ const completed = prd.userStories.filter((s) => s.passes).length;
69
+ const pending = total - completed;
70
+
71
+ const critical = issues.filter((i) => i.severity === "critical").length;
72
+ const warnings = issues.filter((i) => i.severity === "warning").length;
73
+ const info = issues.filter((i) => i.severity === "info").length;
74
+
75
+ // Calculate coverage by category
76
+ const coverage = calculateCoverage(prd);
77
+
78
+ return {
79
+ feature: featureName,
80
+ timestamp: new Date().toISOString(),
81
+ issues,
82
+ summary: {
83
+ total,
84
+ completed,
85
+ pending,
86
+ critical,
87
+ warnings,
88
+ info,
89
+ },
90
+ coverage,
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Check PRD schema validation
96
+ */
97
+ async function checkSchemaValidation(
98
+ prd: PRD,
99
+ issues: ConsistencyIssue[]
100
+ ): Promise<void> {
101
+ // Check for missing required fields
102
+ if (!prd.project || prd.project.trim() === "") {
103
+ issues.push({
104
+ category: "Schema Validation",
105
+ severity: "critical",
106
+ message: "PRD is missing project name",
107
+ recommendation: "Add a project name to the PRD",
108
+ });
109
+ }
110
+
111
+ if (!prd.branchName || prd.branchName.trim() === "") {
112
+ issues.push({
113
+ category: "Schema Validation",
114
+ severity: "critical",
115
+ message: "PRD is missing branch name",
116
+ recommendation: "Add a branch name to the PRD",
117
+ });
118
+ }
119
+
120
+ if (!prd.description || prd.description.trim() === "") {
121
+ issues.push({
122
+ category: "Schema Validation",
123
+ severity: "warning",
124
+ message: "PRD is missing description",
125
+ recommendation: "Add a description to help understand the feature",
126
+ });
127
+ }
128
+
129
+ // Check each user story
130
+ for (const story of prd.userStories) {
131
+ if (!story.id || story.id.trim() === "") {
132
+ issues.push({
133
+ category: "Schema Validation",
134
+ severity: "critical",
135
+ message: `Story has no ID`,
136
+ recommendation: "Assign a unique ID to the story (e.g., US-001)",
137
+ });
138
+ }
139
+
140
+ if (!story.title || story.title.trim() === "") {
141
+ issues.push({
142
+ category: "Schema Validation",
143
+ severity: "critical",
144
+ message: `Story ${story.id} has no title`,
145
+ storyId: story.id,
146
+ recommendation: "Add a descriptive title to the story",
147
+ });
148
+ }
149
+
150
+ if (!story.acceptanceCriteria || story.acceptanceCriteria.length === 0) {
151
+ issues.push({
152
+ category: "Schema Validation",
153
+ severity: "warning",
154
+ message: `Story ${story.id} has no acceptance criteria`,
155
+ storyId: story.id,
156
+ recommendation:
157
+ "Add acceptance criteria to define clear completion requirements",
158
+ });
159
+ }
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Check dependency consistency
165
+ */
166
+ async function checkDependencyConsistency(
167
+ prd: PRD,
168
+ issues: ConsistencyIssue[]
169
+ ): Promise<void> {
170
+ const storyMap = new Map<string, UserStory>();
171
+ for (const story of prd.userStories) {
172
+ storyMap.set(story.id, story);
173
+ }
174
+
175
+ // Check for invalid dependencies
176
+ for (const story of prd.userStories) {
177
+ if (story.dependencies) {
178
+ for (const depId of story.dependencies) {
179
+ if (!storyMap.has(depId)) {
180
+ issues.push({
181
+ category: "Dependency Consistency",
182
+ severity: "critical",
183
+ message: `Story ${story.id} depends on non-existent story ${depId}`,
184
+ storyId: story.id,
185
+ recommendation: `Remove the dependency or add story ${depId}`,
186
+ });
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ // Check for circular dependencies
193
+ const visited = new Set<string>();
194
+ const recursionStack = new Set<string>();
195
+
196
+ function hasCycle(storyId: string, path: string[] = []): string | null {
197
+ if (recursionStack.has(storyId)) {
198
+ return [...path, storyId].join(" -> ");
199
+ }
200
+
201
+ if (visited.has(storyId)) {
202
+ return null;
203
+ }
204
+
205
+ visited.add(storyId);
206
+ recursionStack.add(storyId);
207
+
208
+ const story = storyMap.get(storyId);
209
+ if (story?.dependencies) {
210
+ for (const depId of story.dependencies) {
211
+ const cycle = hasCycle(depId, [...path, storyId]);
212
+ if (cycle) {
213
+ return cycle;
214
+ }
215
+ }
216
+ }
217
+
218
+ recursionStack.delete(storyId);
219
+ return null;
220
+ }
221
+
222
+ for (const story of prd.userStories) {
223
+ const cycle = hasCycle(story.id);
224
+ if (cycle) {
225
+ issues.push({
226
+ category: "Dependency Consistency",
227
+ severity: "critical",
228
+ message: `Circular dependency detected: ${cycle}`,
229
+ recommendation: "Remove one of the dependencies to break the cycle",
230
+ });
231
+ break; // Only report once
232
+ }
233
+ }
234
+
235
+ // Check for completed stories that depend on incomplete stories
236
+ for (const story of prd.userStories) {
237
+ if (story.passes && story.dependencies) {
238
+ for (const depId of story.dependencies) {
239
+ const depStory = storyMap.get(depId);
240
+ if (depStory && !depStory.passes) {
241
+ issues.push({
242
+ category: "Dependency Consistency",
243
+ severity: "warning",
244
+ message: `Story ${story.id} is marked complete but depends on incomplete story ${depId}`,
245
+ storyId: story.id,
246
+ recommendation: `Either mark ${depId} as complete or review ${story.id}'s completion status`,
247
+ });
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Check file existence
256
+ */
257
+ async function checkFileExistence(
258
+ featureDir: string,
259
+ issues: ConsistencyIssue[]
260
+ ): Promise<void> {
261
+ const prdMdPath = join(featureDir, "prd.md");
262
+ const progressPath = join(featureDir, "progress.txt");
263
+ const planPath = join(featureDir, "plan.md");
264
+ const constitutionPath = join(dirname(featureDir), "..", "constitution.md");
265
+
266
+ if (!existsSync(prdMdPath)) {
267
+ issues.push({
268
+ category: "File Existence",
269
+ severity: "info",
270
+ message: "prd.md not found in feature directory",
271
+ recommendation:
272
+ "Consider keeping the source prd.md for documentation purposes",
273
+ });
274
+ }
275
+
276
+ if (!existsSync(progressPath)) {
277
+ issues.push({
278
+ category: "File Existence",
279
+ severity: "warning",
280
+ message: "progress.txt not found",
281
+ recommendation: "Create progress.txt to track learnings across iterations",
282
+ });
283
+ }
284
+
285
+ if (existsSync(planPath)) {
286
+ issues.push({
287
+ category: "File Existence",
288
+ severity: "info",
289
+ message: "plan.md found - technical planning is in use",
290
+ });
291
+ }
292
+
293
+ if (!existsSync(constitutionPath)) {
294
+ issues.push({
295
+ category: "File Existence",
296
+ severity: "info",
297
+ message: "constitution.md not found",
298
+ recommendation:
299
+ "Consider creating a constitution.md for project governance",
300
+ });
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Check story completeness
306
+ */
307
+ async function checkStoryCompleteness(
308
+ prd: PRD,
309
+ issues: ConsistencyIssue[]
310
+ ): Promise<void> {
311
+ // Check for stories with very few acceptance criteria
312
+ for (const story of prd.userStories) {
313
+ if (story.acceptanceCriteria.length < 2) {
314
+ issues.push({
315
+ category: "Story Completeness",
316
+ severity: "info",
317
+ message: `Story ${story.id} has only ${story.acceptanceCriteria.length} acceptance criteria`,
318
+ storyId: story.id,
319
+ recommendation:
320
+ "Consider adding more detailed acceptance criteria for clarity",
321
+ });
322
+ }
323
+ }
324
+
325
+ // Check for stories with empty notes that are incomplete
326
+ for (const story of prd.userStories) {
327
+ if (!story.passes && story.notes && story.notes.trim() !== "") {
328
+ issues.push({
329
+ category: "Story Completeness",
330
+ severity: "info",
331
+ message: `Story ${story.id} is incomplete but has notes: "${story.notes}"`,
332
+ storyId: story.id,
333
+ recommendation: "Review notes to understand blockers or context",
334
+ });
335
+ }
336
+ }
337
+
338
+ // Check for duplicate story IDs
339
+ const idCounts = new Map<string, number>();
340
+ for (const story of prd.userStories) {
341
+ idCounts.set(story.id, (idCounts.get(story.id) || 0) + 1);
342
+ }
343
+
344
+ for (const [id, count] of idCounts.entries()) {
345
+ if (count > 1) {
346
+ issues.push({
347
+ category: "Story Completeness",
348
+ severity: "critical",
349
+ message: `Duplicate story ID detected: ${id} appears ${count} times`,
350
+ recommendation: "Ensure all story IDs are unique",
351
+ });
352
+ }
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Check progress log sync
358
+ */
359
+ async function checkProgressLogSync(
360
+ featureDir: string,
361
+ prd: PRD,
362
+ issues: ConsistencyIssue[]
363
+ ): Promise<void> {
364
+ const progressPath = join(featureDir, "progress.txt");
365
+
366
+ if (!existsSync(progressPath)) {
367
+ return; // Already reported in file existence check
368
+ }
369
+
370
+ try {
371
+ const progressContent = await Bun.file(progressPath).text();
372
+
373
+ // Check if progress log mentions completed stories
374
+ const completedStories = prd.userStories.filter((s) => s.passes);
375
+
376
+ for (const story of completedStories) {
377
+ if (!progressContent.includes(story.id)) {
378
+ issues.push({
379
+ category: "Progress Log Sync",
380
+ severity: "warning",
381
+ message: `Story ${story.id} is marked complete but not mentioned in progress.txt`,
382
+ storyId: story.id,
383
+ recommendation:
384
+ "Ensure progress.txt is updated after completing each story",
385
+ });
386
+ }
387
+ }
388
+ } catch {
389
+ issues.push({
390
+ category: "Progress Log Sync",
391
+ severity: "warning",
392
+ message: "Failed to read progress.txt",
393
+ recommendation: "Check file permissions and format",
394
+ });
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Calculate coverage by category
400
+ */
401
+ function calculateCoverage(prd: PRD): {
402
+ [key: string]: { completed: number; total: number; percentage: number };
403
+ } {
404
+ const coverage: {
405
+ [key: string]: { completed: number; total: number; percentage: number };
406
+ } = {};
407
+
408
+ // Group by phase if available
409
+ const phases = new Set<string>();
410
+ for (const story of prd.userStories) {
411
+ if (story.phase) {
412
+ phases.add(story.phase);
413
+ }
414
+ }
415
+
416
+ for (const phase of phases) {
417
+ const storiesInPhase = prd.userStories.filter((s) => s.phase === phase);
418
+ const completedInPhase = storiesInPhase.filter((s) => s.passes);
419
+
420
+ coverage[phase] = {
421
+ completed: completedInPhase.length,
422
+ total: storiesInPhase.length,
423
+ percentage: Math.round(
424
+ (completedInPhase.length / storiesInPhase.length) * 100
425
+ ),
426
+ };
427
+ }
428
+
429
+ // If no phases, just show overall
430
+ if (phases.size === 0) {
431
+ coverage["Overall"] = {
432
+ completed: prd.userStories.filter((s) => s.passes).length,
433
+ total: prd.userStories.length,
434
+ percentage: Math.round(
435
+ (prd.userStories.filter((s) => s.passes).length /
436
+ prd.userStories.length) *
437
+ 100
438
+ ),
439
+ };
440
+ }
441
+
442
+ return coverage;
443
+ }
444
+
445
+ /**
446
+ * Format analysis report for display
447
+ */
448
+ export function formatReport(report: AnalysisReport): string {
449
+ const lines: string[] = [];
450
+
451
+ lines.push(`\n╔═══════════════════════════════════════════════════════╗`);
452
+ lines.push(`║ Cross-Artifact Consistency Analysis ║`);
453
+ lines.push(`╚═══════════════════════════════════════════════════════╝\n`);
454
+
455
+ lines.push(`Feature: ${report.feature}`);
456
+ lines.push(`Timestamp: ${report.timestamp}\n`);
457
+
458
+ // Summary
459
+ lines.push(`Summary:`);
460
+ lines.push(` Stories: ${report.summary.completed}/${report.summary.total} completed (${report.summary.pending} pending)`);
461
+ lines.push(` Issues: ${report.issues.length} total`);
462
+ lines.push(` Critical: ${report.summary.critical}`);
463
+ lines.push(` Warnings: ${report.summary.warnings}`);
464
+ lines.push(` Info: ${report.summary.info}\n`);
465
+
466
+ // Coverage
467
+ if (Object.keys(report.coverage).length > 0) {
468
+ lines.push(`Coverage by Phase:`);
469
+ for (const [phase, stats] of Object.entries(report.coverage)) {
470
+ const bar = "█".repeat(Math.floor(stats.percentage / 5));
471
+ const emptyBar = "░".repeat(20 - Math.floor(stats.percentage / 5));
472
+ lines.push(
473
+ ` ${phase.padEnd(20)} ${bar}${emptyBar} ${stats.percentage}% (${stats.completed}/${stats.total})`
474
+ );
475
+ }
476
+ lines.push("");
477
+ }
478
+
479
+ // Issues by category
480
+ if (report.issues.length > 0) {
481
+ const categories = new Set(report.issues.map((i) => i.category));
482
+
483
+ lines.push(`Issues by Category:\n`);
484
+
485
+ for (const category of categories) {
486
+ const categoryIssues = report.issues.filter((i) => i.category === category);
487
+
488
+ lines.push(`${category}:`);
489
+
490
+ for (const issue of categoryIssues) {
491
+ const severityIcon =
492
+ issue.severity === "critical"
493
+ ? "🔴"
494
+ : issue.severity === "warning"
495
+ ? "🟡"
496
+ : "🔵";
497
+
498
+ lines.push(` ${severityIcon} ${issue.message}`);
499
+
500
+ if (issue.recommendation) {
501
+ lines.push(` → ${issue.recommendation}`);
502
+ }
503
+ }
504
+
505
+ lines.push("");
506
+ }
507
+ } else {
508
+ lines.push(`✅ No issues found!\n`);
509
+ }
510
+
511
+ return lines.join("\n");
512
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * PRD Module
3
+ *
4
+ * Re-exports PRD types and functions
5
+ */
6
+
7
+ export * from "./types";
8
+ export * from "./parser";
9
+ export * from "./progress";
10
+ export * from "./analyzer";
11
+ export * from "./issues";