@arvorco/relentless 0.3.1 → 0.4.3
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.convert.md +25 -0
- package/.claude/skills/analyze/SKILL.md +113 -40
- package/.claude/skills/analyze/templates/analysis-report.md +138 -0
- package/.claude/skills/checklist/SKILL.md +144 -51
- package/.claude/skills/checklist/templates/checklist.md +43 -11
- package/.claude/skills/clarify/SKILL.md +70 -11
- package/.claude/skills/constitution/SKILL.md +61 -3
- package/.claude/skills/constitution/templates/constitution.md +241 -160
- package/.claude/skills/constitution/templates/prompt.md +150 -20
- package/.claude/skills/convert/SKILL.md +248 -0
- package/.claude/skills/implement/SKILL.md +82 -34
- package/.claude/skills/plan/SKILL.md +139 -27
- package/.claude/skills/plan/templates/plan.md +92 -9
- package/.claude/skills/specify/SKILL.md +112 -20
- package/.claude/skills/specify/templates/spec.md +40 -5
- package/.claude/skills/tasks/SKILL.md +76 -1
- package/.claude/skills/tasks/templates/tasks.md +5 -4
- package/CHANGELOG.md +84 -1
- package/MANUAL.md +40 -0
- package/README.md +268 -13
- package/bin/relentless.ts +292 -5
- package/package.json +2 -2
- package/relentless/config.json +45 -1
- package/relentless/constitution.md +41 -19
- package/relentless/prompt.md +142 -72
- package/src/agents/amp.ts +53 -13
- package/src/agents/claude.ts +70 -15
- package/src/agents/codex.ts +73 -14
- package/src/agents/droid.ts +68 -14
- package/src/agents/exec.ts +96 -0
- package/src/agents/gemini.ts +59 -16
- package/src/agents/opencode.ts +188 -9
- package/src/cli/fallback-order.ts +210 -0
- package/src/cli/index.ts +63 -0
- package/src/cli/mode-flag.ts +198 -0
- package/src/cli/review-flags.ts +192 -0
- package/src/config/loader.ts +16 -1
- package/src/config/schema.ts +157 -2
- package/src/execution/runner.ts +144 -21
- package/src/init/scaffolder.ts +285 -25
- package/src/prd/parser.ts +111 -6
- package/src/prd/types.ts +136 -0
- package/src/review/index.ts +92 -0
- package/src/review/prompt.ts +293 -0
- package/src/review/runner.ts +337 -0
- package/src/review/tasks/docs.ts +529 -0
- package/src/review/tasks/index.ts +80 -0
- package/src/review/tasks/lint.ts +436 -0
- package/src/review/tasks/quality.ts +760 -0
- package/src/review/tasks/security.ts +452 -0
- package/src/review/tasks/test.ts +456 -0
- package/src/review/tasks/typecheck.ts +323 -0
- package/src/review/types.ts +139 -0
- package/src/routing/cascade.ts +310 -0
- package/src/routing/classifier.ts +338 -0
- package/src/routing/estimate.ts +270 -0
- package/src/routing/fallback.ts +512 -0
- package/src/routing/index.ts +124 -0
- package/src/routing/registry.ts +501 -0
- package/src/routing/report.ts +570 -0
- package/src/routing/router.ts +287 -0
- package/src/tui/App.tsx +2 -0
- package/src/tui/TUIRunner.tsx +103 -8
- package/src/tui/components/CurrentStory.tsx +23 -1
- package/src/tui/hooks/useTUI.ts +1 -0
- package/src/tui/types.ts +9 -0
package/bin/relentless.ts
CHANGED
|
@@ -16,7 +16,13 @@ import { loadConfig, findRelentlessDir } from "../src/config";
|
|
|
16
16
|
import { loadPRD, savePRD, parsePRDMarkdown, createPRD, analyzeConsistency, formatReport, generateGitHubIssues } from "../src/prd";
|
|
17
17
|
import { run } from "../src/execution/runner";
|
|
18
18
|
import { runTUI } from "../src/tui/TUIRunner";
|
|
19
|
-
import { initProject, createFeature, listFeatures, createProgressTemplate } from "../src/init/scaffolder";
|
|
19
|
+
import { initProject, createFeature, listFeatures, createProgressTemplate, parseAutoModeFlags } from "../src/init/scaffolder";
|
|
20
|
+
import { parseModeFlagValue, getModeHelpText, VALID_MODES, DEFAULT_MODE } from "../src/cli/mode-flag";
|
|
21
|
+
import { parseFallbackOrderValue, getFallbackOrderHelpText, VALID_HARNESSES } from "../src/cli/fallback-order";
|
|
22
|
+
import { parseReviewFlagsValue, getReviewFlagsHelpText, VALID_REVIEW_MODES } from "../src/cli/review-flags";
|
|
23
|
+
import { estimateFeatureCost, formatCostEstimate, formatCostBreakdown, compareModes, formatModeComparison } from "../src/routing/estimate";
|
|
24
|
+
import { loadHistoricalCosts } from "../src/routing/report";
|
|
25
|
+
import { routeTask } from "../src/routing/router";
|
|
20
26
|
|
|
21
27
|
// Read version from package.json dynamically
|
|
22
28
|
const packageJson = await Bun.file(join(import.meta.dir, "..", "package.json")).json();
|
|
@@ -35,8 +41,14 @@ program
|
|
|
35
41
|
.description("Initialize Relentless in the current project")
|
|
36
42
|
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
37
43
|
.option("-f, --force", "Force reinstall - overwrite existing files", false)
|
|
44
|
+
.option("-y, --yes", "Auto-accept defaults (enables Auto Mode with 'good' mode)", false)
|
|
45
|
+
.option("--no-auto-mode", "Disable Auto Mode without prompting", false)
|
|
38
46
|
.action(async (options) => {
|
|
39
|
-
|
|
47
|
+
const autoModeOptions = parseAutoModeFlags({
|
|
48
|
+
yes: options.yes,
|
|
49
|
+
noAutoMode: !options.autoMode, // Commander converts --no-auto-mode to autoMode: false
|
|
50
|
+
});
|
|
51
|
+
await initProject(options.dir, options.force, autoModeOptions);
|
|
40
52
|
});
|
|
41
53
|
|
|
42
54
|
// Run command
|
|
@@ -44,8 +56,12 @@ program
|
|
|
44
56
|
.command("run")
|
|
45
57
|
.description("Run the orchestration loop for a feature")
|
|
46
58
|
.requiredOption("-f, --feature <name>", "Feature name to run")
|
|
47
|
-
.option("-a, --agent <name>", "Agent to use (claude, amp, opencode, codex, droid, gemini, auto)", "
|
|
59
|
+
.option("-a, --agent <name>", "Agent to use (claude, amp, opencode, codex, droid, gemini, auto)", "auto")
|
|
48
60
|
.option("-m, --max-iterations <n>", "Maximum iterations", "20")
|
|
61
|
+
.option("--mode <mode>", `Cost optimization mode (${VALID_MODES.join(", ")})`)
|
|
62
|
+
.option("--fallback-order <harnesses>", `Harness fallback order (${VALID_HARNESSES.join(",")})`)
|
|
63
|
+
.option("--skip-review", "Skip final review phase", false)
|
|
64
|
+
.option("--review-mode <mode>", `Review quality mode (${VALID_REVIEW_MODES.join(", ")})`)
|
|
49
65
|
.option("--dry-run", "Show what would be executed without running", false)
|
|
50
66
|
.option("--tui", "Use beautiful terminal UI interface", false)
|
|
51
67
|
.option("-d, --dir <path>", "Working directory", process.cwd())
|
|
@@ -57,6 +73,34 @@ program
|
|
|
57
73
|
process.exit(1);
|
|
58
74
|
}
|
|
59
75
|
|
|
76
|
+
// Validate --fallback-order flag
|
|
77
|
+
const fallbackResult = parseFallbackOrderValue(options.fallbackOrder);
|
|
78
|
+
if (!fallbackResult.valid) {
|
|
79
|
+
console.error(chalk.red(fallbackResult.error!));
|
|
80
|
+
console.log(chalk.dim(getFallbackOrderHelpText()));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const fallbackOrder = fallbackResult.order!;
|
|
84
|
+
if (fallbackResult.warning) {
|
|
85
|
+
console.log(chalk.yellow(`⚠️ ${fallbackResult.warning}`));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Validate review flags (--skip-review and --review-mode)
|
|
89
|
+
const reviewResult = parseReviewFlagsValue({
|
|
90
|
+
skipReview: options.skipReview,
|
|
91
|
+
reviewMode: options.reviewMode,
|
|
92
|
+
});
|
|
93
|
+
if (!reviewResult.valid) {
|
|
94
|
+
console.error(chalk.red(reviewResult.error!));
|
|
95
|
+
console.log(chalk.dim(getReviewFlagsHelpText()));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
const skipReview = reviewResult.skipReview!;
|
|
99
|
+
const reviewMode = reviewResult.reviewMode;
|
|
100
|
+
if (reviewResult.warningMessage && skipReview) {
|
|
101
|
+
console.log(chalk.yellow(`⚠️ ${reviewResult.warningMessage}`));
|
|
102
|
+
}
|
|
103
|
+
|
|
60
104
|
const relentlessDir = findRelentlessDir(options.dir);
|
|
61
105
|
if (!relentlessDir) {
|
|
62
106
|
console.error(chalk.red("Relentless not initialized. Run: relentless init"));
|
|
@@ -86,6 +130,38 @@ program
|
|
|
86
130
|
}
|
|
87
131
|
|
|
88
132
|
const config = await loadConfig();
|
|
133
|
+
const prd = await loadPRD(prdPath);
|
|
134
|
+
const prdPreferredMode =
|
|
135
|
+
prd.routingPreference?.type === "auto" ? prd.routingPreference.mode : undefined;
|
|
136
|
+
const modeValue =
|
|
137
|
+
options.mode ?? prdPreferredMode ?? config.autoMode?.defaultMode ?? DEFAULT_MODE;
|
|
138
|
+
const modeResult = parseModeFlagValue(modeValue);
|
|
139
|
+
if (!modeResult.valid) {
|
|
140
|
+
console.error(chalk.red(modeResult.error!));
|
|
141
|
+
console.log(chalk.dim(getModeHelpText()));
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
const mode = modeResult.mode!;
|
|
145
|
+
|
|
146
|
+
// Display cost estimate before execution
|
|
147
|
+
if (config.autoMode?.enabled !== false) {
|
|
148
|
+
const estimate = await estimateFeatureCost(prd, config.autoMode || {}, mode);
|
|
149
|
+
console.log(chalk.cyan(`\n💰 ${formatCostEstimate(estimate)}`));
|
|
150
|
+
if (estimate.storyEstimates.length > 0 && estimate.storyEstimates.length <= 10) {
|
|
151
|
+
console.log(chalk.dim(formatCostBreakdown(estimate.storyEstimates)));
|
|
152
|
+
} else if (estimate.storyEstimates.length > 10) {
|
|
153
|
+
console.log(chalk.dim(` ${estimate.storyEstimates.length} stories to estimate...`));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Log mode and fallback order selection
|
|
158
|
+
console.log(chalk.dim(`Mode: ${mode}`));
|
|
159
|
+
if (options.fallbackOrder) {
|
|
160
|
+
console.log(chalk.dim(`Fallback order: ${fallbackOrder.join(" > ")}`));
|
|
161
|
+
}
|
|
162
|
+
if (!skipReview && reviewMode) {
|
|
163
|
+
console.log(chalk.dim(`Review mode: ${reviewMode}`));
|
|
164
|
+
}
|
|
89
165
|
|
|
90
166
|
// Use TUI if requested
|
|
91
167
|
if (options.tui) {
|
|
@@ -98,6 +174,10 @@ program
|
|
|
98
174
|
feature: options.feature,
|
|
99
175
|
config,
|
|
100
176
|
dryRun: options.dryRun,
|
|
177
|
+
mode,
|
|
178
|
+
fallbackOrder,
|
|
179
|
+
skipReview,
|
|
180
|
+
reviewMode,
|
|
101
181
|
});
|
|
102
182
|
process.exit(success ? 0 : 1);
|
|
103
183
|
}
|
|
@@ -111,6 +191,10 @@ program
|
|
|
111
191
|
promptPath,
|
|
112
192
|
dryRun: options.dryRun,
|
|
113
193
|
config,
|
|
194
|
+
mode,
|
|
195
|
+
fallbackOrder,
|
|
196
|
+
skipReview,
|
|
197
|
+
reviewMode,
|
|
114
198
|
});
|
|
115
199
|
|
|
116
200
|
if (result.success) {
|
|
@@ -130,11 +214,13 @@ program
|
|
|
130
214
|
// Convert command
|
|
131
215
|
program
|
|
132
216
|
.command("convert <tasksMd>")
|
|
133
|
-
.description("Convert tasks.md to prd.json
|
|
217
|
+
.description("Convert tasks.md to prd.json with smart routing classification")
|
|
134
218
|
.requiredOption("-f, --feature <name>", "Feature name")
|
|
135
219
|
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
136
220
|
.option("--auto-number", "Auto-number the feature directory (e.g., 001-feature-name)", false)
|
|
137
221
|
.option("--with-checklist", "Merge checklist items into acceptance criteria", false)
|
|
222
|
+
.option("--skip-routing", "Skip automatic routing classification (not recommended)", false)
|
|
223
|
+
.option("--mode <mode>", `Cost optimization mode for routing (${VALID_MODES.join(", ")})`, "good")
|
|
138
224
|
.action(async (tasksMd, options) => {
|
|
139
225
|
if (!existsSync(tasksMd)) {
|
|
140
226
|
console.error(chalk.red(`File not found: ${tasksMd}`));
|
|
@@ -192,6 +278,51 @@ program
|
|
|
192
278
|
|
|
193
279
|
const prd = createPRD(parsed, finalFeatureName);
|
|
194
280
|
|
|
281
|
+
// ROUTING CLASSIFICATION (unless --skip-routing is set)
|
|
282
|
+
if (!options.skipRouting) {
|
|
283
|
+
// Validate mode
|
|
284
|
+
const modeResult = parseModeFlagValue(options.mode);
|
|
285
|
+
if (!modeResult.valid) {
|
|
286
|
+
console.error(chalk.red(modeResult.error!));
|
|
287
|
+
console.log(chalk.dim(getModeHelpText()));
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
const mode = modeResult.mode!;
|
|
291
|
+
|
|
292
|
+
// Load config for routing
|
|
293
|
+
const config = await loadConfig();
|
|
294
|
+
const autoModeConfig = config.autoMode || { enabled: true, defaultMode: mode };
|
|
295
|
+
|
|
296
|
+
// Use mode from routingPreference if present, otherwise use CLI flag
|
|
297
|
+
const routingMode = prd.routingPreference?.mode ?? mode;
|
|
298
|
+
|
|
299
|
+
console.log(chalk.dim(`\nClassifying ${prd.userStories.length} stories for routing (mode: ${routingMode})...`));
|
|
300
|
+
|
|
301
|
+
let totalEstimatedCost = 0;
|
|
302
|
+
for (const story of prd.userStories) {
|
|
303
|
+
const decision = await routeTask(story, autoModeConfig, routingMode);
|
|
304
|
+
story.routing = {
|
|
305
|
+
complexity: decision.complexity,
|
|
306
|
+
harness: decision.harness,
|
|
307
|
+
model: decision.model,
|
|
308
|
+
mode: decision.mode,
|
|
309
|
+
estimatedCost: decision.estimatedCost,
|
|
310
|
+
classificationReasoning: decision.reasoning,
|
|
311
|
+
};
|
|
312
|
+
totalEstimatedCost += decision.estimatedCost;
|
|
313
|
+
|
|
314
|
+
// Show per-story routing
|
|
315
|
+
const costStr = decision.estimatedCost === 0 ? "free" : `$${decision.estimatedCost.toFixed(4)}`;
|
|
316
|
+
console.log(chalk.dim(` ${story.id}: ${decision.complexity} → ${decision.harness}/${decision.model} (${costStr})`));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Show total estimated cost
|
|
320
|
+
const totalCostStr = totalEstimatedCost === 0 ? "free" : `$${totalEstimatedCost.toFixed(4)}`;
|
|
321
|
+
console.log(chalk.cyan(`\n💰 Total estimated cost: ${totalCostStr}`));
|
|
322
|
+
} else {
|
|
323
|
+
console.log(chalk.yellow("\n⚠️ Routing skipped (--skip-routing). Stories will not have routing metadata."));
|
|
324
|
+
}
|
|
325
|
+
|
|
195
326
|
// Save prd.json to feature folder
|
|
196
327
|
const prdJsonPath = join(featureDir, "prd.json");
|
|
197
328
|
await savePRD(prd, prdJsonPath);
|
|
@@ -200,8 +331,11 @@ program
|
|
|
200
331
|
const tasksMdPath = join(featureDir, "prd.md");
|
|
201
332
|
await Bun.write(tasksMdPath, tasksContent);
|
|
202
333
|
|
|
203
|
-
console.log(chalk.green(
|
|
334
|
+
console.log(chalk.green(`\n✅ Created relentless/features/${finalFeatureName}/`));
|
|
204
335
|
console.log(chalk.dim(` prd.json - ${prd.userStories.length} stories`));
|
|
336
|
+
if (!options.skipRouting) {
|
|
337
|
+
console.log(chalk.dim(` ✓ Routing metadata included`));
|
|
338
|
+
}
|
|
205
339
|
console.log(chalk.dim(` prd.md - from tasks.md`));
|
|
206
340
|
console.log(chalk.dim(` progress.txt - progress log`));
|
|
207
341
|
if (options.withChecklist && existsSync(join(featureDir, "checklist.md"))) {
|
|
@@ -209,6 +343,10 @@ program
|
|
|
209
343
|
}
|
|
210
344
|
console.log(chalk.dim(`\nProject: ${prd.project}`));
|
|
211
345
|
console.log(chalk.dim(`Branch: ${prd.branchName}`));
|
|
346
|
+
|
|
347
|
+
// Suggest next step
|
|
348
|
+
console.log(chalk.dim(`\nNext step:`));
|
|
349
|
+
console.log(chalk.cyan(` relentless run --feature ${finalFeatureName} --mode ${options.mode || "good"}`));
|
|
212
350
|
});
|
|
213
351
|
|
|
214
352
|
// Feature commands
|
|
@@ -643,6 +781,155 @@ program
|
|
|
643
781
|
}
|
|
644
782
|
});
|
|
645
783
|
|
|
784
|
+
// Estimate command - display cost estimates without executing
|
|
785
|
+
program
|
|
786
|
+
.command("estimate")
|
|
787
|
+
.description("Estimate execution costs before running a feature")
|
|
788
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
789
|
+
.option("--mode <mode>", `Cost optimization mode (${VALID_MODES.join(", ")})`)
|
|
790
|
+
.option("--compare", "Show comparison across all modes", false)
|
|
791
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
792
|
+
.action(async (options) => {
|
|
793
|
+
const relentlessDir = findRelentlessDir(options.dir);
|
|
794
|
+
if (!relentlessDir) {
|
|
795
|
+
console.error(chalk.red("Relentless not initialized. Run: relentless init"));
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const featureDir = join(relentlessDir, "features", options.feature);
|
|
800
|
+
if (!existsSync(featureDir)) {
|
|
801
|
+
console.error(chalk.red(`Feature '${options.feature}' not found`));
|
|
802
|
+
console.log(chalk.dim(`Available features: ${listFeatures(options.dir).join(", ") || "none"}`));
|
|
803
|
+
process.exit(1);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const prdPath = join(featureDir, "prd.json");
|
|
807
|
+
if (!existsSync(prdPath)) {
|
|
808
|
+
console.error(chalk.red(`PRD file not found: ${prdPath}`));
|
|
809
|
+
process.exit(1);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const config = await loadConfig();
|
|
813
|
+
const prd = await loadPRD(prdPath);
|
|
814
|
+
const prdPreferredMode =
|
|
815
|
+
prd.routingPreference?.type === "auto" ? prd.routingPreference.mode : undefined;
|
|
816
|
+
|
|
817
|
+
const modeValue =
|
|
818
|
+
options.mode ?? prdPreferredMode ?? config.autoMode?.defaultMode ?? DEFAULT_MODE;
|
|
819
|
+
const modeResult = parseModeFlagValue(modeValue);
|
|
820
|
+
if (!modeResult.valid) {
|
|
821
|
+
console.error(chalk.red(modeResult.error!));
|
|
822
|
+
console.log(chalk.dim(getModeHelpText()));
|
|
823
|
+
process.exit(1);
|
|
824
|
+
}
|
|
825
|
+
const mode = modeResult.mode!;
|
|
826
|
+
|
|
827
|
+
const incompleteCount = prd.userStories.filter((s) => !s.passes).length;
|
|
828
|
+
const totalCount = prd.userStories.length;
|
|
829
|
+
|
|
830
|
+
console.log(chalk.bold(`\n💰 Cost Estimate: ${options.feature}\n`));
|
|
831
|
+
console.log(chalk.dim(`Stories: ${incompleteCount}/${totalCount} incomplete`));
|
|
832
|
+
|
|
833
|
+
if (incompleteCount === 0) {
|
|
834
|
+
console.log(chalk.green("\n✅ All stories are complete. No execution needed.\n"));
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (options.compare) {
|
|
839
|
+
// Show comparison across all modes
|
|
840
|
+
console.log(chalk.dim(`\nComparing all modes...\n`));
|
|
841
|
+
const comparison = await compareModes(prd, config.autoMode || {});
|
|
842
|
+
console.log(formatModeComparison(comparison));
|
|
843
|
+
} else {
|
|
844
|
+
// Show estimate for selected mode
|
|
845
|
+
const estimate = await estimateFeatureCost(prd, config.autoMode || {}, mode);
|
|
846
|
+
console.log(chalk.cyan(`\n${formatCostEstimate(estimate)}\n`));
|
|
847
|
+
|
|
848
|
+
if (estimate.storyEstimates.length > 0) {
|
|
849
|
+
console.log(formatCostBreakdown(estimate.storyEstimates));
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Show brief comparison hint
|
|
853
|
+
console.log(chalk.dim(`\nTip: Use --compare to see cost comparison across all modes`));
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// Report command - show historical cost data
|
|
858
|
+
program
|
|
859
|
+
.command("report")
|
|
860
|
+
.description("Show historical cost reports for a feature")
|
|
861
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
862
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
863
|
+
.option("--last <n>", "Show only last N reports", "10")
|
|
864
|
+
.action(async (options) => {
|
|
865
|
+
const relentlessDir = findRelentlessDir(options.dir);
|
|
866
|
+
if (!relentlessDir) {
|
|
867
|
+
console.error(chalk.red("Relentless not initialized. Run: relentless init"));
|
|
868
|
+
process.exit(1);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const featureDir = join(relentlessDir, "features", options.feature);
|
|
872
|
+
if (!existsSync(featureDir)) {
|
|
873
|
+
console.error(chalk.red(`Feature '${options.feature}' not found`));
|
|
874
|
+
console.log(chalk.dim(`Available features: ${listFeatures(options.dir).join(", ") || "none"}`));
|
|
875
|
+
process.exit(1);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const progressPath = join(featureDir, "progress.txt");
|
|
879
|
+
if (!existsSync(progressPath)) {
|
|
880
|
+
console.error(chalk.red(`Progress file not found: ${progressPath}`));
|
|
881
|
+
process.exit(1);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
console.log(chalk.bold(`\n📊 Cost Report History: ${options.feature}\n`));
|
|
885
|
+
|
|
886
|
+
const history = await loadHistoricalCosts(progressPath);
|
|
887
|
+
|
|
888
|
+
if (history.length === 0) {
|
|
889
|
+
console.log(chalk.dim("No cost reports found yet."));
|
|
890
|
+
console.log(chalk.dim("\nCost reports are generated after feature execution completes."));
|
|
891
|
+
console.log(chalk.dim("Run: relentless run --feature " + options.feature + "\n"));
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Limit to last N reports
|
|
896
|
+
const limit = parseInt(options.last, 10);
|
|
897
|
+
const reportsToShow = history.slice(-limit);
|
|
898
|
+
|
|
899
|
+
// Calculate totals
|
|
900
|
+
let totalCost = 0;
|
|
901
|
+
let totalSavings = 0;
|
|
902
|
+
for (const entry of history) {
|
|
903
|
+
totalCost += entry.actualCost;
|
|
904
|
+
// Savings is calculated against baseline, so we approximate savings amount
|
|
905
|
+
const baselineCost = entry.actualCost / (1 - entry.savingsPercent / 100);
|
|
906
|
+
totalSavings += baselineCost - entry.actualCost;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Display reports
|
|
910
|
+
console.log(chalk.dim("Timestamp Mode Cost Savings"));
|
|
911
|
+
console.log(chalk.dim("─".repeat(60)));
|
|
912
|
+
|
|
913
|
+
for (const entry of reportsToShow) {
|
|
914
|
+
const timestamp = entry.timestamp.substring(0, 19).replace("T", " ");
|
|
915
|
+
const mode = entry.mode.padEnd(8);
|
|
916
|
+
const cost = `$${entry.actualCost.toFixed(2)}`.padStart(8);
|
|
917
|
+
const savings = `${entry.savingsPercent}%`.padStart(6);
|
|
918
|
+
console.log(`${timestamp} ${mode} ${cost} ${savings}`);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
console.log(chalk.dim("─".repeat(60)));
|
|
922
|
+
|
|
923
|
+
// Summary
|
|
924
|
+
if (history.length > limit) {
|
|
925
|
+
console.log(chalk.dim(`\nShowing last ${limit} of ${history.length} reports`));
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
console.log(chalk.cyan(`\nTotal Cost: $${totalCost.toFixed(2)}`));
|
|
929
|
+
console.log(chalk.green(`Total Savings: ~$${totalSavings.toFixed(2)}`));
|
|
930
|
+
console.log(chalk.dim(`Reports: ${history.length}\n`));
|
|
931
|
+
});
|
|
932
|
+
|
|
646
933
|
// PRD command (for agents without skill support)
|
|
647
934
|
program
|
|
648
935
|
.command("prd <description>")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arvorco/relentless",
|
|
3
|
-
"version": "0.3
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Universal AI agent orchestrator - works with Claude Code, Amp, OpenCode, Codex, Droid, and Gemini",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"license": "MIT",
|
|
54
54
|
"repository": {
|
|
55
55
|
"type": "git",
|
|
56
|
-
"url": "https://github.com/ArvorCo/Relentless.git"
|
|
56
|
+
"url": "git+https://github.com/ArvorCo/Relentless.git"
|
|
57
57
|
},
|
|
58
58
|
"homepage": "https://github.com/ArvorCo/Relentless#readme",
|
|
59
59
|
"bugs": {
|
package/relentless/config.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"defaultAgent": "
|
|
2
|
+
"defaultAgent": "auto",
|
|
3
3
|
"agents": {
|
|
4
4
|
"claude": {
|
|
5
5
|
"dangerouslyAllowAll": true
|
|
@@ -34,5 +34,49 @@
|
|
|
34
34
|
},
|
|
35
35
|
"prompt": {
|
|
36
36
|
"path": "prompt.md"
|
|
37
|
+
},
|
|
38
|
+
"autoMode": {
|
|
39
|
+
"enabled": false,
|
|
40
|
+
"defaultMode": "good",
|
|
41
|
+
"fallbackOrder": [
|
|
42
|
+
"claude",
|
|
43
|
+
"codex",
|
|
44
|
+
"droid",
|
|
45
|
+
"opencode",
|
|
46
|
+
"amp",
|
|
47
|
+
"gemini"
|
|
48
|
+
],
|
|
49
|
+
"modeModels": {
|
|
50
|
+
"simple": "haiku-4.5",
|
|
51
|
+
"medium": "sonnet-4.5",
|
|
52
|
+
"complex": "opus-4.5",
|
|
53
|
+
"expert": "opus-4.5"
|
|
54
|
+
},
|
|
55
|
+
"review": {
|
|
56
|
+
"promptUser": true,
|
|
57
|
+
"defaultMode": "good",
|
|
58
|
+
"microTasks": [
|
|
59
|
+
"typecheck",
|
|
60
|
+
"lint",
|
|
61
|
+
"test",
|
|
62
|
+
"security",
|
|
63
|
+
"quality",
|
|
64
|
+
"docs"
|
|
65
|
+
],
|
|
66
|
+
"maxRetries": 3
|
|
67
|
+
},
|
|
68
|
+
"escalation": {
|
|
69
|
+
"enabled": true,
|
|
70
|
+
"maxAttempts": 3,
|
|
71
|
+
"escalationPath": {
|
|
72
|
+
"haiku-4.5": "sonnet-4.5",
|
|
73
|
+
"sonnet-4.5": "opus-4.5",
|
|
74
|
+
"gpt-5.2-low": "gpt-5.2-medium",
|
|
75
|
+
"gpt-5.2-medium": "gpt-5.2-high",
|
|
76
|
+
"gpt-5.2-high": "gpt-5.2-xhigh",
|
|
77
|
+
"grok-code-fast-1": "gpt-5.2-low",
|
|
78
|
+
"gemini-3-flash": "gemini-3-pro"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
37
81
|
}
|
|
38
82
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
<!-- TEMPLATE_VERSION: 2.1.0 -->
|
|
2
|
+
<!-- LAST_UPDATED: 2026-01-20 -->
|
|
3
|
+
|
|
1
4
|
# Relentless Project Constitution
|
|
2
5
|
|
|
3
|
-
**Version:** 2.
|
|
6
|
+
**Version:** 2.1.0
|
|
4
7
|
**Ratified:** 2026-01-13
|
|
5
|
-
**Last Amended:** 2026-01-
|
|
8
|
+
**Last Amended:** 2026-01-20
|
|
6
9
|
|
|
7
10
|
---
|
|
8
11
|
|
|
@@ -27,7 +30,25 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
27
30
|
|
|
28
31
|
## Part I: Foundational Principles
|
|
29
32
|
|
|
30
|
-
### Principle 1:
|
|
33
|
+
### Principle 1: SpecKit Workflow
|
|
34
|
+
|
|
35
|
+
**MUST:**
|
|
36
|
+
- All features MUST follow the 6-step workflow: specify → plan → tasks → convert → analyze → implement
|
|
37
|
+
- Each step MUST produce its corresponding artifact before proceeding
|
|
38
|
+
- Artifacts MUST be validated with `/relentless.analyze` before implementation begins
|
|
39
|
+
- Implementation MUST NOT start without: spec.md, plan.md, tasks.md, checklist.md, prd.json
|
|
40
|
+
|
|
41
|
+
**SHOULD:**
|
|
42
|
+
- Run analysis after any manual artifact edits
|
|
43
|
+
- Keep artifacts in sync throughout development
|
|
44
|
+
- Document deviations from plan in progress.txt
|
|
45
|
+
- Review checklist.md before completing each story
|
|
46
|
+
|
|
47
|
+
**Rationale:** The SpecKit workflow ensures thorough planning, prevents scope creep, and provides structured guidance for AI agents. Each artifact serves a specific purpose in the autonomous execution pipeline.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### Principle 2: Test-Driven Development (TDD)
|
|
31
52
|
|
|
32
53
|
**MUST:**
|
|
33
54
|
- All new features MUST have tests written BEFORE implementation begins
|
|
@@ -46,7 +67,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
46
67
|
|
|
47
68
|
---
|
|
48
69
|
|
|
49
|
-
### Principle
|
|
70
|
+
### Principle 3: Smart Model Routing
|
|
50
71
|
|
|
51
72
|
**MUST:**
|
|
52
73
|
- Planning phase MUST evaluate task complexity before assigning models
|
|
@@ -54,7 +75,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
54
75
|
- Routing decisions MUST be made at planning time, not runtime
|
|
55
76
|
- Model/harness performance history MUST be tracked for future planning
|
|
56
77
|
- Simple tasks MUST be routable to cheaper/free models when user opts in
|
|
57
|
-
- Complex tasks and final reviews MUST use SOTA models (Opus 4.5, GPT-5
|
|
78
|
+
- Complex tasks and final reviews MUST use SOTA models (Opus 4.5, GPT-5.2, etc.)
|
|
58
79
|
|
|
59
80
|
**SHOULD:**
|
|
60
81
|
- Maintain a knowledge base of model/harness capabilities and strengths
|
|
@@ -66,7 +87,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
66
87
|
|
|
67
88
|
---
|
|
68
89
|
|
|
69
|
-
### Principle
|
|
90
|
+
### Principle 4: Parallel Execution with Git Worktrees
|
|
70
91
|
|
|
71
92
|
**MUST:**
|
|
72
93
|
- Parallel tasks MUST use git worktrees for clean isolation
|
|
@@ -86,7 +107,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
86
107
|
|
|
87
108
|
---
|
|
88
109
|
|
|
89
|
-
### Principle
|
|
110
|
+
### Principle 5: Queued Prompts (Mid-Run Input)
|
|
90
111
|
|
|
91
112
|
**MUST:**
|
|
92
113
|
- Implement file-based queue (.queue.txt) for mid-run user input
|
|
@@ -105,11 +126,11 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
105
126
|
|
|
106
127
|
---
|
|
107
128
|
|
|
108
|
-
### Principle
|
|
129
|
+
### Principle 6: Adaptive Final Review
|
|
109
130
|
|
|
110
131
|
**MUST:**
|
|
111
132
|
- Every feature MUST have a final review phase before completion
|
|
112
|
-
- Review MUST be performed by SOTA model (Opus 4.5, GPT-5
|
|
133
|
+
- Review MUST be performed by SOTA model (Opus 4.5, GPT-5.2, or equivalent)
|
|
113
134
|
- Review scope MUST adapt to feature size:
|
|
114
135
|
- Small features: Full codebase review in single context
|
|
115
136
|
- Large features: Multi-context review with summary aggregation
|
|
@@ -126,7 +147,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
126
147
|
|
|
127
148
|
---
|
|
128
149
|
|
|
129
|
-
### Principle
|
|
150
|
+
### Principle 7: Agent-Aware Best Practices
|
|
130
151
|
|
|
131
152
|
**MUST:**
|
|
132
153
|
- Maintain up-to-date knowledge of each agent's capabilities
|
|
@@ -147,7 +168,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
147
168
|
|
|
148
169
|
## Part II: Code Quality Standards
|
|
149
170
|
|
|
150
|
-
### Principle
|
|
171
|
+
### Principle 8: Zero-Lint Policy
|
|
151
172
|
|
|
152
173
|
**MUST:**
|
|
153
174
|
- All code MUST pass lint with zero warnings (not just errors)
|
|
@@ -165,7 +186,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
165
186
|
|
|
166
187
|
---
|
|
167
188
|
|
|
168
|
-
### Principle
|
|
189
|
+
### Principle 9: TypeScript Strictness
|
|
169
190
|
|
|
170
191
|
**MUST:**
|
|
171
192
|
- Use TypeScript strict mode throughout
|
|
@@ -184,7 +205,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
184
205
|
|
|
185
206
|
---
|
|
186
207
|
|
|
187
|
-
### Principle
|
|
208
|
+
### Principle 10: Minimal Dependencies
|
|
188
209
|
|
|
189
210
|
**MUST NOT:**
|
|
190
211
|
- Add dependencies without clear justification
|
|
@@ -207,7 +228,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
207
228
|
|
|
208
229
|
## Part III: Architecture Principles
|
|
209
230
|
|
|
210
|
-
### Principle
|
|
231
|
+
### Principle 11: Clean Architecture
|
|
211
232
|
|
|
212
233
|
**MUST:**
|
|
213
234
|
- Maintain clear separation of concerns
|
|
@@ -225,7 +246,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
225
246
|
|
|
226
247
|
---
|
|
227
248
|
|
|
228
|
-
### Principle
|
|
249
|
+
### Principle 12: Performance First
|
|
229
250
|
|
|
230
251
|
**MUST:**
|
|
231
252
|
- Maintain fast startup time (<1s)
|
|
@@ -243,7 +264,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
243
264
|
|
|
244
265
|
---
|
|
245
266
|
|
|
246
|
-
### Principle
|
|
267
|
+
### Principle 13: Error Handling Excellence
|
|
247
268
|
|
|
248
269
|
**MUST:**
|
|
249
270
|
- Surface errors clearly - never hide failures
|
|
@@ -263,7 +284,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
263
284
|
|
|
264
285
|
## Part IV: Version Control & Documentation
|
|
265
286
|
|
|
266
|
-
### Principle
|
|
287
|
+
### Principle 14: Git Discipline
|
|
267
288
|
|
|
268
289
|
**MUST:**
|
|
269
290
|
- Write clear, descriptive commit messages
|
|
@@ -282,7 +303,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
282
303
|
|
|
283
304
|
---
|
|
284
305
|
|
|
285
|
-
### Principle
|
|
306
|
+
### Principle 15: Documentation Standards
|
|
286
307
|
|
|
287
308
|
**MUST:**
|
|
288
309
|
- Document public APIs and interfaces
|
|
@@ -302,7 +323,7 @@ Our mission is to build one of the best AI orchestration tools in the world - si
|
|
|
302
323
|
|
|
303
324
|
## Part V: Security & Compliance
|
|
304
325
|
|
|
305
|
-
### Principle
|
|
326
|
+
### Principle 16: Security First
|
|
306
327
|
|
|
307
328
|
**MUST:**
|
|
308
329
|
- Never commit secrets to git
|
|
@@ -371,6 +392,7 @@ The following are strategic directions that guide future development:
|
|
|
371
392
|
|
|
372
393
|
| Version | Date | Changes |
|
|
373
394
|
|---------|------|---------|
|
|
395
|
+
| 2.1.0 | 2026-01-20 | Added SpecKit Workflow principle, template version tracking, renumbered principles (now 16 total), improved artifact consistency requirements |
|
|
374
396
|
| 2.0.0 | 2026-01-13 | Major revision: Added smart routing, parallel execution, queued prompts, adaptive review, TDD requirements, agent-aware practices |
|
|
375
397
|
| 1.0.0 | Previous | Initial constitution |
|
|
376
398
|
|