@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.
Files changed (66) hide show
  1. package/.claude/commands/relentless.convert.md +25 -0
  2. package/.claude/skills/analyze/SKILL.md +113 -40
  3. package/.claude/skills/analyze/templates/analysis-report.md +138 -0
  4. package/.claude/skills/checklist/SKILL.md +144 -51
  5. package/.claude/skills/checklist/templates/checklist.md +43 -11
  6. package/.claude/skills/clarify/SKILL.md +70 -11
  7. package/.claude/skills/constitution/SKILL.md +61 -3
  8. package/.claude/skills/constitution/templates/constitution.md +241 -160
  9. package/.claude/skills/constitution/templates/prompt.md +150 -20
  10. package/.claude/skills/convert/SKILL.md +248 -0
  11. package/.claude/skills/implement/SKILL.md +82 -34
  12. package/.claude/skills/plan/SKILL.md +139 -27
  13. package/.claude/skills/plan/templates/plan.md +92 -9
  14. package/.claude/skills/specify/SKILL.md +112 -20
  15. package/.claude/skills/specify/templates/spec.md +40 -5
  16. package/.claude/skills/tasks/SKILL.md +76 -1
  17. package/.claude/skills/tasks/templates/tasks.md +5 -4
  18. package/CHANGELOG.md +84 -1
  19. package/MANUAL.md +40 -0
  20. package/README.md +268 -13
  21. package/bin/relentless.ts +292 -5
  22. package/package.json +2 -2
  23. package/relentless/config.json +45 -1
  24. package/relentless/constitution.md +41 -19
  25. package/relentless/prompt.md +142 -72
  26. package/src/agents/amp.ts +53 -13
  27. package/src/agents/claude.ts +70 -15
  28. package/src/agents/codex.ts +73 -14
  29. package/src/agents/droid.ts +68 -14
  30. package/src/agents/exec.ts +96 -0
  31. package/src/agents/gemini.ts +59 -16
  32. package/src/agents/opencode.ts +188 -9
  33. package/src/cli/fallback-order.ts +210 -0
  34. package/src/cli/index.ts +63 -0
  35. package/src/cli/mode-flag.ts +198 -0
  36. package/src/cli/review-flags.ts +192 -0
  37. package/src/config/loader.ts +16 -1
  38. package/src/config/schema.ts +157 -2
  39. package/src/execution/runner.ts +144 -21
  40. package/src/init/scaffolder.ts +285 -25
  41. package/src/prd/parser.ts +111 -6
  42. package/src/prd/types.ts +136 -0
  43. package/src/review/index.ts +92 -0
  44. package/src/review/prompt.ts +293 -0
  45. package/src/review/runner.ts +337 -0
  46. package/src/review/tasks/docs.ts +529 -0
  47. package/src/review/tasks/index.ts +80 -0
  48. package/src/review/tasks/lint.ts +436 -0
  49. package/src/review/tasks/quality.ts +760 -0
  50. package/src/review/tasks/security.ts +452 -0
  51. package/src/review/tasks/test.ts +456 -0
  52. package/src/review/tasks/typecheck.ts +323 -0
  53. package/src/review/types.ts +139 -0
  54. package/src/routing/cascade.ts +310 -0
  55. package/src/routing/classifier.ts +338 -0
  56. package/src/routing/estimate.ts +270 -0
  57. package/src/routing/fallback.ts +512 -0
  58. package/src/routing/index.ts +124 -0
  59. package/src/routing/registry.ts +501 -0
  60. package/src/routing/report.ts +570 -0
  61. package/src/routing/router.ts +287 -0
  62. package/src/tui/App.tsx +2 -0
  63. package/src/tui/TUIRunner.tsx +103 -8
  64. package/src/tui/components/CurrentStory.tsx +23 -1
  65. package/src/tui/hooks/useTUI.ts +1 -0
  66. 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
- await initProject(options.dir, options.force);
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)", "claude")
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, optionally merging checklist.md criteria")
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(`✅ Created relentless/features/${finalFeatureName}/`));
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.1",
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": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "defaultAgent": "claude",
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.0.0
6
+ **Version:** 2.1.0
4
7
  **Ratified:** 2026-01-13
5
- **Last Amended:** 2026-01-13
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: Test-Driven Development (TDD)
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 2: Smart Model Routing
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-2, etc.)
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 3: Parallel Execution with Git Worktrees
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 4: Queued Prompts (Mid-Run Input)
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 5: Adaptive Final Review
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-2, or equivalent)
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 6: Agent-Aware Best Practices
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 7: Zero-Lint Policy
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 8: TypeScript Strictness
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 9: Minimal Dependencies
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 10: Clean Architecture
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 11: Performance First
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 12: Error Handling Excellence
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 13: Git Discipline
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 14: Documentation Standards
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 15: Security First
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