# @nurix/etna

CLI to bootstrap a project with a Nurix harness. Copies the harness's `CLAUDE.md`, skills, agents, rules, and hooks (plus scripts, if the harness ships any) into the project so they live with the code — version-controllable, editable per project, and visible without leaving the repo.

A second install path also exists and is fully supported: `/plugin install <plugin>@nurixlabs` from inside Claude Code, which installs the harness as a plugin from the `nurixlabs/skills` marketplace. The two paths are first-class equals — pick the one that matches how you want to manage the harness. See [`docs/playbooks/harness/Readme.md`](../../docs/playbooks/harness/Readme.md#the-install-model) for the comparison.

## Usage

From the root of the project you want to bootstrap, pick a harness from the interactive menu:

```sh
npx --yes @nurix/etna
```

…or install one directly by name:

```sh
npx --yes @nurix/etna --name=code
```

The `--yes` flag skips npx's install confirmation, so the command runs unattended. On startup the CLI also **self-updates**: it checks the registry and, if the copy npx resolved is behind the latest published version, re-execs the latest before installing — so `npx --yes @nurix/etna` always runs the newest harness, even when npx has an older copy cached. The check is quick but synchronous; set `ETNA_NO_SELFUPDATE=1` to skip it (CI, offline, or a pinned local build).

After this completes (the `code` harness shown — the component folders differ per harness, and one a harness ships nothing for is simply absent):

```
your-project/
├── CLAUDE.md              ← harness policy (non-git repo: existing file → CLAUDE.md.bak.<ISO>)
├── AGENTS.md              ← Codex: CLAUDE.md byte-for-byte + a generated Codex runtime adapter
├── .prettierrc.json       ← markdown formatting (proseWrap: never)
├── .prettierignore
├── .vscode/settings.json  ← editor settings (markdown formatter on save)
└── .claude/
    ├── skills/            ← boundary, brainstorm, design, documentation, test-plan, workflow-mapper
    ├── agents/            ← brainstorm, change-review, lld-mode, maintenance, performance-review, retro
    ├── rules/             ← database.md, express.md, frontend.md, general.md, nomenclature.md, pnpm.md, testing.md
    ├── hooks/             ← generate-ownership-index (SessionStart) + hooks.json
    ├── settings.json      ← the hook is deep-merged in here (your existing keys preserved)
    └── harness.json       ← {harness, name, version, files, digest, owned} — the installed-harness sentinel
```

Alongside the Claude tree, the installer projects the **Codex** artifacts so the repo also works under OpenAI Codex: `AGENTS.md` (above), `.agents/skills/` (a mirror of `.claude/skills/`, Codex's skill-scan path), and `.codex/agents/*.toml` + `.codex/hooks.json` (its subagents and SessionStart hook) — each surface written only when the harness ships that component kind. Nothing is written outside the project. No marketplace registration, no `enabledPlugins` entry, no global cache dependency — Claude Code picks up the project-scoped agents/skills/rules on its next session start (and Codex reads `AGENTS.md` + `.agents/` + `.codex/`), and the hooks wire in through `.claude/settings.json`.

Re-run `npx --yes @nurix/etna --name=code` anytime to refresh the harness content with what the latest published package ships. Existing same-named files are overwritten, and a component the harness used to own but no longer ships is pruned; files you've added that aren't part of the harness are left alone. In a git repo the previous `CLAUDE.md` and `.claude/` are overwritten and refreshed in place (recover them with `git checkout`); outside a git repo `CLAUDE.md` is backed up to `CLAUDE.md.bak.<ISO-timestamp>` and the whole `.claude/` tree is snapshotted to `.claude.bak.<ISO-timestamp>` first. Because the new `harness.json` carries the installed `{version, digest}`, a re-run whose **major** version is ahead of your repo's stamped `harness-version` is what the reconcile (below) detects as a structural upgrade.

## After install: bootstrap the harness

Installing only _drops the files_ — the harness isn't active for your repo until you **baseline** it. Open Claude Code in the project and drive the reconcile chain once (the installed `CLAUDE.md` carries the same steps under its `## Maintenance` heading, for the agent):

1. **"baseline this repo"** (or "reconcile the harness") — runs the **`maintenance`** agent, which auto-selects **baseline mode** from the missing/behind `harness-version`. It builds the `docs/` tree to the harness shape, stamps `docs/structure.md` with the installed `harness-version`, proposes **modules** (as `module:` labels, derived from your code's bounded contexts), then hands back.
2. If it hands back with a follow-up (typically the module proposal), re-invoke it to finish.

Then work normally. Boundaries aren't generated as files — the `boundary` skill diagnoses cross-cutting contracts from source at plan time. This brownfield path reads your _actual_ code, not a blank slate, so it's safe to run on an existing project with real history.

One thing worth knowing on an existing repo:

- **Your root `CLAUDE.md` is replaced** with the harness policy file (recoverable from git, or from `CLAUDE.md.bak.<ISO>` outside a git repo). If it held project-specific instructions, fold them back in after install — the harness file is the policy skeleton; your domain specifics sit alongside it.

## Migrating from 0.1.x

`0.1.x` registered the harness as a Claude Code plugin via the `nurixlabs/skills` marketplace. `0.2.0` switches to direct file installation. When you re-run `etna` on a project that has a `0.1.x` install, the CLI removes the stale `enabledPlugins["<plugin>@nurixlabs"]` entry from `<project>/.claude/settings.json` so Claude Code doesn't double-load the same content from the global plugin cache.

The `nurixlabs` marketplace entry in `~/.claude/settings.json` is harmless to leave (it doesn't load anything by itself) — remove it manually if you want.

## Available harnesses

Listed by `npx --yes @nurix/etna` (no args) using the bundled `data/harness-index.json`.

| Harness | Source in etna | What it ships |
| --- | --- | --- |
| `code` | [`harness/code/`](../../harness/code/) | The canonical Nurix code-project harness — 6 governance/session agents, 7 workflow skills, 7 stack rules, the ownership-index `SessionStart` hook |
| `discover` | [`harness/discover/`](../../harness/discover/) | The product-discovery harness — 11 owned dimension-collector agents and 9 owned method rules, plus a `tooling-recommendations.md` at the project root. No skills, no hooks |
| `discussion` | [`harness/discussion/`](../../harness/discussion/) | The deliberation-workspace harness — 2 owned agents, 2 owned rules, and the `update-topics` hook that keeps the topic index current. No skills |
| `etna` | [`harness/etna/`](../../harness/etna/) | The forge's own governance harness — a deliberate fork of `code` reframed for authoring artifacts: 5 agents, 4 skills, 4 rules, the ownership-index hook |
| `hr` | [`harness/hr/`](../../harness/hr/) | The hiring / AI-steering evaluation harness — the `interview-eval` and `evaluator` skills plus one batching rule. No agents, no hooks |
| `kb-agi` | [`harness/kb-agi/`](../../harness/kb-agi/) | The self-improving knowledge-base harness — 3 owned vault agents (`kb-init`, `kb-audit`, `kb-apply`) and the owned `kb-export.mjs` script. No skills, no rules |

## How it works

Each harness lives in a single folder in the etna repo — `harness/<name>/`:

- `readme.md` — the **manifest**: identity (name, description, version, keywords) plus the shared `skills:`/`rules:`/`agents:` lists it pulls in from the top-level catalogs. Its body becomes the harness's published README.
- `CLAUDE.md` — the **policy**: pure policy, no frontmatter, installed as a verbatim copy at the consumer repo's root.
- Any owned `agents/`, `rules/`, `hooks/`, `scripts/` — purpose-built components that belong to this harness alone.

Shared root config common to every harness (`.prettierrc.json`, `.prettierignore`, `.vscode/settings.json`) lives once, in `harness/common/`, instead of duplicated per harness.

Two kinds of membership:

- **Shared, listed** — skills, rules, and agents live in the flat top-level `skills/`/`rules/`/`agents/` catalogs, reusable across harnesses. The manifest names the ones it pulls in:

```yaml
# harness/code/readme.md
---
name: code
skills:
  [boundary, test-plan, brainstorm, documentation, workflow-mapper, design]
rules: [general, frontend, express, nomenclature, database, testing, pnpm]
agents:
  [brainstorm, change-review, lld-mode, maintenance, performance-review, retro]
---
```

- **Owned, by location** — hooks, scripts (and any unlisted, harness-specific agents) live inside the harness folder (`harness/<name>/{agents,hooks,scripts}/`). No list — membership is presence in the folder, exactly how a Claude Code plugin ships them. Shared governance agents are listed instead (above) and resolved from the `agents/` catalog, local-first; a harness can still drop its own copy beside it to override one by name.

`scripts/build-data.js` (run by `prepack`) resolves the listed skills/rules/agents from the catalogs (local-first), copies any unlisted harness-owned agents plus the harness's `hooks/` and `scripts/` wholesale into `data/harnesses/<folder>/`, adds the `harness/common/` root files and each bundled agent's `*.codex.toml` twin, and writes the `harness.json` marker (version, `files` list, kit digest); a listed skill/rule/agent that doesn't exist is a hard error. The marketplace channel (`.github/scripts/publish.mjs`) reads the same harness folder and resolves the listed skills/rules/agents the same local-first way — but the two channels' **footprints differ**: the marketplace plugin carries no root policy, and its Codex surface is skills + hooks only (Codex custom agents ship through this CLI). The installer (`bin/etna.js`) reads from the bundle at install time; no GitHub fetch at runtime.

## Building / contributing

`data/` is generated. Don't edit by hand. To regenerate after editing the etna catalogs or manifests:

```sh
cd packages/cli
npm run build:data
```

`prepack` runs `build:data` automatically before `npm publish`, so the bundled data always matches the published package's commit.

## Publishing

CI auto-publishes on push to `dev` when `packages/cli/**` or `harness/**` change — see [`.github/workflows/publish-etna-to-npm.yml`](../../.github/workflows/publish-etna-to-npm.yml). The `publishConfig.access` is `public`, so `npx @nurix/etna` installs and self-updates without any npm token. Publishing still requires auth: the `NPM_PUBLISH_TOKEN` GitHub Actions secret must be a granular access token with **Bypass 2FA** enabled — classic automation tokens are rejected by the npmjs.com 2FA policy on the `nurix-tools` account.
