@nurix/etna 0.20.23 → 0.20.25
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/catalog/index.json +1 -0
- package/data/catalog/rules/cli.md +68 -0
- package/data/catalog/rules/pnpm.md +1 -1
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/CLAUDE.md +1 -1
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/hooks/hooks.json +4 -4
- package/data/harnesses/code/rules/cli.md +68 -0
- package/data/harnesses/code/rules/pnpm.md +1 -1
- package/data/harnesses/code/skills/design/components/index.md +50 -48
- package/data/harnesses/code/skills/documentation/SKILL.md +1 -1
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/hooks/hooks.json +1 -1
- package/data/harnesses/delivery/skills/documentation/SKILL.md +1 -1
- package/data/harnesses/discussion/harness.json +2 -2
- package/data/harnesses/discussion/hooks/hooks.json +1 -1
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/hooks/hooks.json +1 -1
- package/data/harnesses/etna/skills/documentation/SKILL.md +1 -1
- package/lib/install/materialize.mjs +14 -2
- package/package.json +2 -2
package/data/catalog/index.json
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/packages/cli/**"
|
|
4
|
+
- "**/packages/*-cli/**"
|
|
5
|
+
- "**/bin/*.js"
|
|
6
|
+
- "**/bin/*.mjs"
|
|
7
|
+
- "**/src/cli/**"
|
|
8
|
+
- "**/src/commands/**"
|
|
9
|
+
---
|
|
10
|
+
# CLI — credentials, login doors, and the machine contract
|
|
11
|
+
|
|
12
|
+
What a `@nurix/*` command-line tool holds true about **how it authenticates, where it keeps what it is given, and how a script or an agent drives it**. Packaging, `npx` distribution, self-update, managed file blocks and publishing are the `nustack` skill's `cli-design.md` reference; the product API's own gate is the product's feature doc. A CLI that talks to no authenticated service reads only §5.
|
|
13
|
+
|
|
14
|
+
## 1. Two credentials, and the order they are read in
|
|
15
|
+
|
|
16
|
+
| Credential | Held by | Arrives by | Lives | Dies by | Verified by |
|
|
17
|
+
| --- | --- | --- | --- | --- | --- |
|
|
18
|
+
| **Grant** — an OAuth refresh token, rotated on every use | a person's machine | a login door (§2) | until revoked | revocation at the auth service | the auth service; the CLI never sees a secret it can check |
|
|
19
|
+
| **Token** — an opaque, prefixed secret (`nst_pat_…`) | a pipeline, an agent, a headless host | the `<TOOL>_TOKEN` environment variable | until revoked or expired | a column on its row | the product API, locally, against a stored hash |
|
|
20
|
+
|
|
21
|
+
- **Resolution order is fixed and never prompts: env token → OS store → file store → signed out.** A `<TOOL>_TOKEN` in the environment wins outright and touches no store; `login`, `logout` and `doctor` say so instead of acting on the store behind it.
|
|
22
|
+
- **The grant refreshes; the token never does.** A grant spends the refresh token for a fifteen-minute access JWT per resource and writes the rotated pair back. A token is presented as-is on every call, and a `401` against it is final — no refresh, no retry, no browser.
|
|
23
|
+
- **Only the refresh token and the cached access tokens are stored — never a password, never an ID token, never the token a pipeline injected.**
|
|
24
|
+
- **Never a `--token` flag.** `argv` is readable in `ps` and shell history on every host the command runs on; the environment is the only way a secret enters a process. A flag that exists for a legacy caller is documented as deprecated, never added new.
|
|
25
|
+
- **A token cannot mint a token.** Minting, listing and revoking are person-only routes; a credential that can create credentials turns one leak into an unbounded family.
|
|
26
|
+
|
|
27
|
+
## 2. Three login doors, one per situation
|
|
28
|
+
|
|
29
|
+
| Door | When | Shape |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| **Loopback** | a browser runs on the same machine | authorization code + PKCE; `redirect_uri` is `http://127.0.0.1:<ephemeral>/callback`; the listener answers one matching `state` and closes |
|
|
32
|
+
| **Pasted code** | the browser cannot reach the terminal — SSH, a container | the same authorize URL with `redirect_uri` set to the auth service's own code page; the person pastes the code the page shows |
|
|
33
|
+
| **Device grant** (RFC 8628) | a GUI app with no listener of its own | `/device/code`, a `user_code`, polling `/oauth2/token` |
|
|
34
|
+
|
|
35
|
+
- **The CLI hardcodes one hostname — the product's.** The auth service's base URL, the client id and the resource audiences come from the product's discovery route (`GET /api/v1/auth/config`); a literal auth URL in a client pins every installed copy to one deployment.
|
|
36
|
+
- **Send `provider=<id>` on the authorize URL when the client already knows the provider** — the auth service's sign-in page starts that provider server-side with no paint when there is no session. Omitting it is the "click Google, then click Google again" defect.
|
|
37
|
+
- **Never open a browser, and never prompt, when stdin or stdout is not a TTY or when the env token is set.** The refusal is the CLI's own `SIGNED_OUT` code naming the login command, exit 1 — never a bare HTTP status, never a hang waiting on a page nobody can see.
|
|
38
|
+
- **Refusals are coded, and only two spend the credential.** `invalid_grant` is a dead grant: clear the item, run the login door, resume the command. `invalid_target` is a live grant asked for a resource it never covered: re-authorize wider, keep the item. A network failure, a `5xx` or a `429` keeps the item and raises — the item is shared with every client on the machine, and clearing it on a blip signs all of them out.
|
|
39
|
+
|
|
40
|
+
## 3. Where a credential lives
|
|
41
|
+
|
|
42
|
+
- **OS store where one is probed present, a `0600` file where none is, and the absence of an OS store is never a reason to refuse login.** macOS `security`, Linux `secret-tool` (libsecret); otherwise `~/.<tool>/credentials.json`, mode `0600`, in a `0700` directory, written atomically (temp file + rename). `<TOOL>_CREDENTIAL_STORE=auto|keychain|file` overrides the probe.
|
|
43
|
+
- **Never `localStorage`, never a world-readable file, never the project directory.** The link file a project carries (`.<tool>/project.json`) holds identifiers only — a project id, a host, a stack — and is committed; a secret beside it ships with the repo.
|
|
44
|
+
- **One item, several clients — read-modify-write with the schema versioned.** A CLI and a desktop app sharing an item merge into it, preserve keys they do not know, and stamp `schema_version`; a newer schema is refused whole and an older one reads as *sign in again*. Concurrent refreshes race by design — the auth service's reuse window absorbs the honest double refresh, so the second loser is a rotation, never a wipe.
|
|
45
|
+
- **Logout is revoke-then-clear, best effort on the revoke.** An offline machine still clears its local material.
|
|
46
|
+
|
|
47
|
+
## 4. The server's doors
|
|
48
|
+
|
|
49
|
+
- **Every machine credential carries a prefix the gate routes by, and the prefixed branches are ordered after the JWT branch.** Two credentials both presented as `Authorization: Bearer <opaque>` with no prefix cannot be told apart; a prefixed branch ahead of the JWT branch would have to decline every JWT correctly on every path, while one placed last cannot shadow a credential that already resolved.
|
|
50
|
+
- **One credential, one door.** A token minted for one surface is refused at every other, whatever it is worth — the audience rule is what bounds a leak.
|
|
51
|
+
- **A token row is `id`, owner, `label`, `token_hash`, `token_prefix`, `expires_at`, `revoked_at`, `last_used_at`.** The raw value appears in the `201` that minted it and nowhere else; the prefix is what a list shows; revocation sets a column and keeps the row, so the audit trail stays readable and the hash can never be re-issued; an expired, a revoked and an unknown token all answer the same `401`, so a caller never learns which.
|
|
52
|
+
|
|
53
|
+
## 5. The machine contract
|
|
54
|
+
|
|
55
|
+
A script or an agent drives the CLI as often as a person does, and it reads three channels: **stdout carries the one datum** — the URL, the id, or the `--json` envelope — **stderr carries everything else, and the exit code is the verdict.** `cmd > out.txt` with a `0` exit is the whole contract for the caller; a progress line on stdout breaks it. TTY detection, `--yes`, and failing loudly outside a TTY are `cli-design.md` §5; on top of it:
|
|
56
|
+
|
|
57
|
+
- **`--json` implies non-interactive**, and every error is a coded envelope, never prose.
|
|
58
|
+
- **A command that waits on a remote job offers `--no-wait`** (return the id at once) **and, where the job streams a log, `--ci`** (stream the build log, exit with the job's verdict, fall back to polling when the stream dies). The default waits and streams.
|
|
59
|
+
- **Upload and go-live are separate verbs when the platform has a pointer.** `deploy --no-promote` stages, `promote` and `rollback` move the pointer, and a `deploy` without the flag does both. A rollback that rebuilds is a redeploy, not a rollback.
|
|
60
|
+
- **Pack what git would ship.** `.gitignore` plus a `.<tool>ignore` decide what leaves the machine; `--no-gitignore` is the escape hatch, named, never the default.
|
|
61
|
+
|
|
62
|
+
## 6. Pitfalls — symptom → cause → fix
|
|
63
|
+
|
|
64
|
+
- **The sign-in page painted although the client sent `provider=`** → the hint fell off a hop, or the URL carries `error=` from an earlier attempt (the page always paints on `error=`) → follow the chain hop by hop with `curl -s -o /dev/null -D - <url>` and read each `location:`; the hint must survive every hop.
|
|
65
|
+
- **Works on macOS, asks to log in on every run elsewhere** → OS-store-only storage with no file fallback → §3.
|
|
66
|
+
- **CI hangs at the first authenticated command** → a prompt or a browser opened outside a TTY → §2; the fix is the `SIGNED_OUT` refusal, not a longer timeout.
|
|
67
|
+
- **Two commands at once, and the machine is "signed out"** → a concurrent refresh treated `invalid_grant` on the loser as a dead grant → check the service's reuse window before clearing anything (§3).
|
|
68
|
+
- **A `401` on a `<TOOL>_TOKEN` call loops or opens a browser** → the token took the grant's refresh path → §1: a token's `401` is final.
|
|
@@ -9,7 +9,7 @@ paths:
|
|
|
9
9
|
---
|
|
10
10
|
# pnpm — `@nurix/*` Workspaces: naming, boundaries, and consuming
|
|
11
11
|
|
|
12
|
-
How a `@nurix/*` repo is **named**, **structured**, and how its packages are **consumed** — every repo takes the workspace shape, single-package frontends included. Publishing and CLI
|
|
12
|
+
How a `@nurix/*` repo is **named**, **structured**, and how its packages are **consumed** — every repo takes the workspace shape, single-package frontends included. Publishing and a CLI's packaging live in the `nustack` skill, not here — no `nustack` skill in the runtime ⇒ ask before crossing the publish boundary.
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
package/data/harness-index.json
CHANGED
|
@@ -157,7 +157,7 @@ Defaults — ❌ prohibitions hold unless the user explicitly asks otherwise;
|
|
|
157
157
|
- ✅ **Verification** — batched at the gate, never continuous, and the gate is the build's last step, never a separate ask; the regimes and the gate are `.claude/rules/testing.md`, the procedure the `testing` skill (gate mode).
|
|
158
158
|
- ❌ **Dependencies** – Never upgrade pinned packages without asking; security patches and test-tooling installs at `latest` (`testing.md`) are exempt.
|
|
159
159
|
- ❌ **Compatibility** – Never maintain backward compatibility unless specified.
|
|
160
|
-
- ❌ **Branches & Worktrees [override]** –
|
|
160
|
+
- ❌ **Branches & Worktrees [override]** – `dev` and `production` are merge-only. Work on a short-lived branch cut from `dev` (`feat/<slug>`, `fix/<slug>`, `docs/<slug>`; `hotfix/<slug>` off `production`), commit there, and open a pull request into `dev` (or `production` for a hotfix) — never commit to either long-lived branch, and never create a worktree. The branch is created when the first commit is due, not at session start. This replaces any built-in "branch first on the default branch" default: the branch is required, and it is never `main`.
|
|
161
161
|
- ❌ **Protected Files** – Once per session, if `docs/operations/protected-files.md` exists, read it and never modify any path it lists; never modify `AGENTS.md` (harness-regenerated) or `protected-files.md` itself.
|
|
162
162
|
- ✅ **Browser Automation** – If the runtime provides browser automation, use it freely; no need to ask first. If it doesn't — or the executing agent can't hold the browser connection (see Runtime Notes) — return a browser-verification checklist (URLs, actions, expected outcomes) for the orchestrating session or the user to run.
|
|
163
163
|
- ✅ **Bookkeeping [override]** — dispatching outranks a runtime default that reserves sub-agents for explicit requests (§ Delegation), and `hooks/compliance.mjs` enforces it: Stop is blocked on unresolved rows unless the transcript carries a real `subagent_type: bookkeeper` record. Hooks append doc-debt rows to `docs/operations/bookkeeping.md`; three walls read it at three widths (Stop = your session's rows · commit = rows implicating staged files · CI = all), each re-verifying conditions at gate time. Dispatch the `bookkeeper` for your rows — **never drain `auto:`/`note:` rows in your own loop**, whichever agent you are: the drain is dispatched work at every nesting level (a sub-agent dispatches its own `bookkeeper` — § Delegation), and only `needs-session:` rows are yours to resolve directly. A drained row is **deleted**. The full grammar arrives in the deny message when a wall fires.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "code",
|
|
3
3
|
"name": "code",
|
|
4
|
-
"version": "3.19.
|
|
4
|
+
"version": "3.19.3",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
}
|
|
26
26
|
],
|
|
27
27
|
"docsRoot": "docs",
|
|
28
|
-
"digest": "
|
|
28
|
+
"digest": "5181bf532ddd"
|
|
29
29
|
}
|
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
},
|
|
9
9
|
"hooks": {
|
|
10
10
|
"SessionStart": [
|
|
11
|
-
{ "matcher": "", "hooks": [ { "type": "command", "command": "
|
|
11
|
+
{ "matcher": "", "hooks": [ { "type": "command", "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/awareness.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"", "timeout": 10 } ] }
|
|
12
12
|
],
|
|
13
13
|
"PostToolUse": [
|
|
14
|
-
{ "matcher": "Edit|Write|MultiEdit|Bash", "hooks": [ { "type": "command", "command": "
|
|
14
|
+
{ "matcher": "Edit|Write|MultiEdit|Bash", "hooks": [ { "type": "command", "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/awareness.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"", "timeout": 10 }, { "type": "command", "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/build-graph.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"", "timeout": 10 } ] }
|
|
15
15
|
],
|
|
16
16
|
"PreToolUse": [
|
|
17
|
-
{ "matcher": "Edit|Write|MultiEdit|Bash", "hooks": [ { "type": "command", "command": "
|
|
17
|
+
{ "matcher": "Edit|Write|MultiEdit|Bash", "hooks": [ { "type": "command", "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/compliance.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"", "timeout": 10 } ] }
|
|
18
18
|
],
|
|
19
19
|
"Stop": [
|
|
20
|
-
{ "matcher": "", "hooks": [ { "type": "command", "command": "
|
|
20
|
+
{ "matcher": "", "hooks": [ { "type": "command", "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/compliance.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"", "timeout": 15 } ] }
|
|
21
21
|
]
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/packages/cli/**"
|
|
4
|
+
- "**/packages/*-cli/**"
|
|
5
|
+
- "**/bin/*.js"
|
|
6
|
+
- "**/bin/*.mjs"
|
|
7
|
+
- "**/src/cli/**"
|
|
8
|
+
- "**/src/commands/**"
|
|
9
|
+
---
|
|
10
|
+
# CLI — credentials, login doors, and the machine contract
|
|
11
|
+
|
|
12
|
+
What a `@nurix/*` command-line tool holds true about **how it authenticates, where it keeps what it is given, and how a script or an agent drives it**. Packaging, `npx` distribution, self-update, managed file blocks and publishing are the `nustack` skill's `cli-design.md` reference; the product API's own gate is the product's feature doc. A CLI that talks to no authenticated service reads only §5.
|
|
13
|
+
|
|
14
|
+
## 1. Two credentials, and the order they are read in
|
|
15
|
+
|
|
16
|
+
| Credential | Held by | Arrives by | Lives | Dies by | Verified by |
|
|
17
|
+
| --- | --- | --- | --- | --- | --- |
|
|
18
|
+
| **Grant** — an OAuth refresh token, rotated on every use | a person's machine | a login door (§2) | until revoked | revocation at the auth service | the auth service; the CLI never sees a secret it can check |
|
|
19
|
+
| **Token** — an opaque, prefixed secret (`nst_pat_…`) | a pipeline, an agent, a headless host | the `<TOOL>_TOKEN` environment variable | until revoked or expired | a column on its row | the product API, locally, against a stored hash |
|
|
20
|
+
|
|
21
|
+
- **Resolution order is fixed and never prompts: env token → OS store → file store → signed out.** A `<TOOL>_TOKEN` in the environment wins outright and touches no store; `login`, `logout` and `doctor` say so instead of acting on the store behind it.
|
|
22
|
+
- **The grant refreshes; the token never does.** A grant spends the refresh token for a fifteen-minute access JWT per resource and writes the rotated pair back. A token is presented as-is on every call, and a `401` against it is final — no refresh, no retry, no browser.
|
|
23
|
+
- **Only the refresh token and the cached access tokens are stored — never a password, never an ID token, never the token a pipeline injected.**
|
|
24
|
+
- **Never a `--token` flag.** `argv` is readable in `ps` and shell history on every host the command runs on; the environment is the only way a secret enters a process. A flag that exists for a legacy caller is documented as deprecated, never added new.
|
|
25
|
+
- **A token cannot mint a token.** Minting, listing and revoking are person-only routes; a credential that can create credentials turns one leak into an unbounded family.
|
|
26
|
+
|
|
27
|
+
## 2. Three login doors, one per situation
|
|
28
|
+
|
|
29
|
+
| Door | When | Shape |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| **Loopback** | a browser runs on the same machine | authorization code + PKCE; `redirect_uri` is `http://127.0.0.1:<ephemeral>/callback`; the listener answers one matching `state` and closes |
|
|
32
|
+
| **Pasted code** | the browser cannot reach the terminal — SSH, a container | the same authorize URL with `redirect_uri` set to the auth service's own code page; the person pastes the code the page shows |
|
|
33
|
+
| **Device grant** (RFC 8628) | a GUI app with no listener of its own | `/device/code`, a `user_code`, polling `/oauth2/token` |
|
|
34
|
+
|
|
35
|
+
- **The CLI hardcodes one hostname — the product's.** The auth service's base URL, the client id and the resource audiences come from the product's discovery route (`GET /api/v1/auth/config`); a literal auth URL in a client pins every installed copy to one deployment.
|
|
36
|
+
- **Send `provider=<id>` on the authorize URL when the client already knows the provider** — the auth service's sign-in page starts that provider server-side with no paint when there is no session. Omitting it is the "click Google, then click Google again" defect.
|
|
37
|
+
- **Never open a browser, and never prompt, when stdin or stdout is not a TTY or when the env token is set.** The refusal is the CLI's own `SIGNED_OUT` code naming the login command, exit 1 — never a bare HTTP status, never a hang waiting on a page nobody can see.
|
|
38
|
+
- **Refusals are coded, and only two spend the credential.** `invalid_grant` is a dead grant: clear the item, run the login door, resume the command. `invalid_target` is a live grant asked for a resource it never covered: re-authorize wider, keep the item. A network failure, a `5xx` or a `429` keeps the item and raises — the item is shared with every client on the machine, and clearing it on a blip signs all of them out.
|
|
39
|
+
|
|
40
|
+
## 3. Where a credential lives
|
|
41
|
+
|
|
42
|
+
- **OS store where one is probed present, a `0600` file where none is, and the absence of an OS store is never a reason to refuse login.** macOS `security`, Linux `secret-tool` (libsecret); otherwise `~/.<tool>/credentials.json`, mode `0600`, in a `0700` directory, written atomically (temp file + rename). `<TOOL>_CREDENTIAL_STORE=auto|keychain|file` overrides the probe.
|
|
43
|
+
- **Never `localStorage`, never a world-readable file, never the project directory.** The link file a project carries (`.<tool>/project.json`) holds identifiers only — a project id, a host, a stack — and is committed; a secret beside it ships with the repo.
|
|
44
|
+
- **One item, several clients — read-modify-write with the schema versioned.** A CLI and a desktop app sharing an item merge into it, preserve keys they do not know, and stamp `schema_version`; a newer schema is refused whole and an older one reads as *sign in again*. Concurrent refreshes race by design — the auth service's reuse window absorbs the honest double refresh, so the second loser is a rotation, never a wipe.
|
|
45
|
+
- **Logout is revoke-then-clear, best effort on the revoke.** An offline machine still clears its local material.
|
|
46
|
+
|
|
47
|
+
## 4. The server's doors
|
|
48
|
+
|
|
49
|
+
- **Every machine credential carries a prefix the gate routes by, and the prefixed branches are ordered after the JWT branch.** Two credentials both presented as `Authorization: Bearer <opaque>` with no prefix cannot be told apart; a prefixed branch ahead of the JWT branch would have to decline every JWT correctly on every path, while one placed last cannot shadow a credential that already resolved.
|
|
50
|
+
- **One credential, one door.** A token minted for one surface is refused at every other, whatever it is worth — the audience rule is what bounds a leak.
|
|
51
|
+
- **A token row is `id`, owner, `label`, `token_hash`, `token_prefix`, `expires_at`, `revoked_at`, `last_used_at`.** The raw value appears in the `201` that minted it and nowhere else; the prefix is what a list shows; revocation sets a column and keeps the row, so the audit trail stays readable and the hash can never be re-issued; an expired, a revoked and an unknown token all answer the same `401`, so a caller never learns which.
|
|
52
|
+
|
|
53
|
+
## 5. The machine contract
|
|
54
|
+
|
|
55
|
+
A script or an agent drives the CLI as often as a person does, and it reads three channels: **stdout carries the one datum** — the URL, the id, or the `--json` envelope — **stderr carries everything else, and the exit code is the verdict.** `cmd > out.txt` with a `0` exit is the whole contract for the caller; a progress line on stdout breaks it. TTY detection, `--yes`, and failing loudly outside a TTY are `cli-design.md` §5; on top of it:
|
|
56
|
+
|
|
57
|
+
- **`--json` implies non-interactive**, and every error is a coded envelope, never prose.
|
|
58
|
+
- **A command that waits on a remote job offers `--no-wait`** (return the id at once) **and, where the job streams a log, `--ci`** (stream the build log, exit with the job's verdict, fall back to polling when the stream dies). The default waits and streams.
|
|
59
|
+
- **Upload and go-live are separate verbs when the platform has a pointer.** `deploy --no-promote` stages, `promote` and `rollback` move the pointer, and a `deploy` without the flag does both. A rollback that rebuilds is a redeploy, not a rollback.
|
|
60
|
+
- **Pack what git would ship.** `.gitignore` plus a `.<tool>ignore` decide what leaves the machine; `--no-gitignore` is the escape hatch, named, never the default.
|
|
61
|
+
|
|
62
|
+
## 6. Pitfalls — symptom → cause → fix
|
|
63
|
+
|
|
64
|
+
- **The sign-in page painted although the client sent `provider=`** → the hint fell off a hop, or the URL carries `error=` from an earlier attempt (the page always paints on `error=`) → follow the chain hop by hop with `curl -s -o /dev/null -D - <url>` and read each `location:`; the hint must survive every hop.
|
|
65
|
+
- **Works on macOS, asks to log in on every run elsewhere** → OS-store-only storage with no file fallback → §3.
|
|
66
|
+
- **CI hangs at the first authenticated command** → a prompt or a browser opened outside a TTY → §2; the fix is the `SIGNED_OUT` refusal, not a longer timeout.
|
|
67
|
+
- **Two commands at once, and the machine is "signed out"** → a concurrent refresh treated `invalid_grant` on the loser as a dead grant → check the service's reuse window before clearing anything (§3).
|
|
68
|
+
- **A `401` on a `<TOOL>_TOKEN` call loops or opens a browser** → the token took the grant's refresh path → §1: a token's `401` is final.
|
|
@@ -9,7 +9,7 @@ paths:
|
|
|
9
9
|
---
|
|
10
10
|
# pnpm — `@nurix/*` Workspaces: naming, boundaries, and consuming
|
|
11
11
|
|
|
12
|
-
How a `@nurix/*` repo is **named**, **structured**, and how its packages are **consumed** — every repo takes the workspace shape, single-package frontends included. Publishing and CLI
|
|
12
|
+
How a `@nurix/*` repo is **named**, **structured**, and how its packages are **consumed** — every repo takes the workspace shape, single-package frontends included. Publishing and a CLI's packaging live in the `nustack` skill, not here — no `nustack` skill in the runtime ⇒ ask before crossing the publish boundary.
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Component catalog — the fused index
|
|
2
2
|
|
|
3
|
-
> **Generated folder — never hand-edit.** Everything under `skills/design/components/` is emitted by the components-repo regen instruction set (`docs/
|
|
3
|
+
> **Generated folder — never hand-edit.** Everything under `skills/design/components/` is emitted by the components-repo regen instruction set (`docs/skills/design/components-regen-spec.md`, run against the `@nurix/components` library checkout). Regeneration may reshape any file beneath this one; the design skill's only seam with this folder is this index.
|
|
4
4
|
|
|
5
5
|
**Load contract.** This index loads in **both** positions of the project's `primitives` switch (`design.md`, `primitives: nurix | native`) — it is how the skill knows what chrome *exists* to compose with. The per-category deep references beside it ([inputs](inputs.md), [overlays](overlays.md), [collections](collections.md), [navigation](navigation.md), [layout](layout.md), [feedback](feedback.md), [charts](charts.md), [typography](typography.md), [media](media.md), [motion](motion.md), [hooks](hooks.md), [utils](utils.md), plus [setup](setup.md) for install/Tailwind wiring) load **only under `primitives: nurix`**, and only when that category is touched.
|
|
6
6
|
|
|
@@ -14,20 +14,21 @@ button,inputs,Button (element/button),Button,"native <button>, variants via clas
|
|
|
14
14
|
text-input,inputs,"Input (element/input) — onSave commit, regex validator",Input,"<input type=text>, commit on blur/Enter"
|
|
15
15
|
number-input,inputs,"NumberInput (element/number-input) — min/max/step",—,"<input inputmode=numeric>, clamp on commit, Escape reverts"
|
|
16
16
|
textarea,inputs,"Textarea (element/textarea) — Cmd/Ctrl+Enter commits",Textarea,"<textarea>, blur commits, Escape resets draft"
|
|
17
|
+
inline-edit,inputs,"InlineEdit (element/inline-edit) — click-to-edit contentEditable span",—,"span[contenteditable] reading as a label until focused, Enter/blur commits, Escape reverts"
|
|
17
18
|
checkbox,inputs,Checkbox (element/checkbox),Checkbox,"<input type=checkbox> + label, indeterminate via JS"
|
|
18
19
|
switch,inputs,Switch (element/switch),Switch,"role=switch button, aria-checked, Space toggles"
|
|
19
20
|
radio-group,inputs,"RadioGroup + card/segmented variants (element/radio-group)",RadioGroup,"fieldset of <input type=radio>, arrow-key roving focus"
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
combobox,inputs,"Combobox — searchable, multi, async (compound/combobox)","Combobox (Popover + Command)","input + filtered listbox popup, typeahead, option objects not raw values"
|
|
22
|
+
calendar,inputs,"Calendar (compound/calendar) — react-day-picker",Calendar,"month grid <table>, arrow-key day navigation"
|
|
23
|
+
select,inputs,"Select — closed listbox, one string value, no query field (element/select)",Select,"<select> or listbox popup, typeahead, one string value"
|
|
22
24
|
date-picker,inputs,DatePicker (compound/date-picker),Date Picker (Calendar + Popover),"<input type=date> or calendar popover, ISO value"
|
|
23
25
|
date-range-picker,inputs,"DateRangePicker — presets sidebar (compound/date-picker)",—,"two bound date inputs, partial selection reverts on close"
|
|
24
|
-
calendar,inputs,"Calendar (element/calendar) — react-day-picker",Calendar,"month grid <table>, arrow-key day navigation"
|
|
25
26
|
slider,inputs,Slider (element/slider),Slider,"<input type=range> with visible value readout"
|
|
26
27
|
rating,inputs,Rating (element/rating),—,"radio group of icon buttons, half-step optional"
|
|
27
|
-
toggle,inputs,"Toggle · ToggleGroup (element/toggle,
|
|
28
|
+
toggle,inputs,"Toggle · ToggleGroup (element/toggle, compound/toggle-group)","Toggle, Toggle Group","aria-pressed buttons, single or multiple selection"
|
|
28
29
|
otp-input,inputs,InputOTP (compound/input-otp),Input OTP,"segmented one-char inputs, paste distributes across slots"
|
|
29
30
|
icon-picker,inputs,"IconPicker · Icon (compound/icon-picker)",—,"searchable icon grid in a popover, categorized"
|
|
30
|
-
file-upload,inputs,
|
|
31
|
+
file-upload,inputs,"FileUpload family — dropzone, list, item progress (compound/file-picker) · FilePreview (compound/file-preview)",—,"<input type=file> + drop zone, previews, size and count caps"
|
|
31
32
|
form-field,inputs,"Cell — label/content/helper/error slots (compound/cell)",Form + FormField (react-hook-form),"label + control + helper in one block, error slot below control"
|
|
32
33
|
form-validation,inputs,,Form (react-hook-form),"form element with submit-time validation and inline field errors"
|
|
33
34
|
rich-text-editor,inputs,,—,"@tiptap/react editor with toolbar and markdown shortcuts"
|
|
@@ -39,19 +40,20 @@ bottom-drawer,overlays,Sheet side=bottom (block/sheet),Drawer (vaul),"fixed bott
|
|
|
39
40
|
command-palette,overlays,Command (compound/command),Command (cmdk),"filtered list in a dialog, ⌘K opens, arrow-key + Enter"
|
|
40
41
|
dropdown-menu,overlays,DropdownMenu (compound/dropdown-menu),Dropdown Menu,"role=menu under a trigger, arrow-key nav, outside click closes"
|
|
41
42
|
context-menu,overlays,ContextMenu (compound/context-menu),Context Menu,"right-click menu at pointer position"
|
|
43
|
+
speed-dial,overlays,SpeedDial (compound/speed-dial),—,"floating trigger button that fans a bounded set of icon actions out along one axis; the trigger doubles as the close affordance"
|
|
42
44
|
popover,overlays,Popover (element/popover),Popover,"anchored floating panel, dismiss on outside/ESC"
|
|
43
45
|
hover-card,overlays,HoverCard (element/popover),Hover Card,"preview panel on hover intent, delayed open/close"
|
|
44
46
|
tooltip,overlays,Tooltip (element/tooltip),Tooltip,"short hint on hover/focus, never load-bearing content"
|
|
45
47
|
data-table,collections,"Table + defineColumn (block/table)","<Table> + TanStack Table","semantic <table>, sticky header, tabular-nums, real pagination, status chips"
|
|
46
48
|
table-primitives,collections,UiTable* family (block/ui-table),Table,"bare styled <table>/<thead>/<tbody> parts"
|
|
47
49
|
list,collections,"List + ListToolbar + ListPagination (block/list)",—,"<ul> of rows with toolbar and pager"
|
|
48
|
-
record-header,collections
|
|
49
|
-
page-header-stats,collections,"
|
|
50
|
-
gantt-timeline,collections
|
|
50
|
+
record-header,collections,,—,"page-top block: breadcrumbs, title, status bar, actions"
|
|
51
|
+
page-header-stats,collections,"Heading (element/heading) · Stat (element/stat) — composed at the call site",—,"title row + KPI strip + back link"
|
|
52
|
+
gantt-timeline,collections,,—,"time-scaled task rows, date axis, horizontal scroll"
|
|
51
53
|
changes-feed,collections,ChangesList (block/changes-list),—,"grouped change-history feed, entity + operation labels"
|
|
52
|
-
message-thread,collections,"
|
|
53
|
-
chat-bubble,collections
|
|
54
|
-
attachment,collections,
|
|
54
|
+
message-thread,collections,"MessageScroller + useMessageScroller (block/message-scroller)",—,"scrolling feed pinned to bottom, jump-to-new button"
|
|
55
|
+
chat-bubble,collections,,—,"aligned bubble rows with reactions row"
|
|
56
|
+
attachment,collections,"FilePreview (compound/file-preview)",—,"file card: media, title, actions, error state, dashed idle border"
|
|
55
57
|
thread-marker,collections,"Marker — separator/border variants (element/marker)",—,"inline separator row with icon and label"
|
|
56
58
|
json-viewer,collections,,—,"react18-json-view collapsible tree, mono"
|
|
57
59
|
virtualized-list,collections,,—,"react-window fixed-row virtualization for 1k+ rows"
|
|
@@ -65,79 +67,79 @@ file-tree,navigation,FileTree (block/sidebar),—,"nested tree with disclosure c
|
|
|
65
67
|
breadcrumb,navigation,Breadcrumb (compound/breadcrumb),Breadcrumb,"<nav aria-label=breadcrumb>, current page unlinked"
|
|
66
68
|
tabs,navigation,Tabs (compound/tabs),Tabs,"role=tablist, arrow-key switching, one panel per tab"
|
|
67
69
|
pagination,navigation,Pagination (compound/pagination),Pagination,"page buttons + prev/next, aria-current on active page"
|
|
68
|
-
navbar,navigation
|
|
70
|
+
navbar,navigation,,—,"horizontal top <nav> with links and actions"
|
|
69
71
|
navigation-menu,navigation,NavigationMenu (compound/navigation-menu),Navigation Menu,"top-level nav with flyout panels"
|
|
70
72
|
menubar,navigation,Menubar (compound/menubar),Menubar,"desktop-app menu strip with full keyboard model"
|
|
71
|
-
slide-out-menu,navigation,SlideOutMenu (block/slide-out-menu),—,"off-canvas nav panel for narrow viewports"
|
|
72
73
|
icon-rail,navigation,VerticalIconBar (block/vertical-icon-bar),—,"narrow vertical icon strip with tooltips"
|
|
73
74
|
stepper,navigation,,—,"ordered steps with done/current/todo states, aria-current=step"
|
|
74
|
-
alert,feedback,Alert (
|
|
75
|
+
alert,feedback,Alert (compound/alert),Alert,"role=alert block, tone via border + icon"
|
|
75
76
|
empty-state,feedback,EmptyState (compound/empty-state),Empty,"icon + heading + motivation line + primary action"
|
|
76
77
|
error-boundary,feedback,ErrorBoundary (lib/error-boundary),—,"React boundary rendering a retry block"
|
|
77
78
|
not-found,feedback,NotFound (compound/empty-state),—,"404 block with a way back"
|
|
78
79
|
skeleton,feedback,Skeleton (element/skeleton),Skeleton,"pulsing placeholder matching the final layout"
|
|
79
80
|
loader,feedback,Loader (element/loader),Spinner,"centered spinner with optional label"
|
|
80
|
-
text-spinner,feedback
|
|
81
|
+
text-spinner,feedback,,—,"animated unicode frames inline with text"
|
|
81
82
|
progress,feedback,Progress (element/progress),Progress,"role=progressbar with aria-valuenow"
|
|
82
83
|
toast,feedback,Toaster (compound/sonner),Sonner,"stacked corner notifications, auto-dismiss, action slot"
|
|
83
|
-
typing-indicator,feedback,
|
|
84
|
-
sparkline,charts,"Spark — line|bar|pie (element/spark)
|
|
85
|
-
|
|
86
|
-
inline-line,charts,
|
|
87
|
-
pie-donut,charts,"
|
|
88
|
-
segmented-progress,charts,
|
|
89
|
-
area-chart,charts,"
|
|
90
|
-
bar-chart,charts,"
|
|
91
|
-
line-chart,charts,"
|
|
92
|
-
scatter-chart,charts,
|
|
93
|
-
composed-chart,charts,
|
|
94
|
-
histogram,charts,
|
|
84
|
+
typing-indicator,feedback,"Loader — pulsing dots (element/loader)",—,"three-dot pulse row"
|
|
85
|
+
sparkline,charts,"Spark — line|bar|pie (element/spark)",—,"inline no-axis SVG mark, token or self-contained color"
|
|
86
|
+
status-timeline,charts,"StatusTimeline + .Skeleton (compound/status-timeline)",—,"one stacked bar per day of uptime status, hover card per day — no axis, not a bar chart"
|
|
87
|
+
inline-line,charts,"Spark — line (element/spark)",—,"compact line SVG with skeleton"
|
|
88
|
+
pie-donut,charts,"Chart — pie spec (block/chart)",—,"SVG arcs, max ~6 slices, center slot in doughnut"
|
|
89
|
+
segmented-progress,charts,"Chart — progress spec (block/chart)",—,"stacked horizontal segments with legend"
|
|
90
|
+
area-chart,charts,"Chart — area spec (block/chart)",Chart (recharts),"axes + gridlines + filled series + tooltip"
|
|
91
|
+
bar-chart,charts,"Chart — bar spec (block/chart)",Chart (recharts),"grouped/stacked bars, value labels opt-in"
|
|
92
|
+
line-chart,charts,"Chart — line spec (block/chart)",Chart (recharts),"multi-series lines, reference lines, brush"
|
|
93
|
+
scatter-chart,charts,"Chart — scatter spec (block/chart)",—,"x/y point field, hover ring, box select"
|
|
94
|
+
composed-chart,charts,"Chart — composed spec (block/chart)",—,"mixed marks over one shared scale pair"
|
|
95
|
+
histogram,charts,"Chart — bar spec with mode: histogram (block/chart)",—,"binned bars over a continuous axis"
|
|
95
96
|
bullet-chart,charts,"Bullet (gauge; also charts)",—,"measure bar + target tick + zone bands"
|
|
96
|
-
funnel,charts,"
|
|
97
|
-
gauge,charts,"
|
|
98
|
-
contribution-heatmap,charts,
|
|
99
|
-
matrix-heatmap,charts,
|
|
100
|
-
radar,charts,
|
|
101
|
-
ring,charts,
|
|
102
|
-
chart-chrome,charts,"
|
|
97
|
+
funnel,charts,"Chart — funnel spec (block/chart)",—,"ordered stage bands, percent-of-previous labels"
|
|
98
|
+
gauge,charts,"Chart — gauge spec (block/chart)",—,"value arc with threshold zones and center readout"
|
|
99
|
+
contribution-heatmap,charts,"Chart — heatmap-calendar spec (block/chart)",—,"calendar cell grid with level legend"
|
|
100
|
+
matrix-heatmap,charts,"Chart — heatmap-matrix spec (block/chart)",—,"x-by-y cell matrix on the tokenized ramp"
|
|
101
|
+
radar,charts,"Chart — radar spec (block/chart)",—,"spider axes with series polygons"
|
|
102
|
+
ring,charts,"Chart — ring spec (block/chart)",—,"segmented circular progress with center info"
|
|
103
|
+
chart-chrome,charts,"Chart chrome — legend, tooltip and empty state set by the spec theme · ChartErrorCard (block/chart)",—,"shared empty/tooltip/legend chrome for inline charts"
|
|
103
104
|
card,layout,Card family (block/card),Card,"bordered section: header, content, footer"
|
|
104
|
-
stat-card,layout,
|
|
105
|
+
stat-card,layout,"Stat (element/stat) · Chart — stat spec (block/chart)",—,"number + comparison + trend + state + door, composable regions"
|
|
105
106
|
accordion,layout,Accordion (compound/accordion),Accordion,"stacked disclosure sections, single or multiple open"
|
|
107
|
+
collapsible-panel,layout,SidePanel (block/side-panel),—,"in-flow side region docked left or right that collapses to a rail instead of disappearing; siblings reflow, nothing overlays"
|
|
106
108
|
collapsible,layout,"Collapsible (element/collapsible) · CollapsiblePanel (compound/accordion)",Collapsible,"button-controlled region with hidden state"
|
|
107
109
|
split-panes,layout,ResizablePanelGroup (block/resizable),Resizable,"draggable divider panes, keyboard resize"
|
|
108
110
|
scroll-area,layout,ScrollArea (element/scroll-area),Scroll Area,"styled overflow container, visible-on-hover scrollbar"
|
|
109
111
|
separator,layout,"Separator (element/separator) · SeparatorWithText (element/separator)",Separator,"<hr>/role=separator, optional centered label"
|
|
110
112
|
aspect-ratio,layout,AspectRatio (element/aspect-ratio),Aspect Ratio,"CSS aspect-ratio box"
|
|
111
113
|
carousel,layout,Carousel (compound/carousel),Carousel,"scroll-snap track with prev/next controls"
|
|
112
|
-
bento-grid,layout
|
|
113
|
-
editable-card,layout
|
|
114
|
+
bento-grid,layout,,—,"asymmetric feature grid with spanning cells"
|
|
115
|
+
editable-card,layout,,—,"card with edit-in-place body and commit footer"
|
|
114
116
|
drag-reorder,layout,Sortable family (compound/sortable),—,"dnd-kit list reorder, keyboard sortable, drag overlay ghost"
|
|
115
|
-
text,typography
|
|
116
|
-
badge,typography,
|
|
117
|
+
text,typography,,Typography,"semantic p/span with size and weight scale"
|
|
118
|
+
badge,typography,"Chip (element/chip)",Badge,"small status pill, tokenized tone"
|
|
117
119
|
chip,typography,"Chip (element/chip) · Option display (element/select)",—,"removable tag, optional color dot"
|
|
118
120
|
code,typography,Code (element/code),—,"inline <code>, mono"
|
|
119
121
|
kbd,typography,Kbd (element/kbd),Kbd,"<kbd> keycap"
|
|
120
122
|
label,typography,Label (element/label),Label,"<label for> bound to a control"
|
|
121
123
|
link,typography,Link (element/link),—,"<a> with underline discipline"
|
|
122
|
-
secure-text,typography,SecureText (secure-text),—,"masked value with reveal toggle, mono"
|
|
124
|
+
secure-text,typography,"SecureText · maskText (element/secure-text)",—,"masked value with reveal toggle, mono"
|
|
123
125
|
markdown,typography,Markdown (element/markdown),—,"react-markdown rendering with typographic defaults"
|
|
124
126
|
avatar,media,Avatar (element/avatar),Avatar,"image with initials fallback, round"
|
|
125
127
|
copy-button,media,CopyButton (compound/copy-button),—,"clipboard write with copied-state swap"
|
|
126
|
-
icon,media,"
|
|
127
|
-
voice-waveform,
|
|
128
|
+
icon,media,"Icon · IconPicker (compound/icon-picker) + iconsData (lib/icons-data)",—,"lucide icon by name, consistent stroke and size grid"
|
|
129
|
+
voice-waveform,chat,"MicInput (compound/mic-input)",—,"latched dictation button plus mic level bars; button={false} for the meter alone, recorder={…} for record-then-review"
|
|
128
130
|
brand-logo,media,"Logo — blue-black|white-white|lime-white|blue-white (element/logo)",—,"inline SVG logo sized by height"
|
|
129
131
|
shine-border,media,ShineBorder (effect/shine-border),—,"animated gradient border wrapper, decorative"
|
|
130
|
-
theme-toggle,media,
|
|
132
|
+
theme-toggle,media,"ThemeToggle (effect/theme-toggle)",—,"light/dark switch toggling the .dark class on the root element"
|
|
131
133
|
view-switcher,media,ViewSwitcher (compound/tabs),—,"segmented control switching view-type projections"
|
|
132
134
|
animated-number,motion,"AnimatedNumber (element/animated-number) · SlidingNumber (element/animated-number)",—,"tick-up numerals, renders plain text until JS ready"
|
|
133
|
-
stagger-entrance,motion,
|
|
135
|
+
stagger-entrance,motion,"AnimatedList (effect/animated-list)",—,"staggered child arrival, identity moments only, never on 100×/day actions"
|
|
134
136
|
text-effects,motion,"TextEffect · TextMorph · TextScramble · TextShimmer (effect/*)",—,"per-character text animation, low-frequency surfaces only"
|
|
135
137
|
morphing-overlay,motion,"MorphingDialog · MorphingPopover (effect/*)",—,"trigger morphs into overlay, interruptible"
|
|
136
|
-
panel-transitions,motion,"TransitionPanel
|
|
138
|
+
panel-transitions,motion,"TransitionPanel (effect/transition-panel) · Accordion (compound/accordion) · Tabs (compound/tabs)",—,"crossfade/slide between sibling panels, height-animated reveal, interruptible"
|
|
137
139
|
decorative-effects,motion,"Spotlight · GlowEffect · AnimatedBackground · InfiniteSlider · Highlight (effect/*, effect/*)",—,"pointer-tracked washes and loops, decorative only, never on data"
|
|
138
|
-
dynamic-toolbar,motion
|
|
140
|
+
dynamic-toolbar,motion,,—,"toolbar expanding to contextual actions"
|
|
139
141
|
utility-hooks,hooks,"useClickOutside · useFileUpload · useMediaQuery · useIsMobile (lib/*)",—,"outside-click, file-upload state, matchMedia boolean, <768px boolean"
|
|
140
142
|
class-merge,utils,cn (lib/utils),cn,"clsx + tailwind-merge"
|
|
141
|
-
formatting-utils,utils,"formatDate (lib/format) · generateId (lib/id) ·
|
|
143
|
+
formatting-utils,utils,"formatDate (lib/format) · generateId (lib/id) · formatRelativeTime (lib/time)",—,"Intl date formatting, nanoid ids, relative-time strings"
|
|
142
144
|
composition-utils,utils,"getStrictContext (lib/get-strict-context) · composeRefs (lib/compose-refs)",—,"throwing Provider/hook pair, ref composition"
|
|
143
145
|
```
|
|
@@ -113,7 +113,7 @@ The two guarantees are complementary and both break on mixing: a reader holding
|
|
|
113
113
|
| Derive/update an owning doc's `owns:` (feature, package, system) | [`references/frontmatter.md`](references/frontmatter.md) |
|
|
114
114
|
| Update existing doc | § Editing an existing doc (below) |
|
|
115
115
|
| Instruction file (`CLAUDE.md`, a rule, an agent, skill prose) | [`references/instruction-style.md`](references/instruction-style.md) — not these templates; every-load prose stays lean, and the present-tense rule still applies |
|
|
116
|
-
| Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`
|
|
116
|
+
| Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`13`. **No etna checkout available ⇒ ask the user for access to the etna repo, or proceed from first principles** with [`references/instruction-style.md`](references/instruction-style.md) governing the prose. This row is the only place the playbook is referenced |
|
|
117
117
|
| **No template matches** | [`references/untemplated.md`](references/untemplated.md) — shape decisions + the co-authoring workflow |
|
|
118
118
|
|
|
119
119
|
---
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "delivery",
|
|
3
3
|
"name": "delivery",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.36",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
"seeds": [],
|
|
11
11
|
"tools": [],
|
|
12
12
|
"docsRoot": "docs",
|
|
13
|
-
"digest": "
|
|
13
|
+
"digest": "c2d4fb33502b"
|
|
14
14
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "
|
|
9
|
+
"command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/generate-ownership-index.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"",
|
|
10
10
|
"timeout": 10
|
|
11
11
|
}
|
|
12
12
|
]
|
|
@@ -113,7 +113,7 @@ The two guarantees are complementary and both break on mixing: a reader holding
|
|
|
113
113
|
| Derive/update an owning doc's `owns:` (feature, package, system) | [`references/frontmatter.md`](references/frontmatter.md) |
|
|
114
114
|
| Update existing doc | § Editing an existing doc (below) |
|
|
115
115
|
| Instruction file (`CLAUDE.md`, a rule, an agent, skill prose) | [`references/instruction-style.md`](references/instruction-style.md) — not these templates; every-load prose stays lean, and the present-tense rule still applies |
|
|
116
|
-
| Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`
|
|
116
|
+
| Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`13`. **No etna checkout available ⇒ ask the user for access to the etna repo, or proceed from first principles** with [`references/instruction-style.md`](references/instruction-style.md) governing the prose. This row is the only place the playbook is referenced |
|
|
117
117
|
| **No template matches** | [`references/untemplated.md`](references/untemplated.md) — shape decisions + the co-authoring workflow |
|
|
118
118
|
|
|
119
119
|
---
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "discussion",
|
|
3
3
|
"name": "discussion",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.2",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
"seeds": [],
|
|
11
11
|
"tools": [],
|
|
12
12
|
"docsRoot": "docs",
|
|
13
|
-
"digest": "
|
|
13
|
+
"digest": "ed9548ee592a"
|
|
14
14
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"hooks": [
|
|
6
6
|
{
|
|
7
7
|
"type": "command",
|
|
8
|
-
"command": "
|
|
8
|
+
"command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/update-topics.sh\"; [ -f \"$f\" ] || exit 0; bash \"$f\"",
|
|
9
9
|
"timeout": 15,
|
|
10
10
|
"statusMessage": "Syncing workspace metadata"
|
|
11
11
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "etna",
|
|
3
3
|
"name": "etna",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.26",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
"seeds": [],
|
|
11
11
|
"tools": [],
|
|
12
12
|
"docsRoot": "docs",
|
|
13
|
-
"digest": "
|
|
13
|
+
"digest": "6448aac5edb5"
|
|
14
14
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "
|
|
9
|
+
"command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/generate-ownership-index.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"",
|
|
10
10
|
"timeout": 10
|
|
11
11
|
}
|
|
12
12
|
]
|
|
@@ -113,7 +113,7 @@ The two guarantees are complementary and both break on mixing: a reader holding
|
|
|
113
113
|
| Derive/update an owning doc's `owns:` (feature, package, system) | [`references/frontmatter.md`](references/frontmatter.md) |
|
|
114
114
|
| Update existing doc | § Editing an existing doc (below) |
|
|
115
115
|
| Instruction file (`CLAUDE.md`, a rule, an agent, skill prose) | [`references/instruction-style.md`](references/instruction-style.md) — not these templates; every-load prose stays lean, and the present-tense rule still applies |
|
|
116
|
-
| Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`
|
|
116
|
+
| Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`13`. **No etna checkout available ⇒ ask the user for access to the etna repo, or proceed from first principles** with [`references/instruction-style.md`](references/instruction-style.md) governing the prose. This row is the only place the playbook is referenced |
|
|
117
117
|
| **No template matches** | [`references/untemplated.md`](references/untemplated.md) — shape decisions + the co-authoring workflow |
|
|
118
118
|
|
|
119
119
|
---
|
|
@@ -227,10 +227,19 @@ function groupHasCommand(group, command) {
|
|
|
227
227
|
return (group.hooks || []).some((h) => h.command === command);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
// The harness script a hook command runs, by its `.claude/hooks/<name>` path — the identity
|
|
231
|
+
// that survives a change to the command's shape. Null for a command that runs no such script.
|
|
232
|
+
function hookScript(command) {
|
|
233
|
+
const found = /\.claude\/hooks\/([\w.-]+)/.exec(command || '');
|
|
234
|
+
return found ? found[1] : null;
|
|
235
|
+
}
|
|
236
|
+
|
|
230
237
|
// Deep-merge the bundle's hooks.json object into <project>/.claude/settings.json under
|
|
231
238
|
// "hooks", preserving every existing key. Idempotent: for each event, an incoming entry
|
|
232
239
|
// merges into the matcher-group of the same matcher, skipping any hook whose command
|
|
233
|
-
// already exists there; a
|
|
240
|
+
// already exists there; a hook already registered for the same script under an older command
|
|
241
|
+
// shape is replaced in place, never doubled; a new matcher creates a new group. Returns the
|
|
242
|
+
// count of hooks added or replaced.
|
|
234
243
|
function mergeHooks(targetDir, bundledHooksJson) {
|
|
235
244
|
if (!existsSync(bundledHooksJson)) return 0;
|
|
236
245
|
const bundled = readJson(bundledHooksJson);
|
|
@@ -261,7 +270,10 @@ function mergeHooks(targetDir, bundledHooksJson) {
|
|
|
261
270
|
match.hooks = match.hooks || [];
|
|
262
271
|
for (const hook of group.hooks || []) {
|
|
263
272
|
if (groupHasCommand(match, hook.command)) continue;
|
|
264
|
-
|
|
273
|
+
const script = hookScript(hook.command);
|
|
274
|
+
const staleIndex = script ? match.hooks.findIndex((h) => hookScript(h.command) === script) : -1;
|
|
275
|
+
if (staleIndex === -1) match.hooks.push(hook);
|
|
276
|
+
else match.hooks[staleIndex] = hook;
|
|
265
277
|
added += 1;
|
|
266
278
|
}
|
|
267
279
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.25",
|
|
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": {
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"agent",
|
|
43
43
|
"plugin"
|
|
44
44
|
],
|
|
45
|
-
"etnaFingerprint": "
|
|
45
|
+
"etnaFingerprint": "c24bc6f1af10"
|
|
46
46
|
}
|