dev-harness-cli 1.0.5 → 1.2.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.
- package/README.md +158 -230
- package/cli/commands/checkpoint.mjs +2 -2
- package/cli/commands/config.mjs +12 -12
- package/cli/commands/contract.mjs +10 -10
- package/cli/commands/detect-tool.mjs +5 -4
- package/cli/commands/init.mjs +10 -10
- package/cli/commands/learn.mjs +3 -3
- package/cli/commands/pause.mjs +1 -1
- package/cli/commands/phase.mjs +6 -6
- package/cli/commands/resume.mjs +3 -3
- package/cli/commands/rollback.mjs +9 -9
- package/cli/commands/set-mode.mjs +5 -5
- package/cli/commands/status.mjs +9 -9
- package/cli/commands/validate.mjs +7 -7
- package/cli/commands/worktree.mjs +6 -6
- package/cli/{harness-dev.mjs → dev-harness.mjs} +2 -2
- package/cli/lib/config-registry.mjs +2 -2
- package/cli/lib/contract.mjs +3 -3
- package/cli/lib/gates.mjs +11 -9
- package/cli/lib/help.mjs +28 -28
- package/cli/lib/paths.mjs +87 -8
- package/cli/lib/ralph-inner.mjs +2 -2
- package/cli/lib/ralph-outer.mjs +3 -3
- package/cli/lib/ralph-output.mjs +1 -1
- package/cli/lib/scaffold.mjs +18 -18
- package/cli/lib/state.mjs +1 -1
- package/cli/lib/templates.mjs +38 -1
- package/package.json +8 -4
- package/schema/feature-list.schema.json +63 -0
- package/templates/AGENTS.md +19 -15
- package/templates/ci/github-actions.yml +2 -2
- package/templates/ci/gitlab-ci.yml +2 -2
- package/templates/docs/agents/generator.md +1 -1
- package/templates/docs/agents/planner.md +1 -1
- package/templates/docs/agents/simplifier.md +1 -1
- package/templates/docs/phases/build.md +3 -3
- package/templates/docs/phases/define.md +3 -3
- package/templates/docs/phases/plan.md +3 -3
- package/templates/docs/phases/review.md +2 -2
- package/templates/docs/phases/ship.md +3 -3
- package/templates/docs/phases/simplify.md +3 -3
- package/templates/docs/phases/verify.md +2 -2
- package/templates/harness-config.json +42 -0
- package/templates/init.ps1 +1 -1
- package/templates/progress.md +18 -0
package/cli/commands/init.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* init.sh, progress.md, sprint-contract.md) plus project files (feature_list.json,
|
|
7
7
|
* feature-list.schema.json, session-handoff.md, etc.), git init, .gitignore.
|
|
8
8
|
*
|
|
9
|
-
* Usage: harness
|
|
9
|
+
* Usage: dev-harness init [--stack <name>] [--target <dir>] [--agent-tool <name>] [--force] [--no-git] [--json]
|
|
10
10
|
*/
|
|
11
11
|
import { resolve, join } from 'node:path';
|
|
12
12
|
import {
|
|
@@ -144,16 +144,16 @@ export default async function initCommand(args) {
|
|
|
144
144
|
const harnessPaths = [];
|
|
145
145
|
const projectPaths = [];
|
|
146
146
|
|
|
147
|
-
// Template files — known template names
|
|
147
|
+
// Template files — known template names (mapped to harness/ paths by templates.mjs)
|
|
148
148
|
const templateNames = [
|
|
149
|
-
'AGENTS.md', 'harness
|
|
150
|
-
'progress.md', 'sprint-contract.md', 'evaluator-rubric.md',
|
|
149
|
+
'AGENTS.md', 'harness/config.json', 'harness/scripts/init.sh',
|
|
150
|
+
'harness/progress.md', 'harness/sprint-contract.md', 'harness/evaluator-rubric.md',
|
|
151
151
|
];
|
|
152
152
|
for (const name of templateNames) {
|
|
153
153
|
harnessPaths.push(join(targetDir, name));
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
// Extra scaffold files
|
|
156
|
+
// Extra scaffold files (already have harness/ prefix from getExtraFiles)
|
|
157
157
|
for (const relPath of Object.keys(extraFiles)) {
|
|
158
158
|
harnessPaths.push(join(targetDir, relPath));
|
|
159
159
|
}
|
|
@@ -190,8 +190,8 @@ export default async function initCommand(args) {
|
|
|
190
190
|
// Ensure target directory exists
|
|
191
191
|
mkdirSync(targetDir, { recursive: true });
|
|
192
192
|
|
|
193
|
-
// Ensure
|
|
194
|
-
mkdirSync(join(targetDir, '
|
|
193
|
+
// Ensure harness/ directory exists (all harness files go here)
|
|
194
|
+
mkdirSync(join(targetDir, 'harness'), { recursive: true });
|
|
195
195
|
|
|
196
196
|
const created = [];
|
|
197
197
|
const errors = [];
|
|
@@ -289,15 +289,15 @@ export default async function initCommand(args) {
|
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
// Set agentTool in the generated harness
|
|
293
|
-
const configPath = join(targetDir, 'harness
|
|
292
|
+
// Set agentTool in the generated harness/config.json
|
|
293
|
+
const configPath = join(targetDir, 'harness', 'config.json');
|
|
294
294
|
if (existsSync(configPath)) {
|
|
295
295
|
try {
|
|
296
296
|
const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
297
297
|
cfg.agentTool = agentTool;
|
|
298
298
|
writeFileSync(configPath, JSON.stringify(cfg, null, 2) + '\n', 'utf-8');
|
|
299
299
|
} catch (err) {
|
|
300
|
-
errors.push(`harness
|
|
300
|
+
errors.push(`harness/config.json agentTool: ${err.message}`);
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
303
|
}
|
package/cli/commands/learn.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* learn — Append a lesson to progress.md.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* harness
|
|
6
|
-
* harness
|
|
5
|
+
* dev-harness learn "Lesson text here"
|
|
6
|
+
* dev-harness learn "Lesson text" --json
|
|
7
7
|
*/
|
|
8
8
|
import { CliError, EXIT, die } from '../lib/errors.mjs';
|
|
9
9
|
import { appendLesson } from '../lib/progress.mjs';
|
|
@@ -17,7 +17,7 @@ export default async function learnCommand(args) {
|
|
|
17
17
|
if (!message) {
|
|
18
18
|
die(
|
|
19
19
|
new CliError(
|
|
20
|
-
'Lesson message required.\n Example: harness
|
|
20
|
+
'Lesson message required.\n Example: dev-harness learn "Token refresh gotcha — accepts access_token in body"',
|
|
21
21
|
EXIT.USAGE_ERROR,
|
|
22
22
|
),
|
|
23
23
|
json,
|
package/cli/commands/pause.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Sets config.paused = true. The outer loop checks this
|
|
5
5
|
* before starting a new phase in autopilot mode.
|
|
6
6
|
*
|
|
7
|
-
* Usage: harness
|
|
7
|
+
* Usage: dev-harness pause [--json]
|
|
8
8
|
*/
|
|
9
9
|
import { set } from '../lib/state.mjs';
|
|
10
10
|
import { parseCommandArgs } from '../lib/command-helpers.mjs';
|
package/cli/commands/phase.mjs
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* then triggers the outer loop for autopilot advancement.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
|
-
* harness
|
|
9
|
-
* harness
|
|
8
|
+
* dev-harness phase <name>
|
|
9
|
+
* dev-harness phase <name> --json
|
|
10
10
|
*/
|
|
11
11
|
import { CliError, EXIT, die } from '../lib/errors.mjs';
|
|
12
12
|
import { transitionPhase, getPhaseOrder, loadConfig } from '../lib/state.mjs';
|
|
@@ -51,7 +51,7 @@ export default async function phaseCommand(args) {
|
|
|
51
51
|
const { config: preConfig, ok: preOk } = loadConfig(targetDir);
|
|
52
52
|
const preMode = preOk ? (preConfig.mode ?? 'copilot') : 'copilot';
|
|
53
53
|
if (preOk && preConfig.paused && preMode === 'autopilot') {
|
|
54
|
-
const msg = 'Pipeline is paused. Run: harness
|
|
54
|
+
const msg = 'Pipeline is paused. Run: dev-harness resume';
|
|
55
55
|
if (json) {
|
|
56
56
|
process.stdout.write(JSON.stringify({
|
|
57
57
|
command: 'phase',
|
|
@@ -140,11 +140,11 @@ export default async function phaseCommand(args) {
|
|
|
140
140
|
if (pipelineResult.status === 'complete') {
|
|
141
141
|
process.stdout.write(`\n✓ Pipeline complete. All phases done.\n`);
|
|
142
142
|
} else if (pipelineResult.status === 'instruction') {
|
|
143
|
-
process.stdout.write(`\nNext: harness
|
|
143
|
+
process.stdout.write(`\nNext: dev-harness phase ${pipelineResult.nextPhase}\n`);
|
|
144
144
|
}
|
|
145
145
|
} else if (nextPhase) {
|
|
146
146
|
// Copilot: print next step
|
|
147
|
-
process.stdout.write(`Next: harness
|
|
147
|
+
process.stdout.write(`Next: dev-harness phase ${nextPhase}\n`);
|
|
148
148
|
// Auto-prompt: controlled by two independent flags:
|
|
149
149
|
// autoPrompt=true → show the prompt
|
|
150
150
|
// confirmGates=true → require y/n answer before continuing
|
|
@@ -158,7 +158,7 @@ export default async function phaseCommand(args) {
|
|
|
158
158
|
process.stdout.write(`\n✓ Pipeline complete. All phases done.\n`);
|
|
159
159
|
}
|
|
160
160
|
} else if (answer === false) {
|
|
161
|
-
process.stdout.write(` Staying in ${phase.toUpperCase()}. Run: harness
|
|
161
|
+
process.stdout.write(` Staying in ${phase.toUpperCase()}. Run: dev-harness phase ${nextPhase} when ready.\n`);
|
|
162
162
|
}
|
|
163
163
|
// null = no TTY, skipped
|
|
164
164
|
} else {
|
package/cli/commands/resume.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Sets config.paused = false. Allows autopilot to continue.
|
|
5
5
|
*
|
|
6
|
-
* Usage: harness
|
|
6
|
+
* Usage: dev-harness resume [--json]
|
|
7
7
|
*/
|
|
8
8
|
import { set } from '../lib/state.mjs';
|
|
9
9
|
import { parseCommandArgs } from '../lib/command-helpers.mjs';
|
|
@@ -19,14 +19,14 @@ export default async function resumeCommand(args) {
|
|
|
19
19
|
command: 'resume',
|
|
20
20
|
status: result.ok ? 'ok' : 'error',
|
|
21
21
|
message: result.ok
|
|
22
|
-
? 'Pipeline resumed. Run: harness
|
|
22
|
+
? 'Pipeline resumed. Run: dev-harness phase <name> to continue.'
|
|
23
23
|
: (result.error || 'Failed to resume'),
|
|
24
24
|
});
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
if (result.ok) {
|
|
29
|
-
emitHuman('✓ Pipeline resumed. Run: harness
|
|
29
|
+
emitHuman('✓ Pipeline resumed. Run: dev-harness phase <name> to continue.\n');
|
|
30
30
|
} else {
|
|
31
31
|
emitHumanError(`✗ ${result.error}\n`);
|
|
32
32
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* manual/<label> — User-created manual checkpoints
|
|
14
14
|
* recovery/* — Recovery branches (for informational display)
|
|
15
15
|
*
|
|
16
|
-
* Usage: harness
|
|
16
|
+
* Usage: dev-harness rollback <subcommand> [checkpoint]
|
|
17
17
|
*/
|
|
18
18
|
import { die, CliError, EXIT } from '../lib/errors.mjs';
|
|
19
19
|
import { execGit, getGitRoot } from '../lib/git.mjs';
|
|
@@ -67,12 +67,12 @@ export default async function rollbackCommand(args) {
|
|
|
67
67
|
const sub = args.subcommand;
|
|
68
68
|
|
|
69
69
|
if (!sub || !SUBCOMMANDS.includes(sub)) {
|
|
70
|
-
die(new CliError(`Usage: harness
|
|
70
|
+
die(new CliError(`Usage: dev-harness rollback ${SUBCOMMANDS.join('|')}`, EXIT.USAGE_ERROR), json);
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
if (sub !== 'list' && args.positionals.length < 1) {
|
|
75
|
-
die(new CliError(`Usage: harness
|
|
75
|
+
die(new CliError(`Usage: dev-harness rollback ${sub} <checkpoint>`, EXIT.USAGE_ERROR), json);
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -118,7 +118,7 @@ export default async function rollbackCommand(args) {
|
|
|
118
118
|
} else {
|
|
119
119
|
if (checkpoints.length === 0 && recoveryBranches.length === 0) {
|
|
120
120
|
process.stdout.write('No checkpoints found. Phase tags (phase/*) and iteration tags (iter/*) are created automatically when auto-tagging is enabled.\n');
|
|
121
|
-
process.stdout.write('Manual checkpoints: harness
|
|
121
|
+
process.stdout.write('Manual checkpoints: dev-harness checkpoint create <label>\n');
|
|
122
122
|
} else {
|
|
123
123
|
if (checkpoints.length > 0) {
|
|
124
124
|
process.stdout.write('Checkpoints:\n');
|
|
@@ -146,7 +146,7 @@ export default async function rollbackCommand(args) {
|
|
|
146
146
|
// Verify the tag exists
|
|
147
147
|
const tagCheck = execGit(`git rev-parse --verify "${checkpoint}^{commit}"`, gitRoot);
|
|
148
148
|
if (!tagCheck.ok) {
|
|
149
|
-
const msg = `Checkpoint "${checkpoint}" not found. Run: harness
|
|
149
|
+
const msg = `Checkpoint "${checkpoint}" not found. Run: dev-harness rollback list`;
|
|
150
150
|
if (json) {
|
|
151
151
|
process.stdout.write(JSON.stringify({ command: 'rollback', subcommand: 'to', checkpoint, status: 'error', message: msg }) + '\n');
|
|
152
152
|
} else {
|
|
@@ -161,9 +161,9 @@ export default async function rollbackCommand(args) {
|
|
|
161
161
|
|
|
162
162
|
// Restore all files from the checkpoint
|
|
163
163
|
const restoreFiles = [
|
|
164
|
-
'harness
|
|
165
|
-
'progress.md',
|
|
166
|
-
'
|
|
164
|
+
'harness/config.json',
|
|
165
|
+
'harness/progress.md',
|
|
166
|
+
'harness/features/feature-list.json',
|
|
167
167
|
];
|
|
168
168
|
|
|
169
169
|
// First, restore the whole working tree from the tag
|
|
@@ -205,7 +205,7 @@ export default async function rollbackCommand(args) {
|
|
|
205
205
|
// Verify the tag exists
|
|
206
206
|
const tagCheck = execGit(`git rev-parse --verify "${checkpoint}^{commit}"`, gitRoot);
|
|
207
207
|
if (!tagCheck.ok) {
|
|
208
|
-
const msg = `Checkpoint "${checkpoint}" not found. Run: harness
|
|
208
|
+
const msg = `Checkpoint "${checkpoint}" not found. Run: dev-harness rollback list`;
|
|
209
209
|
if (json) {
|
|
210
210
|
process.stdout.write(JSON.stringify({ command: 'rollback', subcommand: 'branch', checkpoint, status: 'error', message: msg }) + '\n');
|
|
211
211
|
} else {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* set-mode — Switch between copilot and autopilot.
|
|
3
3
|
*
|
|
4
|
-
* Usage: harness
|
|
5
|
-
* harness
|
|
6
|
-
* harness
|
|
7
|
-
* harness
|
|
4
|
+
* Usage: dev-harness set-mode <mode>
|
|
5
|
+
* dev-harness set-mode autopilot
|
|
6
|
+
* dev-harness set-mode copilot
|
|
7
|
+
* dev-harness set-mode autopilot --json
|
|
8
8
|
*/
|
|
9
9
|
import { die, CliError, EXIT } from '../lib/errors.mjs';
|
|
10
10
|
import { set, loadConfig, getPhaseOrder } from '../lib/state.mjs';
|
|
@@ -19,7 +19,7 @@ export default async function setModeCommand(args) {
|
|
|
19
19
|
if (!mode || !valid.includes(mode)) {
|
|
20
20
|
die(
|
|
21
21
|
new CliError(
|
|
22
|
-
`Mode required. Valid: ${valid.join(', ')}.\n Example: harness
|
|
22
|
+
`Mode required. Valid: ${valid.join(', ')}.\n Example: dev-harness set-mode autopilot`,
|
|
23
23
|
EXIT.USAGE_ERROR,
|
|
24
24
|
),
|
|
25
25
|
json,
|
package/cli/commands/status.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Reads harness-config.json via state.mjs for live project state,
|
|
5
5
|
* plus runs stack detection and gate checks for current status.
|
|
6
6
|
*
|
|
7
|
-
* Usage: harness
|
|
7
|
+
* Usage: dev-harness status [--json] [--target <dir>]
|
|
8
8
|
*/
|
|
9
9
|
import { resolve, basename } from 'node:path';
|
|
10
10
|
import { detectStack } from '../lib/detect-stack.mjs';
|
|
@@ -61,7 +61,7 @@ export default async function statusCommand(args) {
|
|
|
61
61
|
status: 'ok',
|
|
62
62
|
message: configOk
|
|
63
63
|
? `Phase: ${phase || 'not started'}, Stack: ${stack.label}`
|
|
64
|
-
: 'No harness
|
|
64
|
+
: 'No harness/config.json found — run dev-harness init',
|
|
65
65
|
project: basename(targetDir),
|
|
66
66
|
stack: stack.name,
|
|
67
67
|
stackLabel: stack.label,
|
|
@@ -84,7 +84,7 @@ export default async function statusCommand(args) {
|
|
|
84
84
|
|
|
85
85
|
// ── Human-readable output ─────────────────────────────────────────────
|
|
86
86
|
let out = '';
|
|
87
|
-
out += '═══
|
|
87
|
+
out += '═══ harness Status ═══\n';
|
|
88
88
|
out += line('Project:', basename(targetDir)) + '\n';
|
|
89
89
|
out += line('Stack:', `${stack.label}${stack.name !== 'generic' ? '' : ' (not detected)'}`) + '\n';
|
|
90
90
|
out += line('Mode:', modeLabel(mode)) + '\n';
|
|
@@ -104,7 +104,7 @@ export default async function statusCommand(args) {
|
|
|
104
104
|
out += ' Phase: not started.\n';
|
|
105
105
|
out += '\n';
|
|
106
106
|
} else {
|
|
107
|
-
out += ' No harness
|
|
107
|
+
out += ' No harness/config.json found.\n';
|
|
108
108
|
out += '\n';
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -150,19 +150,19 @@ function gateStatusLabel(status, passing, total) {
|
|
|
150
150
|
|
|
151
151
|
function determineNextAction(targetDir, configOk, config, phase, gateStatus) {
|
|
152
152
|
if (!configOk) {
|
|
153
|
-
return 'Run: harness
|
|
153
|
+
return 'Run: dev-harness init';
|
|
154
154
|
}
|
|
155
155
|
if (!phase) {
|
|
156
|
-
return 'Run: harness
|
|
156
|
+
return 'Run: dev-harness phase define to start';
|
|
157
157
|
}
|
|
158
158
|
if (gateStatus === 'fail') {
|
|
159
|
-
return 'Run: harness
|
|
159
|
+
return 'Run: dev-harness validate to re-check';
|
|
160
160
|
}
|
|
161
161
|
// Determine next phase
|
|
162
162
|
const order = ['define', 'plan', 'build', 'verify', 'review', 'ship'];
|
|
163
163
|
const idx = order.indexOf(phase);
|
|
164
164
|
if (idx >= 0 && idx < order.length - 1) {
|
|
165
|
-
return `Run: harness
|
|
165
|
+
return `Run: dev-harness phase ${order[idx + 1]}`;
|
|
166
166
|
}
|
|
167
|
-
return `Run: harness
|
|
167
|
+
return `Run: dev-harness validate`;
|
|
168
168
|
}
|
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
* Otherwise runs phase-specific checks and reports results.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
|
-
* harness
|
|
9
|
-
* harness
|
|
10
|
-
* harness
|
|
8
|
+
* dev-harness validate — check current phase
|
|
9
|
+
* dev-harness validate --json — machine-readable output
|
|
10
|
+
* dev-harness validate --phase X — check specific phase
|
|
11
11
|
*
|
|
12
12
|
* Examples:
|
|
13
|
-
* harness
|
|
13
|
+
* dev-harness validate
|
|
14
14
|
* # → BUILD Gate: PASS — 3/3 checks pass
|
|
15
15
|
*
|
|
16
|
-
* harness
|
|
16
|
+
* dev-harness validate --json
|
|
17
17
|
* # → {"phase":"build","checks":[...],"overall":false,"failures":["lint"]}
|
|
18
18
|
*/
|
|
19
19
|
import { resolve } from 'node:path';
|
|
@@ -53,7 +53,7 @@ export default async function validateCommand(args) {
|
|
|
53
53
|
if (task) { out.task = task; }
|
|
54
54
|
process.stdout.write(JSON.stringify(out) + '\n');
|
|
55
55
|
} else {
|
|
56
|
-
process.stdout.write('Gates disabled. Enable with: harness
|
|
56
|
+
process.stdout.write('Gates disabled. Enable with: dev-harness config set gates.enabled true\n');
|
|
57
57
|
}
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
@@ -62,7 +62,7 @@ export default async function validateCommand(args) {
|
|
|
62
62
|
if (!phase) {
|
|
63
63
|
die(
|
|
64
64
|
new CliError(
|
|
65
|
-
'No phase found in config. Run: harness
|
|
65
|
+
'No phase found in config. Run: dev-harness init or dev-harness phase <name>',
|
|
66
66
|
EXIT.VALIDATION_FAILURE,
|
|
67
67
|
),
|
|
68
68
|
json,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* prune — git worktree prune (remove orphaned metadata)
|
|
9
9
|
* remove <name> — git worktree remove + optionally delete branch
|
|
10
10
|
*
|
|
11
|
-
* Usage: harness
|
|
11
|
+
* Usage: dev-harness worktree <subcommand> [name] [options]
|
|
12
12
|
*/
|
|
13
13
|
import { existsSync } from 'node:fs';
|
|
14
14
|
import { resolve, dirname } from 'node:path';
|
|
@@ -25,13 +25,13 @@ export default async function worktreeCommand(args) {
|
|
|
25
25
|
const sub = args.subcommand;
|
|
26
26
|
|
|
27
27
|
if (!sub || !SUBCOMMANDS.includes(sub)) {
|
|
28
|
-
die(new CliError(`Usage: harness
|
|
28
|
+
die(new CliError(`Usage: dev-harness worktree ${SUBCOMMANDS.join('|')}`, EXIT.USAGE_ERROR), json);
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const gitRoot = getGitRoot(targetDir);
|
|
33
33
|
if (!gitRoot) {
|
|
34
|
-
const msg = 'Not inside a git repository. Run: git init first or harness
|
|
34
|
+
const msg = 'Not inside a git repository. Run: git init first or dev-harness init';
|
|
35
35
|
if (json) {
|
|
36
36
|
process.stdout.write(JSON.stringify({ command: 'worktree', subcommand: sub, status: 'error', message: msg }) + '\n');
|
|
37
37
|
} else {
|
|
@@ -44,7 +44,7 @@ export default async function worktreeCommand(args) {
|
|
|
44
44
|
if (sub === 'create') {
|
|
45
45
|
const name = args.positionals[0];
|
|
46
46
|
if (!name) {
|
|
47
|
-
die(new CliError('Usage: harness
|
|
47
|
+
die(new CliError('Usage: dev-harness worktree create <name>', EXIT.USAGE_ERROR), json);
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -88,7 +88,7 @@ export default async function worktreeCommand(args) {
|
|
|
88
88
|
|
|
89
89
|
// Scaffold harness in the new worktree — run full init with parent's detected stack
|
|
90
90
|
const stack = detectStack(worktreePath).name;
|
|
91
|
-
const harnessDevPath = new URL('../harness
|
|
91
|
+
const harnessDevPath = new URL('../dev-harness.mjs', import.meta.url).pathname;
|
|
92
92
|
const initResult = execGit(
|
|
93
93
|
`node "${harnessDevPath}" init --stack "${stack}" --force --no-git --json`,
|
|
94
94
|
worktreePath,
|
|
@@ -232,7 +232,7 @@ export default async function worktreeCommand(args) {
|
|
|
232
232
|
if (sub === 'remove') {
|
|
233
233
|
const name = args.positionals[0];
|
|
234
234
|
if (!name) {
|
|
235
|
-
die(new CliError('Usage: harness
|
|
235
|
+
die(new CliError('Usage: dev-harness worktree remove <name>', EXIT.USAGE_ERROR), json);
|
|
236
236
|
return;
|
|
237
237
|
}
|
|
238
238
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* harness
|
|
3
|
+
* dev-harness — Agent-agnostic development harness CLI.
|
|
4
4
|
*
|
|
5
5
|
* Entry point. Parses args, routes to command handler,
|
|
6
6
|
* formats output (human or JSON), handles errors.
|
|
@@ -68,7 +68,7 @@ async function main() {
|
|
|
68
68
|
const loader = COMMANDS[args.command];
|
|
69
69
|
if (!loader) {
|
|
70
70
|
throw new CliError(
|
|
71
|
-
`Unknown command "${args.command}". See harness
|
|
71
|
+
`Unknown command "${args.command}". See dev-harness --help`,
|
|
72
72
|
EXIT.USAGE_ERROR
|
|
73
73
|
);
|
|
74
74
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Single source of truth for parameter descriptions, types, allowed values,
|
|
5
5
|
* and defaults. Used by:
|
|
6
|
-
* - `harness
|
|
6
|
+
* - `dev-harness config list` — interactive parameter listing
|
|
7
7
|
* - docs/CONFIGURATION.md — generated documentation
|
|
8
8
|
* - config command — validation of set values
|
|
9
9
|
*
|
|
@@ -198,7 +198,7 @@ export const CONFIG_PARAMS = [
|
|
|
198
198
|
group: 'Runtime State',
|
|
199
199
|
label: 'Current Phase',
|
|
200
200
|
type: 'string',
|
|
201
|
-
description: 'Current pipeline phase. Managed by harness
|
|
201
|
+
description: 'Current pipeline phase. Managed by dev-harness phase — do not edit.',
|
|
202
202
|
default: null,
|
|
203
203
|
editable: false,
|
|
204
204
|
},
|
package/cli/lib/contract.mjs
CHANGED
|
@@ -168,7 +168,7 @@ ${existingStatus || `## Agreement Status
|
|
|
168
168
|
export function reviewContract(targetDir, decision, notes) {
|
|
169
169
|
const path = CONTRACT_PATH(targetDir);
|
|
170
170
|
if (!existsSync(path)) {
|
|
171
|
-
return { ok: false, error: 'No sprint-contract.md found. Run: harness
|
|
171
|
+
return { ok: false, error: 'No sprint-contract.md found. Run: dev-harness contract propose first', escalated: false };
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
try {
|
|
@@ -276,7 +276,7 @@ export function validateContract(targetDir) {
|
|
|
276
276
|
return {
|
|
277
277
|
name: 'contract-agreed',
|
|
278
278
|
pass: false,
|
|
279
|
-
detail: 'Sprint contract not yet proposed. Run: harness
|
|
279
|
+
detail: 'Sprint contract not yet proposed. Run: dev-harness contract propose',
|
|
280
280
|
};
|
|
281
281
|
}
|
|
282
282
|
|
|
@@ -301,6 +301,6 @@ export function validateContract(targetDir) {
|
|
|
301
301
|
return {
|
|
302
302
|
name: 'contract-agreed',
|
|
303
303
|
pass: false,
|
|
304
|
-
detail: `Sprint contract ${noun} (round ${rounds}/${MAX_NEGOTIATION_ROUNDS}). Run: harness
|
|
304
|
+
detail: `Sprint contract ${noun} (round ${rounds}/${MAX_NEGOTIATION_ROUNDS}). Run: dev-harness contract review`,
|
|
305
305
|
};
|
|
306
306
|
}
|
package/cli/lib/gates.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* that return { name, pass, detail }.
|
|
6
6
|
*
|
|
7
7
|
* Phase gates are disabled by default (gates.enabled: false).
|
|
8
|
-
* Run via: harness
|
|
8
|
+
* Run via: dev-harness validate
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
11
11
|
* import { runChecks, getPhase } from './gates.mjs';
|
|
@@ -18,6 +18,7 @@ import { loadConfig } from './state.mjs';
|
|
|
18
18
|
import { getStackMeta, detectStack } from './detect-stack.mjs';
|
|
19
19
|
import { validateContract } from './contract.mjs';
|
|
20
20
|
import { execGitCheck as execCheck } from './git.mjs';
|
|
21
|
+
import { CONFIG_PATH, RUBRIC_PATH, ARCHITECTURE_PATH, DECISIONS_PATH, HARNESS_DIR } from './paths.mjs';
|
|
21
22
|
import { COVERAGE_TIMEOUT, COVERAGE_THRESHOLD_DEFAULT } from './constants.mjs';
|
|
22
23
|
|
|
23
24
|
function getStackLabel(targetDir) {
|
|
@@ -38,19 +39,20 @@ function checkGitRepo(targetDir) {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function checkConfigExists(targetDir) {
|
|
41
|
-
const cfgPath =
|
|
42
|
+
const cfgPath = CONFIG_PATH(targetDir);
|
|
42
43
|
const exists = existsSync(cfgPath);
|
|
43
44
|
return {
|
|
44
45
|
name: 'config-exists',
|
|
45
46
|
pass: exists,
|
|
46
|
-
detail: exists ? 'harness
|
|
47
|
+
detail: exists ? 'harness/config.json present' : 'Missing: harness/config.json',
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
function checkInitExecutable(targetDir) {
|
|
52
|
+
// init.sh is now at harness/scripts/init.sh
|
|
53
|
+
const initSh = resolve(HARNESS_DIR(targetDir), 'scripts', 'init.sh');
|
|
51
54
|
// Windows has no POSIX executable bit — skip the exec-bit check there.
|
|
52
55
|
if (process.platform === 'win32') {
|
|
53
|
-
const initSh = resolve(targetDir, 'init.sh');
|
|
54
56
|
return {
|
|
55
57
|
name: 'init-executable',
|
|
56
58
|
pass: existsSync(initSh),
|
|
@@ -58,7 +60,7 @@ function checkInitExecutable(targetDir) {
|
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
62
|
try {
|
|
61
|
-
const { exitCode } = execCheck(
|
|
63
|
+
const { exitCode } = execCheck(`test -x "${initSh}"`, targetDir);
|
|
62
64
|
return {
|
|
63
65
|
name: 'init-executable',
|
|
64
66
|
pass: exitCode === 0,
|
|
@@ -177,11 +179,11 @@ function checkContractAgreed(targetDir) {
|
|
|
177
179
|
|
|
178
180
|
/** Check that evaluator-rubric.md exists in the project. */
|
|
179
181
|
function checkRubricExists(targetDir) {
|
|
180
|
-
const found = existsSync(
|
|
182
|
+
const found = existsSync(RUBRIC_PATH(targetDir));
|
|
181
183
|
return {
|
|
182
184
|
name: 'rubric-exists',
|
|
183
185
|
pass: found,
|
|
184
|
-
detail: found ? 'evaluator-rubric.md found' : 'evaluator-rubric.md missing — run init to scaffold',
|
|
186
|
+
detail: found ? 'harness/evaluator-rubric.md found' : 'harness/evaluator-rubric.md missing — run init to scaffold',
|
|
185
187
|
};
|
|
186
188
|
}
|
|
187
189
|
|
|
@@ -291,7 +293,7 @@ function checkChangelogContent(targetDir) {
|
|
|
291
293
|
|
|
292
294
|
/** Check that ARCHITECTURE.md is filled in (if file exists, not just stub). */
|
|
293
295
|
function checkArchitectureDoc(targetDir) {
|
|
294
|
-
const archPath =
|
|
296
|
+
const archPath = ARCHITECTURE_PATH(targetDir);
|
|
295
297
|
if (!existsSync(archPath)) {
|
|
296
298
|
return { name: 'architecture-doc', pass: true, detail: 'ARCHITECTURE.md not present (optional)' };
|
|
297
299
|
}
|
|
@@ -304,7 +306,7 @@ function checkArchitectureDoc(targetDir) {
|
|
|
304
306
|
|
|
305
307
|
/** Check that DECISIONS.md has at least one recorded decision (if file exists). */
|
|
306
308
|
function checkDecisionsLogged(targetDir) {
|
|
307
|
-
const decPath =
|
|
309
|
+
const decPath = DECISIONS_PATH(targetDir);
|
|
308
310
|
if (!existsSync(decPath)) {
|
|
309
311
|
return { name: 'decisions-logged', pass: true, detail: 'DECISIONS.md not present (optional)' };
|
|
310
312
|
}
|