From 9b261d55f9a84515ba194baf59674a5b49337c3e Mon Sep 17 00:00:00 2001 From: coderloganli Date: Sat, 15 Aug 2026 15:41:35 -0400 Subject: [PATCH 1/2] Make the harness installable, and test that it stays so MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repository becomes its own marketplace: one entry whose source is the repository root, so `/plugin marketplace add` and `/plugin install` reach the plugin without a second repository to keep in step. A package.json so `npm test` runs the suite, and CI on ubuntu and windows. The one refusal is a path comparison, and a path bug appears on windows first. The version is now declared in three files, and the marketplace one is what tells an installed copy a newer one exists — a release that bumps plugin.json alone ships to nobody. Test 38 holds them equal, and holds the README install line to the names actually declared. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011mjhmFa9c2dwxmNeH5tboy --- .claude-plugin/marketplace.json | 18 ++++++++++++++++++ .github/workflows/test.yml | 27 +++++++++++++++++++++++++++ README.md | 17 ++++++++++++++++- docs/architecture.md | 6 ++++++ package.json | 14 ++++++++++++++ test/conventions.test.mjs | 23 ++++++++++++++++++++++- 6 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 .claude-plugin/marketplace.json create mode 100644 .github/workflows/test.yml create mode 100644 package.json diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..753b72a --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -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"] + } + ] +} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..fe0d3f9 --- /dev/null +++ b/.github/workflows/test.yml @@ -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@v4 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + + - run: npm test diff --git a/README.md b/README.md index c10bfe8..b1930c1 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 ``` diff --git a/docs/architecture.md b/docs/architecture.md index ba016a1..70b1257 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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. diff --git a/package.json b/package.json new file mode 100644 index 0000000..8f64605 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/test/conventions.test.mjs b/test/conventions.test.mjs index 7ba6d5f..0185724 100644 --- a/test/conventions.test.mjs +++ b/test/conventions.test.mjs @@ -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'; @@ -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 @`. + 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', + ); +}); From d55ba4a159817c4c22c769458468478582dd8e7e Mon Sep 17 00:00:00 2001 From: coderloganli Date: Sat, 15 Aug 2026 18:26:26 -0400 Subject: [PATCH 2/2] Run CI on actions that are not on a deprecated runtime checkout and setup-node at v4 target Node 20, which the runners now force onto Node 24 and will stop carrying. v7 targets node24 directly. Nothing about the matrix changes: the Node versions tested are the plugin's own. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011mjhmFa9c2dwxmNeH5tboy --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fe0d3f9..c3d08ff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,9 +18,9 @@ jobs: node: ['20', '22'] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: node-version: ${{ matrix.node }}