@nurix/etna 0.19.8 → 0.19.10

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "fingerprint": "462804b22bdc",
2
+ "fingerprint": "3745f4d420c5",
3
3
  "harnesses": {
4
4
  "code": {
5
5
  "plugin": "code",
@@ -7,5 +7,5 @@
7
7
  ".prettierignore",
8
8
  ".vscode/settings.json"
9
9
  ],
10
- "digest": "dd5ee7b4d838"
10
+ "digest": "8ae5e686d1d5"
11
11
  }
@@ -1,14 +1,35 @@
1
1
  # Frontend
2
2
 
3
- Tailwind v4 + shadcn-based UI conventions for **web** apps — styling, the `@nurix/components` library, personality tokens, and design-skill routing. Package versions live in [`general.md`](general.md); the Express server and API envelope in [`express.md`](express.md); React Native styling is out of scope — that's [`react-native.md`](react-native.md).
3
+ Tailwind v4 + shadcn-based UI conventions for **web** apps — styling, the `@nurix/components` library, personality tokens, and design-skill routing — plus the TypeScript conventions every package compiles under. Package versions live in [`general.md`](general.md); the Express server and API envelope in [`express.md`](express.md); workspace layout and installing/consuming `@nurix/*` packages in [`pnpm.md`](pnpm.md); React Native styling is out of scope — that's [`react-native.md`](react-native.md).
4
4
 
5
5
  - **Build every UI through the `design` skill** — route the brief through it instead of hand-rolling layouts; it owns the archetype, personality tokens, and `@nurix/components` wiring.
6
6
  - **The brief's stated theme binds** — dark boots dark when the brief says so (the artifact's `theme-default` carries it); light is the default only when the brief is silent.
7
- - **@nurix/\* are restricted-scope npm packages** — installed from the npm registry, auto-authed by a read token in `~/.npmrc`. Just attempt the install; the token authenticates you automatically. Don't pre-empt or second-guess it. If the install actually fails, flag it to the user — never reach for alternatives, a substitute package, or `npm login`.
8
- - **@nurix/components** — the UI component library (restricted-scope npm package, pre-built); import by subpath, e.g. `@nurix/components/button`.
7
+ - **@nurix/components** — the UI component library, a restricted-scope npm package (install, auth, and the no-substitutes guard are [`pnpm.md`](pnpm.md) §3); import by subpath, e.g. `@nurix/components/button`.
9
8
  - No components.json / shadcn CLI scaffold (.tsx, not .jsx) — consume the prebuilt library; never `npx shadcn add`.
10
9
  - Vite: `@tailwindcss/vite` + `@vitejs/plugin-react-swc`. Pin react / react-dom / react/jsx-runtime / react/jsx-dev-runtime to the app's single copy via `resolve.alias` (@nurix/\* ship nested react — keep one hook dispatcher).
11
10
  - Tailwind v4, CSS-only (no tailwind.config.js, no PostCSS): styling runs through the `@tailwindcss/vite` plugin.
12
11
  - CSS entry `globals.css`, imports in order — `@import "./fonts.css"` (generated @fontsource registrations, first) → `@import "./personality.css"` (token source) → `@import "tailwindcss"` → the library's `styles.css` — then `@theme inline {…}` mapping vars to tokens.
13
12
  - Tokens: CSS vars in `:root, :root[data-theme="light"]` + the archetype selector `[data-style="…"]` (personality.css), consumed as semantic Tailwind tokens (bg-card, text-foreground). Honor the archetype's radius/theme decisions (e.g. re-assert `--radius-*` to 0 for a flat archetype). The emitted `globals.css` always carries the active `@custom-variant dark` line; the theme attribute convention is `data-theme`. The library bundles class-variance-authority, clsx, tailwind-merge.
14
13
  - Plus general styling rules (spacing in 2/4/6/8, object-cover, truncate, motion, color tokens — never hardcoded hex in components).
