@nurix/etna 0.19.9 → 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.
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/harness.json +1 -1
- package/data/harnesses/code/rules/frontend.md +24 -3
- package/data/harnesses/code/rules/pnpm.md +4 -4
- package/data/harnesses/discussion/CLAUDE.md +3 -1
- package/data/harnesses/discussion/agents/discussion-baseline.md +146 -6
- package/data/harnesses/discussion/agents/lessons-maintenance.md +31 -0
- package/data/harnesses/discussion/harness.json +2 -2
- package/data/harnesses/discussion/rules/method.md +10 -8
- package/data/harnesses/etna/harness.json +1 -1
- package/package.json +1 -1
- package/data/harnesses/code/rules/typescript.md +0 -21
- package/data/harnesses/etna/rules/npm-cli.md +0 -88
package/data/harness-index.json
CHANGED
|
@@ -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
|
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pnpm — `@nurix/*` Workspaces: naming, boundaries, and consuming
|
|
2
2
|
|
|
3
|
-
How a `@nurix/*`
|
|
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
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -24,7 +24,7 @@ Every workspace package is scoped to **`@nurix/`**. The segment after the scope
|
|
|
24
24
|
|
|
25
25
|
## 2. Monorepo & package boundaries
|
|
26
26
|
|
|
27
|
-
One `tsconfig.base.json` at the repo root is the **single owner of TS policy** — the strict set (see [`
|
|
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
28
|
|
|
29
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
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.
|
|
@@ -36,14 +36,14 @@ One `tsconfig.base.json` at the repo root is the **single owner of TS policy**
|
|
|
36
36
|
|
|
37
37
|
## 3. Consuming `@nurix/*` packages
|
|
38
38
|
|
|
39
|
-
`@nurix/*` packages publish to npm with **`restricted`** access — installing needs read rights to the `nurix` org. (Publishing one is the `nustack
|
|
39
|
+
`@nurix/*` packages publish to npm with **`restricted`** access — installing needs read rights to the `nurix` org. (Publishing one is the `nustack` skill.)
|
|
40
40
|
|
|
41
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
42
|
```ini
|
|
43
43
|
@nurix:registry=https://registry.npmjs.org/
|
|
44
44
|
//registry.npmjs.org/:_authToken=${NPM_READ_TOKEN}
|
|
45
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.
|
|
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
47
|
- **Install** — `npm install @nurix/<name>` (or `pnpm add` / `yarn add`).
|
|
48
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
49
|
```tsx
|
|
@@ -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
|
|
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. **
|
|
33
|
-
4. **
|
|
34
|
-
5. **
|
|
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.
|
|
@@ -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
|
|
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
|
|
79
|
+
## Working with the operator
|
|
74
80
|
|
|
75
|
-
-
|
|
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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.19.
|
|
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,21 +0,0 @@
|
|
|
1
|
-
# TypeScript
|
|
2
|
-
|
|
3
|
-
TypeScript conventions. Compiles under `strict: true` and `noUncheckedIndexedAccess: true`. Workspace layout and consuming `@nurix/*` packages are [`pnpm.md`](pnpm.md).
|
|
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 [`pnpm.md`](pnpm.md) §2 — it travels with package boundaries.
|
|
@@ -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.
|