https://github.com/jeangnc/harness-kit
Framework for building multi-agent harnesses — author plugins once, target every vendor (Claude Code, Codex, …).
https://github.com/jeangnc/harness-kit
agent claude claude-code codex harness markdown marketplace multi-agent plugin skills typescript
Last synced: 25 days ago
JSON representation
Framework for building multi-agent harnesses — author plugins once, target every vendor (Claude Code, Codex, …).
- Host: GitHub
- URL: https://github.com/jeangnc/harness-kit
- Owner: jeangnc
- License: mit
- Created: 2026-05-04T19:51:49.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-05-21T18:54:26.000Z (about 1 month ago)
- Last Synced: 2026-05-22T03:50:38.673Z (about 1 month ago)
- Topics: agent, claude, claude-code, codex, harness, markdown, marketplace, multi-agent, plugin, skills, typescript
- Language: TypeScript
- Homepage:
- Size: 450 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README
# Harness Kit
`harness-kit` is a framework for building multi-agent harnesses — author plugins once, target every vendor.
## Quickstart
```sh
pnpm add @jean.gnc/harness-kit
harness init --marketplace my-harness --vendors claude,codex
# author src/plugins//... (see Authoring below)
harness compile
harness install
```
`harness` is the canonical bin; `harness-kit` is an alias.
## How it works
### `harness.yaml`
A declarative repo config with two required fields:
```yaml
marketplace: my-harness
vendors:
- claude
- codex
```
Created by `harness init`. Read automatically by `compile`, `install`, and `uninstall` via `--repo` (default `.`).
### Vendors
A vendor is a named target with a home directory, a per-vendor manifest path, an `emitPluginManifest` hook, `install`/`uninstall` hooks, and an optional `aliases` hook for fan-out symlink destinations. Built-in:
- **claude** — registers plugins via `claude plugin install`; creates a `CLAUDE.md` symlink whenever an `AGENTS.md` config file is linked.
- **codex** — primes the local cache by copying compiled plugins, then runs `codex plugin marketplace add`.
Writing your own vendor: see [docs/vendors.md](./docs/vendors.md).
### Source layout
```text
src/
/configs/ # vendor-specific config files (e.g. AGENTS.md, settings.json)
.fragments/ # source-only snippets; never emitted to dist (see Fragments vs companions)
plugins// # shared across every declared vendor
.claude-plugin/plugin.json # or PLUGIN.ts
skills//SKILL.md # or SKILL.ts + body.md
agents/.md # optional
commands/.md # optional
hooks/.json # optional
.claude-plugin/marketplace.json # lists the plugins to compile
```
→ Manifest fields, passthrough behavior, and plugin extensions: [docs/marketplace.md](./docs/marketplace.md).
### Compile pipeline
`harness compile` reads vendors from `harness.yaml`, validates the marketplace manifest, discovers plugins, and validates each `hookRequires` against the local artifact IDs. For each declared vendor it emits everything under a single top-level `dist//` subtree:
- `dist//.-plugin/marketplace.json` — the per-vendor marketplace manifest.
- `dist//plugins//` — compiled skills/agents/commands plus the per-vendor plugin manifest.
- `dist//configs/` — vendor-specific config files (everything under `src//configs/` minus dot-prefixed entries).
A top-level `dist/configs.json` link manifest enumerates the symlinks `harness install` will create.
### Install pipeline
`harness install` reads `harness.yaml`, then:
1. Applies links from `dist/configs.json`. Existing symlinks are replaced. Regular files are renamed to `.backup` (incrementing to `.backup.2`, `.backup.3`, …) before the symlink is created. Orphan symlinks pointing back into the repo are swept before applying.
2. For each declared vendor, discovers compiled plugins under `dist//plugins/` and calls the vendor's `install` hook.
`--mode` selects where Claude resolves plugins from: `local` (default) registers the freshly compiled `dist/claude/` tree as a local-scoped marketplace, so uncommitted builds install without publishing; `remote` pulls from the published marketplace. Codex is local-only and ignores the flag.
`--dry-run` prints the plan without touching the filesystem.
## Authoring a skill
Skills are auto-discovered by walking `/plugins//skills//SKILL.md`. The `name` field in frontmatter must match the skill's folder name.
```md
---
name: my-skill
description: What the skill does — single line.
companions:
- file: details.md
summary: Deeper notes.
---
# My Skill
For type safety conventions, see {{skill:dev-tools:typescript}}.
For TDD discipline, see {{skill:superpowers:test-driven-development}}.
For details, see {{ref:details.md}}.
{{companions}}
```
Compiles to (once per declared vendor):
```md
---
name: my-skill
description: What the skill does — single line.
companions:
- file: details.md
summary: Deeper notes.
---
# My Skill
For type safety conventions, see `dev-tools:typescript`.
For TDD discipline, see `superpowers:test-driven-development`.
For details, see `details.md`.
## Companion files (read on demand)
- `details.md` — Deeper notes.
```
### Composing with includes
Use `{{include:./.fragments/foo.md}}` to inline another Markdown file verbatim into the body. Includes expand recursively (an included file may itself contain `{{include:...}}`), and any other placeholders inside the inlined content are resolved against the **host skill**, not the include source.
Constraints:
- Path must be relative and stay inside the skill directory.
- Target must end in `.md`.
- Cycles are detected and fail the compile.
- Included files are not copied into `dist/` and are not flagged as undeclared companions.
### Fragments vs companions
Two kinds of secondary file appear next to skills, plugins, and configs. They have different lifecycles, so harness-kit gives them different conventions.
| Kind | Lifecycle | Convention | Ships to `dist/`? |
| --- | --- | --- | --- |
| **Fragment** | Compile-time only — inlined via `{{include:...}}` | Leading-dot path (e.g. `.fragments/foo.md`) | No |
| **Companion** | Runtime — read by the artifact (skill, hook, command, agent) at execution | No leading dot (e.g. `details.md`, `companions/foo.md`) | Yes |
**The rule is one sentence: leading dot = source-only.** Any source file or directory whose basename starts with `.` is stripped from dist (the vendor manifest dirs like `.claude-plugin/` are still emitted because each vendor writes them separately). Everything else ships as-is.
Example: a skill that inlines a shared snippet at compile time and ships a runtime companion alongside it.
```text
src/plugins/foo/skills/bar/
SKILL.md # uses {{include:./.fragments/snippet.md}} and references details.md
.fragments/snippet.md # stripped from dist
details.md # ships; consumed at runtime
```
After `harness compile`:
```text
dist//plugins/foo/skills/bar/
SKILL.md # snippet content inlined
details.md # ships unchanged
```
### Authoring with TypeScript (alternative)
If you prefer typed metadata, use `SKILL.ts` + sibling `body.md` instead of a single `SKILL.md`:
```ts
// SKILL.ts
import { defineSkill } from "@jean.gnc/harness-kit";
export default defineSkill({
name: "my-skill",
description: "What the skill does — single line.",
companions: [{ file: "details.md", summary: "Deeper notes." }],
});
```
```md
# My Skill
For type safety conventions, see {{skill:dev-tools:typescript}}.
```
A skill folder must contain exactly one of `SKILL.md` or `SKILL.ts`. Both forms run through the same placeholder pipeline and produce identical `dist/` output.
### Placeholder reference
Three reference kinds — `skill`, `command`, `agent` — each resolve against the **local marketplace ∪ installed plugins** and render the scoped `:` handle. The author never picks a prefix based on where a target lives; a single kind covers both local and cross-plugin references.
Resolution is two-tier: `harness compile` resolves strictly against local artifacts and **warns** (without failing) on a reference found in neither the local marketplace nor an installed plugin — so the compile stays green on a machine without those plugins installed. `harness check --mode=all` is the hard gate: the same unresolved reference fails the check. A malformed value (not `:` shape) is always a hard error.
| Placeholder | Renders to | Validation |
| --- | --- | --- |
| `{{skill::}}` | `` `:` `` | Resolves against local marketplace + installed plugins; warns on compile, fails `check --mode=all` |
| `{{command::}}` | `` `/:` `` | Resolves against local marketplace + installed plugins; warns on compile, fails `check --mode=all` |
| `{{agent::}}` | `` `:` `` | Resolves against local marketplace + installed plugins; warns on compile, fails `check --mode=all` |
| `{{ref:}}` | `` `` `` | Must be a file under the skill directory |
| `{{include:}}` | Inlined content of the target file | Must be a `.md` file inside the skill, no cycles |
| `{{companions}}` | Companion files section | Required iff companions are declared |
## CLI
```sh
harness init # scaffold harness.yaml + src//configs/ + src/plugins/
harness compile # compile src/ → dist/ per declared vendors
harness lint # lint compiled markdown under dist/
harness check # validate plugin references against local + installed sources
harness install # link configs + register plugins per declared vendor (--mode=local|remote)
harness uninstall # remove installed plugins per declared vendor
```
→ Full flag reference, bundled lint rules, and `package.json` integration: [docs/cli.md](./docs/cli.md).
## Programmatic API
Everything the CLI does is also a typed module API. See [docs/api.md](./docs/api.md).
## Requirements
- Node ≥ 24
- A package manager (pnpm, npm, yarn — pnpm is what this repo uses)
- The `claude` and/or `codex` CLIs on `$PATH` — only needed to run `harness install` / `uninstall`
## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md).