@nurix/etna 0.1.1

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 ADDED
@@ -0,0 +1,82 @@
1
+ # @nurix/etna
2
+
3
+ CLI to bootstrap a project with a Nurix harness. Installs a Claude Code plugin from the `nurixlabs/skills` marketplace and drops the harness's `CLAUDE.md` at the project root.
4
+
5
+ See [`../../harness/README.md`](../../harness/README.md) for what a harness is.
6
+
7
+ ## Usage
8
+
9
+ From the root of the project you want to bootstrap:
10
+
11
+ ```sh
12
+ GITHUB_TOKEN=ghp_... npx @nurix/etna nurix-harness
13
+ ```
14
+
15
+ After this completes, the directory contains:
16
+
17
+ ```
18
+ your-project/
19
+ ├── CLAUDE.md ← from harness/<folder>/CLAUDE.md (bundled in this package)
20
+ └── .claude/
21
+ ├── settings.json ← enabledPlugins updated
22
+ └── plugins/
23
+ └── nurix-harness/ ← fetched from nurixlabs/skills/plugins/nurix-harness/
24
+ ├── .claude-plugin/plugin.json
25
+ ├── skills/{documentation, workflow-mapper, introspect}/
26
+ ├── agents/{change-review, contract-monitor, dep-health, git-history, maintenance, review, spec-mode}.md
27
+ ├── scripts/{build-ownership-index, check-doc-drift}.sh
28
+ ├── rules/{general, nomenclature}.md
29
+ └── README.md
30
+ ```
31
+
32
+ Open Claude Code in the project; it will pick up the plugin on next start. If Claude Code is already running, run `/reload-plugins`.
33
+
34
+ ## Available harnesses
35
+
36
+ Listed by `npx @nurix/etna` (no args) using the bundled `data/harness-index.json`.
37
+
38
+ | Plugin name | Harness folder in etna | What it ships |
39
+ |---|---|---|
40
+ | `nurix-harness` | [`harness/code/`](../../harness/code/) | The canonical v7 harness — 7 audit agents, 3 workflow skills, 2 rules, 2 scripts |
41
+
42
+ ## Environment
43
+
44
+ | Variable | Required | Purpose |
45
+ |---|---|---|
46
+ | `GITHUB_TOKEN` | Yes (while `nurixlabs/skills` is private) | Personal access token with `repo:read` scope. Access to `nurixlabs/skills` is the access control for the harness. |
47
+ | `NURIX_SKILLS_REF` | No | Git ref to fetch from in `nurixlabs/skills`. Default `dev`. |
48
+
49
+ ## How it works
50
+
51
+ 1. Looks up `<plugin-name>` in the bundled `data/harness-index.json` to find the matching harness folder.
52
+ 2. Walks `nurixlabs/skills/plugins/<plugin-name>/` via the GitHub Contents API and downloads each file into `./.claude/plugins/<plugin-name>/`.
53
+ 3. Copies the bundled `data/harnesses/<harness-folder>/CLAUDE.md` to `./CLAUDE.md`.
54
+ 4. Adds `<plugin-name>@nurixlabs: true` to `./.claude/settings.json` under `enabledPlugins`.
55
+
56
+ Existing `CLAUDE.md` or plugin folder at the destination is renamed to `<name>.bak.<ISO-timestamp>` before install. Nothing is overwritten.
57
+
58
+ ## Why two sources
59
+
60
+ - **Plugin content** comes from the marketplace (`nurixlabs/skills`) so it's always the latest CI-published version.
61
+ - **`CLAUDE.md`** is bundled in this npm package, versioned with the package. Harness policy changes warrant a package version bump.
62
+
63
+ ## Building / contributing
64
+
65
+ `data/` is generated. Don't edit by hand. To regenerate after touching anything under `etna/harness/`:
66
+
67
+ ```sh
68
+ cd packages/cli
69
+ npm run build:data
70
+ ```
71
+
72
+ `prepack` runs `build:data` automatically before `npm publish`, so the bundled data always matches the published package's commit.
73
+
74
+ ## Publishing
75
+
76
+ From `packages/cli/`:
77
+
78
+ ```sh
79
+ npm publish --access restricted
80
+ ```
81
+
82
+ The `publishConfig.access` is `restricted` so the scoped package is private by default.
package/bin/etna.js ADDED
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+ // packages/cli/bin/etna.js
3
+ // `npx @nurix/etna <plugin-name>` — install a Nurix harness into the current directory.
4
+
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, copyFileSync, lstatSync } from 'node:fs';
6
+ import { join, dirname, resolve } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const pkgRoot = resolve(__dirname, '..');
11
+ const dataDir = join(pkgRoot, 'data');
12
+
13
+ const SKILLS_REPO = 'nurixlabs/skills';
14
+ const SKILLS_REF = process.env.NURIX_SKILLS_REF || 'dev';
15
+
16
+ function usage() {
17
+ console.log(`\nUsage: npx @nurix/etna <plugin-name>\n`);
18
+ const idx = loadHarnessIndex();
19
+ const names = Object.keys(idx.plugins);
20
+ if (names.length) {
21
+ console.log(`Available harnesses (plugin → harness folder):`);
22
+ for (const name of names) console.log(` ${name.padEnd(20)} → harness/${idx.plugins[name]}`);
23
+ }
24
+ console.log(`\nEnvironment:`);
25
+ console.log(` GITHUB_TOKEN Required if ${SKILLS_REPO} is private (token with repo:read scope)`);
26
+ console.log(` NURIX_SKILLS_REF Git ref to fetch from (default: dev)`);
27
+ console.log();
28
+ }
29
+
30
+ function loadHarnessIndex() {
31
+ const path = join(dataDir, 'harness-index.json');
32
+ if (!existsSync(path)) {
33
+ die(`harness-index.json missing — run \`npm run build:data\` from the package root.`);
34
+ }
35
+ return JSON.parse(readFileSync(path, 'utf8'));
36
+ }
37
+
38
+ function die(msg, code = 1) {
39
+ console.error(`error: ${msg}`);
40
+ process.exit(code);
41
+ }
42
+
43
+ function ghHeaders() {
44
+ const h = {
45
+ Accept: 'application/vnd.github+json',
46
+ 'User-Agent': '@nurix/etna',
47
+ 'X-GitHub-Api-Version': '2022-11-28',
48
+ };
49
+ if (process.env.GITHUB_TOKEN) h.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
50
+ return h;
51
+ }
52
+
53
+ // Recursively download a GitHub repo subdirectory into a local destination.
54
+ async function fetchPluginFolder(pluginName, destDir) {
55
+ const apiUrl = `https://api.github.com/repos/${SKILLS_REPO}/contents/plugins/${pluginName}?ref=${SKILLS_REF}`;
56
+ await fetchDir(apiUrl, destDir);
57
+ }
58
+
59
+ async function fetchDir(apiUrl, destDir) {
60
+ const res = await fetch(apiUrl, { headers: ghHeaders() });
61
+ if (res.status === 401 || res.status === 403) {
62
+ die(
63
+ `GitHub returned ${res.status} for ${apiUrl}.\n` +
64
+ `${SKILLS_REPO} is private — set GITHUB_TOKEN (a personal access token with repo:read scope).\n` +
65
+ ` export GITHUB_TOKEN=ghp_...`,
66
+ );
67
+ }
68
+ if (res.status === 404) {
69
+ die(`Plugin not found in ${SKILLS_REPO}@${SKILLS_REF}. Has the marketplace publish pipeline run yet?`);
70
+ }
71
+ if (!res.ok) die(`GitHub API ${res.status}: ${await res.text()}`);
72
+
73
+ const items = await res.json();
74
+ mkdirSync(destDir, { recursive: true });
75
+
76
+ for (const item of items) {
77
+ const localPath = join(destDir, item.name);
78
+ if (item.type === 'dir') {
79
+ await fetchDir(item.url, localPath);
80
+ } else if (item.type === 'file') {
81
+ const dl = item.download_url;
82
+ if (!dl) die(`File ${item.path} has no download_url`);
83
+ const fileRes = await fetch(dl, { headers: ghHeaders() });
84
+ if (!fileRes.ok) die(`Failed to download ${item.path}: ${fileRes.status}`);
85
+ const buf = Buffer.from(await fileRes.arrayBuffer());
86
+ writeFileSync(localPath, buf);
87
+ }
88
+ }
89
+ }
90
+
91
+ // Move <path> to <path>.bak.<timestamp> if it exists. Returns the backup path, or null.
92
+ function backupIfExists(path) {
93
+ if (!existsSync(path) && !isSymlink(path)) return null;
94
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
95
+ const backup = `${path}.bak.${ts}`;
96
+ renameSync(path, backup);
97
+ return backup;
98
+ }
99
+
100
+ function isSymlink(path) {
101
+ try {
102
+ return lstatSync(path).isSymbolicLink();
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+
108
+ // Add pluginName to enabledPlugins in .claude/settings.json (creating the file if needed).
109
+ // Uses the conventional shape `"plugin-name@marketplace": true`.
110
+ function enablePlugin(targetDir, pluginName) {
111
+ const claudeDir = join(targetDir, '.claude');
112
+ mkdirSync(claudeDir, { recursive: true });
113
+ const settingsPath = join(claudeDir, 'settings.json');
114
+
115
+ let settings = {};
116
+ if (existsSync(settingsPath)) {
117
+ try {
118
+ settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
119
+ } catch (e) {
120
+ die(`Failed to parse existing ${settingsPath}: ${e.message}`);
121
+ }
122
+ }
123
+
124
+ settings.enabledPlugins = settings.enabledPlugins || {};
125
+ const key = `${pluginName}@nurixlabs`;
126
+ settings.enabledPlugins[key] = true;
127
+
128
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 4) + '\n');
129
+ return { settingsPath, key };
130
+ }
131
+
132
+ async function main(args) {
133
+ if (args.includes('-h') || args.includes('--help') || args.length === 0) {
134
+ usage();
135
+ process.exit(args.length === 0 ? 1 : 0);
136
+ }
137
+
138
+ const pluginName = args[0];
139
+ const idx = loadHarnessIndex();
140
+ const harnessFolder = idx.plugins[pluginName];
141
+ if (!harnessFolder) {
142
+ die(
143
+ `Unknown harness '${pluginName}'. Available: ${Object.keys(idx.plugins).join(', ') || '(none)'}`,
144
+ );
145
+ }
146
+
147
+ const bundledClaude = join(dataDir, 'harnesses', harnessFolder, 'CLAUDE.md');
148
+ if (!existsSync(bundledClaude)) {
149
+ die(`Bundled CLAUDE.md missing for harness '${harnessFolder}'. Rebuild package data.`);
150
+ }
151
+
152
+ const targetDir = process.cwd();
153
+ const pluginDir = join(targetDir, '.claude', 'plugins', pluginName);
154
+ const claudeMdDest = join(targetDir, 'CLAUDE.md');
155
+
156
+ console.log(`\nInstalling ${pluginName} (from harness/${harnessFolder}) into ${targetDir}`);
157
+
158
+ // 1. Backup conflicts
159
+ const pluginBackup = backupIfExists(pluginDir);
160
+ if (pluginBackup) console.log(` backed up existing plugin folder → ${pluginBackup}`);
161
+ const claudeBackup = backupIfExists(claudeMdDest);
162
+ if (claudeBackup) console.log(` backed up existing CLAUDE.md → ${claudeBackup}`);
163
+
164
+ // 2. Fetch plugin from nurixlabs/skills marketplace
165
+ console.log(` fetching plugin from ${SKILLS_REPO}@${SKILLS_REF}…`);
166
+ await fetchPluginFolder(pluginName, pluginDir);
167
+ console.log(` installed plugin → ${pluginDir}`);
168
+
169
+ // 3. Copy bundled CLAUDE.md to project root
170
+ copyFileSync(bundledClaude, claudeMdDest);
171
+ console.log(` installed CLAUDE.md → ${claudeMdDest}`);
172
+
173
+ // 4. Enable plugin in .claude/settings.json
174
+ const { settingsPath, key } = enablePlugin(targetDir, pluginName);
175
+ console.log(` enabled '${key}' in ${settingsPath}`);
176
+
177
+ console.log(`\nDone. Open Claude Code in this directory; it will load the plugin on next start.`);
178
+ console.log(`Run /plugin to verify, or /reload-plugins if it's already running.\n`);
179
+ }
180
+
181
+ main(process.argv.slice(2)).catch((e) => {
182
+ console.error(e?.stack || e);
183
+ process.exit(1);
184
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "plugins": {
3
+ "nurix-harness": "code"
4
+ }
5
+ }
@@ -0,0 +1,181 @@
1
+ # Agent Instructions
2
+
3
+ This file defines how AI agents (Claude, GPT, etc.) should work on this project.
4
+
5
+ ---
6
+
7
+ ## Getting Started
8
+
9
+ - Read **[`docs/README.md`](docs/README.md)** for documentation root (architecture, features, operations, history)
10
+ - Read **[`docs/memory/`](docs/memory/)** for agent memory files (one per agent + `main.md` for shared project memory)
11
+ - Ignore **[`README.md`](README.md)** — it's for human reference only
12
+ - Check **[`package.json`](package.json)** for available scripts. **Detect the package manager from the lockfile** (`pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `package-lock.json` → npm) and use the matching command. Don't hardcode a specific manager.
13
+ - Look at similar modules/files and follow existing patterns
14
+
15
+ > **If you are a Claude agent** (for example, `claude-4.5-sonnet`) — other agents ignore:
16
+ > Check **[`.claude/rules/`](.claude/rules/)** for specialized instructions (automatically loaded into memory)
17
+
18
+ ---
19
+
20
+ ## Workflows
21
+
22
+ For new features or new systems, enter **Spec Mode** before Plan Mode.
23
+
24
+ ### Spec Mode → Plan Mode → Completion
25
+
26
+ 1. **Spec Mode**: Create or update a feature spec in `docs/features/{feature}.md` using the **Feature Documentation template** (Status: Draft) — see the documentation skill (`.claude/skills/documentation/references/templates.md`). Use the **`spec-mode`** agent (`.claude/agents/spec-mode.md`) for codebase research; its findings drop directly into the template's research-derived sections (Affected Modules, Conventions to Follow, Current Behavior, Ownership Conflicts). Interview the user for the design-intent sections (Goal, Goals & Non-Goals, Background, Approach). Present the spec for approval before proceeding.
27
+
28
+ 2. **Plan Mode**: Once the spec is approved, create a plan in `docs/history/plans/YYYY-MM-DD-feature-name.md` using the **Plan template**. Link back to the feature spec in the **Spec** field. The template's **Proposed Changes** subsections are load-bearing — the `introspect` skill parses them to check guardrail impact.
29
+
30
+ 3. **Completion**: When a plan is fully implemented:
31
+ - Mark the plan `Status: Complete` and fill the **Completed:** date. The plan is now frozen.
32
+ - **Flip the feature spec** to `Status: Active` and extend it in place with the implementation sections (Architecture, Key Functions, State, Redux, Files). Same file. Confirm `owns`/`touches` frontmatter against actual code; resolve any unresolved Open Questions by folding them into Background.
33
+ - If `docs/routing.md` exists, use it to find other docs affected by the changes; otherwise glob `docs/features/**/*.md` and check `owns`/`touches` frontmatter. Update affected docs — or create new ones if a touched area has no doc yet.
34
+ - Create a changelog entry in `docs/history/changelog/YYYY-MM-DD-feature-name.md`.
35
+
36
+ `docs/features/` is _what_ we're building (lives on as documentation). `docs/history/plans/` is _how_ we built it (historical record).
37
+
38
+ ### Context Routing
39
+
40
+ Before modifying code in an unfamiliar area:
41
+
42
+ 1. If **[`docs/routing.md`](docs/routing.md)** exists, read it for file-pattern → doc/agent mapping. If it doesn't exist yet, skip — the codebase may not have owned features.
43
+ 2. If **[`docs/OWNERSHIP_INDEX.md`](docs/OWNERSHIP_INDEX.md)** exists, check it for the owning feature doc and read that doc before making changes. If it doesn't exist, glob `docs/features/**/*.md` and check `owns` frontmatter directly.
44
+ 3. If a `docs/memory/` file exists for the domain you're working in, read it for domain knowledge and failure modes
45
+
46
+ For the protocol on *writing* to memory, see **Project Memory** below.
47
+
48
+ ### Project Memory
49
+
50
+ Project-specific learnings live in `docs/memory/` (git-tracked). Cross-project user preferences live in `~/.claude/` (untracked).
51
+
52
+ | File | Owner | Contents |
53
+ |------|-------|----------|
54
+ | `docs/memory/main.md` | Main agent | Project-wide patterns, conventions confirmed in this codebase, recurring failure modes, decisions whose rationale isn't obvious from the code |
55
+ | `docs/memory/{agent-name}.md` | The named sub-agent | Strategy memory for that agent — how to be more effective on this project. Never user-centric. |
56
+ | `~/.claude/projects/...` | Auto-memory system | User preferences, role, communication style, cross-project habits |
57
+
58
+ **Override the default auto-memory location for project-relevant facts.** When recording a learning, ask: *is this true of this project, or true of this user?* Project facts → `docs/memory/`. User facts → `~/.claude/`.
59
+
60
+ `docs/memory/main.md` is scaffolded during baseline. Append entries dated `## YYYY-MM-DD — {topic}`; oldest at bottom is fine.
61
+
62
+ ### Out-of-Scope Discovery
63
+
64
+ When implementing a plan, if you discover work that is important but outside the current plan's scope:
65
+
66
+ - Append it to `docs/history/backlog.md` under the appropriate priority (P0–P3)
67
+ - Format: `- **[P1]** Description — discovered by [plan-name or agent-name]`
68
+ - Do NOT ask permission. Just append and mention it in your response.
69
+
70
+ ### Doc Ownership
71
+
72
+ Feature docs in `docs/features/` must include YAML frontmatter with `owns`, `touches`, and `depends_on` fields. Architecture docs describe cross-cutting patterns and don't own specific files; operations docs describe procedures. Only feature docs need frontmatter.
73
+
74
+ The spec-mode agent checks for ownership conflicts during research. If conflicts are found, address them in the spec before proceeding.
75
+
76
+ ---
77
+
78
+ ## Agents
79
+
80
+ Seven sub-agents and three skills are defined under `.claude/`. Reach for them by name or by trigger phrase:
81
+
82
+ | Component | Type | Use for | Trigger phrases |
83
+ |-----------|------|---------|-----------------|
84
+ | `spec-mode` | Agent | Codebase research before writing a feature spec | Invoked during Spec Mode |
85
+ | `maintenance` | Agent | Periodic audit — fix docs drifting from code | "run maintenance", "audit docs", "check drift" |
86
+ | `contract-monitor` | Agent | Post-implementation contract audit — scan code against guardrails | "check contracts", "audit guardrails", "code drift" |
87
+ | `review` | Agent | Code health — bundle, performance, anti-patterns | "weekly review", "review code health", "check bundle" |
88
+ | `dep-health` | Agent | Dependency supply-chain audit | "audit dependencies", "check CVEs", "dep health" |
89
+ | `git-history` | Agent | Classify recent commits into a velocity/severity CSV | "classify commits", "commit history report" |
90
+ | `change-review` | Agent | Velocity heatmap, risk zones (high churn + low coverage) | "where is risk accumulating", "velocity report" |
91
+ | `documentation` | Skill | Baseline audit, scaffolding, templates | "baseline docs", "create docs", "audit docs" |
92
+ | `workflow-mapper` | Skill | Discover and diagram codebase workflows | "map workflows", "trace flows" |
93
+ | `introspect` | Skill | Pre-implementation impact analysis against guardrails | "introspect this plan", "impact analysis" |
94
+
95
+ ### Guardrails
96
+
97
+ `docs/operations/guardrails.md` documents **cascading failure boundaries** — places where editing one module silently breaks another (shared state shapes, entity registration checklists, cross-module string coupling, persistence rehydration, etc.). Two components depend on it:
98
+
99
+ - **`contract-monitor`** (post-implementation) — scans code for violations of documented guardrails
100
+ - **`introspect`** (pre-implementation) — checks a proposed plan against guardrails to surface cascading risk before coding
101
+
102
+ To create or extend guardrails, invoke the `documentation` skill with "create guardrails" / "add guardrail". Without guardrails populated, `contract-monitor` and `introspect` are not useful.
103
+
104
+ ---
105
+
106
+ ## Boundaries & Defaults
107
+
108
+ Unless explicitly requested:
109
+
110
+ ❌ **Tests** — Never generate, update, or modify test files.
111
+ ❌ **Dependencies** — Never upgrade frameworks, libraries, or dependencies.
112
+ ❌ **Compatibility** — Never maintain backward compatibility unless specified.
113
+ ❌ **Browser Automation** — Never use "Claude in Chrome" (CDP/MCP browser tools) unless the user explicitly requests it.
114
+ ❌ **Protected Files** — Never modify these read-only files:
115
+ `AGENTS.md`, `docs/operations/protected-files.md`
116
+ ✅ **Documentation** — Keep `docs/` updated when code changes affect documented behavior.
117
+ ⚠️ **Plans** — Plans are created directly in `docs/history/plans/` (see Workflow → Plan Mode). Once `Status: Complete` or `Status: Abandoned`, the file is frozen — never edited. If scope changes after completion, write a new dated plan that links back to the old one.
118
+ ⚠️ **Tool Issues** — If any CLI tool, shell alias, or MCP tool behaves unexpectedly (syntax errors, silent failures, wrong defaults), report the exact tool name, command, and error to the user so they can fix or remove the underlying library.
119
+
120
+ ---
121
+
122
+ ## File Structure
123
+
124
+ - Add file path at top of each file:
125
+ `// src/modules/example.js - Module for handling user authentication`
126
+ - Add a 1-line function description as a comment above each function:
127
+ `// Extract JWT token from request based on endpoint type`
128
+ - Keep related code together in a logical hierarchy.
129
+ - No inline comments that restate obvious code. No commented-out code. If a block of code needs a comment to be understandable, refactor for clarity instead.
130
+
131
+ ---
132
+
133
+ ## Documentation
134
+
135
+ - **[`docs/README.md`](docs/README.md)** — Documentation root (start here for all docs)
136
+ - Only `CLAUDE.md`, `AGENTS.md`, and `README.md` are allowed in the repository root
137
+ - One canonical source per topic — link, don't copy
138
+
139
+ ### Folder Structure
140
+
141
+ The six procedural folders below are fixed. Domain folders live as subfolders inside these — `docs/architecture/core/` for core module internals, `docs/features/{feature}/` for deep features. Discovery flows through **[`docs/routing.md`](docs/routing.md)** when present.
142
+
143
+ | Folder | Purpose | Mutability |
144
+ | -------------------- | ------------------------------------------------------ | --------------------- |
145
+ | `docs/architecture/` | System-wide patterns, technical decisions, API specs | Living |
146
+ | `docs/features/` | How implemented features work today | Living |
147
+ | `docs/operations/` | Setup, development, deployment | Living |
148
+ | `docs/history/` | Plans, changelogs, backlog | Frozen or append-only |
149
+ | `docs/memory/` | Project knowledge + agent state | Living |
150
+ | `docs/tests/` | Test plans and automation | Living |
151
+
152
+ ### Naming & Lifecycle
153
+
154
+ - Features: `kebab-case.md` — living reference, status `Active`
155
+ - Plans: `YYYY-MM-DD-kebab-case.md` — frozen after `Complete` or `Abandoned`
156
+ - Changelogs: `YYYY-MM-DD-kebab-case.md` — append-only
157
+ - Memory: `{agent-name}.md` — agent-maintained
158
+
159
+ ### Documentation Maintenance
160
+
161
+ - **Prerequisite**: Before creating changelogs, feature docs, or plans, check that `docs/memory/index.md` exists and records a baseline. If not, prompt the user to baseline docs first.
162
+ - After plan completion: create a `docs/history/changelog/YYYY-MM-DD-feature-name.md` entry + update the index in `docs/history/CHANGELOG.md` (no confirmation needed).
163
+ - Update related docs affected by the changes (use `docs/routing.md` to find the right files)
164
+ - Non-plan changes (refactors, bug fixes, API changes): prompt user first
165
+ - Multi-commit changes: include `Commits: abc1234, def5678`
166
+ - Missing `docs/README.md`: create a documentation index
167
+
168
+ ### Keeping the Loop Tight
169
+
170
+ When you create, move, or delete a doc:
171
+ - If **[`docs/routing.md`](docs/routing.md)** exists, update it — file-pattern routes and keyword routes
172
+ - Update **[`docs/README.md`](docs/README.md)** — add/remove the doc's entry
173
+ - If creating a new top-level domain folder and routing.md exists: add the folder to routing.md's **Top-Level Folders** table
174
+
175
+ When you move or rename source files:
176
+ - Check `owns`/`touches` frontmatter in the owning doc. Find the owning doc via `docs/routing.md` or `docs/OWNERSHIP_INDEX.md` if either is present; otherwise glob `docs/features/**/*.md` and match against `owns` patterns. Update paths to match the new locations.
177
+
178
+ When you create a new doc that owns code:
179
+ - Add `owns`, `touches`, `depends_on` YAML frontmatter
180
+ - Verify `owns` globs don't conflict with existing docs (check `docs/OWNERSHIP_INDEX.md` if present, otherwise compare against `owns` frontmatter across `docs/features/`)
181
+
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@nurix/etna",
3
+ "version": "0.1.1",
4
+ "description": "Bootstrap a project with a Nurix harness — installs a Claude Code plugin from the nurixlabs/skills marketplace and drops the harness's CLAUDE.md at the project root.",
5
+ "type": "module",
6
+ "bin": {
7
+ "etna": "bin/etna.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "data/",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18.0.0"
16
+ },
17
+ "scripts": {
18
+ "build:data": "node scripts/build-data.js",
19
+ "prepack": "node scripts/build-data.js"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/nurixlabs/etna.git",
24
+ "directory": "packages/cli"
25
+ },
26
+ "homepage": "https://github.com/nurixlabs/etna/tree/dev/packages/cli",
27
+ "license": "UNLICENSED",
28
+ "publishConfig": {
29
+ "access": "restricted"
30
+ },
31
+ "keywords": [
32
+ "claude",
33
+ "claude-code",
34
+ "harness",
35
+ "nurix",
36
+ "agent",
37
+ "plugin"
38
+ ]
39
+ }