@nurix/etna 0.1.1 → 0.1.3
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 +25 -37
- package/bin/etna.js +59 -92
- package/data/harness-index.json +9 -1
- package/data/harnesses/code/CLAUDE.md +18 -9
- package/data/harnesses/kb-agi/CLAUDE.md +57 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
# @nurix/etna
|
|
2
2
|
|
|
3
|
-
CLI to bootstrap a project with a Nurix harness.
|
|
3
|
+
CLI to bootstrap a project with a Nurix harness. Produces the same on-disk state as running
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```
|
|
6
|
+
/plugin marketplace add nurixlabs/skills
|
|
7
|
+
/plugin install nurix-harness@nurixlabs
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
inside Claude Code, plus drops the harness's `CLAUDE.md` at the project root. See [`../../harness/README.md`](../../harness/README.md) for what a harness is and why the install model is designed this way.
|
|
6
11
|
|
|
7
12
|
## Usage
|
|
8
13
|
|
|
9
14
|
From the root of the project you want to bootstrap:
|
|
10
15
|
|
|
11
16
|
```sh
|
|
12
|
-
|
|
17
|
+
npx @nurix/etna nurix-harness
|
|
13
18
|
```
|
|
14
19
|
|
|
15
|
-
After this completes,
|
|
20
|
+
After this completes, three things have changed on disk:
|
|
16
21
|
|
|
17
22
|
```
|
|
18
23
|
your-project/
|
|
19
|
-
├── CLAUDE.md
|
|
24
|
+
├── CLAUDE.md ← bundled in this package; the harness's policy
|
|
20
25
|
└── .claude/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
26
|
+
└── settings.json ← enabledPlugins["nurix-harness@nurixlabs"] = true
|
|
27
|
+
|
|
28
|
+
~/.claude/settings.json ← extraKnownMarketplaces["nurixlabs"] registered
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
Open Claude Code in the project; it
|
|
31
|
+
That's the entire footprint. Open Claude Code in the project; on next session start it resolves `nurix-harness@nurixlabs`, clones `nurixlabs/skills` into `~/.claude/plugins/marketplaces/`, fills the plugin cache at `~/.claude/plugins/cache/nurixlabs/nurix-harness/<version>/`, and loads the plugin. The CLI does not touch any of those Claude Code internals itself — that's deliberate (see [Installation goals](../../harness/README.md#installation-goals)).
|
|
32
|
+
|
|
33
|
+
If Claude Code is already running, run `/reload-plugins`.
|
|
33
34
|
|
|
34
35
|
## Available harnesses
|
|
35
36
|
|
|
@@ -37,28 +38,21 @@ Listed by `npx @nurix/etna` (no args) using the bundled `data/harness-index.json
|
|
|
37
38
|
|
|
38
39
|
| Plugin name | Harness folder in etna | What it ships |
|
|
39
40
|
|---|---|---|
|
|
40
|
-
| `nurix-harness` | [`harness/code/`](../../harness/code/) | The canonical v7 harness —
|
|
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`. |
|
|
41
|
+
| `nurix-harness` | [`harness/code/`](../../harness/code/) | The canonical v7 harness — governance agents, baseline workflow skills, ownership/drift scripts |
|
|
48
42
|
|
|
49
43
|
## How it works
|
|
50
44
|
|
|
51
|
-
1. Looks up `<plugin-name>` in the bundled `data/harness-index.json` to find the matching harness folder.
|
|
52
|
-
2.
|
|
53
|
-
3.
|
|
54
|
-
4. Adds `<plugin-name
|
|
45
|
+
1. Looks up `<plugin-name>` in the bundled `data/harness-index.json` to find the matching harness folder and the marketplace metadata.
|
|
46
|
+
2. Copies the bundled `data/harnesses/<harness-folder>/CLAUDE.md` to `./CLAUDE.md` (backing up any existing one to `CLAUDE.md.bak.<ISO-timestamp>`).
|
|
47
|
+
3. Adds the marketplace entry to `~/.claude/settings.json` under `extraKnownMarketplaces`.
|
|
48
|
+
4. Adds `<plugin-name>@<marketplace>: true` to `./.claude/settings.json` under `enabledPlugins`.
|
|
55
49
|
|
|
56
|
-
|
|
50
|
+
No GitHub fetch. No `.claude/plugins/<name>/` folder. Claude Code's plugin resolver handles all of that on next launch.
|
|
57
51
|
|
|
58
52
|
## Why two sources
|
|
59
53
|
|
|
60
|
-
- **Plugin content**
|
|
61
|
-
- **`CLAUDE.md`** is bundled in this npm package, versioned with the package. Harness policy changes warrant a package version bump.
|
|
54
|
+
- **Plugin content** lives in the marketplace (`nurixlabs/skills`) and is fetched by Claude Code at session start — always the latest CI-published version.
|
|
55
|
+
- **`CLAUDE.md`** is bundled in this npm package, versioned with the package. A plugin cannot ship a project-root `CLAUDE.md` (Claude Code's plugin spec doesn't support it), so the CLI ships that half itself. Harness policy changes warrant a package version bump.
|
|
62
56
|
|
|
63
57
|
## Building / contributing
|
|
64
58
|
|
|
@@ -73,10 +67,4 @@ npm run build:data
|
|
|
73
67
|
|
|
74
68
|
## Publishing
|
|
75
69
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
```sh
|
|
79
|
-
npm publish --access restricted
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
The `publishConfig.access` is `restricted` so the scoped package is private by default.
|
|
70
|
+
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 `restricted` so the scoped package is private by default.
|
package/bin/etna.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// packages/cli/bin/etna.js
|
|
3
|
-
// `npx @nurix/etna <plugin-name>` —
|
|
3
|
+
// `npx @nurix/etna <plugin-name>` — bootstrap a Nurix harness in the current directory.
|
|
4
|
+
// Produces the same on-disk state as running `/plugin marketplace add <repo>` followed by
|
|
5
|
+
// `/plugin install <plugin>@<marketplace>` inside Claude Code, plus drops the harness's
|
|
6
|
+
// CLAUDE.md at the project root. Claude Code's plugin resolver fills the cache on next launch.
|
|
4
7
|
|
|
5
8
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, copyFileSync, lstatSync } from 'node:fs';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
6
10
|
import { join, dirname, resolve } from 'node:path';
|
|
7
11
|
import { fileURLToPath } from 'node:url';
|
|
8
12
|
|
|
@@ -10,20 +14,17 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
10
14
|
const pkgRoot = resolve(__dirname, '..');
|
|
11
15
|
const dataDir = join(pkgRoot, 'data');
|
|
12
16
|
|
|
13
|
-
const SKILLS_REPO = 'nurixlabs/skills';
|
|
14
|
-
const SKILLS_REF = process.env.NURIX_SKILLS_REF || 'dev';
|
|
15
|
-
|
|
16
17
|
function usage() {
|
|
17
|
-
console.log(`\nUsage: npx @nurix/etna <plugin-name>\n`);
|
|
18
18
|
const idx = loadHarnessIndex();
|
|
19
|
+
console.log(`\nUsage: npx @nurix/etna <plugin-name>\n`);
|
|
19
20
|
const names = Object.keys(idx.plugins);
|
|
20
21
|
if (names.length) {
|
|
21
|
-
console.log(`Available harnesses (plugin →
|
|
22
|
-
for (const name of names)
|
|
22
|
+
console.log(`Available harnesses (plugin → marketplace):`);
|
|
23
|
+
for (const name of names) {
|
|
24
|
+
console.log(` ${name.padEnd(20)} → ${idx.marketplace.name}`);
|
|
25
|
+
}
|
|
23
26
|
}
|
|
24
|
-
console.log(`\
|
|
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(`\nMarketplace: ${idx.marketplace.source.repo} (github)`);
|
|
27
28
|
console.log();
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -40,54 +41,6 @@ function die(msg, code = 1) {
|
|
|
40
41
|
process.exit(code);
|
|
41
42
|
}
|
|
42
43
|
|
|
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
44
|
// Move <path> to <path>.bak.<timestamp> if it exists. Returns the backup path, or null.
|
|
92
45
|
function backupIfExists(path) {
|
|
93
46
|
if (!existsSync(path) && !isSymlink(path)) return null;
|
|
@@ -105,28 +58,46 @@ function isSymlink(path) {
|
|
|
105
58
|
}
|
|
106
59
|
}
|
|
107
60
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
}
|
|
61
|
+
function readJson(path) {
|
|
62
|
+
if (!existsSync(path)) return {};
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
65
|
+
} catch (e) {
|
|
66
|
+
die(`failed to parse ${path}: ${e.message}`);
|
|
122
67
|
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 2-space indent matches Claude Code's own writes to settings.json.
|
|
71
|
+
function writeJson(path, value) {
|
|
72
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
73
|
+
writeFileSync(path, JSON.stringify(value, null, 2) + '\n');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Register the marketplace in ~/.claude/settings.json (extraKnownMarketplaces).
|
|
77
|
+
// Mirrors what `/plugin marketplace add <owner>/<repo>` writes, plus opts the
|
|
78
|
+
// marketplace into auto-updates (off by default for non-Anthropic marketplaces).
|
|
79
|
+
function registerMarketplace(marketplace) {
|
|
80
|
+
const path = join(homedir(), '.claude', 'settings.json');
|
|
81
|
+
const settings = readJson(path);
|
|
82
|
+
settings.extraKnownMarketplaces = settings.extraKnownMarketplaces || {};
|
|
83
|
+
settings.extraKnownMarketplaces[marketplace.name] = {
|
|
84
|
+
source: marketplace.source,
|
|
85
|
+
autoUpdate: true,
|
|
86
|
+
};
|
|
87
|
+
writeJson(path, settings);
|
|
88
|
+
return path;
|
|
89
|
+
}
|
|
123
90
|
|
|
91
|
+
// Add `<plugin>@<marketplace>: true` to <target>/.claude/settings.json under enabledPlugins.
|
|
92
|
+
// Mirrors what `/plugin install <plugin>@<marketplace>` writes at project scope.
|
|
93
|
+
function enablePlugin(targetDir, pluginName, marketplaceName) {
|
|
94
|
+
const path = join(targetDir, '.claude', 'settings.json');
|
|
95
|
+
const settings = readJson(path);
|
|
124
96
|
settings.enabledPlugins = settings.enabledPlugins || {};
|
|
125
|
-
const key = `${pluginName}
|
|
97
|
+
const key = `${pluginName}@${marketplaceName}`;
|
|
126
98
|
settings.enabledPlugins[key] = true;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return { settingsPath, key };
|
|
99
|
+
writeJson(path, settings);
|
|
100
|
+
return { path, key };
|
|
130
101
|
}
|
|
131
102
|
|
|
132
103
|
async function main(args) {
|
|
@@ -150,32 +121,28 @@ async function main(args) {
|
|
|
150
121
|
}
|
|
151
122
|
|
|
152
123
|
const targetDir = process.cwd();
|
|
153
|
-
const pluginDir = join(targetDir, '.claude', 'plugins', pluginName);
|
|
154
124
|
const claudeMdDest = join(targetDir, 'CLAUDE.md');
|
|
125
|
+
const marketplace = idx.marketplace;
|
|
126
|
+
const fqKey = `${pluginName}@${marketplace.name}`;
|
|
155
127
|
|
|
156
|
-
console.log(`\nInstalling ${
|
|
128
|
+
console.log(`\nInstalling ${fqKey} into ${targetDir}`);
|
|
157
129
|
|
|
158
|
-
// 1. Backup conflicts
|
|
159
|
-
const pluginBackup = backupIfExists(pluginDir);
|
|
160
|
-
if (pluginBackup) console.log(` backed up existing plugin folder → ${pluginBackup}`);
|
|
161
130
|
const claudeBackup = backupIfExists(claudeMdDest);
|
|
162
131
|
if (claudeBackup) console.log(` backed up existing CLAUDE.md → ${claudeBackup}`);
|
|
163
132
|
|
|
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
133
|
copyFileSync(bundledClaude, claudeMdDest);
|
|
171
134
|
console.log(` installed CLAUDE.md → ${claudeMdDest}`);
|
|
172
135
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
136
|
+
const marketplacePath = registerMarketplace(marketplace);
|
|
137
|
+
console.log(` registered marketplace '${marketplace.name}' → ${marketplacePath}`);
|
|
138
|
+
|
|
139
|
+
const { path: enablePath } = enablePlugin(targetDir, pluginName, marketplace.name);
|
|
140
|
+
console.log(` enabled '${fqKey}' → ${enablePath}`);
|
|
176
141
|
|
|
177
|
-
console.log(`\nDone. Open Claude Code in
|
|
178
|
-
console.log(
|
|
142
|
+
console.log(`\nDone. Open Claude Code in ${targetDir}.`);
|
|
143
|
+
console.log(
|
|
144
|
+
`It will fetch ${marketplace.source.repo}, install ${pluginName}, and load it on next session start.\n`,
|
|
145
|
+
);
|
|
179
146
|
}
|
|
180
147
|
|
|
181
148
|
main(process.argv.slice(2)).catch((e) => {
|
package/data/harness-index.json
CHANGED
|
@@ -73,6 +73,13 @@ Feature docs in `docs/features/` must include YAML frontmatter with `owns`, `tou
|
|
|
73
73
|
|
|
74
74
|
The spec-mode agent checks for ownership conflicts during research. If conflicts are found, address them in the spec before proceeding.
|
|
75
75
|
|
|
76
|
+
### Architecture Decisions (ADRs)
|
|
77
|
+
|
|
78
|
+
Record an ADR when a decision (a) changes how state, dependencies, or boundaries are organized, (b) has a rejected alternative worth remembering, and (c) could plausibly be re-litigated. Default: don't write one. Style preferences, local refactors, and obvious-from-code choices don't qualify.
|
|
79
|
+
|
|
80
|
+
- File: `docs/architecture/adr/NNNN-kebab-case.md` (4-digit sequential number). Frozen once written — supersede via a new ADR, never edit.
|
|
81
|
+
- Invoke the `documentation` skill with "record an ADR" or "architecture decision".
|
|
82
|
+
|
|
76
83
|
---
|
|
77
84
|
|
|
78
85
|
## Agents
|
|
@@ -88,7 +95,7 @@ Seven sub-agents and three skills are defined under `.claude/`. Reach for them b
|
|
|
88
95
|
| `dep-health` | Agent | Dependency supply-chain audit | "audit dependencies", "check CVEs", "dep health" |
|
|
89
96
|
| `git-history` | Agent | Classify recent commits into a velocity/severity CSV | "classify commits", "commit history report" |
|
|
90
97
|
| `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" |
|
|
98
|
+
| `documentation` | Skill | Baseline audit, scaffolding, templates, ADRs, guardrails | "baseline docs", "create docs", "audit docs", "record an ADR", "architecture decision", "create guardrails" |
|
|
92
99
|
| `workflow-mapper` | Skill | Discover and diagram codebase workflows | "map workflows", "trace flows" |
|
|
93
100
|
| `introspect` | Skill | Pre-implementation impact analysis against guardrails | "introspect this plan", "impact analysis" |
|
|
94
101
|
|
|
@@ -140,20 +147,22 @@ Unless explicitly requested:
|
|
|
140
147
|
|
|
141
148
|
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
149
|
|
|
143
|
-
| Folder
|
|
144
|
-
|
|
|
145
|
-
| `docs/architecture/`
|
|
146
|
-
| `docs/
|
|
147
|
-
| `docs/
|
|
148
|
-
| `docs/
|
|
149
|
-
| `docs/
|
|
150
|
-
| `docs/
|
|
150
|
+
| Folder | Purpose | Mutability |
|
|
151
|
+
| ------------------------ | -------------------------------------------------------- | --------------------- |
|
|
152
|
+
| `docs/architecture/` | System-wide patterns, technical decisions, API specs | Living |
|
|
153
|
+
| `docs/architecture/adr/` | Architecture Decision Records — numbered, one per file | Frozen (supersede) |
|
|
154
|
+
| `docs/features/` | How implemented features work today | Living |
|
|
155
|
+
| `docs/operations/` | Setup, development, deployment | Living |
|
|
156
|
+
| `docs/history/` | Plans, changelogs, backlog | Frozen or append-only |
|
|
157
|
+
| `docs/memory/` | Project knowledge + agent state | Living |
|
|
158
|
+
| `docs/tests/` | Test plans and automation | Living |
|
|
151
159
|
|
|
152
160
|
### Naming & Lifecycle
|
|
153
161
|
|
|
154
162
|
- Features: `kebab-case.md` — living reference, status `Active`
|
|
155
163
|
- Plans: `YYYY-MM-DD-kebab-case.md` — frozen after `Complete` or `Abandoned`
|
|
156
164
|
- Changelogs: `YYYY-MM-DD-kebab-case.md` — append-only
|
|
165
|
+
- ADRs: `NNNN-kebab-case.md` (4-digit zero-padded number, sequential) — frozen; supersede via a new ADR
|
|
157
166
|
- Memory: `{agent-name}.md` — agent-maintained
|
|
158
167
|
|
|
159
168
|
### Documentation Maintenance
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# nupilot Knowledge Hub — router
|
|
2
|
+
|
|
3
|
+
This repo is a flat Obsidian vault that you read on the user's behalf. It's the bridge from the front (positioning, claims) to the back (product and service specs); detailed execution lives downstream in the service repos.
|
|
4
|
+
|
|
5
|
+
You are the **router**. Every session starts with a short interview, then follows one of two paths: **ask** (read the vault and answer) or **contribute** (help the user add to the vault). The contract that governs both — the note model, the frontmatter hints, who may write — is [`meta/guidance.md`](meta/guidance.md). Read it before you write anything.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Step 0 — first run
|
|
10
|
+
|
|
11
|
+
If `meta/guidance.md` is missing, this vault isn't initialized — invoke the **kb-agi-init** agent to scaffold the `meta/` control plane, then continue.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Step 1 — the opening interview
|
|
16
|
+
|
|
17
|
+
Before answering anything, run a short interview with the `AskUserQuestion` tool to establish two things:
|
|
18
|
+
|
|
19
|
+
1. **What brings you?** → *ask* (you have a question) or *contribute* (you have something to add).
|
|
20
|
+
2. **Your user-type (genre):** → *Sales*, *PM*, or *Contributor*. This sets the default depth and framing of answers, nothing else. (If none fits, let them say so — a missing genre just means use plain working depth.)
|
|
21
|
+
|
|
22
|
+
Then load the matching template from [`meta/genre/`](meta/genre/) — `sales.md`, `pm.md`, or `contributor.md` — and follow the path below.
|
|
23
|
+
|
|
24
|
+
Keep the interview to those two questions. Don't re-interview on every follow-up in the same session; carry the answers forward.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## The ask path — read-only
|
|
29
|
+
|
|
30
|
+
You are **read-only** on this path. You never edit a note. Your only write is appending a query log to `meta/sessions/` (see below).
|
|
31
|
+
|
|
32
|
+
1. **Find the entry note(s)** for the question. Start from [`meta/intent-index.md`](meta/intent-index.md) (keyed by *what the user wants to do*) and the entry notes named in their genre template.
|
|
33
|
+
2. **Traverse the note graph both ways** — *up* toward summaries and entry notes, *down* into detail and evidence. The `[[wikilinks]]` are the structure. Lead with `canonical` notes; treat `canonical: false` ones as background.
|
|
34
|
+
3. **Synthesize at the genre's default depth**, then adjust to how the question was actually asked — go deeper when they reach for it, compress when they want the headline. Depth shapes the answer; it never hides notes.
|
|
35
|
+
4. **Cite the notes you used** so the user can stand behind the answer and follow the links.
|
|
36
|
+
5. **Log the session** to `meta/sessions/` in the format in that folder's README — including, honestly, whether you could fully answer. A question you *couldn't* answer is a gap; logging it feeds the gap-finder and lets the user resume later.
|
|
37
|
+
|
|
38
|
+
If the vault can't answer, say so plainly and log it as a gap. Don't invent an answer the notes don't support.
|
|
39
|
+
|
|
40
|
+
## The contribute path — the human steers
|
|
41
|
+
|
|
42
|
+
Here the user is *adding* knowledge, not reading. This path is deliberately thin and grows with use. There are **no sub-agents** — it's you and the user in a normal session.
|
|
43
|
+
|
|
44
|
+
1. **Orient them** with [`meta/guidance.md`](meta/guidance.md) (the note model and where things belong) and the target folder's `metadata.md` (what belongs there, what's already nearby).
|
|
45
|
+
2. **What does the hub want next?** Run the `kb-find-gaps` agent (it reads `meta/sessions/` gaps + folder `metadata.md`) — that's the live content roadmap.
|
|
46
|
+
3. **Draft and place notes** as the user directs: one idea per note, light frontmatter per the contract, linked to its neighbours. Links form naturally as you write.
|
|
47
|
+
4. Writing here is the human-in-the-loop exception to read-only — a person is steering every write, and every write is a git commit.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## What you do *not* do
|
|
52
|
+
|
|
53
|
+
- **You don't run upkeep.** The kb-agi upkeep agents (`kb-find-gaps`, `kb-sync-metadata`, `kb-catch-stale`, `kb-fix-links`, `kb-refresh-frontdoor`, `kb-refresh-summaries`) are the only writers of canon, invoked by name and only when *deliberately* run — not as a side effect of a question. Don't trigger them from the ask path.
|
|
54
|
+
- **You don't hand-edit `meta/_kg/`** — it's a derived graph, regenerated from the notes.
|
|
55
|
+
- **You don't mutate notes on the ask path.** Read-only means read-only.
|
|
56
|
+
|
|
57
|
+
Everything is git-checked-in, so any write is reviewable and reversible (doctrine P3).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"bin": {
|