Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "https://json.schemastore.org/claude-code-marketplace.json",
"name": "harness",
"owner": {
"name": "coderloganli",
"email": "coder_logan_li@outlook.com"
},
"plugins": [
{
"name": "harness",
"source": "./",
"description": "Governs a project's documents, a task's workspace, and the ten stages a task passes through.",
"version": "0.2.0",
"license": "MIT",
"keywords": ["workflow", "worktree", "adr", "documentation", "code-review"]
}
]
}
27 changes: 27 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: test

on:
push:
branches: [main]
pull_request:

jobs:
test:
# The harness resolves workspaces, worktrees and repository paths itself, and
# the one refusal is a path comparison. Windows is in the matrix because that
# is where a path bug would appear first.
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node: ['20', '22']

steps:
- uses: actions/checkout@v7

- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node }}

- run: npm test
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Claude Code Harness

[![test](https://github.com/coderloganli/dev-harness/actions/workflows/test.yml/badge.svg)](https://github.com/coderloganli/dev-harness/actions/workflows/test.yml)

A harness for Claude Code: it makes Claude build software the way a careful engineer
does — read the design first, agree it before writing code, write the failing test
before the feature, work in a scratch space that cannot damage anything, and keep
Expand All @@ -16,7 +18,7 @@ It governs three things, and they are independent of each other:

> **Status: early.** The harness has been through one real task — its own, fixing
> five defects that its first run exposed — and the ten stages, the refusal and the
> approval dialogs all held. There is no marketplace entry yet.
> approval dialogs all held.

## Why

Expand Down Expand Up @@ -112,6 +114,19 @@ session it was worked in. Weeks later, ask for "that task about search ranking"
one was recorded — a task whose session was not says so, rather than offering a
resume that goes nowhere.

## Installing

The repository is its own marketplace, so adding it and installing from it are two
lines in Claude Code:

```
/plugin marketplace add coderloganli/dev-harness
/plugin install harness@harness
```

If the install summary says `Run /reload-plugins to activate.`, run that too. To pick
up a later version, `/plugin marketplace update harness`.

## Getting started

```
Expand Down
6 changes: 6 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ something a hook can do.
| **Templates** | Skeletons for the documents `init` creates |
| **Project config** | A few facts about one project |

The repository is also its own marketplace: `.claude-plugin/marketplace.json` lists
one plugin whose source is the repository root, so `/plugin marketplace add` and
`/plugin install` reach it without a second repository to keep in step. Its `version`
is what tells an installed copy that a newer one exists, so it moves with
`plugin.json`'s.

The line that matters: **a skill is text Claude may or may not follow; the server
and the hooks are programs that run either way.** Every rule below is placed on one
side of that line on purpose.
Expand Down
14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "claude-code-harness",
"version": "0.2.0",
"private": true,
"description": "Governs a project's documents, a task's workspace, and the ten stages a task passes through.",
"license": "MIT",
"type": "module",
"engines": {
"node": ">=20"
},
"scripts": {
"test": "node --test"
}
}
23 changes: 22 additions & 1 deletion test/conventions.test.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Test cases 9-11: conventions the plugin states about itself, asserted against the
// Test cases 9-11 and 38: conventions the plugin states about itself, asserted against the
// repository so that a fix cannot look done while a stale copy survives somewhere.

import assert from 'node:assert/strict';
Expand Down Expand Up @@ -65,3 +65,24 @@ test('11 — one product document, under 200 lines', () => {
'nothing should still link to docs/prd.md',
);
});

test('38 — one version, declared in three files, and an install line that reaches it', () => {
const plugin = JSON.parse(read('.claude-plugin/plugin.json'));
const marketplace = JSON.parse(read('.claude-plugin/marketplace.json'));
const pkg = JSON.parse(read('package.json'));

// The marketplace version is what tells an installed copy a newer one exists. A
// release that bumps plugin.json alone ships to nobody, and nothing at runtime
// would say so.
const entry = marketplace.plugins.find((p) => p.name === plugin.name);
assert.ok(entry, `marketplace.json must list a plugin named ${plugin.name}`);
assert.equal(entry.version, plugin.version, 'the marketplace entry must carry the plugin.json version');
assert.equal(pkg.version, plugin.version, 'package.json must carry the plugin.json version');

// README tells the user to type `/plugin install <plugin>@<marketplace>`.
assert.match(
read('README.md'),
new RegExp(`/plugin install ${plugin.name}@${marketplace.name}\\b`),
'the README install line must name the plugin and the marketplace as they are declared',
);
});