@nurix/etna 0.20.26 → 0.20.28

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.
Files changed (56) hide show
  1. package/README.md +15 -14
  2. package/bin/etna.js +61 -19
  3. package/data/harness-index.json +1 -1
  4. package/data/harnesses/code/codex/agents/bookkeeper.toml +1 -1
  5. package/data/harnesses/code/harness.json +6 -2
  6. package/data/harnesses/code/hooks/awareness.mjs +50 -2
  7. package/data/harnesses/code/hooks/hooks.json +1 -1
  8. package/data/harnesses/code/hooks/lib/backlog-detector.mjs +82 -0
  9. package/data/harnesses/code/hooks/lib/records-door.mjs +54 -0
  10. package/data/harnesses/code/hooks/mount.mjs +470 -0
  11. package/data/harnesses/code/skills/documentation/SKILL.md +6 -6
  12. package/data/harnesses/code/skills/documentation/references/examples.md +29 -25
  13. package/data/harnesses/code/skills/documentation/references/style-guide.md +211 -335
  14. package/data/harnesses/code/skills/documentation/references/untemplated.md +2 -1
  15. package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +33 -17
  16. package/data/harnesses/code/skills/documentation/templates/doctrine.md +15 -16
  17. package/data/harnesses/code/skills/documentation/templates/feature.md +53 -17
  18. package/data/harnesses/code/skills/documentation/templates/handover.md +1 -1
  19. package/data/harnesses/code/skills/documentation/templates/package.md +38 -15
  20. package/data/harnesses/code/skills/documentation/templates/root-readme.md +16 -16
  21. package/data/harnesses/code/skills/documentation/templates/setup.md +1 -1
  22. package/data/harnesses/code/skills/documentation/templates/system.md +37 -15
  23. package/data/harnesses/delivery/harness.json +6 -2
  24. package/data/harnesses/delivery/skills/documentation/SKILL.md +6 -6
  25. package/data/harnesses/delivery/skills/documentation/references/examples.md +29 -25
  26. package/data/harnesses/delivery/skills/documentation/references/style-guide.md +211 -335
  27. package/data/harnesses/delivery/skills/documentation/references/untemplated.md +2 -1
  28. package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +33 -17
  29. package/data/harnesses/delivery/skills/documentation/templates/doctrine.md +15 -16
  30. package/data/harnesses/delivery/skills/documentation/templates/feature.md +53 -17
  31. package/data/harnesses/delivery/skills/documentation/templates/handover.md +1 -1
  32. package/data/harnesses/delivery/skills/documentation/templates/package.md +38 -15
  33. package/data/harnesses/delivery/skills/documentation/templates/root-readme.md +16 -16
  34. package/data/harnesses/delivery/skills/documentation/templates/setup.md +1 -1
  35. package/data/harnesses/delivery/skills/documentation/templates/system.md +37 -15
  36. package/data/harnesses/discover/harness.json +4 -0
  37. package/data/harnesses/discussion/harness.json +4 -0
  38. package/data/harnesses/etna/harness.json +6 -2
  39. package/data/harnesses/etna/skills/documentation/SKILL.md +6 -6
  40. package/data/harnesses/etna/skills/documentation/references/examples.md +29 -25
  41. package/data/harnesses/etna/skills/documentation/references/style-guide.md +211 -335
  42. package/data/harnesses/etna/skills/documentation/references/untemplated.md +2 -1
  43. package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +33 -17
  44. package/data/harnesses/etna/skills/documentation/templates/doctrine.md +15 -16
  45. package/data/harnesses/etna/skills/documentation/templates/feature.md +53 -17
  46. package/data/harnesses/etna/skills/documentation/templates/handover.md +1 -1
  47. package/data/harnesses/etna/skills/documentation/templates/package.md +38 -15
  48. package/data/harnesses/etna/skills/documentation/templates/root-readme.md +16 -16
  49. package/data/harnesses/etna/skills/documentation/templates/setup.md +1 -1
  50. package/data/harnesses/etna/skills/documentation/templates/system.md +37 -15
  51. package/data/harnesses/hr/harness.json +4 -0
  52. package/data/harnesses/kb-agi/harness.json +4 -0
  53. package/lib/install/materialize.mjs +51 -23
  54. package/lib/install/mount.mjs +448 -0
  55. package/lib/install/plan.mjs +24 -29
  56. package/package.json +6 -3
@@ -21,11 +21,13 @@
21
21
  // project's living copy, never refreshed and never pruned.
22
22
  // merge — folded into whatever the project already has, key by key. Only `.claude/settings.json`,
23
23
  // which carries the user's own hooks and permissions beside the harness's.
24
+ // mount — the gitignored harness (`isMountedPath`), written here and at session start by `mount.mjs`.
24
25
 
25
26
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
26
27
  import { join, relative, sep } from 'node:path';
27
28
  import { buildCodexAdapter } from '../codex/agents-md.mjs';
28
29
  import { toCodexCommand } from '../codex/hooks.mjs';
30
+ import { isMountedPath, hasWorkerConfig, DEFAULT_ORIGIN } from './mount.mjs';
29
31
 
30
32
  // Subfolders bundled inside each harness payload that map 1:1 onto <project>/.claude/<name>/.
31
33
  // Order matches Claude Code's own folder layout. hooks/ carries the hook scripts + lib/ (and