14
+
15
+ ## TypeScript
16
+
17
+ TypeScript conventions. Compiles under `strict: true` and `noUncheckedIndexedAccess: true`.
18
+
19
+ ### Type Safety
20
+
21
+ - Never `any` to silence the checker. Unknown value → type `unknown`, then narrow (type guard / `zod` parse).
22
+ - Never weaken strictness. No `// @ts-nocheck`; `// @ts-expect-error` only with a same-line reason.
23
+ - Infer locals & return types; annotate at boundaries (exports, props, contracts).
24
+
25
+ ### Generics
26
+
27
+ Shared libraries (`@nurix/components`, etc.) are generic on purpose — that's what types `cell(row)`, checks `slug` against `keyof TData`, and ties `data`/`columns` together. Keep the safety; never widen to `any` to dodge it. State the row type **once per file**, then let inference carry it:
28
+
29
+ - **Bind a repeated factory once** (preferred): `const col = defineColumn<BriefRow>;` → `col({...})`. No `<T>` per call; `row` stays typed. Canonical: `modules/briefs/BriefsListPage.tsx`.
30
+ - **Infer JSX type args from props:** `<Table data={rows} columns={columns} />`, not `<Table<BriefRow> …>`.
31
+ - **Anchor with a collection annotation** when nothing else can: `const columns: ColumnDefinition<BriefRow>[] = [...]`.
32
+
33
+ Don't repeat `fn<Type>(…)` / `<Comp<Type>>` at every call site — cosmetic only (same safety, same emitted JS).
34
+
35
+ > Monorepo TS policy (`tsconfig.base.json` + thin per-package `extends`, source-first internal packages, `peerDependencies`, pnpm `catalog:`) lives in [`pnpm.md`](pnpm.md) §2 — it travels with package boundaries.
@@ -0,0 +1,54 @@
1
+ # pnpm — `@nurix/*` Workspaces: naming, boundaries, and consuming
2
+
3
+ How a `@nurix/*` repo is **named**, **structured**, and how its published packages are **consumed**. Every repo takes the workspace shape — a frontend with a single package still uses the multi-package layout, so this rule applies everywhere. Publishing a package — the pipeline, tokens, and the publish workflow — lives in the `nustack` skill, not here. TypeScript language conventions are [`frontend.md`](frontend.md).
4
+
5
+ ---
6
+
7
+ ## 1. Package naming
8
+
9
+ Every workspace package is scoped to **`@nurix/`**. The segment after the scope encodes **intent** — the dot vs. hyphen is semantic, not cosmetic:
10
+
11
+ | Form | Meaning | Examples |
12
+ | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
13
+ | `@nurix/workflows-sdk` (hyphen, single segment) | The **published, externally-destined** surface. A clean `@nurix/*` name is a published _contract_ — changing an export or peer dep is a breaking change for consumers outside this repo. | `@nurix/workflows-sdk` |
14
+ | `@nurix/workflows.<name>` (dot separator) | **Internal** to the workflows product — `private: true`, refactor freely, no external consumers. | `@nurix/workflows.agent-core`, `.worker`, `.gateway`, `.secrets`, `.connector`, `.playground`, `.playground-tests` |
15
+ | `@nurix/<name>` (plain, no `workflows` prefix) | Cross-repo **shared libs** owned _elsewhere_ and merely consumed here — never renamed. | `@nurix/contracts`, `@nurix/components` |
16
+
17
+ - The `<name>` segment is always **kebab-case** (`agent-core`, `playground-tests`).
18
+ - The dot is **not** a path separator, so subpath exports still resolve: `@nurix/workflows.agent-core/nodes`.
19
+ - The dot tracks "internal product package," not the `private` flag exactly — `@nurix/workflows.secrets` is `private: false` because it's extraction-bound (slated to move to a sibling repo/namespace), yet still wears the dotted internal name. Today `-sdk` is the only clean published form.
20
+
21
+ **Naming a new package:** internal to this repo? → `@nurix/workflows.<thing>`, `private: true`, kebab-case `<thing>` (the default). Externally consumed contract? → clean `@nurix/<thing>` (rare). Shared lib from another repo? → keep its existing `@nurix/<name>`.
22
+
23
+ ---
24
+
25
+ ## 2. Monorepo & package boundaries
26
+
27
+ One `tsconfig.base.json` at the repo root is the **single owner of TS policy** — the strict set (see [`frontend.md`](frontend.md)), `target`, and any option uniform across packages (here, `module`/`moduleResolution`). Each package's `tsconfig.json` is thin: it `extends` the base, then adds only its own *coordinates* (`rootDir`/`outDir`/`include`) and genuine *environment* overrides (`lib`/`types`/`jsx`, plus `module`/`moduleResolution` when a package's module system genuinely differs — a Worker, browser, or NodeNext package). That is environment, not policy. Never re-declare or weaken a *policy* option locally (no per-package `strict: false`, no widening `noUncheckedIndexedAccess`). A framework that owns its own base (e.g. an Astro app on `astro/tsconfigs/strict`) is the one allowed exception.
28
+
29
+ - **Internal packages are source-first.** A `private: true` workspace package exposes its API through the `exports` field pointing at TypeScript source (`"types"`/`"default"` → `./src/index.ts`) — consumed across the workspace with **no build step**. Prefer the `exports` field; don't add a bare `main`/`module` to new packages. Add one typed subpath per public surface (`"./nodes": { "types": "./src/nodes/index.ts", "default": "./src/nodes/index.ts" }`); consumers import the subpath, **never** a deep relative path into another package's `src/`.
30
+ - **Published packages build to `dist` — never ship raw `src`.** The moment a package crosses a publish boundary (`private: false`), it compiles with `tsc` (`declaration: true`, `declarationMap: true` from the base), ships `files: ["dist"]`, and points `exports`/`types` at `./dist/*` (use `publishConfig.exports` to swap source→dist on publish if the dev entry stays on source). `prepublishOnly` runs the build.
31
+ - **Declare host singletons as `peerDependencies`** on published libraries (`react`, `react-dom`, the framework client) so consumers keep one copy; never bundle them. Apps depend on them directly.
32
+ - **Single-source shared dep versions with pnpm `catalog:`** — `typescript`, `zod`, `vitest`, `react`, type packages. One version per dependency across the workspace so every package's types resolve identically; no per-manifest version drift.
33
+ - **Reach for TS project references only when it hurts** — adopt `references` once the typecheck/build graph is slow enough to feel. Until then, `pnpm -r typecheck` over independent `extends` configs is the correct default.
34
+
35
+ ---
36
+
37
+ ## 3. Consuming `@nurix/*` packages
38
+
39
+ `@nurix/*` packages publish to npm with **`restricted`** access — installing needs read rights to the `nurix` org. (Publishing one is the `nustack` skill.)
40
+
41
+ - **Auth** — `npm login` once (the npm user must be a `nurix` member), **or** drop a read token in `.npmrc` (the only CI option; with it you don't need `npm login`):
42
+ ```ini
43
+ @nurix:registry=https://registry.npmjs.org/
44
+ //registry.npmjs.org/:_authToken=${NPM_READ_TOKEN}
45
+ ```
46
+ A `403` on install means auth failed — check `npm whoami` (local) or the `NPM_READ_TOKEN` secret (CI). The read token only needs `Read` on the `nurix` scope. Just attempt the install — the token authenticates ambiently; don't pre-empt or second-guess it. If the install actually fails, flag it to the user — **never** reach for alternatives, a substitute package, or `npm login` swaps.
47
+ - **Install** — `npm install @nurix/<name>` (or `pnpm add` / `yarn add`).
48
+ - **Import by subpath, never the barrel** — bundlers tree-shake subpaths reliably; root-barrel imports less so. Available subpaths are the package's `exports` keys.
49
+ ```tsx
50
+ import { Button } from "@nurix/components/button";
51
+ import { useMobile } from "@nurix/components/hooks/use-mobile";
52
+ ```
53
+ - **Styles** — packages that ship CSS need their stylesheet imported **once** at the app entry: `import "@nurix/components/styles.css";`. Unstyled components are almost always a missing styles import. Tailwind v4 doesn't scan `node_modules`, but the shipped stylesheet is precompiled — no `@source` directive needed unless you consume the source via a workspace link.
54
+ - **CLIs** ship a `bin` — run directly: `npx @nurix/etna@latest` (or `npm install -g`).
@@ -13,7 +13,9 @@ Strictly separated — never mix them:
13
13
  | Layer | Lives in |
14
14
  | --- | --- |
15
15
  | **Method** (timeless) | this file + `.claude/rules/method.md` (how sessions run) + `.claude/rules/artifacts.md` (what gets written) |
16
- | **State** (current) | `metadata/` — `TOPICS.md` (topic index) + `SESSIONS.md` (session ledger) |
16
+ | **State** (current) | `metadata/` — `TOPICS.md` (topic index) + `SESSIONS.md` (session ledger) + `lessons/` (operator layer) + `ops/` (maintenance run-logs) |
17
17
  | **Record** (append-only) | each topic's `README.md` + artifacts |
18
18
 
19
+ **The operator layer**: `metadata/lessons/` — read `DIGEST.md` at session start alongside the routing files; it is **prior, not canon** (its reading-protocol block governs). Everything else under `lessons/` (append-only `log/`, `forecasts.md`) loads on demand only. The `lessons-maintenance` agent (instruction set: `metadata/lessons/distillation.md`) runs **only during user sessions** — spawn it in the background when the `metadata/ops/` cursor falls a session behind.
20
+
19
21
  The rules are the method — portable, abstract, generic examples; nothing depends on any local topic. Live exemplars are reached through `metadata/TOPICS.md`. Workspace-local method notes go to `metadata/method-changelog.md`; the harness itself is versioned by `.claude/harness.json` and documented in the etna forge (`docs/architecture/harness/discussion/`).
@@ -17,8 +17,8 @@ Decide once, up front, from two reads:
17
17
  | Workspace version | Mode |
18
18
  | --- | --- |
19
19
  | `metadata/TOPICS.md` absent | **bootstrap** — fresh workspace, scaffold everything |
20
- | present, behind on **major** | **reconcile** — converge to the current shape, then re-stamp |
21
- | present, behind on minor/patch | re-stamp only; report, change nothing else |
20
+ | present, behind on **major or minor** | **reconcile** — converge to the current shape, then re-stamp |
21
+ | present, behind on patch | re-stamp only; report, change nothing else |
22
22
  | equal | verify-only — report what's present, stop |
23
23
 
24
24
  **Precondition:** a root `CLAUDE.md` must exist — if it doesn't, the harness wasn't installed; stop and say so.
@@ -29,9 +29,10 @@ Decide once, up front, from two reads:
29
29
 
30
30
  1. **Git.** If `.git/` is absent: `git init`. Ensure the root `.gitignore` exists with the minimal set (below). The workspace **must** end this run as a git repository — the session-close auto-commit depends on it.
31
31
  2. **Seed `metadata/`.** Create `metadata/TOPICS.md`, `metadata/SESSIONS.md`, and `metadata/method-changelog.md` from the templates below — create only what's missing; **never overwrite an existing file**. Stamp `harness-version:` in `TOPICS.md` frontmatter from `.claude/harness.json`.
32
- 3. **Adopt existing content.** If topic-shaped folders already exist (a folder with a `README.md` that isn't harness machinery), fold them forward: add a `TOPICS.md` entry per topic (state phrase from its README's TL;DR/open threads), and create a `metadata.md` map for any topic lacking one (one line per file — name + what it is, from each file's H1/banner; bulk directories one line total). Adoption is registration, not rewriting — READMEs stay untouched.
33
- 4. **Commit.** One commit: `🔧 baseline: discussion harness v<version>`.
34
- 5. **Hand off.** Report created vs skipped vs adopted, then point at the next actions: start the first topic (say what you're thinking about), or resume via `metadata/TOPICS.md`.
32
+ 3. **Seed the operator layer.** Create `metadata/lessons/` (`distillation.md`, `dimensions.md`, `DIGEST.md`, `forecasts.md`, empty `log/` with a `.gitkeep`) and `metadata/ops/lessons-miner.md` from the templates below — create only what's missing. These ship **empty**: every install accumulates its own operator's lessons via the `lessons-maintenance` agent; no personal content ever ships with the harness.
33
+ 4. **Adopt existing content.** If topic-shaped folders already exist (a folder with a `README.md` that isn't harness machinery), fold them forward: add a `TOPICS.md` entry per topic (state phrase from its README's TL;DR/open threads), and create a `metadata.md` map for any topic lacking one (one line per file — name + what it is, from each file's H1/banner; bulk directories one line total). Adoption is registration, not rewriting — READMEs stay untouched.
34
+ 5. **Commit.** One commit: `🔧 baseline: discussion harness v<version>`.
35
+ 6. **Hand off.** Report created vs skipped vs adopted, then point at the next actions: start the first topic (say what you're thinking about), or resume via `metadata/TOPICS.md`.
35
36
 
36
37
  ## Reconcile mode
37
38
 
@@ -44,7 +45,7 @@ Declarative, not a migration ladder — converge to the **current** shape; detec
44
45
  | Method changelog inside `CLAUDE.md` | `metadata/method-changelog.md` (copy the entries out; the CLI owns `CLAUDE.md` — flag, don't edit it) |
45
46
  | Instrument folders at the root | flag for the user to relocate outside the workspace (never move data yourself) |
46
47
 
47
- Only fold what is present. Then re-stamp `harness-version:`, commit `🔧 baseline: reconciled to discussion harness v<version>`, and report every move.
48
+ Only fold what is present. Also seed any missing operator-layer files (`metadata/lessons/` + `metadata/ops/` — same templates below, create-only; introduced in v0.2.0). Then re-stamp `harness-version:`, commit `🔧 baseline: reconciled to discussion harness v<version>`, and report every move.
48
49
 
49
50
  ## Rules
50
51
 
@@ -91,3 +92,142 @@ dist/
91
92
  .DS_Store
92
93
  .claude/*.log
93
94
  ````
95
+
96
+ `metadata/lessons/distillation.md`:
97
+
98
+ ````markdown
99
+ # Lessons layer — instruction set (miner + distillation)
100
+
101
+ > Operating instructions for the `lessons-maintenance` agent over `metadata/lessons/`. Refine the bar here, dated — this file is the layer's source of truth, and the layer is workspace-self-contained (nothing here depends on files outside `metadata/`).
102
+
103
+ ## The two tiers
104
+
105
+ - `log/` — append-only occurrence record: new files per run, never edited, never loaded by default.
106
+ - `DIGEST.md` — the always-loaded operator profile, rewritten in place by distillation only, in a single Write.
107
+ - `forecasts.md` — the operator's falsifiable claims: appended **by the main agent in the moment** (the one main-agent write here), confidence elicited; distillation resolves triggers and mines calibration. `confidence: —` = retro-mined, calibration-excluded.
108
+ - `dimensions.md` — the capture taxonomy (D0–D31) for the grammar's dimension field.
109
+
110
+ ## Run doctrine
111
+
112
+ - Runs happen **only during user sessions**, periodically. Trigger: at session routing, a cursor a session or more behind (or empty) → spawn in the background.
113
+ - **Single-flight:** read the `Claim:` line in `metadata/ops/lessons-miner.md`; a live claim younger than ~1 h → skip and report. Otherwise claim (ISO timestamp), run, clear at close; cursor changed under you → abort the distill step and report.
114
+ - Every run: claim → read cursor → mine the delta (transcripts + workspace record + fired forecast triggers) → append one or more log files (occurrence-ID prefix unique per file — check the `log/` tail) → distill → rewrite `DIGEST.md` → update cursor + append run entry → clear claim.
115
+ - **The distill step also:** re-aggregates the critic-findings matrix over new completeness-critic files (re-testing register disconfirmation conditions); runs an absence audit (families with zero occurrences — is the silence expected?); every ~10 sessions re-clusters the taxonomy from the raw log (D0 items first).
116
+ - **Operator relay:** the run report ends with "`inferred` entries awaiting disposition: <list or none>" — the spawning session surfaces it to the operator.
117
+
118
+ ## Transcript source contract (re-verify on Claude Code schema drift)
119
+
120
+ - Source: `~/.claude/projects/<project-slug>/*.jsonl`, top level only; `<project-slug>` = the workspace's absolute path with every non-alphanumeric character replaced by `-`. Directory absent → best suffix match under `~/.claude/projects/`; none → **logged skip, never silent**.
121
+ - Operator text = `type=="user"`, not `isMeta`, string/text content — then EXCLUDE by signature: tool_result blocks, `<system-reminder>`, `<command-name>`/`<local-command-stdout>` wrappers, compaction summaries, hook-spawned headless sessions. Sanity-check conversational register before mining.
122
+ - Parse failure → logged skip with reason. Transcripts expire (~30-day TTL): the verbatim quote in the log is the durable evidence; `src:` paths are best-effort pointers.
123
+
124
+ ## Occurrence grammar (log lines)
125
+
126
+ `- [<prefix>-<seq>] <ISO date> · <source> · <op|collab> · D<nn> · "<verbatim quote or —>" · <one-line observation> · src: <path>`
127
+
128
+ - Only what is on the record — no invention; verbatim quotes preferred, trimmed; empty quote = bare `—`.
129
+ - **Redaction:** never copy credentials, tokens, or third-party personal data — paraphrase with `[redacted: <kind>]`.
130
+ - Dimension per `dimensions.md`; D0 always available — never force a fit. Capture strengths as well as deficits, both directions (`op` = about the operator, `collab` = about the pair). Capture-only dimensions are logged, never distilled.
131
+ - **Evidence hygiene:** digest entries cite occurrence IDs; candidate-lesson IDs only with a file anchor; n-counts are direct occurrences.
132
+
133
+ ## Distillation bar (defaults — refine per workspace)
134
+
135
+ 1. **Admission:** n≥2 independent occurrences (different sessions or topics); an explicit operator ruling admits at n=1, tagged `ratified`.
136
+ 2. **Task grammar only** — dated situation + action + count; no psychological verdicts, no trait adjectives.
137
+ 3. **Provenance tags:** `ratified` (operator ruling) · `quoted` (verbatim behavior on record) · `inferred` (agent pattern-read = *observed pending objection*, stands until the operator objects). Inferred entries never cite inferred entries as evidence.
138
+ 4. **Dedup / merge / modify / kill:** distillation's call. Merging increments n; killing removes from DIGEST only — the log never loses the record.
139
+ 5. **Staleness:** every entry carries `last-confirmed` (a banner-level declaration may cover a uniform date); stale (~10 sessions unconfirmed) → demote back to log, noted in the run entry.
140
+ 6. **Taste entries** carry a paired failure-mode line (the taste's known downside).
141
+ 7. **No cap:** compress as hard as honesty allows; past **100 lines**, prepend `⚠ LOAD WARNING: digest exceeds quota (<n> lines) — operator attention requested.` — the session-start loader surfaces it.
142
+ 8. **Invariant header:** DIGEST's reading-protocol block survives every rewrite verbatim.
143
+ 9. **Capture-tier exclusion:** dimensions marked capture-only in `dimensions.md` never enter DIGEST, in any paraphrase.
144
+ ````
145
+
146
+ `metadata/lessons/dimensions.md`:
147
+
148
+ ````markdown
149
+ # Capture taxonomy — dimensions D0–D31
150
+
151
+ > The vocabulary for the occurrence grammar's `D<nn>` field ([distillation.md](distillation.md)). **Capture-only** dimensions are logged but never distill into the digest. Refine per workspace, dated; keep this file self-contained.
152
+
153
+ - **D0 — Uncategorized** (mandatory slot: never force a fit; re-clustered periodically)
154
+
155
+ **Error-shaped** — corrections of the operator
156
+ - D1 falsified-claim corrections · D2 recurring structural blind-spot classes · D3 calibration misses · D4 omission signature
157
+
158
+ **Style-shaped** — taste, register, vocabulary, values
159
+ - D5 output-shape taste rulings · D6 coined lexicon · D7 value hierarchy / trade-off constants · D8 register & comprehension demands · D9 delight markers
160
+
161
+ **Process-shaped** — decision habits, rhythms, scoping
162
+ - D10 disposition statistics · D11 ratification style · D12 question/brief quality · D13 scoping & boundary-drawing habits · D14 escalation patterns · D15 engagement rhythm · D16 re-litigation attempts · D17 deferral debt
163
+
164
+ **Collaboration-shaped** — the pair, not the human
165
+ - D18 corrections of the AI · D19 delegation grammar · D20 verification-vs-acceptance trust boundary · D21 standing division-of-labor rulings · D22 infrastructure-failure etiquette
166
+
167
+ **Strength-shaped** — what reliably works
168
+ - D23 vindicated framings · D24 productive reframes · D25 whitespace-spotting · D26 domain-competence map
169
+
170
+ **Forward-shaped** — predictions and intent
171
+ - D27 explicit forecasts · D28 intent-vs-realized drift
172
+
173
+ **Observability frontier**
174
+ - D29 attention/energy signature (**capture-only** by default) · D30 trust-drift over time · D31 narration style (**capture-only** by default)
175
+ ````
176
+
177
+ `metadata/lessons/DIGEST.md`:
178
+
179
+ ````markdown
180
+ # Operator digest — the always-loaded lessons tier
181
+
182
+ > **State — rewritten in place by the distillation agent only** (bar: [distillation.md](distillation.md)). Scope: workspace-observable behavior only — not a portrait of the person in general. Task-grammar only, no psychological verdicts. Quota: past 100 lines a `⚠ LOAD WARNING` line appears above this banner — loader, surface it to the operator.
183
+
184
+ ## Reading protocol (invariant)
185
+
186
+ - Everything below is **prior, not canon**: it calibrates communication; it never forecloses challenge or pushback.
187
+ - Surface a lesson when acting on it; never silently compensate.
188
+ - `inferred` entries are *observed pending objection* — unratified hypotheses.
189
+ - `op` describes the operator; `collab` describes the working pair — only collab entries read as operating guidance.
190
+
191
+ ## Operator (`op`)
192
+
193
+ (no entries yet — run the `lessons-maintenance` agent)
194
+
195
+ ## Blind-spot register (`op`, inferred)
196
+
197
+ (no entries yet)
198
+
199
+ ## Collaboration (`collab`)
200
+
201
+ (no entries yet)
202
+ ````
203
+
204
+ `metadata/lessons/forecasts.md`:
205
+
206
+ ````markdown
207
+ # Forecast register
208
+
209
+ > Append-only. Grammar: `- [F<seq>] <ISO date> · "<claim, operator's words>" · confidence: <low/med/high> · resolves-when: <trigger> · resolution: open|correct|wrong|void (<date>)`. Captured in the moment a falsifiable call is made; the distillation agent resolves triggers and mines calibration — the one dimension that can never be back-filled.
210
+
211
+ (no entries yet)
212
+ ````
213
+
214
+ `metadata/ops/lessons-miner.md`:
215
+
216
+ ````markdown
217
+ # lessons-miner — run log
218
+
219
+ > Operational state for the `lessons-maintenance` agent (instruction set: `metadata/lessons/distillation.md`). Claim + cursor rewritten in place; runs appended at the bottom. Maintenance runs only during user sessions; abrupt closes are expected — this file holds enough state to resume next session, including over old sessions.
220
+
221
+ ## Claim
222
+
223
+ (none)
224
+
225
+ ## Cursor
226
+
227
+ Grammar: workspace record = last-processed git commit; transcripts = per-file processed-line counts (grown past its count → mine the delta; new file → mine whole; `re-scan` → re-mine whole and dedupe by quote).
228
+
229
+ - **Workspace record:** (nothing yet)
230
+ - **Transcripts:** (nothing yet)
231
+
232
+ ## Runs
233
+ ````
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: lessons-maintenance
3
+ description: Maintains the operator-lessons layer — mines the delta of session history (Claude transcript JSONL) and the workspace record into the append-only occurrence log at metadata/lessons/log/, then re-distills the always-loaded operator digest under the bar in metadata/lessons/distillation.md. Resumable from the metadata/ops/lessons-miner.md cursor after abrupt session ends; runs only during user sessions. Trigger periodically mid-session in the background, whenever the cursor is a session or more behind, or on "run lessons maintenance", "update the digest", "mine the sessions".
4
+ tools: Read, Glob, Grep, Bash, Write, Edit
5
+ model: inherit
6
+ ---
7
+
8
+ You are the **lessons-maintenance** agent. One job: keep the operator-lessons layer current — log what the record shows, distill what the evidence supports, and get out of the way. You are resumable by design: all run state lives in `metadata/ops/lessons-miner.md`, never in conversation.
9
+
10
+ ## Contract
11
+
12
+ - **Instruction set:** `metadata/lessons/distillation.md` is the source of truth for the run doctrine, the transcript source contract, the occurrence grammar, the admission bar, and lifecycle authority; `metadata/lessons/dimensions.md` is the capture taxonomy. Read both first and follow them exactly; where this prompt and those files disagree, the files win.
13
+ - **Write boundary:** you write only `metadata/lessons/**` and `metadata/ops/lessons-miner.md` (the main agent holds one carve-out: in-the-moment appends to `forecasts.md`). Log files are append-only — you create new ones, never edit filed ones. `DIGEST.md` you rewrite whole, in a single Write. You never touch topic folders, rules, or `CLAUDE.md`.
14
+ - **When you run:** only during a user session (the main agent spawns you, typically in the background). Abrupt session ends are normal — the next run resumes from the cursor.
15
+
16
+ ## The run
17
+
18
+ 1. **Claim (single-flight).** Read the `Claim:` line in `metadata/ops/lessons-miner.md`. A live claim younger than ~1 h → stop and report "run in flight". Otherwise write your ISO-timestamp claim.
19
+ 2. **Cursor.** Read the deterministic cursor: workspace-record git commit + per-transcript-file processed-line counts.
20
+ 3. **Mine the delta.** Two source families, per the transcript source contract in `distillation.md`:
21
+ - **Session transcripts** — `~/.claude/projects/<project-slug>/*.jsonl`, top level only; `<project-slug>` = the workspace's absolute path with every non-alphanumeric character replaced by `-` (directory absent → best suffix match under `~/.claude/projects/`; none → logged skip, never silent). Mine only files new or grown past their cursor count (`re-scan` entries: re-mine whole and dedupe by quote). Do NOT Read files raw — extract the operator's own typed messages with jq (`type=="user"`, not `isMeta`, string/text content), then exclude by signature: tool results, `<system-reminder>`/`<command-…>` wrappers, compaction summaries, hook-spawned headless sessions. Sanity-check conversational register before mining. Timestamps give rhythm evidence (capture-only).
22
+ - **Workspace record** — provenance entries, synthesis pushback registers, completeness-critic files, and ledger entries newer than the record cursor; plus `forecasts.md` entries whose resolution trigger has fired.
23
+ 4. **Log.** Append one or more dated log files to `metadata/lessons/log/`, occurrence grammar per the instruction set — occurrence-ID prefix unique per file (check the `log/` tail before choosing); redaction rule applies; both directions, strengths as well as deficits; capture-only dimensions logged, never distilled.
24
+ 5. **Distill.** Rewrite `DIGEST.md` under the bar: admit, merge, modify, retire — your call, per the instruction set's authority grant. Also: re-aggregate `critic-findings-matrix.md` over new critic files and re-test register disconfirmation conditions; run the absence audit; re-cluster D0 items on the ~10-session cadence. The reading-protocol block survives verbatim. Compress hard; past the quota, prepend the load-warning line rather than cutting truth. If the cursor changed under you since your claim, abort this step and report.
25
+ 6. **Close.** Update the cursor, append a run entry (date · sources processed · occurrences added · digest delta · skips with reasons), clear your claim, and report — ending with the line: **"`inferred` entries awaiting disposition: <list or none>"** (the spawning session relays it to the operator).
26
+
27
+ ## Rules
28
+
29
+ - Evidence or silence — no invention; verbatim quotes preferred, trimmed; never quote credentials or third-party personal data (paraphrase with `[redacted: <kind>]`).
30
+ - The operator's explicit statements outrank your inferences; `inferred` entries stand only as *observed pending objection* until the operator disposes.
31
+ - A clean run that finds nothing is valid — report it and add no noise.
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "harness": "discussion",
3
3
  "name": "discussion",
4
- "version": "0.1.1",
4
+ "version": "0.2.1",
5
5
  "files": [
6
6
  ".prettierrc.json",
7
7
  ".prettierignore",
8
8
  ".vscode/settings.json"
9
9
  ],
10
- "digest": "ac56353ca016"
10
+ "digest": "14700b32c049"
11
11
  }
@@ -5,7 +5,7 @@ How sessions run in this workspace. What gets written is [artifacts.md](artifact
5
5
  ## Workspace layout
6
6
 
7
7
  - **Topic folders** — one folder = one long-running topic. Kebab-case noun phrase naming the **inquiry, not the deliverable** (`pricing-research`, not `pricing-report`). Every topic has a `README.md` (the record), a `metadata.md` (the map), and a `TOPICS.md` entry.
8
- - **`metadata/`** — all workspace state: `TOPICS.md` (topic index), `SESSIONS.md` (session ledger), `method-changelog.md` (workspace-local method notes; the harness itself versions via `.claude/harness.json`). State lives here and nowhere else.
8
+ - **`metadata/`** — all workspace state: `TOPICS.md` (topic index), `SESSIONS.md` (session ledger), `method-changelog.md` (workspace-local method notes; the harness itself versions via `.claude/harness.json`), `lessons/` (the operator layer: append-only log + always-loaded digest + forecasts), `ops/` (maintenance-agent run-logs). State lives here and nowhere else.
9
9
  - **External instruments** — code/data a topic depends on (scraper, corpus, pipeline) lives **outside the workspace**, never at the root: this folder holds only topics and the harness. Record the dependency on the depending topic's README (external path + ratification status); exclusions are recorded on the excluder's side.
10
10
  - **Git** — the workspace **is a git repository** (the `discussion-baseline` agent initializes one). The SessionEnd hook auto-commits each session's changes as one `📝 session close` batch — never per-file commits. Milestone commits by hand are still welcome: `✨` content, `🔧` workspace/method. Root `.gitignore` stays minimal.
11
11
  - **Grandfathering** — when the method changes, never reshuffle existing artifacts to match. New artifacts follow current rules; old layouts stay, flagged where confusing.
@@ -14,7 +14,7 @@ How sessions run in this workspace. What gets written is [artifacts.md](artifact
14
14
 
15
15
  A session is one sitting; the durable unit is the topic. Route before substantive work:
16
16
 
17
- 1. Read `metadata/TOPICS.md` and the last entry of `metadata/SESSIONS.md` (fallback: list folders, read the first ~15 lines of each README).
17
+ 1. Read `metadata/TOPICS.md`, the last entry of `metadata/SESSIONS.md`, and the operator digest `metadata/lessons/DIGEST.md` (fallback: list folders, read the first ~15 lines of each README).
18
18
  2. Match the opening message against the topics. Likely continuation → recommend **that topic only** ("sounds like `<topic>/` — pick it up there?"). Unambiguous → skip the ask, just confirm the folder.
19
19
  3. No plausible match → ask: continue an existing topic or start new (one-line summaries + open threads).
20
20
  4. Existing topic → read its `metadata.md`, then its README **fully** before anything substantive. Never re-litigate settled decisions.
@@ -66,16 +66,18 @@ Method lives in the rules (timeless); state lives in `metadata/` (current); topi
66
66
 
67
67
  One entry per session, appended at the bottom. An entry is the session's **summary**, rewritten in place when the session resumes — never a resume-log. Format: `## YYYY-MM-DD — <goal headline>` + bullets: `**Goal:**` (the user's stated aim; update it if it shifts mid-session) · `**Touched:**` · `**Filed:**` (linked) · `**Close state:**` (where it stopped, what it awaits). Entries point into topics; content never lives here.
68
68
 
69
+ ### `lessons/` + `ops/` — the operator layer
70
+
71
+ - `metadata/lessons/` is two-tier: an append-only occurrence **`log/`** (one dated file per maintenance run, never edited, never loaded by default) distilled into **`DIGEST.md`** — the operator profile read at session start (**prior, not canon**; its reading-protocol block governs). Beside them: `forecasts.md` (the operator's falsifiable claims, captured in the moment; resolutions mined for calibration) and `distillation.md` (the maintenance instruction set + admission bar).
72
+ - `metadata/ops/` holds maintenance run-logs — cursor + run entries — so abrupt session ends resume cleanly next session.
73
+ - The **`lessons-maintenance` agent** does the writing here, with one carve-out: the main agent appends to `forecasts.md` in the moment a falsifiable claim is made. The agent mines the delta of raw session transcripts and the workspace record, appends to the log, and re-distills the digest. It runs **only during user sessions**: at routing, glance at the ops cursor — a session or more behind (or empty) → spawn it in the background; when its run closes, relay its "inferred entries awaiting disposition" line to the operator. Lessons are evidence-linked occurrences in task grammar, never psychological verdicts; AI-inferred entries stand as *observed pending objection* until the operator disposes; capture-only dimensions never enter the digest.
74
+
69
75
  ### The SessionEnd hook
70
76
 
71
77
  Safety net only: `settings.json` → script in `.claude/hooks/`, `$CLAUDE_PROJECT_DIR`-relative. Script: recursion-guard env sentinel · stdin JSON → `transcript_path`, silent exit-0 on anything missing · headless `claude -p`, tool-allowlisted to write **only the two `metadata/` state files**, told to read this rule for the grammars · then **one batched auto-commit** of the session's changes · `nohup … & disown` to outlive the timeout · appends `.claude/topics-sync.log`. Needs `jq` + `claude` on PATH (missing → logged). Failures are logged, never surfaced: stale index → check the log, reconcile by hand.
72
78
 
73
- ## Working with this user
79
+ ## Working with the operator
74
80
 
75
- - Developer; thinks **data-model-first** and in systems (storage / flow / view). Meet them there.
76
- - *"Throw a lot at me — I'll tell you what's relevant."* Exhaustive named-pattern catalogs over cautious summaries.
77
- - **Push back explicitly** — dedicated pushback sections on their claims, not watered-down hedging. When they push back on you, find the precise boundary of what was wrong; don't abandon everything.
78
- - They coin patterns mid-discussion: name them crisply, credit the framing, build canon in their vocabulary.
79
- - Scale effort to the ask: sweeps for capture/research; direct opinionated prose for "what's your view".
81
+ The operator profile lives in `metadata/lessons/DIGEST.md` — maintained by the `lessons-maintenance` agent, loaded at session start, evidence-linked into the layer's log: taste, decision patterns, the blind-spot register, collaboration lessons. It is a **prior, not canon** — it calibrates communication and briefing, never forecloses pushback. Personal content lives there and nowhere else; these rules stay portable and impersonal.
80
82
 
81
83
  **Sub-agents validate, the main agent judges** (standing rule): agents gather, cross-check, attack; the main agent assembles the deliverable and adjudicates every finding itself. Every validation round is adjudicated on the record — N findings → accepted (reason each) / **rejected as intentional** (reason each); never auto-apply. Overrules carry citations into the raw corpus. Record sweep telemetry in provenance: agents, model, lanes, sources.
@@ -7,5 +7,5 @@
7
7
  ".prettierignore",
8
8
  ".vscode/settings.json"
9
9
  ],
10
- "digest": "8313ddf886d3"
10
+ "digest": "ac886b2bc4b1"
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.19.8",
3
+ "version": "0.19.10",
4
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
5
  "type": "module",
6
6
  "bin": {
@@ -1,150 +0,0 @@
1
- # Nustack — `@nurix/*` Packages: naming, monorepo layout, and publishing
2
-
3
- How a `@nurix/*` multi-package repo is **named**, **structured**, and **shipped**. For consuming an already-published `@nurix/*` package (auth, install, imports), see the Consuming section of [`typescript.md`](typescript.md).
4
-
5
- ---
6
-
7
- ## 1. Package naming
8
-
9
- Every workspace package is scoped to **`@nurix/`**. The segment after the scope encodes **intent** — the dot vs. hyphen is semantic, not cosmetic:
10
-
11
- | Form | Meaning | Examples |
12
- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
13
- | `@nurix/workflows-sdk` (hyphen, single segment) | The **published, externally-destined** surface. A clean `@nurix/*` name is a published _contract_ — changing an export or peer dep is a breaking change for consumers outside this repo. | `@nurix/workflows-sdk` |
14
- | `@nurix/workflows.<name>` (dot separator) | **Internal** to the workflows product — `private: true`, refactor freely, no external consumers. | `@nurix/workflows.agent-core`, `.worker`, `.gateway`, `.secrets`, `.connector`, `.playground`, `.playground-tests` |
15
- | `@nurix/<name>` (plain, no `workflows` prefix) | Cross-repo **shared libs** owned _elsewhere_ and merely consumed here — never renamed. | `@nurix/contracts`, `@nurix/components` |
16
-
17
- - The `<name>` segment is always **kebab-case** (`agent-core`, `playground-tests`).
18
- - The dot is **not** a path separator, so subpath exports still resolve: `@nurix/workflows.agent-core/nodes`.
19
- - The dot tracks "internal product package," not the `private` flag exactly — `@nurix/workflows.secrets` is `private: false` because it's extraction-bound (slated to move to a sibling repo/namespace), yet still wears the dotted internal name. Today `-sdk` is the only clean published form.
20
-
21
- **Naming a new package:** internal to this repo? → `@nurix/workflows.<thing>`, `private: true`, kebab-case `<thing>` (the default). Externally consumed contract? → clean `@nurix/<thing>` (rare). Shared lib from another repo? → keep its existing `@nurix/<name>`.
22
-
23
- ---
24
-
25
- ## 2. Monorepo & package boundaries
26
-
27
- One `tsconfig.base.json` at the repo root is the **single owner of TS policy** — the strict set (see [`typescript.md`](typescript.md)), `target`, and any option uniform across packages (here, `module`/`moduleResolution`). Each package's `tsconfig.json` is thin: it `extends` the base, then adds only its own *coordinates* (`rootDir`/`outDir`/`include`) and genuine *environment* overrides (`lib`/`types`/`jsx`, plus `module`/`moduleResolution` when a package's module system genuinely differs — a Worker, browser, or NodeNext package). That is environment, not policy. Never re-declare or weaken a *policy* option locally (no per-package `strict: false`, no widening `noUncheckedIndexedAccess`). A framework that owns its own base (e.g. an Astro app on `astro/tsconfigs/strict`) is the one allowed exception.
28
-
29
- - **Internal packages are source-first.** A `private: true` workspace package exposes its API through the `exports` field pointing at TypeScript source (`"types"`/`"default"` → `./src/index.ts`) — consumed across the workspace with **no build step**. Prefer the `exports` field; don't add a bare `main`/`module` to new packages. Add one typed subpath per public surface (`"./nodes": { "types": "./src/nodes/index.ts", "default": "./src/nodes/index.ts" }`); consumers import the subpath, **never** a deep relative path into another package's `src/`.
30
- - **Published packages build to `dist` — never ship raw `src`.** The moment a package crosses a publish boundary (`private: false`), it compiles with `tsc` (`declaration: true`, `declarationMap: true` from the base), ships `files: ["dist"]`, and points `exports`/`types` at `./dist/*` (use `publishConfig.exports` to swap source→dist on publish if the dev entry stays on source). `prepublishOnly` runs the build.
31
- - **Declare host singletons as `peerDependencies`** on published libraries (`react`, `react-dom`, the framework client) so consumers keep one copy; never bundle them. Apps depend on them directly.
32
- - **Single-source shared dep versions with pnpm `catalog:`** — `typescript`, `zod`, `vitest`, `react`, type packages. One version per dependency across the workspace so every package's types resolve identically; no per-manifest version drift.
33
- - **Reach for TS project references only when it hurts** — adopt `references` once the typecheck/build graph is slow enough to feel. Until then, `pnpm -r typecheck` over independent `extends` configs is the correct default.
34
-
35
- ---
36
-
37
- ## 3. Publishing & the release pipeline
38
-
39
- `@nurix/*` packages publish to **npmjs.org with `restricted` access** — only npm users with read rights to the `nurix` org can install them. Reference implementations: `~/dev/components` (`@nurix/components`, library) and `~/dev/etna/packages/cli` (`@nurix/etna`, CLI).
40
-
41
- ### 3.1 Prerequisites (one-time, org-wide)
42
-
43
- 1. **npm org `nurix`** exists and you're a member with publish rights — <https://www.npmjs.com/settings/nurix/packages>.
44
- 2. **An npm automation token** with `Read and write` for the `nurix` scope — npm → Generate New Token → Automation.
45
- 3. **The token added as a GitHub Actions secret** named `NPM_PUBLISH_TOKEN` on every publishing repo. `NPM_PUBLISH_TOKEN` is the convention; pick another name only with a strong reason.
46
-
47
- ### 3.2 `package.json` — required fields
48
-
49
- ```jsonc
50
- {
51
- "name": "@nurix/<package-name>", // The scope makes it discoverable to org members.
52
- "version": "0.1.0", // Start at 0.1.0; the workflow bumps from here.
53
- "private": false, // MUST be false (or omitted). `npm publish` refuses if true.
54
- "publishConfig": { "access": "restricted" }, // Scoped + private: only org members can install.
55
- "files": ["dist"] // Whitelist of what ships. Anything outside is excluded.
56
- }
57
- ```
58
-
59
- - **`publishConfig.access`** — `"restricted"` (recommended): visible only to `nurix`-org readers. `"public"`: world-readable (free, but only if you mean it). Omitting it on a scoped package defaults to `restricted`; be explicit anyway.
60
- - **`files`** — without it, npm publishes everything not in `.gitignore`, **including source**. Library → `["dist"]`; CLI (etna) → `["bin/", "data/", "README.md"]`. Don't list `package.json`/`README.md` — npm always includes them.
61
- - **Build hooks** — `prepack` for build steps that must produce files in `files` (also runs on install-as-git-dep); `prepublishOnly` for publish-only steps. Either alone is fine; `@nurix/components` runs both.
62
- - **Other useful fields** — `type: "module"`, `engines.node`, `bin` (CLIs), `main`/`module`/`types`/`exports` (libraries), `sideEffects: ["**/*.css"]`, `repository` (with `directory` for monorepos), `license: "UNLICENSED"`.
63
-
64
- ### 3.3 The publish workflow (generic template)
65
-
66
- The dual-trigger pattern: a push to the default branch auto-ships a patch; a manual dispatch picks minor/major.
67
-
68
- ```yaml
69
- name: Publish to npm
70
- on:
71
- push:
72
- branches: [dev] # your default branch
73
- paths: # ONLY paths that change what ships — else every README edit publishes
74
- - 'src/**'
75
- - 'scripts/**'
76
- - 'package.json'
77
- - 'package-lock.json'
78
- - '.github/workflows/publish.yml'
79
- workflow_dispatch:
80
- inputs:
81
- bump: { description: 'Version bump', type: choice, options: [patch, minor, major], default: patch }
82
- permissions: { contents: write } # push the version-bump commit + tag
83
- concurrency: { group: publish, cancel-in-progress: false } # monorepo: group: publish-<pkg>
84
- jobs:
85
- publish:
86
- runs-on: ubuntu-latest
87
- steps:
88
- - uses: actions/checkout@v5
89
- with: { fetch-depth: 0, token: '${{ secrets.GITHUB_TOKEN }}' }
90
- - uses: actions/setup-node@v5
91
- with: { node-version: 22, registry-url: 'https://registry.npmjs.org' } # writes .npmrc using $NODE_AUTH_TOKEN
92
- - run: npm ci # skip ONLY if the build is pure-Node with no deps (e.g. @nurix/etna)
93
- - run: |
94
- git config user.name "github-actions[bot]"
95
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
96
- - run: npm version ${{ inputs.bump || 'patch' }} -m "chore: release v%s [skip ci]"
97
- - run: npm publish # prepublishOnly / prepack runs the build
98
- env: { NODE_AUTH_TOKEN: '${{ secrets.NPM_PUBLISH_TOKEN }}' }
99
- - run: git push --follow-tags # pushes the bump commit AND the v<version> tag npm created
100
- ```
101
-
102
- **Why dual-trigger is safe** — three safeguards prevent an infinite publish loop: (1) `paths:` scopes the push trigger to artifact-affecting files; (2) `[skip ci]` in the bump commit makes GitHub skip the workflow that bump would otherwise re-trigger; (3) `concurrency: cancel-in-progress: false` queues near-simultaneous merges instead of racing for a version number.
103
-
104
- **`registry-url` is load-bearing** — without `registry-url: https://registry.npmjs.org` on `setup-node`, it never writes the `.npmrc` line that uses `$NODE_AUTH_TOKEN`, and the publish falls back to anonymous auth and fails.
105
-
106
- ### 3.4 The three ways to ship
107
-
108
- - **A. Auto on merge (common).** Land on the default branch; if a `paths:`-filtered file changed, the workflow fires and ships a patch.
109
- - **B. Manual dispatch (minor/major).** **Actions → Publish to npm → Run workflow**, pick `bump = minor|major`.
110
- - **C. Local (CI down / one-off):** `npm version <patch|minor|major>` → `npm publish` → `git push --follow-tags`.
111
-
112
- Local auth uses two tokens, two homes: a **read** token in `~/.npmrc` (`//registry.npmjs.org/:_authToken=…`) for `npm install`, and `NPM_PUBLISH_TOKEN` as an env var (sourced from `~/.zsh_secrets` or equivalent, **not** in `.npmrc` or the repo) for `npm publish`.
113
-
114
- > **Agent guard — if `NPM_PUBLISH_TOKEN` isn't in the environment, do not work around it.** No `npm login` to swap the user's auth, no rewriting `~/.npmrc`, no substituting the read token (it lacks publish rights → `403`), no `--registry` overrides, no temp-file token. Instead **stop and report**: "This repo can't publish locally — `NPM_PUBLISH_TOKEN` isn't set. Source `~/.zsh_secrets` (or wherever you keep it) and re-run, or trigger the CI workflow." Some users publish exclusively from CI — that's the default path for a reason.
115
-
116
- Verify after any flow: `https://www.npmjs.com/package/@nurix/<name>` and a clean-dir `npx @nurix/<name>@latest`.
117
-
118
- ### 3.5 Monorepo deviations
119
-
120
- When the package lives in `packages/<pkg>/` of a multi-package repo:
121
-
122
- - **`working-directory: packages/<pkg>`** on the bump and publish steps so they operate on the right `package.json`.
123
- - **Scoped concurrency** — `group: publish-<pkg>` lets sibling packages publish in parallel.
124
- - **Tag prefix — only with multiple tagging packages.** `npm version --tag-version-prefix="<pkg>-v"` (→ `<pkg>-v0.2.0`) is worth it only when more than one package cuts release tags, so they don't collide. A single publishable package (etna's case, even though it's laid out as a monorepo) uses plain `v0.2.0`.
125
-
126
- A repo with one publishable package only needs `working-directory`; skip the prefix, and concurrency scoping is optional.
127
-
128
- ### 3.6 Common pitfalls
129
-
130
- - **402 Payment Required** — `access: "public"` on an org without a plan for free public scoped packages. Use `"restricted"` (or upgrade the plan).
131
- - **403 Forbidden** — `NPM_PUBLISH_TOKEN` missing/wrong, or its user lacks `nurix` write access, or it's a read-only (not Automation) token.
132
- - **"Cannot publish over previously published version x.y.z"** — a bump ran but publish failed and was re-run without rebasing. `git reset` the bump and let CI re-bump, or `npm version patch && git push --follow-tags && npm publish`.
133
- - **Publish succeeds but `npx @nurix/foo` is "command not found"** — no `bin` field, or `bin` points outside `files`, or the script is missing its `#!/usr/bin/env node` shebang.
134
- - **"Git working directory not clean" on `npm version`** — the checkout has untracked/modified files; build only into `.gitignore`d paths (the components repo builds into `dist/`).
135
- - **Published tarball contains source** — missing `files` in `package.json`. Add `["dist"]` and republish.
136
- - **Auto-publish fires too often** — no `paths:` filter or one that's too broad. Tighten it to artifact-affecting files, or drop the push trigger for `workflow_dispatch`-only.
137
- - **Workflow loops forever** — the bump commit lacks `[skip ci]`. Invoke `npm version` with `-m "chore: release v%s [skip ci]"`.
138
- - **A normal commit silently skips its own push** — the inverse trap. GitHub scans the head commit's **whole message** (subject _and_ body) for skip directives (`[skip ci]`, `[ci skip]`, `[no ci]`, `[skip actions]`, `***NO_CI***`). If any hand-written commit merely _mentions_ the literal token in prose, the entire push is skipped and the publish never fires (symptom: branch moved, paths matched, Actions on — yet zero runs for the SHA). Never write the literal token in a commit message or PR body unless you mean it; to ship a push that carried it by accident, re-trigger via **Run workflow** / `gh workflow run … --ref dev`.
139
-
140
- ---
141
-
142
- ## 4. Quick checklist for a new `@nurix/*` package
143
-
144
- - [ ] `package.json`: `name: "@nurix/X"`, `version: "0.1.0"`, `private: false`, `publishConfig.access: "restricted"`, `files: [...]`
145
- - [ ] Build step wired into `prepack` (or `prepublishOnly`)
146
- - [ ] Name follows §1; if it's a workspace member, boundaries follow §2
147
- - [ ] `.github/workflows/publish.yml` from §3.3
148
- - [ ] `NPM_PUBLISH_TOKEN` secret added to the repo
149
- - [ ] First publish from local (`npm publish`) to verify the pipeline, then a no-op commit to verify CI
150
- - [ ] README notes the `@nurix` scope so consumers know they need org access
@@ -1,40 +0,0 @@
1
- # TypeScript
2
-
3
- TypeScript conventions. Compiles under `strict: true` and `noUncheckedIndexedAccess: true`.
4
-
5
- ## Type Safety
6
-
7
- - Never `any` to silence the checker. Unknown value → type `unknown`, then narrow (type guard / `zod` parse).
8
- - Never weaken strictness. No `// @ts-nocheck`; `// @ts-expect-error` only with a same-line reason.
9
- - Infer locals & return types; annotate at boundaries (exports, props, contracts).
10
-
11
- ## Generics
12
-
13
- Shared libraries (`@nurix/components`, etc.) are generic on purpose — that's what types `cell(row)`, checks `slug` against `keyof TData`, and ties `data`/`columns` together. Keep the safety; never widen to `any` to dodge it. State the row type **once per file**, then let inference carry it:
14
-
15
- - **Bind a repeated factory once** (preferred): `const col = defineColumn<BriefRow>;` → `col({...})`. No `<T>` per call; `row` stays typed. Canonical: `modules/briefs/BriefsListPage.tsx`.
16
- - **Infer JSX type args from props:** `<Table data={rows} columns={columns} />`, not `<Table<BriefRow> …>`.
17
- - **Anchor with a collection annotation** when nothing else can: `const columns: ColumnDefinition<BriefRow>[] = [...]`.
18
-
19
- Don't repeat `fn<Type>(…)` / `<Comp<Type>>` at every call site — cosmetic only (same safety, same emitted JS).
20
-
21
- > Monorepo TS policy (`tsconfig.base.json` + thin per-package `extends`, source-first internal packages, `peerDependencies`, pnpm `catalog:`) lives in [`nustack.md`](nustack.md) §2 — it travels with package boundaries and publishing.
22
-
23
- ## Consuming `@nurix/*` packages
24
-
25
- `@nurix/*` packages publish to npm with **`restricted`** access — installing needs read rights to the `nurix` org. (Publishing one is [`nustack.md`](nustack.md) §3.)
26
-
27
- - **Auth** — `npm login` once (the npm user must be a `nurix` member), **or** drop a read token in `.npmrc` (the only CI option; with it you don't need `npm login`):
28
- ```ini
29
- @nurix:registry=https://registry.npmjs.org/
30
- //registry.npmjs.org/:_authToken=${NPM_READ_TOKEN}
31
- ```
32
- A `403` on install means auth failed — check `npm whoami` (local) or the `NPM_READ_TOKEN` secret (CI). The read token only needs `Read` on the `nurix` scope.
33
- - **Install** — `npm install @nurix/<name>` (or `pnpm add` / `yarn add`).
34
- - **Import by subpath, never the barrel** — bundlers tree-shake subpaths reliably; root-barrel imports less so. Available subpaths are the package's `exports` keys.
35
- ```tsx
36
- import { Button } from "@nurix/components/button";
37
- import { useMobile } from "@nurix/components/hooks/use-mobile";
38
- ```
39
- - **Styles** — packages that ship CSS need their stylesheet imported **once** at the app entry: `import "@nurix/components/styles.css";`. Unstyled components are almost always a missing styles import. Tailwind v4 doesn't scan `node_modules`, but the shipped stylesheet is precompiled — no `@source` directive needed unless you consume the source via a workspace link.
40
- - **CLIs** ship a `bin` — run directly: `npx @nurix/etna@latest` (or `npm install -g`).
@@ -1,88 +0,0 @@
1
- # npm CLI Design — the `@nurix/*` pattern
2
-
3
- Conventions for building a distributable Nurix command-line tool, distilled from the two we ship: **`@nurix/etna`** (`packages/cli/` here — the harness installer, plain ESM JS) and **`@nurix/apollo`** (`~/dev/apollo/packages/cli/` — the service-bootstrap CLI, TypeScript → `dist/`). When you build or change a `@nurix` CLI, follow these. Where the two diverge, both variants are noted as valid.
4
-
5
- ## 1. Packaging
6
-
7
- - **Scoped + restricted.** Name is `@nurix/<tool>`; `"publishConfig": { "access": "restricted" }`. Never publish public.
8
- - **ESM only** — `"type": "module"`.
9
- - **`bin` maps the command to its entry** — `{ "etna": "bin/etna.js" }` / `{ "apollo": "dist/index.js" }`. The entry has a `#!/usr/bin/env node` shebang.
10
- - **`files` is an allowlist** — ship only the runnable artifact: `["bin/", "data/", "README.md"]` (etna) or `["dist"]` (apollo). Never ship `src/`, tests, or the repo.
11
- - **`engines.node` ≥ 22** for new CLIs (matches the workspace Node baseline).
12
- - **One version source** — read the running version from `package.json` at startup; don't hardcode it elsewhere.
13
- - **Minimal deps.** Both ship with essentially one runtime dep (`@clack/prompts`); apollo adds `commander` + `ajv`. Keep the dependency surface tiny — it's installed on every `npx` run.
14
-
15
- ## 2. Distribution: `npx`-first, install into the project, never globally
16
-
17
- - The canonical entry is **`npx @nurix/<tool>`** — no global install step, no marketplace, no plugin-enablement dance. The user runs `npx`, the tool does its job in the current directory.
18
- - **The CLI writes ONLY inside the folder it was invoked in.** An installer run as `npx @nurix/etna` in a project must touch only that project tree — never `$HOME`, never `~/.claude`, never any shared/user-level location. Reaching outside the target folder is surprising, clobber-prone, and wrong. (This is a hard-won rule: an earlier etna build installed agents to `~/.claude/agents/` and it was reverted precisely for this reason.)
19
- - **Idempotent refresh.** Re-running the CLI re-pulls the latest and overwrites the files it owns (force), while leaving anything the user authored untouched. "Re-run anytime to refresh" must always be safe.
20
-
21
- ## 3. Always run the latest — self-update by re-exec
22
-
23
- **The problem:** `npx` caches aggressively. A bare `npx @nurix/<tool>` can keep running a *stale* unpacked copy long after a newer version is published (npm's packument cache resolves an old `latest`, and npx reuses the matching install). Users shouldn't have to remember `@latest`.
24
-
25
- **The mechanism** (etna's `selfUpdateOrReexec()`, run before anything else): on startup, ask the registry for the latest version; if this copy is behind, re-exec the latest and hand off.
26
-
27
- ```js
28
- function selfUpdateOrReexec() {
29
- if (process.env.TOOL_SELFUPDATED || process.env.TOOL_NO_SELFUPDATE) return;
30
- const here = readJson(join(pkgRoot, 'package.json')).version;
31
- if (!here) return;
32
- const view = spawnSync('npm', ['view', '@nurix/<tool>', 'version', '--prefer-online'], { encoding: 'utf8' });
33
- const latest = view.status === 0 ? (view.stdout || '').trim() : null;
34
- if (!latest || latest === here) return; // current, offline, or unauthed → run this copy
35
- const child = spawnSync('npx', ['-y', '--prefer-online', `@nurix/<tool>@${latest}`, ...process.argv.slice(2)],
36
- { stdio: 'inherit', env: { ...process.env, TOOL_SELFUPDATED: '1' } });
37
- if (!child.error) process.exit(child.status ?? 0); // re-exec ran → hand off complete
38
- // npx unavailable (child.error) → fall through and run this (older) copy.
39
- }
40
- ```
41
-
42
- Required properties:
43
-
44
- - **`--prefer-online`** on both the `npm view` and the `npx` re-exec — defeats the metadata cache so npx fetches the fresh tarball.
45
- - **Loop guard** — set `<TOOL>_SELFUPDATED=1` in the child's env so the re-exec'd process doesn't try to update again.
46
- - **Opt-out** — `<TOOL>_NO_SELFUPDATE=1` skips the whole thing (CI, offline, or running an intentionally pinned/local-dev build). This is also how you test a local build.
47
- - **Degrade gracefully** — *any* failure (offline, unauthed, `npm`/`npx` missing, timeout) falls through to running the current copy. The update is a convenience, never a gate.
48
- - **Reuse existing npm auth** — the `npm view` rides the caller's npm credentials (the same auth that installed the restricted package); never prompt for login.
49
-
50
- ## 4. Editing the user's files: own a delimited block, idempotently
51
-
52
- When the CLI must modify a file it doesn't own (`.env`, `settings.json`, `.gitignore`), it **owns only a delimited region** and rewrites that region in place — never the whole file.
53
-
54
- - apollo wraps its `.env` writes in a managed block:
55
- `# >>> apollo:managed — do not edit by hand; the apollo CLI rewrites this block >>>` … `# <<< apollo:managed <<<`. Reads/merges/removes happen *inside* the markers; everything outside belongs to the user.
56
- - etna merges its hooks into `.claude/settings.json` and migrates/retires only its own prior entries.
57
- - Rewrites are **idempotent** (re-running converges to the same block) and **reversible** (logout/uninstall removes exactly the managed region).
58
- - If the block holds secrets, ensure `.gitignore` covers the file so they're never committed.
59
-
60
- ## 5. Interactive when it can, flag-driven when it can't
61
-
62
- - Use **`@clack/prompts`** for the interactive path (TTY): intro/outro, spinners, notes.
63
- - When **not a TTY**, require explicit flags and fail loudly with the correct syntax rather than guessing — e.g. etna needs `--name=<harness>` outside a TTY, and a removed/renamed flag (the dropped positional form in 0.18.0) errors with the new syntax instead of silently misfiring.
64
- - A bare invocation in a TTY may present a menu; the same invocation headless must error, not hang.
65
-
66
- ## 6. Detect the package manager from the lockfile
67
-
68
- When the CLI installs packages or runs scripts on the user's behalf, detect the manager: `pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, else npm. Never hardcode one. (apollo's `detectPackageManager()`.)
69
-
70
- ## 7. Publishing is CI-owned
71
-
72
- Publish from CI on push to `dev`/`main`, path-filtered to the CLI package — never `npm publish` by hand for a routine change.
73
-
74
- - **Two valid version models:**
75
- - **Fingerprint auto-bump (etna):** the build stamps a content hash (`fingerprint`) of the shipped payload; CI bumps the **patch** automatically when the fingerprint changed, publishes, and tags. Never hand-bump for a content change — let the fingerprint drive it; hand-edit the version only for a deliberate minor/major.
76
- - **Manual bump input (apollo):** a `workflow_dispatch` `bump` input (`patch`/`minor`/`major`) drives `npm version`.
77
- - **Common to both:**
78
- - The bump commit message carries **`[skip ci]`** so the publish workflow doesn't re-trigger itself.
79
- - `npm version --no-git-tag-version` (file-only), then commit + tag separately — `npm version` skips the git step when run in a subdirectory of the repo root.
80
- - Push the bump back to the branch and tag with a **package-scoped prefix** (`v…` for etna, `apollo-v…` for apollo) via `git push origin HEAD:<ref> --follow-tags`.
81
- - `prepack` / `prepublishOnly` builds the shipped artifact (`data/` via `build-data.js`, or `dist/` via `tsc`) so what's published always matches source.
82
- - `registry-url: https://registry.npmjs.org` in the Node setup so `.npmrc` uses `$NODE_AUTH_TOKEN`.
83
-
84
- ## 8. Robustness defaults
85
-
86
- - **Never hard-block on an advisory/network check.** Version currency, registry lookups, telemetry — all degrade to a one-line notice and continue.
87
- - **Restricted-scope auth is ambient** — the read token in `~/.npmrc` (or `$NODE_AUTH_TOKEN` in CI) authenticates installs and `npm view`. Just attempt the operation; never prompt for `npm login` or reach for a substitute package.
88
- - **Keep the hot path fast** — everything runs on every `npx`, so avoid heavy startup work beyond the self-update check.