@nurix/etna 0.19.83 → 0.19.84
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/lib/codex/skills.mjs +5 -2
- package/lib/install/materialize.mjs +31 -1
- package/package.json +44 -44
package/lib/codex/skills.mjs
CHANGED
|
@@ -8,13 +8,16 @@
|
|
|
8
8
|
import { existsSync, cpSync, rmSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// `snapshot: false` skips the no-git safety copy. The harness installer passes it because it has
|
|
12
|
+
// already cleared .agents/skills and rewritten it from the bundle, so the directory holds only
|
|
13
|
+
// bytes that install just produced — snapshotting those protects nothing and leaves .bak litter.
|
|
14
|
+
export function projectSkills({ targetDir, projectHasGit, snapshot = true }) {
|
|
12
15
|
const src = join(targetDir, '.claude', 'skills');
|
|
13
16
|
if (!existsSync(src)) return null;
|
|
14
17
|
const dest = join(targetDir, '.agents', 'skills');
|
|
15
18
|
let warning = null;
|
|
16
19
|
if (existsSync(dest)) {
|
|
17
|
-
if (!projectHasGit) {
|
|
20
|
+
if (!projectHasGit && snapshot) {
|
|
18
21
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
19
22
|
cpSync(dest, `${dest}.bak.${ts}`, { recursive: true });
|
|
20
23
|
warning = 'No .git here — snapshotted existing .agents/skills/ before rebuild.';
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from 'node:fs';
|
|
31
31
|
import { join, dirname } from 'node:path';
|
|
32
32
|
import { planHarnessFiles, PRUNABLE_SUBDIRS, CODEX_REBUILT_DIRS } from './plan.mjs';
|
|
33
|
+
import { projectSkills } from '../codex/skills.mjs';
|
|
33
34
|
|
|
34
35
|
// Hook scripts shipped by earlier harness versions (the legacy memory-ledger tier) and since retired.
|
|
35
36
|
// mergeHooks is additive, so an upgrade must explicitly remove both the stale settings.json
|
|
@@ -153,6 +154,27 @@ function retireOldHooks(targetDir, bundledHarnessDir) {
|
|
|
153
154
|
return removed;
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
// Artifacts a prior install leaves behind, in the order they are cheapest to trust. `.claude/` is
|
|
158
|
+
// deliberately NOT the only one: a repo that gitignores it never commits `harness.json`, so a fresh
|
|
159
|
+
// CI checkout of an already-harnessed repo carries no marker there and would read as first contact
|
|
160
|
+
// on EVERY roll — which seeds pristine scaffolds over the project's real docs, backs the originals
|
|
161
|
+
// up as .bkp, and does it again next time, since the gitignore is what stops the marker persisting.
|
|
162
|
+
// The Codex artifacts and AGENTS.md are written by the same install and are conventionally tracked,
|
|
163
|
+
// so any one of them present is proof this repo has been harnessed before.
|
|
164
|
+
const PRIOR_INSTALL_MARKERS = [
|
|
165
|
+
join('.claude', 'harness.json'),
|
|
166
|
+
join('.codex', 'hooks.json'),
|
|
167
|
+
join('.agents', 'skills'),
|
|
168
|
+
'AGENTS.md',
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
// First contact means "this repo has never been harnessed", which is what licenses the installer to
|
|
172
|
+
// move a colliding file aside as .bkp and write the pristine seed over it. Getting it wrong in the
|
|
173
|
+
// permissive direction is destructive, so any evidence of a prior install settles it.
|
|
174
|
+
function isFirstContact(targetDir) {
|
|
175
|
+
return !PRIOR_INSTALL_MARKERS.some((rel) => existsSync(join(targetDir, rel)));
|
|
176
|
+
}
|
|
177
|
+
|
|
156
178
|
// Read the `owned` manifest the PREVIOUS install recorded in the project's harness.json.
|
|
157
179
|
// Absent (first install, or pre-pruning version) ⇒ {} ⇒ nothing is pruned this round.
|
|
158
180
|
function readInstalledOwned(targetDir) {
|
|
@@ -310,7 +332,7 @@ export function materializeHarness({ bundledHarnessDir, targetDir, pluginName, p
|
|
|
310
332
|
// to the human). Git is not the distinction: history protects bytes, but the .bkp file is what
|
|
311
333
|
// makes the fold duty visible to the next session. The harness is deliberately unapologetic
|
|
312
334
|
// about its own process; the backup is how it stays honest about yours.
|
|
313
|
-
const firstContact =
|
|
335
|
+
const firstContact = isFirstContact(targetDir);
|
|
314
336
|
void projectHasGit; // kept for caller compatibility; the .bkp rule does not branch on it
|
|
315
337
|
|
|
316
338
|
// The Codex mirrors are rebuilt wholesale, not merged: they are byte projections of the Claude
|
|
@@ -381,6 +403,14 @@ export function materializeHarness({ bundledHarnessDir, targetDir, pluginName, p
|
|
|
381
403
|
}
|
|
382
404
|
}
|
|
383
405
|
|
|
406
|
+
// Re-project the Codex skill mirror from the INSTALLED tree, after both prunes. The write pass
|
|
407
|
+
// above only emits what the bundle ships, so a project-local skill — one installed by
|
|
408
|
+
// `--skill=<name>` and deliberately absent from `owned`, which is what makes it survive pruning —
|
|
409
|
+
// was present for Claude and missing for Codex after every roll. Ordering is load-bearing: run
|
|
410
|
+
// before the prunes and a skill this bundle dropped would be mirrored straight back.
|
|
411
|
+
const mirrored = projectSkills({ targetDir, projectHasGit, snapshot: false });
|
|
412
|
+
if (mirrored?.installed) installed.push(mirrored.installed);
|
|
413
|
+
|
|
384
414
|
// Retire hooks earlier harness versions shipped, before merging, so the additive merge never
|
|
385
415
|
// resurrects an entry pointing at a script this bundle no longer carries.
|
|
386
416
|
for (const r of retireOldHooks(targetDir, bundledHarnessDir)) notices.push(`removed ${r}`);
|
package/package.json
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
2
|
+
"name": "@nurix/etna",
|
|
3
|
+
"version": "0.19.84",
|
|
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).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"etna": "bin/etna.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@clack/prompts": "^0.11.0"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"lib/",
|
|
15
|
+
"data/",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18.0.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "node --test 'tests/**/*.test.mjs'",
|
|
23
|
+
"build:data": "node scripts/build-data.js",
|
|
24
|
+
"prepack": "node scripts/build-data.js",
|
|
25
|
+
"validate:codex-agents": "node lib/codex/validate-codex-agents.mjs"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/nurixlabs/etna.git",
|
|
30
|
+
"directory": "packages/cli"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/nurixlabs/etna/tree/dev/packages/cli",
|
|
33
|
+
"license": "UNLICENSED",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"claude",
|
|
39
|
+
"claude-code",
|
|
40
|
+
"harness",
|
|
41
|
+
"nurix",
|
|
42
|
+
"agent",
|
|
43
|
+
"plugin"
|
|
44
|
+
],
|
|
45
|
+
"etnaFingerprint": "574f23e45199"
|
|
46
46
|
}
|