@@ -166,30 +168,7 @@ function codexHooks(bundleDir) {
166
168
  const CAPABILITY_RULES = { 'cloudflare.md': targetHasWorkerConfig };
167
169
 
168
170
  function targetHasWorkerConfig(targetDir) {
169
- const names = ['wrangler.jsonc', 'wrangler.toml'];
170
- if (names.some((n) => existsSync(join(targetDir, n)))) return true;
171
- let entries = [];
172
- try {
173
- entries = readdirSync(targetDir, { withFileTypes: true });
174
- } catch {
175
- return false;
176
- }
177
- for (const e of entries) {
178
- if (!e.isDirectory() || e.name === 'node_modules' || e.name.startsWith('.')) continue;
179
- if (names.some((n) => existsSync(join(targetDir, e.name, n)))) return true;
180
- // one more level: packages/*/wrangler.jsonc — the common monorepo shape
181
- let sub = [];
182
- try {
183
- sub = readdirSync(join(targetDir, e.name), { withFileTypes: true });
184
- } catch {
185
- continue;
186
- }
187
- for (const s of sub) {
188
- if (!s.isDirectory() || s.name === 'node_modules' || s.name.startsWith('.')) continue;
189
- if (names.some((n) => existsSync(join(targetDir, e.name, s.name, n)))) return true;
190
- }
191
- }
192
- return false;
171
+ return hasWorkerConfig(targetDir);
193
172
  }
194
173
 
195
174
  // A rule the target's capabilities exclude. No targetDir (payload verification, byte-identity
@@ -209,19 +188,32 @@ function ruleExcluded(sub, rel, targetDir) {
209
188
  //
210
189
  // Read from the target, never from the bundle. An absent or unreadable marker returns nothing and
211
190
  // the manifest default stands.
191
+ // `mount.origin` and `mount.track` are consumer-owned the same way.
212
192
  function consumerOwnedMarkerKeys(targetDir) {
213
193
  if (!targetDir) return {};
214
194
  try {
215
195
  const prior = JSON.parse(readFileSync(join(targetDir, '.claude', 'harness.json'), 'utf8'));
216
- return typeof prior.docsRoot === 'string' && prior.docsRoot.trim()
217
- ? { docsRoot: prior.docsRoot.trim() }
218
- : {};
196
+ const keys = {};
197
+ if (typeof prior.docsRoot === 'string' && prior.docsRoot.trim()) keys.docsRoot = prior.docsRoot.trim();
198
+ const mount = prior.mount && typeof prior.mount === 'object' ? prior.mount : {};
199
+ if (typeof mount.origin === 'string' && mount.origin.trim()) keys.origin = mount.origin.trim();
200
+ if (mount.track === 'latest' || mount.track === 'pinned') keys.track = mount.track;
201
+ return keys;
219
202
  } catch (e) {
220
203
  return {};
221
204
  }
222
205
  }
223
206
 
224
- export function planHarnessFiles(bundleDir, targetDir = null) {
207
+ // The marker's `mount` block: the CLI's flags, else the consumer's value, else the bundle's, else the defaults.
208
+ function markerMount(meta, prior, override = {}) {
209
+ const bundled = meta.mount && typeof meta.mount === 'object' ? meta.mount : {};
210
+ return {
211
+ origin: override.origin || prior.origin || bundled.origin || DEFAULT_ORIGIN,
212
+ track: override.track || prior.track || bundled.track || 'latest',
213
+ };
214
+ }
215
+
216
+ export function planHarnessFiles(bundleDir, targetDir = null, options = {}) {
225
217
  const claudeMdPath = join(bundleDir, 'CLAUDE.md');
226
218
  if (!existsSync(claudeMdPath)) {
227
219
  throw new Error(`Bundled CLAUDE.md missing at ${claudeMdPath}. Rebuild package data.`);
@@ -300,9 +292,11 @@ export function planHarnessFiles(bundleDir, targetDir = null) {
300
292
  const markerPath = join(bundleDir, 'harness.json');
301
293
  const meta = existsSync(markerPath) ? JSON.parse(readFileSync(markerPath, 'utf8')) : {};
302
294
  if (existsSync(markerPath)) {
295
+ const { origin, track, ...prior } = consumerOwnedMarkerKeys(targetDir);
296
+ const mount = markerMount(meta, { origin, track }, options.mount);
303
297
  files.push({
304
298
  path: '.claude/harness.json',
305
- content: asJson({ ...meta, ...consumerOwnedMarkerKeys(targetDir), owned }),
299
+ content: asJson({ ...meta, ...prior, mount, owned }),
306
300
  policy: 'own',
307
301
  });
308
302
  }
@@ -318,6 +312,7 @@ export function planHarnessFiles(bundleDir, targetDir = null) {
318
312
  // Sorted so the plan is byte-stable across machines — readdir order is not guaranteed, and an
319
313
  // unstable order would make two identical builds look different to anything comparing them.
320
314
  files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
315
+ for (const file of files) if (file.policy === 'own' && isMountedPath(file.path)) file.policy = 'mount';
321
316
 
322
317
  const seen = new Set();
323
318
  for (const file of files) {
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.20.26",
4
- "description": "Bootstrap a project with a Nurix harness — copies the harness's CLAUDE.md, skills, agents, rules, and hooks directly into the project from a bundled payload (no marketplace fetch, no network).",
3
+ "version": "0.20.28",
4
+ "description": "Bootstrap a project with a Nurix harness — writes the committed contract (CLAUDE.md, AGENTS.md, .claude/harness.json, settings.json, the mount hook) and mounts the harness itself, gitignored, from a bundled payload; sessions keep it current from the CDN.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "etna": "bin/etna.js"
8
8
  },
9
+ "exports": {
10
+ "./mount": "./lib/install/mount.mjs"
11
+ },
9
12
  "dependencies": {
10
13
  "@clack/prompts": "^0.11.0"
11
14
  },
@@ -43,5 +46,5 @@
43
46
  "agent",
44
47
  "plugin"
45
48
  ],
46
- "etnaFingerprint": "9db89d92a35b"
49
+ "etnaFingerprint": "47047f5258d8"
47
50
  }