dev-harness-cli 1.0.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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +299 -0
  3. package/adapters/amazon-q/README.md +23 -0
  4. package/adapters/antigravity/README.md +22 -0
  5. package/adapters/claude-code/README.md +30 -0
  6. package/adapters/cline/README.md +23 -0
  7. package/adapters/codex/README.md +31 -0
  8. package/adapters/copilot/README.md +23 -0
  9. package/adapters/cursor/README.md +29 -0
  10. package/adapters/gemini/README.md +23 -0
  11. package/adapters/generic/README.md +40 -0
  12. package/adapters/hermes/README.md +31 -0
  13. package/adapters/hermes/SKILL.md +89 -0
  14. package/adapters/hermes/scripts/init.mjs +27 -0
  15. package/adapters/hermes/scripts/phase.mjs +27 -0
  16. package/adapters/hermes/scripts/validate.mjs +27 -0
  17. package/adapters/kilo-code/README.md +23 -0
  18. package/adapters/openclaw/README.md +22 -0
  19. package/adapters/pi/README.md +22 -0
  20. package/adapters/roo/README.md +23 -0
  21. package/adapters/windsurf/README.md +23 -0
  22. package/cli/commands/checkpoint.mjs +94 -0
  23. package/cli/commands/config.mjs +268 -0
  24. package/cli/commands/contract.mjs +155 -0
  25. package/cli/commands/detect-tool.mjs +112 -0
  26. package/cli/commands/init.mjs +351 -0
  27. package/cli/commands/learn.mjs +47 -0
  28. package/cli/commands/pause.mjs +34 -0
  29. package/cli/commands/phase.mjs +182 -0
  30. package/cli/commands/resume.mjs +33 -0
  31. package/cli/commands/rollback.mjs +261 -0
  32. package/cli/commands/set-mode.mjs +75 -0
  33. package/cli/commands/status.mjs +168 -0
  34. package/cli/commands/validate.mjs +118 -0
  35. package/cli/commands/worktree.mjs +298 -0
  36. package/cli/harness-dev.mjs +88 -0
  37. package/cli/lib/args.mjs +111 -0
  38. package/cli/lib/command-helpers.mjs +50 -0
  39. package/cli/lib/config-registry.mjs +329 -0
  40. package/cli/lib/constants.mjs +30 -0
  41. package/cli/lib/contract.mjs +306 -0
  42. package/cli/lib/detect-stack.mjs +235 -0
  43. package/cli/lib/errors.mjs +71 -0
  44. package/cli/lib/file-io.mjs +90 -0
  45. package/cli/lib/gates.mjs +492 -0
  46. package/cli/lib/git.mjs +144 -0
  47. package/cli/lib/help.mjs +246 -0
  48. package/cli/lib/modes.mjs +92 -0
  49. package/cli/lib/output.mjs +49 -0
  50. package/cli/lib/paths.mjs +75 -0
  51. package/cli/lib/phases.mjs +58 -0
  52. package/cli/lib/platform.mjs +78 -0
  53. package/cli/lib/progress.mjs +357 -0
  54. package/cli/lib/ralph-inner.mjs +314 -0
  55. package/cli/lib/ralph-outer.mjs +249 -0
  56. package/cli/lib/ralph-output.mjs +178 -0
  57. package/cli/lib/scaffold.mjs +431 -0
  58. package/cli/lib/schemas/stacks.json +477 -0
  59. package/cli/lib/state.mjs +333 -0
  60. package/cli/lib/templates.mjs +264 -0
  61. package/cli/lib/tool-registry.mjs +218 -0
  62. package/cli/lib/validate-schema.mjs +131 -0
  63. package/cli/lib/vars.mjs +114 -0
  64. package/package.json +50 -0
  65. package/schema/harness-config.schema.json +127 -0
  66. package/templates/AGENTS.md +63 -0
  67. package/templates/ci/github-actions.yml +78 -0
  68. package/templates/ci/gitlab-ci.yml +59 -0
  69. package/templates/docs/agents/evaluator.md +14 -0
  70. package/templates/docs/agents/generator.md +13 -0
  71. package/templates/docs/agents/planner.md +13 -0
  72. package/templates/docs/agents/simplifier.md +13 -0
  73. package/templates/docs/phases/build.md +41 -0
  74. package/templates/docs/phases/define.md +51 -0
  75. package/templates/docs/phases/plan.md +36 -0
  76. package/templates/docs/phases/review.md +42 -0
  77. package/templates/docs/phases/ship.md +43 -0
  78. package/templates/docs/phases/simplify.md +40 -0
  79. package/templates/docs/phases/verify.md +38 -0
  80. package/templates/evaluator-rubric.md +28 -0
  81. package/templates/init.ps1 +97 -0
  82. package/templates/init.sh +102 -0
  83. package/templates/sprint-contract.md +31 -0
