dev-harness-cli 1.0.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -227
- package/cli/commands/config.mjs +1 -1
- package/cli/commands/detect-tool.mjs +2 -1
- package/cli/commands/init.mjs +9 -9
- package/cli/commands/rollback.mjs +3 -3
- package/cli/commands/status.mjs +3 -3
- package/cli/lib/gates.mjs +10 -8
- package/cli/lib/help.mjs +1 -1
- package/cli/lib/paths.mjs +87 -8
- package/cli/lib/scaffold.mjs +18 -18
- package/cli/lib/templates.mjs +38 -1
- package/package.json +5 -1
- package/schema/feature-list.schema.json +63 -0
- package/templates/AGENTS.md +14 -10
- package/templates/ci/github-actions.yml +1 -1
- package/templates/ci/gitlab-ci.yml +1 -1
- package/templates/docs/agents/planner.md +1 -1
- package/templates/harness-config.json +42 -0
- package/templates/progress.md +18 -0
package/README.md
CHANGED
|
@@ -1,298 +1,226 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# Dev Harness
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
### Agent-agnostic development pipeline CLI
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
Scaffold · Phase orchestration · Gate validation · Iteration
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/dev-harness-cli)
|
|
10
|
+
[](https://opensource.org/licenses/MIT)
|
|
11
|
+
[](https://nodejs.org)
|
|
12
|
+
[](#)
|
|
13
|
+
|
|
14
|
+
**Works with any coding agent:** Claude Code · Codex · Cursor · Aider · Continue · OpenCode · Windsurf · Gemini · GitHub Copilot · Cline · Roo · Kilo Code · Amazon Q · and more
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## What is this?
|
|
21
|
+
|
|
22
|
+
**Dev Harness** is a CLI tool that brings structure to AI-assisted software development. Instead of ad-hoc prompting, it enforces a **phase pipeline** with **gate validation** — ensuring specs are written before code, code is reviewed before shipping, and nothing gets skipped.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
define → plan → build → verify → [simplify] → review → ship
|
|
9
26
|
```
|
|
10
27
|
|
|
28
|
+
Each phase has **deterministic gates** (checks) that must pass before advancing. The agent does the work; harness validates the result.
|
|
29
|
+
|
|
11
30
|
## Install
|
|
12
31
|
|
|
13
32
|
```bash
|
|
14
33
|
# Quick start (no install)
|
|
15
|
-
npx dev-harness-cli --
|
|
34
|
+
npx dev-harness-cli init --stack python --target my-project
|
|
16
35
|
|
|
17
36
|
# Global install
|
|
18
37
|
npm install -g dev-harness-cli
|
|
19
38
|
harness-dev --help
|
|
20
|
-
|
|
21
|
-
# Or clone and install
|
|
22
|
-
git clone https://github.com/bakr-bagaber/dev-harness.git
|
|
23
|
-
cd dev-harness && npm install -g .
|
|
24
39
|
```
|
|
25
40
|
|
|
26
|
-
Requires **Node.js >= 18**.
|
|
41
|
+
Requires **Node.js >= 18**. Zero runtime dependencies.
|
|
27
42
|
|
|
28
43
|
## Quick Start
|
|
29
44
|
|
|
30
45
|
```bash
|
|
31
|
-
# Scaffold a new project
|
|
32
|
-
harness-dev init --stack
|
|
33
|
-
|
|
34
|
-
# Check status
|
|
46
|
+
# 1. Scaffold a new project
|
|
47
|
+
harness-dev init --stack node --target my-app
|
|
35
48
|
cd my-app
|
|
49
|
+
|
|
50
|
+
# 2. Check status
|
|
36
51
|
harness-dev status
|
|
37
52
|
|
|
38
|
-
#
|
|
53
|
+
# 3. Run the DEFINE phase (agent writes specs)
|
|
39
54
|
harness-dev phase define
|
|
55
|
+
|
|
56
|
+
# 4. Validate (run gate checks)
|
|
57
|
+
harness-dev validate
|
|
58
|
+
|
|
59
|
+
# 5. Continue through pipeline
|
|
60
|
+
harness-dev phase plan
|
|
61
|
+
harness-dev phase build
|
|
62
|
+
harness-dev phase verify
|
|
63
|
+
harness-dev phase review
|
|
64
|
+
harness-dev phase ship
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Project Structure
|
|
68
|
+
|
|
69
|
+
When you run `harness-dev init`, all harness-managed files go into a `harness/` subfolder — keeping your project root clean:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
my-project/
|
|
73
|
+
├── AGENTS.md # Agent instructions (root — tools expect it here)
|
|
74
|
+
├── .gitignore # Git ignore rules
|
|
75
|
+
├── package.json # Your project's package file
|
|
76
|
+
├── src/ # Your source code
|
|
77
|
+
├── tests/ # Your tests
|
|
78
|
+
└── harness/ # All harness-managed files
|
|
79
|
+
├── config.json # Harness configuration + state
|
|
80
|
+
├── progress.md # Session state + lessons learned
|
|
81
|
+
├── sprint-contract.md # Pre-build agreement
|
|
82
|
+
├── evaluator-rubric.md # Quality scorecard
|
|
83
|
+
├── session-handoff.md # Context for session transitions
|
|
84
|
+
├── clean-state-checklist.md
|
|
85
|
+
├── features/
|
|
86
|
+
│ ├── feature-list.json # Feature tracking
|
|
87
|
+
│ └── feature-list.schema.json
|
|
88
|
+
├── docs/
|
|
89
|
+
│ ├── ARCHITECTURE.md # Architecture decisions
|
|
90
|
+
│ ├── CONSTRAINTS.md # Technical constraints
|
|
91
|
+
│ ├── DECISIONS.md # Decision log
|
|
92
|
+
│ ├── api-patterns.md # API conventions
|
|
93
|
+
│ ├── agents/ # Agent role guides
|
|
94
|
+
│ │ ├── planner.md
|
|
95
|
+
│ │ ├── generator.md
|
|
96
|
+
│ │ ├── evaluator.md
|
|
97
|
+
│ │ └── simplifier.md
|
|
98
|
+
│ └── phases/ # Phase instructions
|
|
99
|
+
│ ├── define.md
|
|
100
|
+
│ ├── plan.md
|
|
101
|
+
│ ├── build.md
|
|
102
|
+
│ ├── verify.md
|
|
103
|
+
│ ├── simplify.md
|
|
104
|
+
│ ├── review.md
|
|
105
|
+
│ └── ship.md
|
|
106
|
+
├── ci/
|
|
107
|
+
│ ├── github-actions.yml
|
|
108
|
+
│ └── gitlab-ci.yml
|
|
109
|
+
└── scripts/
|
|
110
|
+
├── init.sh
|
|
111
|
+
└── init.ps1
|
|
40
112
|
```
|
|
41
113
|
|
|
42
114
|
## Supported Stacks
|
|
43
115
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
|
47
|
-
|
|
48
|
-
| Kotlin | `build.gradle.kts`, `*.kt` | `build.gradle.kts` |
|
|
116
|
+
31 built-in stacks + custom stack support:
|
|
117
|
+
|
|
118
|
+
| Stack | Detection Files | Config File |
|
|
119
|
+
|-------|----------------|-------------|
|
|
49
120
|
| Node.js | `package.json`, `*.js`, `*.ts` | `package.json` |
|
|
50
|
-
|
|
|
121
|
+
| Python | `pyproject.toml`, `setup.py`, `*.py` | `pyproject.toml` |
|
|
51
122
|
| Rust | `Cargo.toml`, `*.rs` | `Cargo.toml` |
|
|
52
|
-
|
|
|
53
|
-
|
|
|
123
|
+
| Go | `go.mod`, `*.go` | `go.mod` |
|
|
124
|
+
| Java | `pom.xml`, `build.gradle`, `*.java` | `pom.xml` |
|
|
125
|
+
| C/C++ | `*.c`, `*.cpp`, `*.hpp` | `CMakeLists.txt` |
|
|
54
126
|
| .NET | `*.cs`, `*.fs` | `global.json` |
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
127
|
+
| Ruby | `Gemfile`, `*.rb` | `Gemfile` |
|
|
128
|
+
| PHP | `composer.json`, `*.php` | `composer.json` |
|
|
129
|
+
| Swift | `Package.swift`, `*.swift` | `Package.swift` |
|
|
130
|
+
| + 21 more | | |
|
|
131
|
+
|
|
132
|
+
**Custom stacks:** Pass any name to `--stack` and fill `stackMeta` in `harness/config.json` during DEFINE phase.
|
|
59
133
|
|
|
60
134
|
## Commands
|
|
61
135
|
|
|
62
136
|
| Command | Description |
|
|
63
137
|
|---------|-------------|
|
|
64
|
-
| `init` | Scaffold project
|
|
65
|
-
| `status` | Show current
|
|
66
|
-
| `phase <name>` | Invoke a phase |
|
|
67
|
-
| `validate` | Run gate checks |
|
|
68
|
-
| `config
|
|
69
|
-
| `
|
|
70
|
-
| `set
|
|
71
|
-
| `
|
|
138
|
+
| `init` | Scaffold a new project with harness structure |
|
|
139
|
+
| `status` | Show current phase, stack, features, gates |
|
|
140
|
+
| `phase <name>` | Invoke a pipeline phase |
|
|
141
|
+
| `validate` | Run gate checks for current phase |
|
|
142
|
+
| `config list` | List all 29 configurable parameters |
|
|
143
|
+
| `config get <key>` | Get a config value |
|
|
144
|
+
| `config set <key> <value>` | Set a config value |
|
|
145
|
+
| `set-mode <copilot\|autopilot>` | Switch execution mode |
|
|
146
|
+
| `pause` / `resume` | Pause/resume autopilot |
|
|
72
147
|
| `contract propose/review/status/escalate` | Sprint contract negotiation |
|
|
73
|
-
| `
|
|
74
|
-
| `checkpoint create <label>` |
|
|
75
|
-
| `rollback list/to/branch` |
|
|
148
|
+
| `learn <message>` | Save a lesson to progress.md |
|
|
149
|
+
| `checkpoint create <label>` | Create a manual checkpoint |
|
|
150
|
+
| `rollback list/to/branch` | Restore to checkpoint |
|
|
151
|
+
| `worktree create/list/remove` | Git worktree management |
|
|
152
|
+
| `detect-tool` | Detect available agent tools |
|
|
76
153
|
|
|
77
|
-
##
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
INIT → DEFINE → PLAN → BUILD → VERIFY → [SIMPLIFY] → REVIEW → SHIP
|
|
81
|
-
```
|
|
154
|
+
## Agent Tool Integration
|
|
82
155
|
|
|
83
|
-
|
|
84
|
-
- **Copilot** (default): one phase at a time, human decides when to advance
|
|
85
|
-
- **Autopilot**: auto-advances through pipeline after each gate passes
|
|
156
|
+
Harness works with any coding agent. Use `--agent-tool` during init to generate tool-specific files:
|
|
86
157
|
|
|
87
|
-
|
|
158
|
+
```bash
|
|
159
|
+
# Claude Code → generates CLAUDE.md
|
|
160
|
+
harness-dev init --stack node --agent-tool claude-code --target my-app
|
|
88
161
|
|
|
89
|
-
|
|
162
|
+
# Cursor → generates .cursorrules
|
|
163
|
+
harness-dev init --stack node --agent-tool cursor --target my-app
|
|
90
164
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"currentPhase":"define","mode":"copilot","recentLessons":[]}
|
|
165
|
+
# GitHub Copilot → generates .github/copilot-instructions.md
|
|
166
|
+
harness-dev init --stack node --agent-tool copilot --target my-app
|
|
94
167
|
```
|
|
95
168
|
|
|
96
|
-
|
|
169
|
+
**18 supported tools:** claude-code, cursor, windsurf, gemini, copilot, cline, roo, kilo-code, amazon-q, codex, opencode, continue, aider, antigravity, openclaw, pi, hermes, generic.
|
|
97
170
|
|
|
98
|
-
|
|
171
|
+
See [docs/TOOL_INTEGRATION.md](docs/TOOL_INTEGRATION.md) for per-tool setup guides.
|
|
99
172
|
|
|
100
|
-
##
|
|
173
|
+
## Gates
|
|
101
174
|
|
|
102
|
-
|
|
103
|
-
cli/
|
|
104
|
-
├── harness-dev.mjs — Entry point + command router
|
|
105
|
-
├── lib/
|
|
106
|
-
│ ├── args.mjs — Argument parser
|
|
107
|
-
│ ├── errors.mjs — Error handling + exit codes
|
|
108
|
-
│ ├── help.mjs — Help text + per-command help
|
|
109
|
-
│ ├── detect-stack.mjs — 13-stack detection engine
|
|
110
|
-
│ ├── vars.mjs — Stack variable resolution
|
|
111
|
-
│ ├── templates.mjs — Template engine ({{VAR}} substitution)
|
|
112
|
-
│ ├── state.mjs — Config I/O + phase transitions
|
|
113
|
-
│ ├── phases.mjs — Pure phase pipeline logic
|
|
114
|
-
│ ├── progress.mjs — progress.md reader/writer
|
|
115
|
-
│ ├── gates.mjs — Phase gate validation
|
|
116
|
-
│ ├── ralph-inner.mjs — Inner loop engine
|
|
117
|
-
│ ├── ralph-outer.mjs — Outer loop engine
|
|
118
|
-
│ ├── ralph-output.mjs — Phase instruction text builders
|
|
119
|
-
│ ├── modes.mjs — Copilot/autopilot modes
|
|
120
|
-
│ ├── contract.mjs — Sprint contract negotiation
|
|
121
|
-
│ ├── git.mjs — Centralized git operations
|
|
122
|
-
│ ├── paths.mjs — Centralized path resolution
|
|
123
|
-
│ ├── file-io.mjs — JSON/text I/O helpers
|
|
124
|
-
│ ├── output.mjs — JSON/human output helpers
|
|
125
|
-
│ ├── command-helpers.mjs— Shared arg parsing + phaseLabel
|
|
126
|
-
│ ├── constants.mjs — Centralized magic numbers
|
|
127
|
-
│ ├── scaffold.mjs — Stack-specific scaffolding assets
|
|
128
|
-
│ ├── validate-schema.mjs— Minimal JSON-schema validator
|
|
129
|
-
│ └── schemas/
|
|
130
|
-
│ └── stacks.json — 13-stack metadata (CLI-internal)
|
|
131
|
-
├── commands/ — 13 command handlers
|
|
132
|
-
│ ├── init.mjs, status.mjs, phase.mjs, validate.mjs, config.mjs
|
|
133
|
-
│ ├── learn.mjs, set-mode.mjs, pause.mjs, resume.mjs
|
|
134
|
-
│ └── contract.mjs, worktree.mjs, rollback.mjs, checkpoint.mjs
|
|
135
|
-
templates/ — Scaffold templates (AGENTS.md, init.sh, ci/, docs/, etc.)
|
|
136
|
-
schema/ — Published JSON schemas (harness-config, feature-list)
|
|
137
|
-
test/ — Test suites (test-t*.mjs + run-all.mjs)
|
|
138
|
-
dist/install.sh — One-liner installation script (curl-pipe-bash)
|
|
139
|
-
adapters/ — Tool adapters (claude-code, cursor, codex, hermes, generic)
|
|
140
|
-
docs/ — TOOL_INTEGRATION.md (per-tool setup guides)
|
|
141
|
-
references/ — Historical audit reports
|
|
142
|
-
history/ — Project audit log, changelog, decisions, issues
|
|
143
|
-
docs-site-templates/ — Docusaurus/Sphinx scaffolds (experimental)
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Agent Integration
|
|
147
|
-
|
|
148
|
-
harness-dev works with any coding agent via stdout JSON contracts and AGENTS.md project conventions. Use `--agent-tool` at scaffold time to generate tool-specific files, or `detect-tool` to discover which tools are configured.
|
|
175
|
+
Gates are deterministic checks that must pass before advancing to the next phase. Enable with:
|
|
149
176
|
|
|
150
177
|
```bash
|
|
151
|
-
|
|
152
|
-
harness-dev init --stack node --agent-tool claude-code --target my-project
|
|
153
|
-
|
|
154
|
-
# Or scaffold generically (AGENTS.md only — works with most tools)
|
|
155
|
-
harness-dev init --stack node --target my-project
|
|
156
|
-
|
|
157
|
-
# Detect which tools are configured
|
|
158
|
-
harness-dev detect-tool --target my-project
|
|
178
|
+
harness-dev config set gates.enabled true
|
|
159
179
|
```
|
|
160
180
|
|
|
161
|
-
|
|
181
|
+
| Phase | Gates |
|
|
182
|
+
|-------|-------|
|
|
183
|
+
| DEFINE | feature-branch, contract-agreed |
|
|
184
|
+
| PLAN | git-clean |
|
|
185
|
+
| BUILD | (coverage if enabled) |
|
|
186
|
+
| VERIFY | (coverage if enabled) |
|
|
187
|
+
| SIMPLIFY | git-clean, no-empty-dirs |
|
|
188
|
+
| REVIEW | branch-up-to-date, rubric-exists, readme-exists, architecture-doc, decisions-logged |
|
|
189
|
+
| SHIP | git-clean, tagged, changelog, readme-exists, license-exists, changelog-content, contributing-exists, no-empty-dirs |
|
|
162
190
|
|
|
163
|
-
|
|
191
|
+
## Configuration
|
|
164
192
|
|
|
165
|
-
|
|
193
|
+
All configuration lives in `harness/config.json`. View with:
|
|
166
194
|
|
|
167
195
|
```bash
|
|
168
|
-
harness-dev
|
|
169
|
-
cd my-project
|
|
170
|
-
# Claude reads CLAUDE.md automatically
|
|
171
|
-
harness-dev phase build
|
|
172
|
-
# Claude follows the phase instructions, runs:
|
|
173
|
-
harness-dev validate
|
|
196
|
+
harness-dev config list
|
|
174
197
|
```
|
|
175
198
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
```bash
|
|
179
|
-
harness-dev init --stack go --agent-tool codex --target my-project
|
|
180
|
-
cd my-project
|
|
181
|
-
# Codex reads AGENTS.md from project root
|
|
182
|
-
harness-dev status --json
|
|
183
|
-
# → Machine-readable state for agent decision-making
|
|
184
|
-
```
|
|
199
|
+
29 parameters across 8 groups: Execution, Stack, Agent Tool, Gates, Git, Phases, Agent Tones, Runtime State.
|
|
185
200
|
|
|
186
|
-
|
|
201
|
+
See [docs/CONFIGURATION.md](docs/CONFIGURATION.md) for full reference.
|
|
187
202
|
|
|
188
|
-
|
|
189
|
-
harness-dev init --stack rust --agent-tool cursor --target my-project
|
|
190
|
-
cd my-project
|
|
191
|
-
# .cursorrules generated with harness conventions
|
|
192
|
-
harness-dev phase build
|
|
193
|
-
```
|
|
203
|
+
## JSON Output
|
|
194
204
|
|
|
195
|
-
|
|
205
|
+
All commands support `--json` for machine-parseable output:
|
|
196
206
|
|
|
197
207
|
```bash
|
|
198
|
-
harness-dev
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
# → Agent implements → calls `harness-dev validate`
|
|
202
|
-
# → Gate passes → `harness-dev phase verify`
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
## API Reference
|
|
206
|
-
|
|
207
|
-
### JSON Output Contract (all commands)
|
|
208
|
-
|
|
209
|
-
```json
|
|
210
|
-
{
|
|
211
|
-
"command": "<command_name>",
|
|
212
|
-
"status": "ok" | "not_implemented" | "error",
|
|
213
|
-
"message": "Human-readable status or error detail"
|
|
214
|
-
}
|
|
208
|
+
harness-dev status --json
|
|
209
|
+
harness-dev phase define --json
|
|
210
|
+
harness-dev validate --json
|
|
215
211
|
```
|
|
216
212
|
|
|
217
|
-
Additional command-specific fields are included (e.g. `currentPhase`, `stack`, `mode`).
|
|
218
|
-
|
|
219
|
-
### Error Contract
|
|
220
|
-
|
|
221
213
|
```json
|
|
222
214
|
{
|
|
223
|
-
"
|
|
224
|
-
"
|
|
225
|
-
"
|
|
215
|
+
"command": "status",
|
|
216
|
+
"status": "ok",
|
|
217
|
+
"currentPhase": "define",
|
|
218
|
+
"stack": "node",
|
|
219
|
+
"mode": "copilot"
|
|
226
220
|
}
|
|
227
221
|
```
|
|
228
222
|
|
|
229
|
-
Errors
|
|
230
|
-
|
|
231
|
-
### Exit Codes
|
|
232
|
-
|
|
233
|
-
| Code | Meaning |
|
|
234
|
-
|------|---------|
|
|
235
|
-
| 0 | Success |
|
|
236
|
-
| 1 | Validation failure |
|
|
237
|
-
| 2 | Usage error |
|
|
238
|
-
| 3 | Internal error |
|
|
239
|
-
|
|
240
|
-
### All Commands
|
|
241
|
-
|
|
242
|
-
| Command | JSON Fields | Description |
|
|
243
|
-
|---------|-------------|-------------|
|
|
244
|
-
| `init` | `project`, `stack`, `filesCreated` | Scaffold harness project |
|
|
245
|
-
| `status` | `currentPhase`, `mode`, `stack`, `gateStatus`, `checksPassing`, `checksTotal`, `recentLessons`, `nextAction` | Show current state |
|
|
246
|
-
| `phase <name>` | `phase`, `previousPhase`, `gateResult`, `iteration` | Invoke a phase |
|
|
247
|
-
| `validate` | `phase`, `checks[]`, `overall`, `failures[]` | Run gate checks |
|
|
248
|
-
| `config get <key>` | `key`, `value` | Read config value |
|
|
249
|
-
| `config set <key> <value>` | `key`, `previous`, `current` | Write config value |
|
|
250
|
-
| `learn <msg>` | `lesson` | Append a lesson |
|
|
251
|
-
| `set-mode copilot\|autopilot` | `previous`, `current` | Switch mode |
|
|
252
|
-
| `pause` / `resume` | `paused` | Control autopilot |
|
|
253
|
-
| `contract propose\|review\|status\|escalate` | `status`, `agreed`, `round`, `pinned` | Sprint contract |
|
|
254
|
-
| `worktree create\|list\|prune\|remove` | `worktrees[]`, `action`, `name` | Git worktree |
|
|
255
|
-
| `checkpoint create <label>` | `tag`, `commit` | Git checkpoint |
|
|
256
|
-
| `rollback list\|to\|branch` | `checkpoints[]`, `restored` | Rollback |
|
|
257
|
-
|
|
258
|
-
## Project Structure
|
|
259
|
-
|
|
260
|
-
```
|
|
261
|
-
cli/ — CLI source
|
|
262
|
-
├── harness-dev.mjs — Entry point + command router
|
|
263
|
-
├── lib/ — Core libraries
|
|
264
|
-
│ ├── git.mjs — Centralized git operations
|
|
265
|
-
│ ├── state.mjs — Config I/O + phase transitions (re-exports phases.mjs)
|
|
266
|
-
│ ├── phases.mjs — Pure phase pipeline logic
|
|
267
|
-
│ ├── ralph-inner.mjs — Inner loop (work → validate → retry)
|
|
268
|
-
│ ├── ralph-outer.mjs — Outer loop (phase auto-advance)
|
|
269
|
-
│ ├── ralph-output.mjs — Phase instruction text builders
|
|
270
|
-
│ ├── gates.mjs — Phase gate validation
|
|
271
|
-
│ ├── contract.mjs — Sprint contract negotiation
|
|
272
|
-
│ ├── paths.mjs — Centralized path resolution
|
|
273
|
-
│ ├── file-io.mjs — JSON/text I/O helpers
|
|
274
|
-
│ ├── output.mjs — JSON/human output helpers
|
|
275
|
-
│ ├── command-helpers.mjs — Shared arg parsing + phaseLabel
|
|
276
|
-
│ ├── constants.mjs — Centralized magic numbers
|
|
277
|
-
│ ├── scaffold.mjs — Stack-specific scaffolding assets
|
|
278
|
-
│ ├── templates.mjs — Template engine
|
|
279
|
-
│ ├── detect-stack.mjs — Stack detection
|
|
280
|
-
│ ├── validate-schema.mjs — Minimal JSON-schema validator
|
|
281
|
-
│ └── schemas/stacks.json — Stack metadata (CLI-internal)
|
|
282
|
-
├── commands/ — Command handlers (13 commands)
|
|
283
|
-
templates/ — Scaffold templates (AGENTS.md, init.sh, ci/, docs/, etc.)
|
|
284
|
-
schema/ — Published JSON schemas (harness-config, feature-list)
|
|
285
|
-
test/ — Test suites (test-t*.mjs + run-all.mjs)
|
|
286
|
-
dist/install.sh — One-liner install script
|
|
287
|
-
adapters/ — Tool adapters (claude-code, cursor, codex, hermes, generic)
|
|
288
|
-
docs/ — TOOL_INTEGRATION.md (per-tool setup guides)
|
|
289
|
-
references/ — Historical audit reports (T5-T14)
|
|
290
|
-
history/ — Project audit log, changelog, decisions, issues
|
|
291
|
-
docs-site-templates/ — Docusaurus/Sphinx scaffolds (experimental, T25)
|
|
292
|
-
PROJECT_PLAN.md — Full task breakdown (T1-T20)
|
|
293
|
-
SPEC.md — Original architecture specification
|
|
294
|
-
dev-harness.md — Internal project note (Obsidian)
|
|
295
|
-
```
|
|
223
|
+
Errors go to stderr. Exit codes: `0` success, `1` validation, `2` usage, `3` internal.
|
|
296
224
|
|
|
297
225
|
## License
|
|
298
226
|
|
package/cli/commands/config.mjs
CHANGED
|
@@ -75,7 +75,7 @@ export default async function configCommand(args) {
|
|
|
75
75
|
// Human output — grouped table
|
|
76
76
|
process.stdout.write('═══ Harness Configuration ═══\n\n');
|
|
77
77
|
if (!ok) {
|
|
78
|
-
process.stdout.write(' No harness
|
|
78
|
+
process.stdout.write(' No harness/config.json found. Run: harness-dev init\n\n');
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -20,12 +20,13 @@ import { parseCommandArgs } from '../lib/command-helpers.mjs';
|
|
|
20
20
|
import { emitJson, emitHuman } from '../lib/output.mjs';
|
|
21
21
|
import { loadConfig } from '../lib/state.mjs';
|
|
22
22
|
import { getAllDetectionSignatures, AGENTS_MD_TOOLS, TOOL_REGISTRY } from '../lib/tool-registry.mjs';
|
|
23
|
+
import { AGENTS_PATH } from '../lib/paths.mjs';
|
|
23
24
|
|
|
24
25
|
export default async function detectToolCommand(args) {
|
|
25
26
|
const { json, targetDir } = parseCommandArgs(args);
|
|
26
27
|
|
|
27
28
|
const detected = [];
|
|
28
|
-
const hasAgentsMd = existsSync(
|
|
29
|
+
const hasAgentsMd = existsSync(AGENTS_PATH(targetDir));
|
|
29
30
|
|
|
30
31
|
// 1. Scan for tool-specific detection files (from registry)
|
|
31
32
|
for (const { tool, file } of getAllDetectionSignatures()) {
|
package/cli/commands/init.mjs
CHANGED
|
@@ -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
|
}
|
|
@@ -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
|
package/cli/commands/status.mjs
CHANGED
|
@@ -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 harness-dev 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
|
|
package/cli/lib/gates.mjs
CHANGED
|
@@ -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
|
}
|
package/cli/lib/help.mjs
CHANGED
package/cli/lib/paths.mjs
CHANGED
|
@@ -37,39 +37,118 @@ export const FEATURE_LIST_SCHEMA_PATH = resolve(SCHEMA_DIR, 'feature-list.schema
|
|
|
37
37
|
export const STACKS_SCHEMA_PATH = resolve(LIB_DIR, 'schemas', 'stacks.json');
|
|
38
38
|
|
|
39
39
|
// ── Project-relative paths (target project, not this CLI) ────────────────────
|
|
40
|
+
// All harness-managed files live under harness/ with subfolder grouping:
|
|
41
|
+
// harness/ — config, progress, contract, rubric, handoff, checklist
|
|
42
|
+
// harness/features/ — feature list + schema
|
|
43
|
+
// harness/docs/ — architecture, constraints, decisions, agent docs, phase docs
|
|
44
|
+
// harness/ci/ — CI/CD templates
|
|
45
|
+
// harness/scripts/ — init scripts
|
|
46
|
+
// AGENTS.md stays in root (agent tools expect it there).
|
|
40
47
|
|
|
41
48
|
/**
|
|
42
|
-
*
|
|
49
|
+
* Harness root directory within a target project.
|
|
50
|
+
* @param {string} targetDir
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export function HARNESS_DIR(targetDir) {
|
|
54
|
+
return resolve(targetDir, 'harness');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Path to a project's config.json (harness/config.json).
|
|
43
59
|
* @param {string} targetDir
|
|
44
60
|
* @returns {string}
|
|
45
61
|
*/
|
|
46
62
|
export function CONFIG_PATH(targetDir) {
|
|
47
|
-
return resolve(targetDir, '
|
|
63
|
+
return resolve(HARNESS_DIR(targetDir), 'config.json');
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
/**
|
|
51
|
-
* Path to a project's
|
|
67
|
+
* Path to a project's feature-list.json (harness/features/feature-list.json).
|
|
52
68
|
* @param {string} targetDir
|
|
53
69
|
* @returns {string}
|
|
54
70
|
*/
|
|
55
71
|
export function FEATURE_LIST_PATH(targetDir) {
|
|
56
|
-
return resolve(targetDir, '
|
|
72
|
+
return resolve(HARNESS_DIR(targetDir), 'features', 'feature-list.json');
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
/**
|
|
60
|
-
* Path to a project's sprint-contract.md.
|
|
76
|
+
* Path to a project's sprint-contract.md (harness/sprint-contract.md).
|
|
61
77
|
* @param {string} targetDir
|
|
62
78
|
* @returns {string}
|
|
63
79
|
*/
|
|
64
80
|
export function CONTRACT_PATH(targetDir) {
|
|
65
|
-
return resolve(targetDir, 'sprint-contract.md');
|
|
81
|
+
return resolve(HARNESS_DIR(targetDir), 'sprint-contract.md');
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
/**
|
|
69
|
-
* Path to a project's progress.md.
|
|
85
|
+
* Path to a project's progress.md (harness/progress.md).
|
|
70
86
|
* @param {string} targetDir
|
|
71
87
|
* @returns {string}
|
|
72
88
|
*/
|
|
73
89
|
export function PROGRESS_PATH(targetDir) {
|
|
74
|
-
return resolve(targetDir, 'progress.md');
|
|
90
|
+
return resolve(HARNESS_DIR(targetDir), 'progress.md');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Path to a project's evaluator-rubric.md (harness/evaluator-rubric.md).
|
|
95
|
+
* @param {string} targetDir
|
|
96
|
+
* @returns {string}
|
|
97
|
+
*/
|
|
98
|
+
export function RUBRIC_PATH(targetDir) {
|
|
99
|
+
return resolve(HARNESS_DIR(targetDir), 'evaluator-rubric.md');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Path to a project's session-handoff.md (harness/session-handoff.md).
|
|
104
|
+
* @param {string} targetDir
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
export function HANDOFF_PATH(targetDir) {
|
|
108
|
+
return resolve(HARNESS_DIR(targetDir), 'session-handoff.md');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Path to a project's clean-state-checklist.md (harness/clean-state-checklist.md).
|
|
113
|
+
* @param {string} targetDir
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
116
|
+
export function CHECKLIST_PATH(targetDir) {
|
|
117
|
+
return resolve(HARNESS_DIR(targetDir), 'clean-state-checklist.md');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Path to a project's ARCHITECTURE.md (harness/docs/ARCHITECTURE.md).
|
|
122
|
+
* @param {string} targetDir
|
|
123
|
+
* @returns {string}
|
|
124
|
+
*/
|
|
125
|
+
export function ARCHITECTURE_PATH(targetDir) {
|
|
126
|
+
return resolve(HARNESS_DIR(targetDir), 'docs', 'ARCHITECTURE.md');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Path to a project's CONSTRAINTS.md (harness/docs/CONSTRAINTS.md).
|
|
131
|
+
* @param {string} targetDir
|
|
132
|
+
* @returns {string}
|
|
133
|
+
*/
|
|
134
|
+
export function CONSTRAINTS_PATH(targetDir) {
|
|
135
|
+
return resolve(HARNESS_DIR(targetDir), 'docs', 'CONSTRAINTS.md');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Path to a project's DECISIONS.md (harness/docs/DECISIONS.md).
|
|
140
|
+
* @param {string} targetDir
|
|
141
|
+
* @returns {string}
|
|
142
|
+
*/
|
|
143
|
+
export function DECISIONS_PATH(targetDir) {
|
|
144
|
+
return resolve(HARNESS_DIR(targetDir), 'docs', 'DECISIONS.md');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Path to a project's AGENTS.md (stays in root — agent tools expect it there).
|
|
149
|
+
* @param {string} targetDir
|
|
150
|
+
* @returns {string}
|
|
151
|
+
*/
|
|
152
|
+
export function AGENTS_PATH(targetDir) {
|
|
153
|
+
return resolve(targetDir, 'AGENTS.md');
|
|
75
154
|
}
|
package/cli/lib/scaffold.mjs
CHANGED
|
@@ -212,12 +212,13 @@ work/
|
|
|
212
212
|
// ── Extra scaffolding files (not covered by templates) ───────────────────────
|
|
213
213
|
|
|
214
214
|
/**
|
|
215
|
-
* Inline content for files beyond the
|
|
216
|
-
* Key is relative output path, value is file content.
|
|
215
|
+
* Inline content for files beyond the template-based ones.
|
|
216
|
+
* Key is relative output path (under harness/), value is file content.
|
|
217
|
+
* All harness-managed files go under harness/ with subfolder grouping.
|
|
217
218
|
*/
|
|
218
219
|
export function getExtraFiles(stack) {
|
|
219
220
|
return {
|
|
220
|
-
'
|
|
221
|
+
'harness/features/feature-list.json': JSON.stringify({
|
|
221
222
|
version: '0.1',
|
|
222
223
|
features: [
|
|
223
224
|
{
|
|
@@ -232,7 +233,7 @@ export function getExtraFiles(stack) {
|
|
|
232
233
|
],
|
|
233
234
|
}, null, 2) + '\n',
|
|
234
235
|
|
|
235
|
-
'feature-list.schema.json': JSON.stringify({
|
|
236
|
+
'harness/features/feature-list.schema.json': JSON.stringify({
|
|
236
237
|
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
237
238
|
title: 'Feature List',
|
|
238
239
|
type: 'object',
|
|
@@ -266,7 +267,7 @@ export function getExtraFiles(stack) {
|
|
|
266
267
|
},
|
|
267
268
|
}, null, 2) + '\n',
|
|
268
269
|
|
|
269
|
-
'session-handoff.md': `# Session Handoff
|
|
270
|
+
'harness/session-handoff.md': `# Session Handoff
|
|
270
271
|
|
|
271
272
|
## Context
|
|
272
273
|
|
|
@@ -296,7 +297,7 @@ export function getExtraFiles(stack) {
|
|
|
296
297
|
| ... | ... |
|
|
297
298
|
`,
|
|
298
299
|
|
|
299
|
-
'clean-state-checklist.md': `# Clean State Checklist
|
|
300
|
+
'harness/clean-state-checklist.md': `# Clean State Checklist
|
|
300
301
|
|
|
301
302
|
Run this before starting any phase to ensure deterministic state.
|
|
302
303
|
|
|
@@ -308,10 +309,10 @@ Run this before starting any phase to ensure deterministic state.
|
|
|
308
309
|
|
|
309
310
|
## Harness
|
|
310
311
|
|
|
311
|
-
- [ ] \`harness
|
|
312
|
+
- [ ] \`harness/config.json\` exists and valid
|
|
312
313
|
- [ ] Current phase matches what we're about to run
|
|
313
|
-
- [ ] \`progress.md\` has latest Session State
|
|
314
|
-
- [ ] \`
|
|
314
|
+
- [ ] \`harness/progress.md\` has latest Session State
|
|
315
|
+
- [ ] \`harness/features/feature-list.json\` up-to-date
|
|
315
316
|
|
|
316
317
|
## Environment
|
|
317
318
|
|
|
@@ -320,7 +321,7 @@ Run this before starting any phase to ensure deterministic state.
|
|
|
320
321
|
- [ ] No stale background processes
|
|
321
322
|
`,
|
|
322
323
|
|
|
323
|
-
'ARCHITECTURE.md': `# Architecture
|
|
324
|
+
'harness/docs/ARCHITECTURE.md': `# Architecture
|
|
324
325
|
|
|
325
326
|
## Module Structure
|
|
326
327
|
|
|
@@ -330,7 +331,7 @@ src/
|
|
|
330
331
|
\`\`\`
|
|
331
332
|
`,
|
|
332
333
|
|
|
333
|
-
'CONSTRAINTS.md': `# Constraints
|
|
334
|
+
'harness/docs/CONSTRAINTS.md': `# Constraints
|
|
334
335
|
|
|
335
336
|
## Technical
|
|
336
337
|
|
|
@@ -351,7 +352,7 @@ src/
|
|
|
351
352
|
- Fail fast, fail loud
|
|
352
353
|
`,
|
|
353
354
|
|
|
354
|
-
'DECISIONS.md': `# Decisions
|
|
355
|
+
'harness/docs/DECISIONS.md': `# Decisions
|
|
355
356
|
|
|
356
357
|
<!-- Record architectural and design decisions here. Use the format below. -->
|
|
357
358
|
|
|
@@ -372,7 +373,7 @@ src/
|
|
|
372
373
|
| | | |
|
|
373
374
|
`,
|
|
374
375
|
|
|
375
|
-
'docs/api-patterns.md': `# API Patterns
|
|
376
|
+
'harness/docs/api-patterns.md': `# API Patterns
|
|
376
377
|
|
|
377
378
|
## Conventions
|
|
378
379
|
|
|
@@ -417,11 +418,10 @@ export function getVersionFileContent(stack) {
|
|
|
417
418
|
* Return .gitignore content for the given stack.
|
|
418
419
|
*/
|
|
419
420
|
export function getGitignoreContent(stack) {
|
|
420
|
-
return `# Harness
|
|
421
|
-
harness
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
progress.md
|
|
421
|
+
return `# Harness runtime state (regenerated by harness-dev)
|
|
422
|
+
harness/config.json
|
|
423
|
+
harness/features/feature-list.json
|
|
424
|
+
harness/progress.md
|
|
425
425
|
|
|
426
426
|
${GITIGNORE_PATTERNS[stack] || GITIGNORE_PATTERNS.generic}
|
|
427
427
|
# OS
|
package/cli/lib/templates.mjs
CHANGED
|
@@ -111,6 +111,42 @@ export function discoverTemplates() {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Map a template's relative path to its output path under harness/.
|
|
116
|
+
* Templates that are harness-managed go into harness/ subfolders.
|
|
117
|
+
* AGENTS.md stays in project root (agent tools expect it there).
|
|
118
|
+
*
|
|
119
|
+
* Mapping:
|
|
120
|
+
* AGENTS.md → AGENTS.md (root)
|
|
121
|
+
* harness-config.json → harness/config.json
|
|
122
|
+
* progress.md → harness/progress.md
|
|
123
|
+
* sprint-contract.md → harness/sprint-contract.md
|
|
124
|
+
* evaluator-rubric.md → harness/evaluator-rubric.md
|
|
125
|
+
* init.sh / init.ps1 → harness/scripts/init.sh
|
|
126
|
+
* ci/* → harness/ci/*
|
|
127
|
+
* docs/* → harness/docs/*
|
|
128
|
+
*
|
|
129
|
+
* @param {string} relPath — relative path within templates/
|
|
130
|
+
* @returns {string} — relative output path within target
|
|
131
|
+
*/
|
|
132
|
+
function mapTemplateOutput(relPath) {
|
|
133
|
+
// AGENTS.md stays in root
|
|
134
|
+
if (relPath === 'AGENTS.md') return relPath;
|
|
135
|
+
// harness-config.json → harness/config.json
|
|
136
|
+
if (relPath === 'harness-config.json') return 'harness/config.json';
|
|
137
|
+
// Top-level harness files → harness/
|
|
138
|
+
const harnessRootFiles = ['progress.md', 'sprint-contract.md', 'evaluator-rubric.md'];
|
|
139
|
+
if (harnessRootFiles.includes(relPath)) return `harness/${relPath}`;
|
|
140
|
+
// init scripts → harness/scripts/
|
|
141
|
+
if (relPath === 'init.sh' || relPath === 'init.ps1') return `harness/scripts/${relPath}`;
|
|
142
|
+
// ci/ → harness/ci/
|
|
143
|
+
if (relPath.startsWith('ci/')) return `harness/${relPath}`;
|
|
144
|
+
// docs/ → harness/docs/
|
|
145
|
+
if (relPath.startsWith('docs/')) return `harness/${relPath}`;
|
|
146
|
+
// Default: put under harness/
|
|
147
|
+
return `harness/${relPath}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
114
150
|
/**
|
|
115
151
|
* Run the template engine.
|
|
116
152
|
*
|
|
@@ -147,7 +183,8 @@ export function generateTemplates(opts) {
|
|
|
147
183
|
const relativePath = tmplPath.startsWith(TEMPLATES_DIR + '/')
|
|
148
184
|
? tmplPath.slice(TEMPLATES_DIR.length + 1)
|
|
149
185
|
: basename(tmplPath);
|
|
150
|
-
const
|
|
186
|
+
const outputRel = mapTemplateOutput(relativePath);
|
|
187
|
+
const outPath = join(target, outputRel);
|
|
151
188
|
const outDir = dirname(outPath);
|
|
152
189
|
|
|
153
190
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dev-harness-cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Agent-agnostic software development harness CLI — scaffold, phase orchestration, gate validation for any coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,11 @@
|
|
|
37
37
|
"cli"
|
|
38
38
|
],
|
|
39
39
|
"scripts": {
|
|
40
|
+
"lint": "eslint cli/",
|
|
41
|
+
"lint:fix": "eslint cli/ --fix",
|
|
40
42
|
"check": "node --check cli/harness-dev.mjs && echo 'Syntax OK'",
|
|
43
|
+
"test": "node test/run-all.mjs",
|
|
44
|
+
"test:verbose": "node test/run-all.mjs --verbose",
|
|
41
45
|
"postinstall": "node -e \"try{process.stdout.write('harness-dev installed. Run: npx harness-dev --help\\n')}catch(e){}\""
|
|
42
46
|
},
|
|
43
47
|
"devDependencies": {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Feature List",
|
|
4
|
+
"description": "Schema for feature_list.json — tracks features and their tasks through the pipeline",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["version", "features"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"version": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Schema version",
|
|
11
|
+
"default": "0.1"
|
|
12
|
+
},
|
|
13
|
+
"features": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"required": ["id", "name", "passes", "tasks"],
|
|
18
|
+
"properties": {
|
|
19
|
+
"id": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Unique feature identifier (e.g. feature-001)"
|
|
22
|
+
},
|
|
23
|
+
"name": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Human-readable feature name"
|
|
26
|
+
},
|
|
27
|
+
"description": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Optional feature description"
|
|
30
|
+
},
|
|
31
|
+
"passes": {
|
|
32
|
+
"type": "boolean",
|
|
33
|
+
"default": false,
|
|
34
|
+
"description": "All tasks completed and verified"
|
|
35
|
+
},
|
|
36
|
+
"tasks": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"items": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"required": ["id", "description", "status"],
|
|
41
|
+
"properties": {
|
|
42
|
+
"id": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Unique task identifier"
|
|
45
|
+
},
|
|
46
|
+
"description": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Task description"
|
|
49
|
+
},
|
|
50
|
+
"status": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"enum": ["pending", "in_progress", "complete", "blocked"],
|
|
53
|
+
"default": "pending",
|
|
54
|
+
"description": "Task status"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/templates/AGENTS.md
CHANGED
|
@@ -18,7 +18,7 @@ harness-dev validate # Check gate criteria
|
|
|
18
18
|
|
|
19
19
|
INIT → DEFINE → PLAN → BUILD → VERIFY → [SIMPLIFY] → REVIEW → SHIP
|
|
20
20
|
|
|
21
|
-
See `docs/phases/` for phase-specific instructions.
|
|
21
|
+
See `harness/docs/phases/` for phase-specific instructions.
|
|
22
22
|
|
|
23
23
|
## Agent Roles
|
|
24
24
|
|
|
@@ -31,21 +31,25 @@ See `docs/phases/` for phase-specific instructions.
|
|
|
31
31
|
|
|
32
32
|
## Key Files
|
|
33
33
|
|
|
34
|
+
All harness-managed files live under `harness/` (except `AGENTS.md` which stays in root for agent tool compatibility).
|
|
35
|
+
|
|
34
36
|
| File | Purpose |
|
|
35
37
|
|------|---------|
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
|
|
|
38
|
+
| `AGENTS.md` | This file — agent instructions (root) |
|
|
39
|
+
| `harness/config.json` | Config + state |
|
|
40
|
+
| `harness/features/feature-list.json` | Feature list with passes |
|
|
41
|
+
| `harness/progress.md` | Session state + lessons |
|
|
42
|
+
| `harness/sprint-contract.md` | Pre-build agreement |
|
|
43
|
+
| `harness/scripts/init.sh` | Install → verify → start |
|
|
44
|
+
| `harness/evaluator-rubric.md` | Quality scorecard (6 dimensions, 0-2) |
|
|
45
|
+
| `harness/docs/` | Architecture, constraints, decisions, agent guides, phase guides |
|
|
46
|
+
|
|
43
47
|
## Rules (non-negotiable)
|
|
44
48
|
|
|
45
49
|
1. No agent evaluates its own work — Evaluator always judges
|
|
46
|
-
2. Read `progress.md` + this file before each operation
|
|
50
|
+
2. Read `harness/progress.md` + this file before each operation
|
|
47
51
|
3. Commit frequently — each iteration is a checkpoint
|
|
48
|
-
4. If unsure → read the role guide in `docs/agents/`
|
|
52
|
+
4. If unsure → read the role guide in `harness/docs/agents/`
|
|
49
53
|
5. Never skip gates — run `harness-dev validate` after each phase
|
|
50
54
|
6. Fresh context per retry — pass `--git-ops` to `harness-dev phase <name>` to auto-reset the working tree on retry (off by default; agent-agnostic)
|
|
51
55
|
7. **No files in project root** unless they are harness-managed files (listed in Key Files above) or standard project files (README.md, LICENSE, CHANGELOG.md, CONTRIBUTING.md, .gitignore, and the stack config file like package.json/pyproject.toml/Cargo.toml). All source code, tests, scripts, and docs go in subdirectories.
|
|
@@ -9,5 +9,5 @@ You design the approach. You write criteria. You set scope.
|
|
|
9
9
|
- Set exclusions explicitly ("We will NOT build X")
|
|
10
10
|
- Hand off to Generator when criteria are clear
|
|
11
11
|
- In DEFINE: interview the user, write PRD in specs/*.md
|
|
12
|
-
- In PLAN: decompose features into tasks in
|
|
12
|
+
- In PLAN: decompose features into tasks in harness/features/feature-list.json
|
|
13
13
|
- Review gate criteria with Evaluator before proceeding
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"stack": "{{stack}}",
|
|
4
|
+
"stackMeta": null,
|
|
5
|
+
"agentTool": null,
|
|
6
|
+
"mode": "copilot",
|
|
7
|
+
"currentPhase": null,
|
|
8
|
+
"paused": false,
|
|
9
|
+
"features": {
|
|
10
|
+
"remaining": 0,
|
|
11
|
+
"passing": 0,
|
|
12
|
+
"total": 0
|
|
13
|
+
},
|
|
14
|
+
"gates": {
|
|
15
|
+
"enabled": false,
|
|
16
|
+
"checks": ["all"]
|
|
17
|
+
},
|
|
18
|
+
"git": {
|
|
19
|
+
"autoCommit": false,
|
|
20
|
+
"autoTag": false,
|
|
21
|
+
"resetOnRetry": false,
|
|
22
|
+
"branch": null,
|
|
23
|
+
"clean": true,
|
|
24
|
+
"hasUpstream": false,
|
|
25
|
+
"lastCommitMessage": null
|
|
26
|
+
},
|
|
27
|
+
"phases": {
|
|
28
|
+
"enabled": ["define", "plan", "build", "verify", "review", "ship"]
|
|
29
|
+
},
|
|
30
|
+
"agents": {
|
|
31
|
+
"tone": {
|
|
32
|
+
"planner": "Analytical and precise. Define clear boundaries.",
|
|
33
|
+
"generator": "Focused and practical. Build what's specified, nothing more.",
|
|
34
|
+
"evaluator": "Skeptical and thorough. Accept only compelling evidence.",
|
|
35
|
+
"simplifier": "Relentless about clarity. Delete more than you add."
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"maxRetries": 3,
|
|
39
|
+
"retryCount": 0,
|
|
40
|
+
"pipelineIteration": 0,
|
|
41
|
+
"gateHistory": []
|
|
42
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Progress: {{stackLabel}}
|
|
2
|
+
|
|
3
|
+
## Session State
|
|
4
|
+
|
|
5
|
+
Current Phase: not started
|
|
6
|
+
Current Feature: —
|
|
7
|
+
Gate Status: pending
|
|
8
|
+
Next Action: —
|
|
9
|
+
Retry Count: 0/{{maxRetries}}
|
|
10
|
+
|
|
11
|
+
## Lessons
|
|
12
|
+
|
|
13
|
+
<!-- Use \`harness-dev learn "lesson here"\` to add lessons. -->
|
|
14
|
+
|
|
15
|
+
## Checkpoints
|
|
16
|
+
|
|
17
|
+
| Tag | Phase | Date | Notes |
|
|
18
|
+
|-----|-------|------|-------|
|