@@ -0,0 +1,249 @@
1
+ /**
2
+ * ralph-outer — Outer Ralph Loop Engine.
3
+ *
4
+ * Advances through the phase pipeline in order. In copilot mode,
5
+ * runs one phase and stops. In autopilot mode, auto-advances
6
+ * through all remaining phases after each gate passes.
7
+ *
8
+ * The outer loop does NOT iterate tasks or features — that's
9
+ * entirely the inner loop's job (T8).
10
+ *
11
+ * Usage:
12
+ * import { continuePipeline, runAutopilot } from './ralph-outer.mjs';
13
+ * const result = continuePipeline('/path/to/project', 'build');
14
+ */
15
+ import { loadConfig, transitionPhase, set as stateSet, getPhaseOrder } from './state.mjs';
16
+ import { runPhase, loadFeatureList } from './ralph-inner.mjs';
17
+ import { existsSync } from 'node:fs';
18
+ import { execGit } from './git.mjs';
19
+
20
+ /**
21
+ * After a phase completes (gate passes), continue the pipeline.
22
+ *
23
+ * In copilot mode: prints next-step instructions and stops.
24
+ * In autopilot mode: auto-advances to the next phase and runs it.
25
+ *
26
+ * @param {string} targetDir
27
+ * @param {string} completedPhase — the phase that just finished
28
+ * @param {object} [options]
29
+ * @param {boolean} [options.json] — JSON output mode
30
+ * @param {boolean} [options.verbose] — print detailed output
31
+ * @returns {{ ok: boolean, status: string, message: string, currentPhase: string|null, phasesRemaining: number }}
32
+ */
33
+ export function continuePipeline(targetDir, completedPhase, options = {}) {
34
+ const { json = false, verbose = false } = options;
35
+
36
+ const { config, ok } = loadConfig(targetDir);
37
+ if (!ok) {
38
+ return { ok: false, status: 'error', message: 'Cannot load config', currentPhase: null, phasesRemaining: 0 };
39
+ }
40
+
41
+ const mode = config.mode ?? 'copilot';
42
+ const order = getPhaseOrder(config.phases?.enabled);
43
+ const phaseIdx = order.indexOf(completedPhase);
44
+ const nextPhase = (phaseIdx >= 0 && phaseIdx < order.length - 1) ? order[phaseIdx + 1] : null;
45
+ if (!nextPhase) {
46
+ // Pipeline complete
47
+ let msg = `Pipeline complete after "${completedPhase}".`;
48
+
49
+ // Increment pipeline iteration
50
+ if (config.pipelineIteration === undefined) {config.pipelineIteration = 0;}
51
+ config.pipelineIteration = (config.pipelineIteration || 0) + 1;
52
+ stateSet(targetDir, 'pipelineIteration', config.pipelineIteration);
53
+
54
+ // Count remaining features
55
+ let featuresRemaining = 0;
56
+ try {
57
+ const fl = loadFeatureList(targetDir);
58
+ // fl.ok === false means the file is missing OR malformed JSON.
59
+ // Missing file is benign (early phases have no feature list yet);
60
+ // a present-but-unparseable file is a real error we must surface.
61
+ if (fl.ok === false && fl.path && existsSync(fl.path)) {
62
+ process.stderr.write(
63
+ `Warning: feature_list.json at ${fl.path} is present but could not be parsed. Treating as 0 features.\n`,
64
+ );
65
+ }
66
+ featuresRemaining = fl.features ? fl.features.filter(f => !f.passes).length : 0;
67
+ } catch (err) {
68
+ // Defensive: loadFeatureList never throws, but guard anyway.
69
+ process.stderr.write(`Warning: failed to read feature list: ${err.message}\n`);
70
+ }
71
+
72
+ msg += ` Iteration ${config.pipelineIteration}.`;
73
+ if (featuresRemaining > 0) {
74
+ msg += ` ${featuresRemaining} feature(s) remaining.`;
75
+ } else {
76
+ msg += ' All features complete.';
77
+ }
78
+
79
+ if (!json && verbose) {
80
+ process.stdout.write(`\n${msg}\n`);
81
+ }
82
+ // Git auto-tag if enabled
83
+ if (config.git?.autoTag) {
84
+ autoTag(targetDir, completedPhase, json);
85
+ }
86
+ return {
87
+ ok: true,
88
+ status: 'complete',
89
+ message: msg,
90
+ currentPhase: completedPhase,
91
+ phasesRemaining: 0,
92
+ pipelineIteration: config.pipelineIteration,
93
+ featuresRemaining,
94
+ };
95
+ }
96
+
97
+ const phasesRemaining = order.length - phaseIdx - 1;
98
+
99
+ if (mode === 'copilot') {
100
+ // Copilot: print instructions for next phase
101
+ const msg = `${completedPhase.toUpperCase()} complete. Next: harness-dev phase ${nextPhase}`;
102
+ if (json) {
103
+ return {
104
+ ok: true,
105
+ status: 'instruction',
106
+ message: msg,
107
+ currentPhase: completedPhase,
108
+ phasesRemaining,
109
+ nextPhase,
110
+ };
111
+ }
112
+ if (verbose) {
113
+ process.stdout.write(`\n ✓ ${completedPhase.toUpperCase()} complete.\n`);
114
+ process.stdout.write(` ▶ ${msg}\n`);
115
+ }
116
+ return {
117
+ ok: true,
118
+ status: 'instruction',
119
+ message: msg,
120
+ currentPhase: completedPhase,
121
+ phasesRemaining,
122
+ nextPhase,
123
+ };
124
+ }
125
+
126
+ // ── Autopilot mode ────────────────────────────────────────────────────
127
+ // Auto-advance: transition to next phase and run inner loop
128
+
129
+ // Re-check pause before auto-advancing (user may have paused during phase execution)
130
+ if (config.paused) {
131
+ const msg = `Autopilot paused after "${completedPhase}". Run: harness-dev resume`;
132
+ if (verbose && !json) {
133
+ process.stdout.write(`\n ⏸ ${msg}\n`);
134
+ }
135
+ return {
136
+ ok: true,
137
+ status: 'paused',
138
+ message: msg,
139
+ currentPhase: completedPhase,
140
+ nextPhase: null,
141
+ phasesRemaining,
142
+ };
143
+ }
144
+
145
+ if (verbose && !json) {
146
+ process.stdout.write(`\n ● Autopilot: advancing to "${nextPhase}"...\n`);
147
+ }
148
+
149
+ // Transition to next phase
150
+ const transResult = transitionPhase(targetDir, nextPhase);
151
+ if (!transResult.ok) {
152
+ return {
153
+ ok: false,
154
+ status: 'error',
155
+ message: `Autopilot: transition to "${nextPhase}" failed: ${transResult.error}`,
156
+ currentPhase: completedPhase,
157
+ phasesRemaining,
158
+ };
159
+ }
160
+
161
+ // Run inner loop for next phase
162
+ const loopResult = runPhase(targetDir, nextPhase, { json });
163
+
164
+ if (loopResult.status === 'escalated') {
165
+ // Retries exhausted — stop pipeline, escalate to human
166
+ if (!json && verbose) {
167
+ process.stdout.write(`\n ✗ ${nextPhase.toUpperCase()} — ${loopResult.message}\n`);
168
+ process.stdout.write(` Escalating to human.\n`);
169
+ }
170
+ return {
171
+ ok: false,
172
+ status: 'escalated',
173
+ message: loopResult.message,
174
+ currentPhase: nextPhase,
175
+ nextPhase: null,
176
+ phasesRemaining,
177
+ details: loopResult.details,
178
+ };
179
+ }
180
+
181
+ if (loopResult.status === 'complete') {
182
+ // Phase completed successfully — continue the chain
183
+ if (verbose && !json) {
184
+ process.stdout.write(` ✓ ${nextPhase.toUpperCase()} complete.\n`);
185
+ }
186
+ return continuePipeline(targetDir, nextPhase, options);
187
+ }
188
+
189
+ // Phase returned instruction or error — stop chain
190
+ const orderRemaining = getPhaseOrder();
191
+ const currentIdx = orderRemaining.indexOf(nextPhase);
192
+ const pipelineNext = (currentIdx >= 0 && currentIdx < orderRemaining.length - 1) ? orderRemaining[currentIdx + 1] : null;
193
+ return {
194
+ ok: loopResult.ok,
195
+ status: loopResult.status,
196
+ message: loopResult.message,
197
+ currentPhase: nextPhase,
198
+ nextPhase: pipelineNext,
199
+ phasesRemaining,
200
+ details: loopResult.details,
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Run the full autopilot pipeline from current state through SHIP.
206
+ *
207
+ * This is a convenience wrapper — normally autopilot is triggered
208
+ * by calling `harness-dev phase <name>` while in autopilot mode.
209
+ *
210
+ * @param {string} targetDir
211
+ * @param {object} [options]
212
+ * @returns {{ ok: boolean, status: string, message: string }}
213
+ */
214
+ export function runAutopilot(targetDir, options = {}) {
215
+ const { config, ok } = loadConfig(targetDir);
216
+ if (!ok) {
217
+ return { ok: false, status: 'error', message: 'Cannot load config' };
218
+ }
219
+
220
+ const currentPhase = config.currentPhase;
221
+ const order = getPhaseOrder(config.phases?.enabled);
222
+
223
+ if (!currentPhase || !order.includes(currentPhase)) {
224
+ // Start from the beginning
225
+ const firstPhase = order[0];
226
+ if (!firstPhase) {
227
+ return { ok: false, status: 'error', message: 'No phases enabled in config' };
228
+ }
229
+ const transResult = transitionPhase(targetDir, firstPhase);
230
+ if (!transResult.ok) {
231
+ return { ok: false, status: 'error', message: transResult.error || 'Transition failed' };
232
+ }
233
+ return continuePipeline(targetDir, firstPhase, options);
234
+ }
235
+
236
+ // Already in a phase — continue from here
237
+ return continuePipeline(targetDir, currentPhase, options);
238
+ }
239
+
240
+ /** Create a git tag for pipeline iteration. */
241
+ function autoTag(targetDir, phase, json) {
242
+ const now = new Date();
243
+ const tag = `pipeline-${now.toISOString().slice(0, 10)}-${now.getTime().toString(36)}`;
244
+ const r = execGit(`git tag "${tag}"`, targetDir);
245
+ if (r.ok && !json) {
246
+ process.stdout.write(` ● Git tag: ${tag}\n`);
247
+ }
248
+ // Not a git repo or tag failed — skip silently
249
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * ralph-output — Human-readable instruction builders for Ralph loop phases.
3
+ *
4
+ * Extracted from ralph-inner.mjs to separate instruction text generation
5
+ * from loop orchestration logic. Each builder returns a string of agent
6
+ * instructions for a phase/feature/task combination.
7
+ *
8
+ * Usage:
9
+ * import { buildFeatureIterateOutput, buildDeliverableRetryOutput } from "./ralph-output.mjs";
10
+ */
11
+ import { phaseLabel } from './command-helpers.mjs';
12
+
13
+ // ── Output builders ──────────────────────────────────────────────────────────
14
+
15
+ /**
16
+ * Build human-readable instructions for the SIMPLIFY phase.
17
+ * Spec: PROJECT_PLAN.md lines 602-631.
18
+ */
19
+ export function buildSimplifyOutput(feature, task, maxRetries, resetOnRetry, autoCommit) {
20
+ let out = '';
21
+ out += `═══ SIMPLIFY PHASE ═══\n`;
22
+ out += `\n`;
23
+ out += `This is a feature-iterate phase. Pick one feature at a time.\n`;
24
+ out += `If validation fails (up to ${maxRetries} attempts), retry with fresh context.\n`;
25
+ out += `\n`;
26
+ out += `Current feature: "${feature.name}" (${feature.id})\n`;
27
+ out += `\n`;
28
+ out += `Planner: identify code smells, excessive nesting,\n`;
29
+ out += ` DRY violations, premature optimization\n`;
30
+ out += ` Set targets: "flatten nested loop X",\n`;
31
+ out += ` "extract validation logic from controller Y"\n`;
32
+ out += `\n`;
33
+ out += `Simplifier (Generator persona): refactor code for clarity\n`;
34
+ out += ` - Extract repeated logic into shared functions\n`;
35
+ out += ` - Flatten nested conditionals (max 4 levels)\n`;
36
+ out += ` - Remove dead code and commented-out blocks\n`;
37
+ out += ` - Rename unclear variables\n`;
38
+ out += ` - Break functions exceeding ~40 lines\n`;
39
+ out += ` - ⚠ Never change behavior — tests must still pass\n`;
40
+ out += `\n`;
41
+ out += `Evaluator: verify against these criteria:\n`;
42
+ out += ` - No dead code or unused imports\n`;
43
+ out += ` - No commented-out code blocks\n`;
44
+ out += ` - No nesting beyond 4 levels\n`;
45
+ out += ` - No DRY violations (same logic repeated 3+ times)\n`;
46
+ out += ` - All tests still pass after refactoring\n`;
47
+ out += `\n`;
48
+ out += `Iteration: validate --feature ${feature.id} --task ${task.id}\n`;
49
+ out += ` → pass → next feature\n`;
50
+ out += ` → fail (≤${maxRetries}x) → retry with fresh context\n`;
51
+ out += ` → fail (>${maxRetries}x) → escalate to human\n`;
52
+ if (resetOnRetry) { out += `\n Git reset on retry: enabled\n`; }
53
+ if (autoCommit) { out += ` Auto-commit: enabled\n`; }
54
+ return out;
55
+ }
56
+
57
+ /**
58
+ * Build human-readable instructions for a feature-iterate phase.
59
+ */
60
+ export function buildFeatureIterateOutput(phase, feature, task, mode, maxRetries, resetOnRetry, autoCommit) {
61
+ if (phase === 'simplify') {
62
+ return buildSimplifyOutput(feature, task, maxRetries, resetOnRetry, autoCommit);
63
+ }
64
+ let out = '';
65
+ out += `═══ ${phaseLabel(phase)} PHASE ═══\n`;
66
+ out += `\n`;
67
+ out += `This is a feature-iterate phase. You pick one incomplete\n`;
68
+ out += `feature at a time. If validation fails (up to ${maxRetries}\n`;
69
+ out += `attempts), retry that task with fresh context.\n`;
70
+ out += `\n`;
71
+ out += `Current feature: "${feature.name}" (${feature.id})\n`;
72
+ out += `Current task: "${task.description}" (${task.id})\n`;
73
+ out += `\n`;
74
+ out += `Planner: pick next feature from feature_list.json where passes=false\n`;
75
+ out += ` Select one uncompleted task from that feature's task list\n`;
76
+ out += `\n`;
77
+ out += `Generator: implement ONE task only. When done, call validate.\n`;
78
+ out += `\n`;
79
+ out += `Evaluator: verify against that task's acceptance criteria.\n`;
80
+ out += ` Run the verification commands yourself.\n`;
81
+ out += `\n`;
82
+ out += `Iteration pattern:\n`;
83
+ out += ` Pick task → implement → validate --feature ${feature.id} --task ${task.id}\n`;
84
+ out += ` → Pass: mark task complete, pick next task\n`;
85
+ out += ` → Fail (≤${maxRetries}x): git auto-commit, retry with fresh context\n`;
86
+ out += ` → Fail (>${maxRetries}x): escalate to human\n`;
87
+ if (resetOnRetry) {out += `\n Git reset on retry: enabled\n`;}
88
+ if (autoCommit) {out += ` Auto-commit: enabled\n`;}
89
+ return out;
90
+ }
91
+
92
+ /**
93
+ * Build human-readable instructions for a deliverable-retry phase.
94
+ */
95
+ export function buildDeliverableRetryOutput(phase, mode, maxRetries, resetOnRetry, autoCommit) {
96
+ let out = '';
97
+ out += `═══ ${phaseLabel(phase)} PHASE ═══\n`;
98
+ out += `\n`;
99
+ out += `This is a deliverable-retry phase. You produce one deliverable.\n`;
100
+ out += `If validation fails (up to ${maxRetries} attempts), retry with fresh context.\n`;
101
+ out += `\n`;
102
+
103
+ // Phase-specific planner/generator/evaluator instructions
104
+ switch (phase) {
105
+ case 'define':
106
+ out += `Planner: interview the user, write PRD in specs/*.md,\n`;
107
+ out += ` define acceptance criteria per feature\n`;
108
+ if (mode === 'autopilot') {
109
+ out += `\n`;
110
+ out += ` Write plans/outer-loop-plan.md with:\n`;
111
+ out += ` - Feature delivery order\n`;
112
+ out += ` - Estimated iterations per feature\n`;
113
+ out += ` - Risk factors and escalation thresholds\n`;
114
+ }
115
+ out += `\n`;
116
+ out += `Generator: produce spec documents (specs/*.md,\n`;
117
+ out += ` sprint-contract.md) following the PRD\n`;
118
+ if (mode === 'autopilot') {
119
+ out += ` Create plans/outer-loop-plan.md\n`;
120
+ }
121
+ out += `\n`;
122
+ out += `Evaluator: verify against these criteria:\n`;
123
+ out += ` - All 5 spec sections present (overview, requirements,\n`;
124
+ out += ` acceptance criteria, edge cases, open questions)\n`;
125
+ out += ` - No TODO/FIXME placeholders in specs\n`;
126
+ out += ` - Sprint Contract agreed between Planner and Evaluator\n`;
127
+ break;
128
+ case 'plan':
129
+ out += `Planner: decompose features into tasks in feature_list.json\n`;
130
+ out += ` Define task dependencies and effort estimates\n`;
131
+ out += `\n`;
132
+ out += `Generator: populate feature_list.json with all features\n`;
133
+ out += ` and tasks for this sprint\n`;
134
+ out += `\n`;
135
+ out += `Evaluator: verify against these criteria:\n`;
136
+ out += ` - feature_list.json is valid JSON\n`;
137
+ out += ` - All features have at least one task\n`;
138
+ out += ` - DAG of tasks is acyclic\n`;
139
+ break;
140
+ case 'review':
141
+ out += `Planner: review all phase gates have passed\n`;
142
+ out += ` Identify any outstanding blockers\n`;
143
+ out += `\n`;
144
+ out += `Generator: update evaluator-rubric.md with results\n`;
145
+ out += ` Ensure CHANGELOG.md is updated\n`;
146
+ out += `\n`;
147
+ out += `Evaluator: verify against these criteria:\n`;
148
+ out += ` - Branch up-to-date with main\n`;
149
+ out += ` - All gates pass (lint, tests, coverage)\n`;
150
+ out += ` - Sprint contract acceptance criteria met\n`;
151
+ break;
152
+ case 'ship':
153
+ out += `Planner: verify pipeline is complete\n`;
154
+ out += ` Prepare release notes\n`;
155
+ out += `\n`;
156
+ out += `Generator: tag commit, update changelog,\n`;
157
+ out += ` verify git clean\n`;
158
+ out += `\n`;
159
+ out += `Evaluator: verify against these criteria:\n`;
160
+ out += ` - Git status is clean\n`;
161
+ out += ` - HEAD is tagged\n`;
162
+ out += ` - CHANGELOG.md updated\n`;
163
+ break;
164
+ default:
165
+ out += `Planner: define scope of this deliverable\n`;
166
+ out += `\n`;
167
+ out += `Generator: produce the phase deliverable\n`;
168
+ out += `\n`;
169
+ out += `Evaluator: verify against phase criteria\n`;
170
+ break;
171
+ }
172
+
173
+ out += `\n`;
174
+ out += `When done, run: harness-dev validate\n`;
175
+ if (resetOnRetry) {out += `Git reset on retry: enabled\n`;}
176
+ if (autoCommit) {out += `Auto-commit: enabled\n`;}
177
+ return out;
178
+ }