@nurix/etna 0.20.18 → 0.20.20
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/bin/etna.js +4 -4
- package/data/catalog/skills/nustack/references/cli-design.md +8 -8
- package/data/catalog/skills/nustack/references/monorepo-setup.md +1 -1
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/.mcp.json +1 -1
- package/data/harnesses/code/.vscode/settings.json +1 -1
- package/data/harnesses/code/CLAUDE.md +1 -1
- package/data/harnesses/code/agents/issue-bridge.md +1 -1
- package/data/harnesses/code/agents/maintenance.md +2 -2
- package/data/harnesses/code/harness.json +7 -7
- package/data/harnesses/code/hooks/awareness.mjs +10 -2
- package/data/harnesses/code/hooks/lib/graph.mjs +3 -3
- package/data/harnesses/code/skills/design/SKILL.md +5 -5
- package/data/harnesses/code/skills/design/references/intake.md +1 -1
- package/data/harnesses/code/skills/issue-bridge/SKILL.md +6 -6
- package/data/harnesses/code/skills/nustack-graph/SKILL.md +64 -0
- package/data/harnesses/delivery/.vscode/settings.json +1 -1
- package/data/harnesses/delivery/agents/maintenance.md +2 -2
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/discover/.vscode/settings.json +1 -1
- package/data/harnesses/discover/CLAUDE.md +1 -1
- package/data/harnesses/discover/agents/session.md +1 -1
- package/data/harnesses/discover/codex/agents/session.toml +1 -1
- package/data/harnesses/discover/harness.json +2 -2
- package/data/harnesses/discover/rules/cartography-flows.md +1 -1
- package/data/harnesses/discover/rules/discovery.md +1 -1
- package/data/harnesses/discover/rules/ingestion.md +1 -1
- package/data/harnesses/discover/rules/tradecraft.md +11 -11
- package/data/harnesses/discussion/.vscode/settings.json +1 -1
- package/data/harnesses/discussion/harness.json +1 -1
- package/data/harnesses/etna/.vscode/settings.json +1 -1
- package/data/harnesses/etna/agents/maintenance.md +2 -2
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/hr/.vscode/settings.json +1 -1
- package/data/harnesses/hr/harness.json +1 -1
- package/data/harnesses/kb-agi/.vscode/settings.json +1 -1
- package/data/harnesses/kb-agi/harness.json +1 -1
- package/lib/install/materialize.mjs +3 -3
- package/lib/install/plan.mjs +5 -5
- package/package.json +2 -2
- package/data/harnesses/code/skills/codegraph/SKILL.md +0 -57
package/bin/etna.js
CHANGED
|
@@ -23,7 +23,7 @@ import { materializeHarness, readJson as readJsonOrThrow } from '../lib/install/
|
|
|
23
23
|
import { projectSkills } from '../lib/codex/skills.mjs';
|
|
24
24
|
|
|
25
25
|
// WHERE A HARNESS'S FILES LAND is not decided here — `lib/install/materialize.mjs` owns that
|
|
26
|
-
// mapping, because the
|
|
26
|
+
// mapping, because the NuStack push ships the same tree and the two must never diverge. This
|
|
27
27
|
// file owns the CLI around it: argument parsing, the interactive pick, and the reporting.
|
|
28
28
|
|
|
29
29
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -313,12 +313,12 @@ async function main(args) {
|
|
|
313
313
|
process.exit(1);
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
// Transition alias: the `
|
|
316
|
+
// Transition alias: the `nustack` harness merged into `code` (the code harness is pre-baked —
|
|
317
317
|
// it ships the docs scaffold as seeds and auto-baselines on first contact). Scripts and docs
|
|
318
318
|
// that pin the old name keep working through the alias; it retires after one window.
|
|
319
319
|
let requested = name;
|
|
320
|
-
if (requested === '
|
|
321
|
-
p.log.info('`
|
|
320
|
+
if (requested === 'nustack' && !harnesses.nustack) {
|
|
321
|
+
p.log.info('`nustack` merged into `code` — installing `code` (the pre-baked harness).');
|
|
322
322
|
requested = 'code';
|
|
323
323
|
}
|
|
324
324
|
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Load when: building or changing a distributable `@nurix/*` command-line tool.
|
|
4
4
|
|
|
5
|
-
Conventions for building a distributable Nurix command-line tool, distilled from the two we ship: **`@nurix/etna`** (`packages/cli/` in the etna repo — the harness installer, plain ESM JS) and **`@nurix/
|
|
5
|
+
Conventions for building a distributable Nurix command-line tool, distilled from the two we ship: **`@nurix/etna`** (`packages/cli/` in the etna repo — the harness installer, plain ESM JS) and **`@nurix/nustack`** (`~/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.
|
|
6
6
|
|
|
7
7
|
## 1. Packaging
|
|
8
8
|
|
|
9
9
|
The base `package.json` fields — scope, `publishConfig.access: "restricted"`, `files`, build hooks — are [`publishing.md`](publishing.md) §2. CLI-specific on top:
|
|
10
10
|
|
|
11
11
|
- **ESM only** — `"type": "module"`.
|
|
12
|
-
- **`bin` maps the command to its entry** — `{ "etna": "bin/etna.js" }` / `{ "
|
|
13
|
-
- **`files` ships only the runnable artifact** — `["bin/", "data/", "README.md"]` (etna) or `["dist"]` (
|
|
12
|
+
- **`bin` maps the command to its entry** — `{ "etna": "bin/etna.js" }` / `{ "nustack": "dist/index.js" }`. The entry has a `#!/usr/bin/env node` shebang.
|
|
13
|
+
- **`files` ships only the runnable artifact** — `["bin/", "data/", "README.md"]` (etna) or `["dist"]` (nustack). Never ship `src/`, tests, or the repo.
|
|
14
14
|
- **`engines.node` ≥ 22** for new CLIs (matches the workspace Node baseline).
|
|
15
15
|
- **One version source** — read the running version from `package.json` at startup; don't hardcode it elsewhere.
|
|
16
|
-
- **Minimal deps.** Both ship with essentially one runtime dep (`@clack/prompts`);
|
|
16
|
+
- **Minimal deps.** Both ship with essentially one runtime dep (`@clack/prompts`); nustack adds `commander` + `ajv`. Keep the dependency surface tiny — it's installed on every `npx` run.
|
|
17
17
|
|
|
18
18
|
## 2. Distribution: `npx`-first, install into the project, never globally
|
|
19
19
|
|
|
@@ -54,8 +54,8 @@ Required properties:
|
|
|
54
54
|
|
|
55
55
|
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.
|
|
56
56
|
|
|
57
|
-
-
|
|
58
|
-
`# >>>
|
|
57
|
+
- nustack wraps its `.env` writes in a managed block:
|
|
58
|
+
`# >>> nustack:managed — do not edit by hand; the nustack CLI rewrites this block >>>` … `# <<< nustack:managed <<<`. Reads/merges/removes happen *inside* the markers; everything outside belongs to the user.
|
|
59
59
|
- etna merges its hooks into `.claude/settings.json` and migrates/retires only its own prior entries.
|
|
60
60
|
- Rewrites are **idempotent** (re-running converges to the same block) and **reversible** (logout/uninstall removes exactly the managed region).
|
|
61
61
|
- If the block holds secrets, ensure `.gitignore` covers the file so they're never committed.
|
|
@@ -68,7 +68,7 @@ When the CLI must modify a file it doesn't own (`.env`, `settings.json`, `.gitig
|
|
|
68
68
|
|
|
69
69
|
## 6. Detect the package manager from the lockfile
|
|
70
70
|
|
|
71
|
-
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. (
|
|
71
|
+
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. (nustack's `detectPackageManager()`.)
|
|
72
72
|
|
|
73
73
|
## 7. Publishing is CI-owned
|
|
74
74
|
|
|
@@ -76,7 +76,7 @@ Publish from CI on push to `dev`/`main`, path-filtered to the CLI package — ne
|
|
|
76
76
|
|
|
77
77
|
- **Two valid version models:**
|
|
78
78
|
- **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.
|
|
79
|
-
- **Manual bump input (
|
|
79
|
+
- **Manual bump input (nustack):** a `workflow_dispatch` `bump` input (`patch`/`minor`/`major`) drives `npm version`.
|
|
80
80
|
- **`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.
|
|
81
81
|
|
|
82
82
|
## 8. Robustness defaults
|
|
@@ -46,7 +46,7 @@ onlyBuiltDependencies: # postinstall allow-list — see §4
|
|
|
46
46
|
}
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
- **Fan out with `pnpm -r`; alias with `--filter`.** `--if-present` when not every member has the script; per-app conveniences read `"dev:admin": "pnpm --filter @nurix/
|
|
49
|
+
- **Fan out with `pnpm -r`; alias with `--filter`.** `--if-present` when not every member has the script; per-app conveniences read `"dev:admin": "pnpm --filter @nurix/nustack-app dev"` (nustack) or `"worker:dev": "pnpm --filter @sync/worker dev"` (sync-info).
|
|
50
50
|
- **One toolchain config at root.** `eslint`/`prettier` run from the root over the whole tree — never per-package configs.
|
|
51
51
|
- **No turbo/nx.** Plain `pnpm -r` is the baseline; reach for a task-graph runner only when the build graph is slow enough to feel — the same threshold as TS project references (`pnpm.md` §2).
|
|
52
52
|
|
package/data/harness-index.json
CHANGED
|
@@ -58,7 +58,7 @@ Three peer modes, then a verification gate and a Completion step — enter where
|
|
|
58
58
|
- **File the request, end the turn — never wait in the container.** Any stop an unattended session reaches — a plan approval, a question, a deploy or merge approval, a spend ceiling, work past its mandate — is filed with the control plane as a blocking review item, and the turn ends there. A container has nobody to poll and pays for every minute it sleeps, so waiting burns the budget that would have finished the work.
|
|
59
59
|
- **The mandate is injected, not inferred.** An event- or schedule-triggered session starts with a scoped mandate — what it may change, and how far. **Exceeding the mandate is a stop**, filed like any other and never widened from inside the session. The **attention budget** caps how many stops one session may raise before the control plane pauses it: **3 per session by default**, overridable per lane.
|
|
60
60
|
- **Run caps end a session; they never stretch it.** An unattended session carries a **spend ceiling** and a **wall-clock ceiling**. Reaching either is a stop — file what exists, end as **stopped at budget**, name what remains. A silent continuation past a cap is **never** permitted.
|
|
61
|
-
- **Hygiene fails open, authority fails closed.** Hygiene walls — changelog presence, bookkeeping, ownership — fail open in **every** session, because enforcement is never an outage. Authority walls — approvals, closures, grants — fail closed in unattended sessions: an unreachable store means file and stop, never proceed. A sign-off bound to a commit is read from the control plane before the gated step it guards, through the control plane's own command line (`
|
|
61
|
+
- **Hygiene fails open, authority fails closed.** Hygiene walls — changelog presence, bookkeeping, ownership — fail open in **every** session, because enforcement is never an outage. Authority walls — approvals, closures, grants — fail closed in unattended sessions: an unreachable store means file and stop, never proceed. A sign-off bound to a commit is read from the control plane before the gated step it guards, through the control plane's own command line (`nustack review check --commit <sha>`: `0` signed, `1` not signed, `2` unreachable or signed out), and `2` is a stop.
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
@@ -10,7 +10,7 @@ model: sonnet
|
|
|
10
10
|
You are the execution shell for bulk tracker work. Your FIRST action: read the `issue-bridge` skill in `.claude/skills/issue-bridge/SKILL.md` — it is the method — then the connector pack for the tracker(s) in play under its `references/trackers/`. This file adds only what a batch needs beyond the skill:
|
|
11
11
|
|
|
12
12
|
- **Batching.** Many items → triage each independently through the skill's method; one dossier per item, never a merged mega-dossier. Order by tractability (concrete symptoms first); if the batch is larger than the session can honestly cover, cover fewer items *fully* and name what you dropped — announced omission over silent thinning.
|
|
13
|
-
- **Shared context once.** Load the ownership index, the owning-doc map, and the
|
|
13
|
+
- **Shared context once.** Load the ownership index, the owning-doc map, and the `nustack` tool tier once at the start; reuse across items instead of re-deriving per issue.
|
|
14
14
|
- **Approval aggregation.** Anything that would write to the tracker accumulates into one approval list presented at the end — never one ask per item, never a write without the ask.
|
|
15
15
|
- **Report contract** (machine-read): per item — id · classification · verdict (one line) · confidence · dossier path; then the batch tail — items dropped (with reason), tracker writes awaiting approval, defects found in the skill or connector packs (a field run is always also a test of the method).
|
|
16
16
|
- All the skill's boundaries bind you unchanged; a standing repo ruling (e.g. "never post to this upstream") overrides any approval you're offered.
|
|
@@ -237,9 +237,9 @@ Apply the **§ Strategy memory** retirement procedure to `docs/memory/maintenanc
|
|
|
237
237
|
- **Enforce the memory cap on every run** (§ Strategy memory) — you are its only writer, so no one else can do it.
|
|
238
238
|
- **A clean report is valid** — do not invent issues.
|
|
239
239
|
- **Declarative, idempotent.** Re-running baseline mode on a current repo reshapes nothing — a second pass is just the audit plus the module proposal. Converge to the current shape; never replay a version ladder; never reconstruct a past shape.
|
|
240
|
-
- **Brownfield extraction (operator-ruled 2026-08-19).** When baseline mode meets a repo with a real source tree and few or no feature docs, the module *proposal* is not enough — run the extraction pass: produce feature docs for the recognized modules (docs/features/, full frontmatter: `owns:` · `module:` · `keywords:` · `status:` · `last-reviewed:` · `type: feature` · `derivation: "extracted (baseline YYYY-MM-DD,
|
|
240
|
+
- **Brownfield extraction (operator-ruled 2026-08-19).** When baseline mode meets a repo with a real source tree and few or no feature docs, the module *proposal* is not enough — run the extraction pass: produce feature docs for the recognized modules (docs/features/, full frontmatter: `owns:` · `module:` · `keywords:` · `status:` · `last-reviewed:` · `type: feature` · `derivation: "extracted (baseline YYYY-MM-DD, nustack graph + source read)"`), plus the architecture, tests, and operations layers. Use the `nustack-graph` skill where installed — localize through the index before reading; the docs record intent, the index answers facts. **Every extracted feature doc carries a `## History & Ownership` section** — top contributors with recency, churn posture, and bus-factor notes, dated "as of YYYY-MM-DD" (operator ruling: this section is mandatory in extraction output even though doc-lint flags dated narrative in truth docs; the findings are accepted for now, and a future harness version may retire the section in favor of live index pointers). `status:` is evidence-graded: `deprecated` only when history shows the area dead, never by guess. Scale by fan-out (one extraction lane per module, a completeness critic over the result) rather than one long cold read.
|
|
241
241
|
- **Diagrams ride the baseline (operator-ruled 2026-08-24).** Whenever baseline mode produces or converges the architecture layer, draw two ASCII diagrams from the real source, each in its own fenced code block: (1) an **architecture diagram** in the system-design/architecture doc — the actual processes, entry points, and wire surfaces as boxes, the calls/queues/files between them as labelled arrows; (2) a **DB ER diagram** wherever the repo declares a relational schema (DDL, migrations, an ORM schema) — one box per table listing its key columns, FK edges drawn to the table they reference, cardinality on the edge; it lands in the domain-model doc, or `docs/architecture/data-model.md` when none exists. Box-drawing characters (`┌─┐│└┘├┤`), width ≤ 100 columns. Derived only from what the code declares — a diagram never invents a component the source does not show, and a repo with no relational schema gets **no** ER diagram rather than an imagined one. Diagrams are truth-document sections: later baselines and audits converge them in place like any other section.
|
|
242
|
-
- **Tool tiers.** The SessionStart context names each manifest-declared tool with its tier; calibrate the run to it, never stall on it. **Present-fresh** (`Tool:
|
|
242
|
+
- **Tool tiers.** The SessionStart context names each manifest-declared tool with its tier; calibrate the run to it, never stall on it. **Present-fresh** (`Tool: nustack 0.9.0`): use it — localize every question through `nustack graph` before reading source, and never run an index step first: the family re-indexes a stale store before it answers, so the only freshness decision left is the deliberate `--no-refresh`. **Present-stale** (below the catalog minimum): still use it, but note the version gap and the upgrade command in the run report — a stale contract may lack answer shapes the skill assumes. **Absent** (`Tool: nustack MISSING`): fall back to the cold method (Glob/Grep/read), state in the run report that the tool was absent and give the install command — degraded output is acceptable, silent degradation is not.
|
|
243
243
|
- **One sentinel.** `docs/structure.md` `harness-version` is the only baseline/version marker.
|
|
244
244
|
- **Modules are labels, layout-blind.** Seed from the code's bounded contexts; never from `pnpm-workspace.yaml`/workspace walls. Propose, don't force; acting is a `module:` tag, never a folder.
|
|
245
245
|
- **You orchestrate docs; the `documentation` skill supplies templates.** It is a library — never expect it to baseline.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "code",
|
|
3
3
|
"name": "code",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.17.2",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
],
|
|
18
18
|
"tools": [
|
|
19
19
|
{
|
|
20
|
-
"name": "
|
|
21
|
-
"probe": "
|
|
22
|
-
"min": "0.
|
|
23
|
-
"install": "npm install -g @nurix/
|
|
24
|
-
"skill": "
|
|
20
|
+
"name": "nustack",
|
|
21
|
+
"probe": "nustack --version",
|
|
22
|
+
"min": "0.9.0",
|
|
23
|
+
"install": "npm install -g @nurix/nustack",
|
|
24
|
+
"skill": "nustack-graph"
|
|
25
25
|
}
|
|
26
26
|
],
|
|
27
27
|
"docsRoot": "docs",
|
|
28
|
-
"digest": "
|
|
28
|
+
"digest": "c19a8512dc42"
|
|
29
29
|
}
|
|
@@ -73,7 +73,7 @@ function detectPackageManager(projectDir) {
|
|
|
73
73
|
// .claude/harness.json at install): probed at SessionStart, never assumed, never required.
|
|
74
74
|
//
|
|
75
75
|
// TWO TIERS, deliberately — `present` and `absent`. An earlier draft reported `present-fresh`,
|
|
76
|
-
// which the probe cannot actually know: `
|
|
76
|
+
// which the probe cannot actually know: `nustack --version` proves a binary exists and says
|
|
77
77
|
// nothing about whether THIS repo is indexed. A 22k-commit repo indexed at 23% once reported
|
|
78
78
|
// present-fresh, and a probe that over-claims is worse than an absent tool, because absence
|
|
79
79
|
// degrades honestly. Index freshness is established by the skill on its first query, where the
|
|
@@ -106,7 +106,15 @@ function toolsLines(projectDir) {
|
|
|
106
106
|
if (v && v.localeCompare(tool.min, undefined, { numeric: true }) < 0)
|
|
107
107
|
extra = ` — BELOW min ${tool.min}; update: ${tool.install || "see etna/tools/" + tool.name + ".md"}`;
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
// The present line NAMES the tool, because the probe's own output cannot be relied on to.
|
|
110
|
+
// Binaries disagree: `codegraph --version` printed "codegraph 0.2.1", `nustack --version`
|
|
111
|
+
// prints a bare "0.9.0" — which rendered as `Tool: 0.9.0`, a line that tells a session
|
|
112
|
+
// nothing about WHICH tool is present while the absent line right below has always said so.
|
|
113
|
+
// The prefix is skipped when the binary already named itself, so neither shape doubles up.
|
|
114
|
+
const named = version.toLowerCase().startsWith(String(tool.name).toLowerCase())
|
|
115
|
+
? version
|
|
116
|
+
: `${tool.name} ${version}`;
|
|
117
|
+
out.push(`Tool: ${named}${extra}`);
|
|
110
118
|
} else {
|
|
111
119
|
out.push(
|
|
112
120
|
`Tool: ${tool.name} MISSING — agents fall back to cold reads; install: ${tool.install || "see etna/tools/" + tool.name + ".md"}`,
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// A build graph names the documents a run owes, the edges between them, and the caps that bound
|
|
4
4
|
// it. This module decides WHAT happens next; it never touches the filesystem, the transcript, or
|
|
5
5
|
// the clock beyond an injected timestamp — every adapter (Claude Code's hooks here, OpenCode's
|
|
6
|
-
// plugin in
|
|
6
|
+
// plugin in NuStack's generation box) hands it what it read and applies what it returns.
|
|
7
7
|
//
|
|
8
8
|
// The division is the point: one library decides, several runtimes carry the decision, and the
|
|
9
9
|
// two can never disagree about what "done" means. The export set below is a cross-repo contract
|
|
10
|
-
// (
|
|
10
|
+
// (NuStack imports this file from the installed `.claude/hooks/lib/graph.mjs`) — a rename is a
|
|
11
11
|
// versioned change, not a refactor, and `tests/parity.test.mjs` pins it.
|
|
12
12
|
//
|
|
13
13
|
// Absence is dormancy; invalidity is a loud refusal. No graph file ⇒ nothing here ever runs. A
|
|
@@ -243,7 +243,7 @@ export function reconcile(graph, state, observed, now = new Date().toISOString()
|
|
|
243
243
|
return next;
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
// The corrupt-state path, and
|
|
246
|
+
// The corrupt-state path, and NuStack's resume path: derive a state from the disk. `prev` carries
|
|
247
247
|
// only the attempt counters — a resumed run must not get its budget back.
|
|
248
248
|
export function rebuildState(graph, observed, prev = null) {
|
|
249
249
|
const base = initialState(graph, prev?.runId ?? null);
|
|
@@ -7,7 +7,7 @@ description: "Use when building, improving, judging, or extracting operator web
|
|
|
7
7
|
|
|
8
8
|
You already bring execution and competent default taste. This skill hands you what you cannot generate: a **chosen identity** (the design.md packages), **ratified what-to-use-when knowledge** (the decision layers), **battle-tested primitives** (chrome recipes + the component reference), and **believable content** (the demo-data corpora). The bar: every project gets its own perceptible identity, and the identity survives to the pixel.
|
|
9
9
|
|
|
10
|
-
This file is the router. The skill carries the instructions, the engines, and every subjective decision layer; only the **hand-curated taste dictionaries** — identity packages, color combinations, font pairings, gradients, chart styles, corpora — and the workspace's **design memory** are served, by the design MCP on
|
|
10
|
+
This file is the router. The skill carries the instructions, the engines, and every subjective decision layer; only the **hand-curated taste dictionaries** — identity packages, color combinations, font pairings, gradients, chart styles, corpora — and the workspace's **design memory** are served, by the design MCP on NuStack. Knowledge loads on demand per the load contract below — never all at once. Whole-canvas coherence is the asset everything here serves; the databases must never drown it.
|
|
11
11
|
|
|
12
12
|
## Entry
|
|
13
13
|
|
|
@@ -15,9 +15,9 @@ This file is the router. The skill carries the instructions, the engines, and ev
|
|
|
15
15
|
- **The fast path** — a bare component-API question ("how do I use the data table", any `@nurix/components/*` import) reads the component index (see the load contract) and answers. It never enters the five steps.
|
|
16
16
|
- **Refuse with a redirect** — marketing sites, storefronts, and mobile-native briefs: no carried material covers them. Foreign web stacks are **not** refused; the primitives switch handles them.
|
|
17
17
|
|
|
18
|
-
## The design MCP (on
|
|
18
|
+
## The design MCP (on NuStack)
|
|
19
19
|
|
|
20
|
-
The taste dictionaries and the workspace's design memory are served by
|
|
20
|
+
The taste dictionaries and the workspace's design memory are served by NuStack Studio at **`{nustack-base}/design/mcp`** — JSON-RPC over one POST, configured as an MCP server in the runtime. The endpoint is currently open (no credential). Four verbs:
|
|
21
21
|
|
|
22
22
|
| Verb | Use |
|
|
23
23
|
| --- | --- |
|
|
@@ -27,7 +27,7 @@ The taste dictionaries and the workspace's design memory are served by Apollo St
|
|
|
27
27
|
| `record` | Append decision lines / Tier-2 dispositions / the gate report (with the current app as context), and push preference updates — one call |
|
|
28
28
|
|
|
29
29
|
- **Fetch, choose, persist.** Shortlist from search, choose reading the brief, then `get_entry` **exactly one** entry and keep it for the session. The brief outranks the index; search is a retrieval aid, never a router.
|
|
30
|
-
- **Memory is the workspace.** Design taste lives at **workspace level, never per app** — an app inherits its workspace's taste. The `workspaceId` is one committed line in the project repo (or ambient
|
|
30
|
+
- **Memory is the workspace.** Design taste lives at **workspace level, never per app** — an app inherits its workspace's taste. The `workspaceId` is one committed line in the project repo (or ambient NuStack context). Sessions open with `get_preferences`; decisions are written through `record` as they happen — one line each, never silent. Every request may declare `_meta.versions.schema_version: 1`; on a `-32002` error, tell the user to update the design skill and stop.
|
|
31
31
|
- **Catalog unreachable at intake blocks with a clear error** — intake cannot choose an identity without the catalog; no vendored fallback. A failed `record` is retried once, then surfaced — never dropped.
|
|
32
32
|
|
|
33
33
|
## The shape: five steps, one gate
|
|
@@ -59,7 +59,7 @@ The project repo keeps exactly what a build needs offline and reproducibly — a
|
|
|
59
59
|
|
|
60
60
|
- **`design.md`** (in the design folder — `packages/common/` at the base of a multi-package workspace, `src/common/` in a single-package repo) — the working copy of the workspace's look: the resolved fork of the chosen package with provenance line, brief bindings, the primitives switch, and every recorded deviation with its one-line reason. The artifacts beside it (`design.tokens.css`, and `personality.css` under `nurix`) are derived from it, never hand-edited. **Locate it mechanically, never by search:** `node scripts/validate-design-md.mjs --project .` resolves the one path from the repo's shape and fails closed on a copy anywhere else, naming where it belongs; a repo where it resolves to nothing has no fork yet, and first intake writes it at that path. After any local edit that survives the ship gate, push the new text via `record`; at session start compare the repo copy against `get_preferences` — on mismatch, tell the user which is newer and ask; never silently overwrite either.
|
|
61
61
|
- **`product.md`** — users and jobs, roles, action frequency, risk, accessibility commitments, brand anti-references. Package choice, UX decisions, motion intensity, and demo data all cite it.
|
|
62
|
-
- **The `workspaceId`** — one committed line (or ambient
|
|
62
|
+
- **The `workspaceId`** — one committed line (or ambient NuStack context); what binds the repo to its workspace's memory.
|
|
63
63
|
|
|
64
64
|
No decision files, no taste files — the decision log and the taste profile are workspace preference data, written through `record`, read through `get_preferences`, and visible in Studio. Humans audit them there; runs never read the log for truth.
|
|
65
65
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Read the brief and choose from the menus: one identity package (the design.md system), any axis overrides the brief demands, layout blueprints per surface, and the product domain for content. Every choice is one recorded line.
|
|
4
4
|
|
|
5
|
-
**Returning workspace first.** If the repo carries a `workspaceId` (or the
|
|
5
|
+
**Returning workspace first.** If the repo carries a `workspaceId` (or the NuStack context supplies one), open with `get_preferences`: the workspace's `design.md`, `taste.md`, and `product.md` rows are the session's context — then run the drift check (compare the repo's fork — `node scripts/validate-design-md.mjs --project .` resolves its one path from the repo shape and fails closed on a copy anywhere else — against the server row; on mismatch, tell the user which is newer and ask). A workspace with a recorded identity re-enters the steps with that identity already chosen; the rules below are for first intake.
|
|
6
6
|
|
|
7
7
|
## Rules
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: issue-bridge
|
|
3
|
-
description: The bridge between a traditional issue tracker (GitHub Issues, Jira, Linear, …) and the AI-first SDLC. Two modes — **triage** (inbound — a tracker item is routed through the ownership index to its owning feature docs, localized with
|
|
3
|
+
description: The bridge between a traditional issue tracker (GitHub Issues, Jira, Linear, …) and the AI-first SDLC. Two modes — **triage** (inbound — a tracker item is routed through the ownership index to its owning feature docs, localized with `nustack graph`, and lands as a confidence-graded triage dossier ready for a fix plan) and **file** (outbound — a verified harness finding becomes a tracker-native issue with checkable evidence, and the created ID is written back into the originating row). Tracker access goes through a connector (see the per-tracker packs under references/trackers/); tracker writes are always approval-gated per run. Use on "triage issue #N / PROJ-123 / ENG-123", "triage the new issues", "file this finding as a ticket", "/issue-bridge".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# issue-bridge — the tracker bridge method
|
|
@@ -10,7 +10,7 @@ description: The bridge between a traditional issue tracker (GitHub Issues, Jira
|
|
|
10
10
|
You bridge two systems of record. Neither is asked to change:
|
|
11
11
|
|
|
12
12
|
- **The tracker owns** intake, priority, assignment, status, discussion. You never relocate those.
|
|
13
|
-
- **The AI layer owns** intent (feature docs say *why* — invariants, vocabulary, scope), location (the ownership index and
|
|
13
|
+
- **The AI layer owns** intent (feature docs say *why* — invariants, vocabulary, scope), location (the ownership index and `nustack graph` say *what and where*), and history (git-derived).
|
|
14
14
|
- **The bridge rule: inbound enrich, outbound evidence.** An inbound item gains location, owning docs, and blast radius. An outbound item carries citations a human can verify without trusting you.
|
|
15
15
|
|
|
16
16
|
## Connectors
|
|
@@ -19,7 +19,7 @@ Tracker access is **this skill's own knowledge**, not a harness-declared tool. I
|
|
|
19
19
|
|
|
20
20
|
## Tool tier
|
|
21
21
|
|
|
22
|
-
Read the
|
|
22
|
+
Read the `nustack` status from the session context (`Tool:` line). **Present-fresh** → localize every question through `nustack graph` (`find`, `where-defined`, `who-calls`, `refs`, `impact`, `blame`, `tests-covering`) before reading source. **Present-stale** → use it, note the gap. **Absent** → Glob/Grep/read, announced. Never run an index step first — the family re-indexes a stale store before it answers. One carve-out: **syntax-level detail — decorator lists and their order, full function bodies, statement sequence — is not in the index's query surface**; go straight to source for that class, whatever the tier. Decorator *application* is indexed as a reference, attributed to the decorated declaration; what the decorator does is not.
|
|
23
23
|
|
|
24
24
|
## Mode selection
|
|
25
25
|
|
|
@@ -30,15 +30,15 @@ Read the codegraph status from the session context (`Tool:` line). **Present-fre
|
|
|
30
30
|
1. **Fetch** through the connector (per the pack). No access → pasted-text mode, missing fields named.
|
|
31
31
|
2. **Classify.** Bug / feature request / question / docs-gap. Questions get owning-doc pointers and a direct answer, not a dossier.
|
|
32
32
|
3. **Route.** Match the issue's vocabulary against the ownership index and feature docs' `keywords:`. Read the owning doc(s) **fully** — intent and invariants first, code second. No owning doc → say so; the absence is itself a finding.
|
|
33
|
-
4. **Localize.** Names from the issue go through
|
|
33
|
+
4. **Localize.** Names from the issue go through `nustack graph`; every location claim carries `file:line`.
|
|
34
34
|
5. **Diagnose.** Root-cause hypothesis, confidence-graded — `certain` / `inferred` / `unresolved` — and never present inferred as certain. Blast radius via `impact`/`coupling`; test surface via `tests-covering`.
|
|
35
|
-
6. **Dedup.** Search the tracker for siblings and priors (per the pack); check
|
|
35
|
+
6. **Dedup.** Search the tracker for siblings and priors (per the pack); check `nustack graph drift` for the same-bug-in-a-clone class.
|
|
36
36
|
7. **Write the dossier** to `docs/operations/triage/YYYY-MM-DD-<tracker>-<id>-<slug>.md` and return the verdict summary.
|
|
37
37
|
8. **Posting back** (comment, label, assign, transition) is **approval-gated per run**: show exactly what would be posted, post only on an explicit yes in this session. A standing repo ruling (e.g. "never post to this upstream") overrides any approval.
|
|
38
38
|
|
|
39
39
|
## The dossier
|
|
40
40
|
|
|
41
|
-
Frontmatter: `type: triage`, `tracker`, `date`, `confidence`, and the subject key — `issue: {id: <n>, url: <url>}` with a **numeric** id for a tracker item, `issue: {id: local:<record-id>}` for a locally-authored record, `pr: {id: <n>}` when the dossier resolves a pull request. This shape is machine-read: the
|
|
41
|
+
Frontmatter: `type: triage`, `tracker`, `date`, `confidence`, and the subject key — `issue: {id: <n>, url: <url>}` with a **numeric** id for a tracker item, `issue: {id: local:<record-id>}` for a locally-authored record, `pr: {id: <n>}` when the dossier resolves a pull request. This shape is machine-read: the NuStack desktop's Issues panel joins dossiers to tracker rows on exactly this key, so a quoted number, a missing id, or a renamed key leaves the dossier orphaned — written but never surfaced beside its issue. Sections, in order: **Verdict** (1–3 sentences) · Classification · Owning docs (paths) · Root cause (cited, graded) · Blast radius · Suggested fix plan (feeds the plan → PR loop; you never implement) · Test surface · Duplicates & related · **Prior-thread corrections** (claims already on the tracker thread — bot answers, maintainer comments — that your evidence shows to be wrong, each with the citation that refutes it) · Open questions. A dossier is a dated working artifact — it never claims `owns:` globs and never masquerades as a feature doc.
|
|
42
42
|
|
|
43
43
|
## File — harness finding in, tracker issue out
|
|
44
44
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nustack-graph
|
|
3
|
+
requires-tool: nustack
|
|
4
|
+
description: Query the code graph before reading source. An LLM-first codebase index (symbols, call and data references, git history, co-change coupling, ownership) answering whole questions in one call, through `nustack graph`. Use it to localize, scope, and assess blast radius on this repository; use grep/read for string literals, mechanism, and data flow — the skill states which is which. Applies to any task that starts with "where is", "who calls", "what changed", "who owns", "what breaks", or "is this tested".
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# nustack graph — the index-first method
|
|
8
|
+
|
|
9
|
+
**If the tool is absent** (`nustack --version` fails): do not stall. Read `.claude/state/decisions.tsv` for `tool.nustack`.
|
|
10
|
+
|
|
11
|
+
- **`declined`** — fall back to the cold method (grep, targeted reads, `git log`) **silently**. Do not mention the tool, do not re-offer. The user has answered.
|
|
12
|
+
- **undecided** (no file, no key, or anything unrecognised) — fall back to the cold method *and* make the offer **once**: this repository would answer "where is / who calls / what changed / who owns" from an index instead of a grep sweep. `npx @nurix/nustack graph overview` runs it without installing anything; `npm install -g @nurix/nustack` puts it on `PATH`. **Ask, never act** — a global npm install modifies the machine, and indexing writes to `~/.codegraph`. Record the answer with `recordDecision` either way, so the question is asked at most once per repo per machine.
|
|
13
|
+
- **`accepted` but the binary is gone** — treat as undecided; a failed or removed install is not a standing yes.
|
|
14
|
+
|
|
15
|
+
**A failed install is `absent`, not an error to retry.** Global installs fail on machines without permission; say so once, fall back, and move on. `npx @nurix/nustack` needs no install at all and is the shorter ladder when one command is all you want.
|
|
16
|
+
|
|
17
|
+
**An explicit invocation overrides a stored decline.** If the user asks for this skill by name, honour it and re-offer — "stop asking" must never become "never usable".
|
|
18
|
+
|
|
19
|
+
**A repository that has never been indexed** answers with the tool saying so rather than with a wrong answer. That is the tool degrading correctly, not a failure: `nustack graph index` builds it, under the same ask-first rule.
|
|
20
|
+
|
|
21
|
+
**Freshness is not your problem.** `nustack graph` re-indexes a stale store before it answers, so a query after a commit reports the new HEAD without anyone running an index first. Pass `--no-refresh` when you deliberately want the index as it stands — answering from a known-old index is a choice you can make, never one you make by accident. This is the one thing that changed when the code graph moved behind `nustack`: there is no staleness ritual left to perform.
|
|
22
|
+
|
|
23
|
+
Everything below assumes the tool is present.
|
|
24
|
+
|
|
25
|
+
`nustack graph` defaults to the git root of the working directory — inside this repository, no flags are needed; `--repo <pathOrId>` asks about another one. Every answer states its own confidence per section (`CERTAIN` / `INFERRED` / `UNRESOLVED`), announces anything the token budget dropped, and says what an empty section means. Trust those labels.
|
|
26
|
+
|
|
27
|
+
## The method order
|
|
28
|
+
|
|
29
|
+
Localize through the index **before** opening any file, in this order; then read only the files it points at.
|
|
30
|
+
|
|
31
|
+
1. **`nustack graph find <fragment>`** — symbols and file paths by name fragment. The entry point when you only have issue vocabulary. Symbol names only — it will never match string literals, dict keys, or SQL fragments (grep for those, without shame).
|
|
32
|
+
2. **`nustack graph where-defined <symbol>`** / **`nustack graph outline <file>`** — pin the definition; map a big file. `outline` turns a 100+ declaration file into a navigable, line-numbered list — use it instead of reading god files blind. On a large file add `--budget 0` (see traps).
|
|
33
|
+
3. **`nustack graph who-calls <symbol>`** / **`nustack graph impact <target>`** — callers, and blast radius (static reach + historical co-change reach, reported separately on purpose: they disagree, and the disagreement is information).
|
|
34
|
+
4. **`nustack graph churn --days N --path <dir>`** · **`coupling --path <file>`** · **`owners <path>`** · **`tests-covering <file>`** — the history layer: what's hot, what changes together (including *hidden coupling* — co-change with no static reference, the one fact reading code cannot reveal), who knows this area and how recently, which test file historically guards this file.
|
|
35
|
+
5. **`nustack graph god-objects --minSymbols 30`** / **`hotspots`** / **`overview`** — repo-scale orientation and risk.
|
|
36
|
+
|
|
37
|
+
Then read, with `sed -n` ranges at the lines the index gave you. The index does discovery; reading does diagnosis. Add `--json` when a wrapper needs structure — the answer arrives inside the CLI's machine envelope, `{ nustackJsonVersion, command, ok, data }`.
|
|
38
|
+
|
|
39
|
+
There is also **`nustack graph blame <file> [--line N]`** — the commits behind a file or one specific line, answered by git at query time; **`nustack graph refs <name>`** — the data-reference layer: where a name is used as a dict/object key, attribute/field, import, or type annotation, with read/write direction, and a cross-language flag when the same name spans Python and TypeScript files (this answers "who reads `x_axis_sort`" / "who writes `chart.query_context`" in one call — the questions that used to need grep); and **`nustack graph drift`** — near-duplicate function bodies crossed with co-change history: similar code whose files never change together is an unfinished refactor waiting to disagree. `who-calls` additionally tabulates which keyword arguments each call site passes, so "which call sites omit this option" is one query.
|
|
40
|
+
|
|
41
|
+
**`nustack graph status`** reports what the index holds for this repository — indexed HEAD, file and symbol counts, whether it is stale — and **`nustack graph index [--commits N]`** rebuilds it explicitly. Reach for `status` when an answer surprises you and you want to know what it was computed from; you should never need `index` to make a query correct.
|
|
42
|
+
|
|
43
|
+
## The boundary — what the index cannot answer (fall back immediately, don't retry)
|
|
44
|
+
|
|
45
|
+
- **Free-text string literals** (SQL templates, prose, format strings) — grep. Identifier-shaped keys and fields ARE indexed (`refs <name>`); anything longer or with spaces is not.
|
|
46
|
+
- **Framework reflection** (Marshmallow `@post_load`, signal handlers, `appbuilder.add_api(Cls)` registration) — invisible to call edges. Bare decorator *application* (`@has_access_api`) IS indexed as a reference; what the decorator does and when it runs is not.
|
|
47
|
+
- **Data flow and mechanism** ("does this value reach that function", "what order do these run in") — always reading work.
|
|
48
|
+
|
|
49
|
+
Indexed symbol forms include statement declarations, `const X = () => {}`, named function expressions inside wrapper calls (`typedMemo(function DataTable() {})`), TS/JS class fields, and Python class-body fields (`is_dttm = Column(...)`); nested members render as `Parent › member` in `outline`.
|
|
50
|
+
|
|
51
|
+
## Traps (each cost a prior agent real time)
|
|
52
|
+
|
|
53
|
+
- **Bare-name collision.** Call references match by name, not resolved binding. When a name has several definitions, `who-calls` lists every candidate and says so in its subject line — read that section before trusting the call sites, and pass `--scope <path-fragment>` (also on `find`, `where-defined`, `impact`) to narrow to a package.
|
|
54
|
+
- **`nustack graph functions` lists the graph's own verbs**, not the repository's functions. For repo functions use `find`. An explicit `--limit` lifts the default token budget, so a full `outline` needs no `--budget 0` incantation (an explicit `--budget` still wins).
|
|
55
|
+
- **Churn windows.** `churn --days 180` returning nothing does not mean quiet history — widen the window. Directory-scope churn ranks by raw commit count and buries small-but-critical files; for specific files use `owners`/`coupling` per file.
|
|
56
|
+
- **`tests-covering` is co-change evidence, not executed coverage** — it names the right test file to open; it cannot say the specific scenario is tested.
|
|
57
|
+
- **Coupling on a busy file has a noise tail** — partners at <15% shared commits are usually PR-cohabitation, not real coupling. Verify a partner with a read before adding it to a touch-list.
|
|
58
|
+
- **The history layer sees one window, and the refresh keeps resetting it to 5000 commits.** `churn`, `owners`, `coupling`, `blame` and `tests-covering` answer over the ingested commits, and an index — the automatic one included — takes the most recent 5000 unless `index --commits N` says otherwise. On a deeper repository those answers are true of the window, not of the repository, and the next refresh silently narrows a wider window back. Say "over the last N commits" when you report them, and re-ask for the depth you need.
|
|
59
|
+
- **`owners` is authorship weight, not a file-touch tally** — an author is counted once per commit touching the path, however many of the path's files that commit changed. Do not transcribe its numbers as "files changed".
|
|
60
|
+
- **The first query after a long gap pays for the re-index.** Absorption is not free: on a large repository the refresh takes tens of seconds before the answer appears. That is one wait, not a per-query cost — and `--no-refresh` skips it when you would rather have the old answer now.
|
|
61
|
+
|
|
62
|
+
## Division of truth in this repository
|
|
63
|
+
|
|
64
|
+
`docs/` is the source of truth for **intent** — what a feature is for, its invariants, decisions, vocabulary. The code graph is the source of truth for **facts** — where things are, who calls what, what changed together, who owns what. Never copy live facts (caller counts, churn numbers) into docs as if permanent; never ask the index for intent.
|
|
@@ -237,9 +237,9 @@ Apply the **§ Strategy memory** retirement procedure to `docs/memory/maintenanc
|
|
|
237
237
|
- **Enforce the memory cap on every run** (§ Strategy memory) — you are its only writer, so no one else can do it.
|
|
238
238
|
- **A clean report is valid** — do not invent issues.
|
|
239
239
|
- **Declarative, idempotent.** Re-running baseline mode on a current repo reshapes nothing — a second pass is just the audit plus the module proposal. Converge to the current shape; never replay a version ladder; never reconstruct a past shape.
|
|
240
|
-
- **Brownfield extraction (operator-ruled 2026-08-19).** When baseline mode meets a repo with a real source tree and few or no feature docs, the module *proposal* is not enough — run the extraction pass: produce feature docs for the recognized modules (docs/features/, full frontmatter: `owns:` · `module:` · `keywords:` · `status:` · `last-reviewed:` · `type: feature` · `derivation: "extracted (baseline YYYY-MM-DD,
|
|
240
|
+
- **Brownfield extraction (operator-ruled 2026-08-19).** When baseline mode meets a repo with a real source tree and few or no feature docs, the module *proposal* is not enough — run the extraction pass: produce feature docs for the recognized modules (docs/features/, full frontmatter: `owns:` · `module:` · `keywords:` · `status:` · `last-reviewed:` · `type: feature` · `derivation: "extracted (baseline YYYY-MM-DD, nustack graph + source read)"`), plus the architecture, tests, and operations layers. Use the `nustack-graph` skill where installed — localize through the index before reading; the docs record intent, the index answers facts. **Every extracted feature doc carries a `## History & Ownership` section** — top contributors with recency, churn posture, and bus-factor notes, dated "as of YYYY-MM-DD" (operator ruling: this section is mandatory in extraction output even though doc-lint flags dated narrative in truth docs; the findings are accepted for now, and a future harness version may retire the section in favor of live index pointers). `status:` is evidence-graded: `deprecated` only when history shows the area dead, never by guess. Scale by fan-out (one extraction lane per module, a completeness critic over the result) rather than one long cold read.
|
|
241
241
|
- **Diagrams ride the baseline (operator-ruled 2026-08-24).** Whenever baseline mode produces or converges the architecture layer, draw two ASCII diagrams from the real source, each in its own fenced code block: (1) an **architecture diagram** in the system-design/architecture doc — the actual processes, entry points, and wire surfaces as boxes, the calls/queues/files between them as labelled arrows; (2) a **DB ER diagram** wherever the repo declares a relational schema (DDL, migrations, an ORM schema) — one box per table listing its key columns, FK edges drawn to the table they reference, cardinality on the edge; it lands in the domain-model doc, or `docs/architecture/data-model.md` when none exists. Box-drawing characters (`┌─┐│└┘├┤`), width ≤ 100 columns. Derived only from what the code declares — a diagram never invents a component the source does not show, and a repo with no relational schema gets **no** ER diagram rather than an imagined one. Diagrams are truth-document sections: later baselines and audits converge them in place like any other section.
|
|
242
|
-
- **Tool tiers.** The SessionStart context names each manifest-declared tool with its tier; calibrate the run to it, never stall on it. **Present-fresh** (`Tool:
|
|
242
|
+
- **Tool tiers.** The SessionStart context names each manifest-declared tool with its tier; calibrate the run to it, never stall on it. **Present-fresh** (`Tool: nustack 0.9.0`): use it — localize every question through `nustack graph` before reading source, and never run an index step first: the family re-indexes a stale store before it answers, so the only freshness decision left is the deliberate `--no-refresh`. **Present-stale** (below the catalog minimum): still use it, but note the version gap and the upgrade command in the run report — a stale contract may lack answer shapes the skill assumes. **Absent** (`Tool: nustack MISSING`): fall back to the cold method (Glob/Grep/read), state in the run report that the tool was absent and give the install command — degraded output is acceptable, silent degradation is not.
|
|
243
243
|
- **One sentinel.** `docs/structure.md` `harness-version` is the only baseline/version marker.
|
|
244
244
|
- **Modules are labels, layout-blind.** Seed from the code's bounded contexts; never from `pnpm-workspace.yaml`/workspace walls. Propose, don't force; acting is a `module:` tag, never a folder.
|
|
245
245
|
- **You orchestrate docs; the `documentation` skill supplies templates.** It is a library — never expect it to baseline.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "delivery",
|
|
3
3
|
"name": "delivery",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.34",
|
|
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": "1d2b1f2607d6"
|
|
14
14
|
}
|
|
@@ -86,7 +86,7 @@ Cheap, read-only. Scope the target, grade its access, detect which dimensions it
|
|
|
86
86
|
|
|
87
87
|
Dispatch one collector per available dimension; collect exhaustively before context is lost. Every collector writes `research/<target>/dimensions/<dim>/_summary.md` (with the **provenance frontmatter** — method · confidence · completeness · gaps) + `raw/`, obeying `.claude/rules/ingestion.md`.
|
|
88
88
|
|
|
89
|
-
- **Run independents in parallel** — dispatch `docs`, `packages`, `api`, `website`, `community`, `deployed-client-bundle`, `infra-backend-fingerprint`, `distribution-artifacts` together (`subagent_type='<dimension>'`, one message, multiple Agent calls). Seed each collector with **warm entry points (URLs / repos / hosts), not claimed facts** — any fact **or inference** carried from Discovery (a price, a tier name, a version — **or a derived hypothesis like "localStorage-key X ⇒ backend is Y"**) is tagged `verify:may-be-stale` so the collector **re-derives it instead of anchoring** on a possibly-stale (or possibly-wrong) value. A Discovery _inference_ is never seeded as a plan fact: label it a hypothesis in `00-recon-plan.md` and tag it `verify:` in the seed, so a collector that inherits it reports it hedged rather than echoing it as observed (a real run seeded an `
|
|
89
|
+
- **Run independents in parallel** — dispatch `docs`, `packages`, `api`, `website`, `community`, `deployed-client-bundle`, `infra-backend-fingerprint`, `distribution-artifacts` together (`subagent_type='<dimension>'`, one message, multiple Agent calls). Seed each collector with **warm entry points (URLs / repos / hosts), not claimed facts** — any fact **or inference** carried from Discovery (a price, a tier name, a version — **or a derived hypothesis like "localStorage-key X ⇒ backend is Y"**) is tagged `verify:may-be-stale` so the collector **re-derives it instead of anchoring** on a possibly-stale (or possibly-wrong) value. A Discovery _inference_ is never seeded as a plan fact: label it a hypothesis in `00-recon-plan.md` and tag it `verify:` in the seed, so a collector that inherits it reports it hedged rather than echoing it as observed (a real run seeded an `nustackAnonId ⇒ GraphQL backend` guess that two captures then repeated before the session refuted it). **A sibling collector's _summary_ is a claim, not a source** — when seeding one collector with something another collector reported, tag it `verify:` too, unless the main session has actually read the underlying `raw/` artifact. A returned summary is a compressed, unverified assertion; relaying it into a fresh brief launders it into apparent provenance, and the receiving sub-agent (which cannot see the conversation) has no way to tell a seeded claim from an established fact. (Origin: Emergent — the orchestrator relayed a "documented limitation: the connector cannot deploy" line from one collector's summary into another's brief; it was **not in the corpus**, and only the receiving agent's own verification caught it.) **Every QUANTITATIVE claim in a brief carries its lane.** The rule above covers a *sibling's summary*; it does not cover a number the **orchestrator itself** picked up in passing — from a search result, a page it skimmed, or its own recollection — and then re-emitted as framing. A figure the main session cannot name a `raw/` anchor for is either seeded **`verify:`** or **omitted**; it is never stated flat. A number in a brief reads as established even when nothing established it, and the receiving agent cannot tell the difference. (Origin: indus-sarvam — the orchestrator seeded a "₹98.68 crore IndiaAI funding" figure into the positioning brief as context; the receiving agent traced it to a **third-party user's GitHub issue body**, not to any first-party source, and had to de-launder it. The vendor states only "compute provided under the IndiaAI mission", with no amount.) Only one dimension may drive the browser at a time (see `ingestion` §7 rule 9): the parallel collectors use WebFetch/curl, leaving Chrome to `session`.
|
|
90
90
|
- **Codebase fan-out** — dispatch `codebase`; it clones + maps + returns a per-package work-list; then the main session fans out one reader per substantial package.
|
|
91
91
|
- **Gated dimensions** — `session` and `wire-capture` (rung ≥2) need explicit user authorization first (see `ingestion` §7). `session` follows `.claude/rules/ingestion.md` §9. When `session` is authorized, **dispatch the non-browser collectors in the background (`run_in_background`) and drive the gated browser session in the main session concurrently** — they don't contend (Chrome belongs to the main session; the collectors use WebFetch/curl), so the whole of Ingestion runs in parallel.
|
|
92
92
|
- **Shared seam** — **seed the two shared files `dimensions/_shared/{api-path-catalog.md, feature-flags.md}` (empty, with a header) once, before the collector fan-out**; bundle/session/wire/ distribution then each _append_ a `source:`-tagged section to the same physical file (never their own copy). One owner dimension per static asset; a second mine of the same asset is one source, not corroboration (`ingestion` §6).
|
|
@@ -13,7 +13,7 @@ You collect the **authenticated runtime** into `research/<target>/dimensions/ses
|
|
|
13
13
|
- **§9** — the session playbook for this one dimension: prerequisites (§9.1), the three-pass strategy (§9.2), the P0→P6 phases (§9.3), runtime taps + the credential-scrub wrapper (§9.4), and the capture contract (§9.5).
|
|
14
14
|
- **§7.0** — the **runnable redaction layer** (`SCRUB` / `redactSecrets` / `ULTRA`): install it inside the tap wrapper so no secret reaches `raw/`.
|
|
15
15
|
|
|
16
|
-
Also read `.claude/rules/tradecraft.md` — the **keyboard-level reference** for this dimension: copy-paste fetch/XHR/WebSocket/EventSource/Pusher taps (install them _first_, before navigating), the ~27-entry Chrome-MCP gotcha catalogue, the dead-end recovery table, the
|
|
16
|
+
Also read `.claude/rules/tradecraft.md` — the **keyboard-level reference** for this dimension: copy-paste fetch/XHR/WebSocket/EventSource/Pusher taps (install them _first_, before navigating), the ~27-entry Chrome-MCP gotcha catalogue, the dead-end recovery table, the NuStack-cache read-side recipe, and the storage/auth/envelope/realtime fingerprint tables. Don't re-derive a tap you can paste.
|
|
17
17
|
|
|
18
18
|
## This dimension is special
|
|
19
19
|
|
|
@@ -10,7 +10,7 @@ You collect the **authenticated runtime** into `research/<target>/dimensions/ses
|
|
|
10
10
|
- **§9** — the session playbook for this one dimension: prerequisites (§9.1), the three-pass strategy (§9.2), the P0→P6 phases (§9.3), runtime taps + the credential-scrub wrapper (§9.4), and the capture contract (§9.5).
|
|
11
11
|
- **§7.0** — the **runnable redaction layer** (`SCRUB` / `redactSecrets` / `ULTRA`): install it inside the tap wrapper so no secret reaches `raw/`.
|
|
12
12
|
|
|
13
|
-
Also read `.claude/rules/tradecraft.md` — the **keyboard-level reference** for this dimension: copy-paste fetch/XHR/WebSocket/EventSource/Pusher taps (install them _first_, before navigating), the ~27-entry Chrome-MCP gotcha catalogue, the dead-end recovery table, the
|
|
13
|
+
Also read `.claude/rules/tradecraft.md` — the **keyboard-level reference** for this dimension: copy-paste fetch/XHR/WebSocket/EventSource/Pusher taps (install them _first_, before navigating), the ~27-entry Chrome-MCP gotcha catalogue, the dead-end recovery table, the NuStack-cache read-side recipe, and the storage/auth/envelope/realtime fingerprint tables. Don't re-derive a tap you can paste.
|
|
14
14
|
|
|
15
15
|
## This dimension is special
|
|
16
16
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "discover",
|
|
3
3
|
"name": "discover",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.19",
|
|
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": "1bad8e1f03eb"
|
|
14
14
|
}
|
|
@@ -25,7 +25,7 @@ diagrams — the same backend is the same participant everywhere):
|
|
|
25
25
|
| `JobQueue` | an async batch / worker / job system (the work happens off-request) |
|
|
26
26
|
| `Realtime` | a WS / SSE / long-poll channel |
|
|
27
27
|
| `3rdParty` | a named external vendor (AI mesh, payments, CDP, email, telephony) |
|
|
28
|
-
| `Store` | browser storage / client cache (
|
|
28
|
+
| `Store` | browser storage / client cache (NuStack, Redux-persist, IndexedDB) |
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
@@ -169,7 +169,7 @@ Discovery records the resolved vector in the `access_grade` frontmatter of `00-r
|
|
|
169
169
|
> operation; the conclusion happened to be right, but on **other** evidence — an Indian legal entity, 28/43
|
|
170
170
|
> engineering roles in Bangalore, and INR billing — while the PoP reasoning was simply invalid.)
|
|
171
171
|
|
|
172
|
-
> **Hypotheses are not facts (seed them tagged, never echo them).** Any _derived inference_ Discovery makes beyond the four graded axes — e.g. "this `localStorage` key implies a GraphQL backend", "this host is the realtime endpoint" — is recorded in `00-recon-plan.md` **as a labelled hypothesis**, never as a plan fact, and is tagged `verify:may-be-stale` when seeded to a collector (CLAUDE.md Mode 2) so Ingestion **re-derives** rather than echoes it. A wrong inference seeded as framing propagates: a real run's `
|
|
172
|
+
> **Hypotheses are not facts (seed them tagged, never echo them).** Any _derived inference_ Discovery makes beyond the four graded axes — e.g. "this `localStorage` key implies a GraphQL backend", "this host is the realtime endpoint" — is recorded in `00-recon-plan.md` **as a labelled hypothesis**, never as a plan fact, and is tagged `verify:may-be-stale` when seeded to a collector (CLAUDE.md Mode 2) so Ingestion **re-derives** rather than echoes it. A wrong inference seeded as framing propagates: a real run's `nustackAnonId ⇒ GraphQL` guess was repeated (hedged) by two captures before the `session` dimension refuted it (the backend was RSC + Server Actions; the key was an NuStack.io tracker).
|
|
173
173
|
|
|
174
174
|
---
|
|
175
175
|
|
|
@@ -664,7 +664,7 @@ The same phases apply whether the target is an AI builder, analytics dashboard,
|
|
|
664
664
|
|
|
665
665
|
### 9.4 Runtime taps (Pass-1 instrumentation, run in-page)
|
|
666
666
|
|
|
667
|
-
> **The runnable taps + the ~27-entry Chrome-MCP gotcha catalogue + the dead-end recovery table + the
|
|
667
|
+
> **The runnable taps + the ~27-entry Chrome-MCP gotcha catalogue + the dead-end recovery table + the NuStack read-side recipe + the fingerprint tables live in `tradecraft.md`** — paste them, do not re-derive. The prose below is the _why_; that file is the _keyboard layer_. The §7.0 redaction scrubbers (`SCRUB`/`redactSecrets`/`ULTRA`) install inside these tap wrappers.
|
|
668
668
|
|
|
669
669
|
> **Install the in-page interceptor FIRST, then move section-to-section WITHOUT a full reload.** CDP `read_network_requests` is unreliable for an already-loaded single-page app: it starts tracking only when first called and silently misses requests already in flight or served from cache (seen on a real run where only a third-party analytics beacon was captured while the app's own API calls went unseen). Treat it as a _supplement_; the in-page interceptor is the primary capture. A full-page `navigate` wipes the interceptor, so traverse the app using whatever it exposes — **clicking in-app nav works on any stack**; or call the client router if one is reachable (`history.pushState`+`popstate`, React Router / Vue Router / Angular Router, or a globally-exposed router such as `window.next.router` on Next.js). The principle is framework-agnostic; the specific call is just whatever that app ships.
|
|
670
670
|
|
|
@@ -405,7 +405,7 @@ The Chrome-MCP browser-driving layer has ~27 recurring failure modes; every one
|
|
|
405
405
|
|
|
406
406
|
1. **Output that _looks_ like cookie/query data is blocked** (`[BLOCKED: Cookie/query string data]`) — a `javascript_tool` result with JWT-shaped strings, long base64, long hex, many UUIDs, or query-string substrings is blocked on the _returned string_, not on what the code does → run the returned value through a `SCRUB()` (and, when that's not enough, the `ULTRA()` regex) before returning. _(§2.1)_
|
|
407
407
|
2. **`read_network_requests` only tracks AFTER its first call** — initial page-load requests are invisible (it starts capturing on first invocation) → install the fetch/XHR taps **before** navigation, or reload after the first call. _(§2.2)_
|
|
408
|
-
3. **`read_network_requests` misses cross-origin XHRs + cache-served loads (GraphQL SPAs)** — it may not surface cross-origin API XHRs (e.g. `i.jasper.ai/gw/graphql` from `app.jasper.ai`), and on reload data is served from the
|
|
408
|
+
3. **`read_network_requests` misses cross-origin XHRs + cache-served loads (GraphQL SPAs)** — it may not surface cross-origin API XHRs (e.g. `i.jasper.ai/gw/graphql` from `app.jasper.ai`), and on reload data is served from the NuStack/SWR cache so no request fires at all → don't conclude "no API calls"; pivot to the client cache and force a refetch to capture verbatim bodies. _(§2.2 corollary)_
|
|
409
409
|
4. **`EventSource` taps miss `text/event-stream` over `fetch`** — many products read SSE-formatted bodies via `fetch().body`, not `EventSource`, so the ES tap returns empty even while streaming → always run **both** the EventSource tap and the fetch/streaming-body tap. _(§2.3)_
|
|
410
410
|
5. **Long-poll buffers rotate fast on heartbeats** — heartbeats fly every few seconds and evict interesting frames from bounded buffers → skip frames smaller than ~350 bytes when storing, and dedupe events by `id` so retried polls don't re-add. _(§2.4)_
|
|
411
411
|
6. **`find` returns ghost duplicates in dynamic UIs** — modern apps render a textarea+button pair twice (visible+hidden, or in a portal); `find` returns both refs but only one is wired → default to coordinate clicks for action-bearing buttons; use refs only for stable structural elements. _(§2.5)_
|
|
@@ -510,7 +510,7 @@ When a surface is genuinely closed read-only, the failure mode is a recovery cue
|
|
|
510
510
|
| **Sleep/wait loop** (`wait → check → wait → check`) | trigger the action, **wait once, then query the history/trajectory endpoint** directly — server-side state beats babysitting client-side polling | §2.10 |
|
|
511
511
|
| **`cloned.text()` never resolves** (held-open long-poll) | don't chase streaming chunks — hit the `/history` (or `/state`, `/snapshot`, `/page-metadata`) endpoint instead | §2.14 / §1.8 |
|
|
512
512
|
| **JS-readable token cookie** tempts `credentials:'include'` | a readable cookie ≠ cookie-auth — probe **Bearer header / cookie / raw-token** all three to pin the real header model; the API often rejects the raw cookie under `ACAO:*` and requires explicit `Authorization: Bearer` | §2.26 |
|
|
513
|
-
| **`read_network_requests` shows only telemetry** (cross-origin XHRs / cache-served reload, GraphQL SPA) | pivot to the **
|
|
513
|
+
| **`read_network_requests` shows only telemetry** (cross-origin XHRs / cache-served reload, GraphQL SPA) | pivot to the **NuStack/client cache** (`window.__NUSTACK_CLIENT__.cache.extract()`) and **force a refetch** to capture verbatim wire bodies — don't conclude "no API calls" | §2.2 corollary / §1.13 |
|
|
514
514
|
| **Realtime hub URL absent from bundle grep** (runtime-constructed, e.g. SignalR `initializeHub`) | record realtime **presence + transport as confirmed**, URL + message contract as **open (needs Pass 2)** — never report "no realtime" on a grep miss | §2.27 — AdCreative |
|
|
515
515
|
| **Authoring/builder surface paywalled** (input/form schema hidden) | open the **run flow** instead (it fetches the richer record incl. the input form), capture the typed-input union, optionally run **one** cheap action to capture the write mutation + output record — deep server-side bits (system prompt, model) stay open, don't fabricate | §1.14 — Jasper |
|
|
516
516
|
| **GraphQL introspection disabled** (`{ __schema }` → 400 "introspection is not allowed") | fall back to **cache census + wire-body capture** — the POST body still carries the full query text; force a re-send via `refetchQueries` and the fetch tap captures it verbatim | §1.13 — Jasper |
|
|
@@ -546,13 +546,13 @@ For same-origin cookie-auth products (Clerk, Next.js axios proxies):
|
|
|
546
546
|
|
|
547
547
|
## GraphQL read-side mining + classification fingerprint tables
|
|
548
548
|
|
|
549
|
-
Two reusable assets for the `session` dimension (ingestion §9.4 runtime taps). Asset 1 is an **executable read-side recipe** for any
|
|
549
|
+
Two reusable assets for the `session` dimension (ingestion §9.4 runtime taps). Asset 1 is an **executable read-side recipe** for any NuStack/GraphQL SPA; Asset 2 is a set of **classification tables** that turn a raw observation (a storage key, a host, an envelope shape) into a _named_ stack finding. Every code block below is runnable verbatim in the page console / in-page tap — do not paraphrase it into prose.
|
|
550
550
|
|
|
551
551
|
---
|
|
552
552
|
|
|
553
|
-
### 1. The
|
|
553
|
+
### 1. The NuStack-cache read-side recipe (the richest Pass-1 GraphQL source)
|
|
554
554
|
|
|
555
|
-
> **Why this is the richest Pass-1 GraphQL source.** For
|
|
555
|
+
> **Why this is the richest Pass-1 GraphQL source.** For NuStack-backed SPAs (look for `window.__NUSTACK_CLIENT__`), the **normalized client cache is the single richest read-only source** — often better than network capture, which may miss cross-origin GraphQL XHRs and won't fire at all when data is served from cache on reload (a real run on Jasper saw the network panel show only telemetry because the reload was served entirely from the NuStack cache). The cache holds a normalized entity census, every entity's field schema, and enum distributions across all entities — the whole read-side data model — with **zero network cost and no state change**. Introspection is almost always disabled in prod (`{ __schema { ... } }` → `400 "introspection is not allowed"`), so the cache census + verbatim wire-body capture _is_ the schema-recovery path, not a fallback.
|
|
556
556
|
|
|
557
557
|
### 1a. Entity census, field schema, and enum distributions
|
|
558
558
|
|
|
@@ -560,7 +560,7 @@ Dump the normalized cache, count entities per type, decode one entity of a type
|
|
|
560
560
|
|
|
561
561
|
```js
|
|
562
562
|
// 1) entity census — what types exist and how many
|
|
563
|
-
const ex = window.
|
|
563
|
+
const ex = window.__NUSTACK_CLIENT__.cache.extract();
|
|
564
564
|
const counts = {};
|
|
565
565
|
Object.keys(ex).forEach((k) => {
|
|
566
566
|
const t = k.split(":")[0];
|
|
@@ -581,7 +581,7 @@ const dist = (f) =>
|
|
|
581
581
|
Recover every live operation's name, variable keys, and selection-set AST — even when the production build has stripped the query source text:
|
|
582
582
|
|
|
583
583
|
```js
|
|
584
|
-
const arr = [...window.
|
|
584
|
+
const arr = [...window.__NUSTACK_CLIENT__.getObservableQueries("all").values()];
|
|
585
585
|
arr.map((q) => {
|
|
586
586
|
const d = q.query || q.options.query;
|
|
587
587
|
return {
|
|
@@ -597,7 +597,7 @@ arr.map((q) => {
|
|
|
597
597
|
Production strips `DocumentNode.loc.source.body`, but the **POST body still contains the full query**. Install a fetch tap that keeps `init.body`, then **force a re-send** so the tap captures the exact query string + fragments + variables:
|
|
598
598
|
|
|
599
599
|
```js
|
|
600
|
-
await window.
|
|
600
|
+
await window.__NUSTACK_CLIENT__.refetchQueries({ include: ["Web_Library"] });
|
|
601
601
|
```
|
|
602
602
|
|
|
603
603
|
The fetch tap then holds the verbatim query/mutation text + fragments + variables for that operation. (This is how the Jasper `Web_Library` / `Web_CreateAppSnapshot` queries and the 11-type `ContextItem` union were recovered.)
|
|
@@ -623,7 +623,7 @@ Inferences from `localStorage` / `sessionStorage` / cookie / IndexedDB **key nam
|
|
|
623
623
|
| `auth0.*` | Auth0 |
|
|
624
624
|
| `persist:*` | Redux Persist |
|
|
625
625
|
| `_pinia_*` | Pinia (Vue state) |
|
|
626
|
-
| `
|
|
626
|
+
| `nustack-cache-persist` | NuStack Client |
|
|
627
627
|
| `i18next*` | i18next |
|
|
628
628
|
| `ph_phc_*` | PostHog |
|
|
629
629
|
| `_ga*`, `_gid` | Google Analytics |
|
|
@@ -684,7 +684,7 @@ Inferences from `localStorage` / `sessionStorage` / cookie / IndexedDB **key nam
|
|
|
684
684
|
| --- | --- |
|
|
685
685
|
| **JSON-Schema-driven (Huma / similar)** | Every response carries inline `"$schema": "https://api.x.com/<TypeName>OutputBody.json"`; RFC 7807 error envelope |
|
|
686
686
|
| **Hand-rolled FastAPI / Express** | No `$schema` field; minimal error shape (`{detail: "..."}`) |
|
|
687
|
-
| **GraphQL** | Single `/graphql` endpoint, query/variables in body, often `
|
|
687
|
+
| **GraphQL** | Single `/graphql` endpoint, query/variables in body, often `nustack-cache-persist` storage |
|
|
688
688
|
| **gRPC-Web / Connect / tRPC** | `application/grpc-web*` content-type, or `/trpc/<procedure>` endpoints |
|
|
689
689
|
| **OData** | `$filter`, `$expand`, `$select` query params |
|
|
690
690
|
| **JSON-API** | `data.attributes` / `data.relationships` envelope |
|
|
@@ -716,7 +716,7 @@ A single product often uses **multiple** realtime channels with different roles
|
|
|
716
716
|
| **Pusher / Ably / Pubnub** | `wss://ws-*.pusher.com/...`, `realtime.ably.io/...` — WebSocket constructor wrap + library global tap; events namespaced per channel (`user-{uuid}`, `workspace-{id}`, `presence-doc-{id}`) |
|
|
717
717
|
| **Firestore WebChannel** | `firestore.googleapis.com/.../Listen/channel` GET-with-streamed-body, `text/plain` |
|
|
718
718
|
| **Firebase RTDB WebSocket** | `wss://*.firebaseio.com/.ws?ns=<project>` (often presence only) |
|
|
719
|
-
| **GraphQL Subscriptions** | `wss://...` with `connection_init` / `subscribe` / `next` / `complete` frames (
|
|
719
|
+
| **GraphQL Subscriptions** | `wss://...` with `connection_init` / `subscribe` / `next` / `complete` frames (NuStack/Hasura) |
|
|
720
720
|
| **gRPC-Web / Connect** | `application/grpc-web*` content-type — fetch response-body tap (binary) |
|
|
721
721
|
| **WS-OT/CRDT (Yjs, Automerge)** | Binary-framed WS, small + bursty frame sizes |
|
|
722
722
|
| **No streaming on dashboard** (per-call only) | Voice/per-session platforms: dashboard is REST-only; realtime lives only at the per-call layer (WebRTC + data channel), never visible from admin pages |
|
|
@@ -237,9 +237,9 @@ Apply the **§ Strategy memory** retirement procedure to `docs/memory/maintenanc
|
|
|
237
237
|
- **Enforce the memory cap on every run** (§ Strategy memory) — you are its only writer, so no one else can do it.
|
|
238
238
|
- **A clean report is valid** — do not invent issues.
|
|
239
239
|
- **Declarative, idempotent.** Re-running baseline mode on a current repo reshapes nothing — a second pass is just the audit plus the module proposal. Converge to the current shape; never replay a version ladder; never reconstruct a past shape.
|
|
240
|
-
- **Brownfield extraction (operator-ruled 2026-08-19).** When baseline mode meets a repo with a real source tree and few or no feature docs, the module *proposal* is not enough — run the extraction pass: produce feature docs for the recognized modules (docs/features/, full frontmatter: `owns:` · `module:` · `keywords:` · `status:` · `last-reviewed:` · `type: feature` · `derivation: "extracted (baseline YYYY-MM-DD,
|
|
240
|
+
- **Brownfield extraction (operator-ruled 2026-08-19).** When baseline mode meets a repo with a real source tree and few or no feature docs, the module *proposal* is not enough — run the extraction pass: produce feature docs for the recognized modules (docs/features/, full frontmatter: `owns:` · `module:` · `keywords:` · `status:` · `last-reviewed:` · `type: feature` · `derivation: "extracted (baseline YYYY-MM-DD, nustack graph + source read)"`), plus the architecture, tests, and operations layers. Use the `nustack-graph` skill where installed — localize through the index before reading; the docs record intent, the index answers facts. **Every extracted feature doc carries a `## History & Ownership` section** — top contributors with recency, churn posture, and bus-factor notes, dated "as of YYYY-MM-DD" (operator ruling: this section is mandatory in extraction output even though doc-lint flags dated narrative in truth docs; the findings are accepted for now, and a future harness version may retire the section in favor of live index pointers). `status:` is evidence-graded: `deprecated` only when history shows the area dead, never by guess. Scale by fan-out (one extraction lane per module, a completeness critic over the result) rather than one long cold read.
|
|
241
241
|
- **Diagrams ride the baseline (operator-ruled 2026-08-24).** Whenever baseline mode produces or converges the architecture layer, draw two ASCII diagrams from the real source, each in its own fenced code block: (1) an **architecture diagram** in the system-design/architecture doc — the actual processes, entry points, and wire surfaces as boxes, the calls/queues/files between them as labelled arrows; (2) a **DB ER diagram** wherever the repo declares a relational schema (DDL, migrations, an ORM schema) — one box per table listing its key columns, FK edges drawn to the table they reference, cardinality on the edge; it lands in the domain-model doc, or `docs/architecture/data-model.md` when none exists. Box-drawing characters (`┌─┐│└┘├┤`), width ≤ 100 columns. Derived only from what the code declares — a diagram never invents a component the source does not show, and a repo with no relational schema gets **no** ER diagram rather than an imagined one. Diagrams are truth-document sections: later baselines and audits converge them in place like any other section.
|
|
242
|
-
- **Tool tiers.** The SessionStart context names each manifest-declared tool with its tier; calibrate the run to it, never stall on it. **Present-fresh** (`Tool:
|
|
242
|
+
- **Tool tiers.** The SessionStart context names each manifest-declared tool with its tier; calibrate the run to it, never stall on it. **Present-fresh** (`Tool: nustack 0.9.0`): use it — localize every question through `nustack graph` before reading source, and never run an index step first: the family re-indexes a stale store before it answers, so the only freshness decision left is the deliberate `--no-refresh`. **Present-stale** (below the catalog minimum): still use it, but note the version gap and the upgrade command in the run report — a stale contract may lack answer shapes the skill assumes. **Absent** (`Tool: nustack MISSING`): fall back to the cold method (Glob/Grep/read), state in the run report that the tool was absent and give the install command — degraded output is acceptable, silent degradation is not.
|
|
243
243
|
- **One sentinel.** `docs/structure.md` `harness-version` is the only baseline/version marker.
|
|
244
244
|
- **Modules are labels, layout-blind.** Seed from the code's bounded contexts; never from `pnpm-workspace.yaml`/workspace walls. Propose, don't force; acting is a `module:` tag, never a folder.
|
|
245
245
|
- **You orchestrate docs; the `documentation` skill supplies templates.** It is a library — never expect it to baseline.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "etna",
|
|
3
3
|
"name": "etna",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.24",
|
|
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": "ee486a3733a1"
|
|
14
14
|
}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// harness's files land — a bundle says `skills/design/SKILL.md`, a project says
|
|
4
4
|
// `.claude/skills/design/SKILL.md`, and this module is the only place that mapping exists.
|
|
5
5
|
//
|
|
6
|
-
// TWO SHIPPERS, ONE MAPPING. `bin/etna.js` calls this against the user's cwd; the
|
|
6
|
+
// TWO SHIPPERS, ONE MAPPING. `bin/etna.js` calls this against the user's cwd; the NuStack push
|
|
7
7
|
// (`.github/scripts/push-harnesses.mjs`) calls it against a throwaway directory and ships the
|
|
8
|
-
// result, so an App's export from
|
|
8
|
+
// result, so an App's export from NuStack extracts as the same tree `npx @nurix/etna` writes.
|
|
9
9
|
// They diverged once — the push sent the BUNDLE layout, so a downloaded project had its
|
|
10
10
|
// instruction tree at the archive root, no AGENTS.md, and no wired hooks, while the manifest
|
|
11
11
|
// claimed otherwise. One function is what stops that recurring.
|
|
@@ -274,7 +274,7 @@ function mergeHooks(targetDir, bundledHooksJson) {
|
|
|
274
274
|
* Write one compiled harness into `targetDir` as an installed project tree.
|
|
275
275
|
*
|
|
276
276
|
* THE PLAN DECIDES WHAT AND WHERE; THIS DECIDES HOW IT MEETS AN EXISTING PROJECT. Placement lives
|
|
277
|
-
* in `plan.mjs` and is shared with the
|
|
277
|
+
* in `plan.mjs` and is shared with the NuStack push, so the two shippers cannot disagree about a
|
|
278
278
|
* path. Everything here is the part a payload has no use for: backing up what git cannot recover,
|
|
279
279
|
* rebuilding the Codex mirrors wholesale, pruning components a prior version owned, retiring hook
|
|
280
280
|
* entries pointing at scripts this bundle no longer carries, and folding the harness's hooks into
|
package/lib/install/plan.mjs
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
// This is the single authority on WHERE a harness's files land and WHAT is in them. Both shippers
|
|
6
6
|
// consume it and neither re-derives it:
|
|
7
7
|
//
|
|
8
|
-
// push-harnesses.mjs → publishes the array to
|
|
8
|
+
// push-harnesses.mjs → publishes the array to NuStack verbatim
|
|
9
9
|
// materializeHarness → writes the array to a project, applying each entry's policy
|
|
10
10
|
//
|
|
11
|
-
// They diverged once, and that is the whole reason this file exists. The
|
|
12
|
-
// the BUNDLE directory and send those paths, so a project downloaded from
|
|
11
|
+
// They diverged once, and that is the whole reason this file exists. The NuStack push used to walk
|
|
12
|
+
// the BUNDLE directory and send those paths, so a project downloaded from NuStack Studio extracted
|
|
13
13
|
// with its instruction tree at the archive root where no runtime looks for it, no AGENTS.md, and no
|
|
14
14
|
// hooks wired into settings.json — while the export manifest claimed the folder was ready to open.
|
|
15
15
|
// Nothing failed: every file was present, only its location was wrong.
|
|
@@ -49,7 +49,7 @@ export const PRUNABLE_SUBDIRS = ['skills', 'agents', 'rules', 'scripts'];
|
|
|
49
49
|
export const CODEX_REBUILT_DIRS = ['.agents/skills', '.codex/agents'];
|
|
50
50
|
|
|
51
51
|
// Every file under dir, as posix paths relative to it. Posix regardless of host, because these
|
|
52
|
-
// become ZIP entry names on
|
|
52
|
+
// become ZIP entry names on NuStack's side and a backslash is a legal filename character on the
|
|
53
53
|
// systems that extract them.
|
|
54
54
|
function filesUnder(dir, prefix = '') {
|
|
55
55
|
const out = [];
|
|
@@ -58,7 +58,7 @@ function filesUnder(dir, prefix = '') {
|
|
|
58
58
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
59
59
|
if (entry.isDirectory()) out.push(...filesUnder(join(dir, entry.name), rel));
|
|
60
60
|
else if (entry.isFile()) out.push(rel);
|
|
61
|
-
// A dirent that is neither is almost certainly a symlink. Refusing beats skipping:
|
|
61
|
+
// A dirent that is neither is almost certainly a symlink. Refusing beats skipping: NuStack
|
|
62
62
|
// stores content, so there is no link to follow on the other side, and a silently short
|
|
63
63
|
// payload is the exact failure mode this module exists to prevent.
|
|
64
64
|
else
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.20",
|
|
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": "02ce05f605be"
|
|
46
46
|
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: codegraph
|
|
3
|
-
requires-tool: codegraph
|
|
4
|
-
description: Query the codegraph index before reading source. An LLM-first codebase index (symbols, call references, git history, co-change coupling, ownership) answering whole questions in one call. Use it to localize, scope, and assess blast radius on this repository; use grep/read for string literals, mechanism, and data flow — the skill states which is which. Applies to any task that starts with "where is", "who calls", "what changed", "who owns", "what breaks", or "is this tested".
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# codegraph — the index-first method
|
|
8
|
-
|
|
9
|
-
**If the tool is absent** (`codegraph --version` fails): do not stall. Read `.claude/state/decisions.tsv` for `tool.codegraph`.
|
|
10
|
-
|
|
11
|
-
- **`declined`** — fall back to the cold method (grep, targeted reads, `git log`) **silently**. Do not mention the tool, do not re-offer. The user has answered.
|
|
12
|
-
- **undecided** (no file, no key, or anything unrecognised) — fall back to the cold method *and* make the offer **once**: this repository would answer "where is / who calls / what changed / who owns" from an index instead of a grep sweep; install with `npm install -g @nurix/codegraph`, then `codegraph index`. **Ask, never act** — a global npm install modifies the machine, and indexing writes to `~/.codegraph`. Record the answer with `recordDecision` either way, so the question is asked at most once per repo per machine.
|
|
13
|
-
- **`accepted` but the binary is gone** — treat as undecided; a failed or removed install is not a standing yes.
|
|
14
|
-
|
|
15
|
-
**A failed install is `absent`, not an error to retry.** Global installs fail on machines without permission; say so once, fall back, and move on.
|
|
16
|
-
|
|
17
|
-
**An explicit invocation overrides a stored decline.** If the user asks for this skill by name, honour it and re-offer — "stop asking" must never become "never usable".
|
|
18
|
-
|
|
19
|
-
**If the repository is not indexed** (a query answers `UNAVAILABLE: this repository has not been indexed yet`), that is the tool degrading correctly, not a failure: offer `codegraph index` under the same ask-first rule. The SessionStart probe deliberately does **not** claim index freshness — it can only see the binary — so this is where index state is actually established.
|
|
20
|
-
|
|
21
|
-
Everything below assumes the tool is present and the repository indexed.
|
|
22
|
-
|
|
23
|
-
The `codegraph` CLI is on PATH and defaults to the git root of the working directory — inside this repository, no flags are needed. The index is prebuilt; `codegraph projects` shows freshness (a `[STALE]` marker means HEAD moved since indexing — refresh with `codegraph index --commits 30000`, ~40s). Every answer states its own confidence per section (`CERTAIN` / `INFERRED` / `UNRESOLVED`), announces anything the token budget dropped, and says what an empty section means. Trust those labels.
|
|
24
|
-
|
|
25
|
-
## The method order
|
|
26
|
-
|
|
27
|
-
Localize through the index **before** opening any file, in this order; then read only the files it points at.
|
|
28
|
-
|
|
29
|
-
1. **`codegraph find <fragment>`** — symbols and file paths by name fragment. The entry point when you only have issue vocabulary. Symbol names only — it will never match string literals, dict keys, or SQL fragments (grep for those, without shame).
|
|
30
|
-
2. **`codegraph where-defined <symbol>`** / **`codegraph outline <file>`** — pin the definition; map a big file. `outline` turns a 100+ declaration file into a navigable, line-numbered list — use it instead of reading god files blind. On a large file add `--budget 0` (see traps).
|
|
31
|
-
3. **`codegraph who-calls <symbol>`** / **`codegraph impact <target>`** — callers, and blast radius (static reach + historical co-change reach, reported separately on purpose: they disagree, and the disagreement is information).
|
|
32
|
-
4. **`codegraph churn --days N --path <dir>`** · **`coupling --path <file>`** · **`owners <path>`** · **`tests-covering <file>`** — the history layer: what's hot, what changes together (including *hidden coupling* — co-change with no static reference, the one fact reading code cannot reveal), who knows this area and how recently, which test file historically guards this file.
|
|
33
|
-
5. **`codegraph god-objects --minSymbols 30`** / **`hotspots`** / **`overview`** — repo-scale orientation and risk.
|
|
34
|
-
|
|
35
|
-
Then read, with `sed -n` ranges at the lines the index gave you. The index does discovery; reading does diagnosis. Add `--json` when a wrapper needs structure.
|
|
36
|
-
|
|
37
|
-
There is also **`codegraph blame <file> [--line N]`** — the commits behind a file or one specific line, answered by git at query time; **`codegraph refs <name>`** — the data-reference layer: where a name is used as a dict/object key, attribute/field, import, or type annotation, with read/write direction, and a cross-language flag when the same name spans Python and TypeScript files (this answers "who reads `x_axis_sort`" / "who writes `chart.query_context`" in one call — the questions that used to need grep); and **`codegraph drift`** — near-duplicate function bodies crossed with co-change history: similar code whose files never change together is an unfinished refactor waiting to disagree. `who-calls` additionally tabulates which keyword arguments each call site passes, so "which call sites omit this option" is one query.
|
|
38
|
-
|
|
39
|
-
## The boundary — what the index cannot answer (fall back immediately, don't retry)
|
|
40
|
-
|
|
41
|
-
- **Free-text string literals** (SQL templates, prose, format strings) — grep. Identifier-shaped keys and fields ARE indexed (`refs <name>`); anything longer or with spaces is not.
|
|
42
|
-
- **Framework reflection** (Marshmallow `@post_load`, signal handlers, `appbuilder.add_api(Cls)` registration) — invisible to call edges. Bare decorator *application* (`@has_access_api`) IS indexed as a reference; what the decorator does and when it runs is not.
|
|
43
|
-
- **Data flow and mechanism** ("does this value reach that function", "what order do these run in") — always reading work.
|
|
44
|
-
|
|
45
|
-
Indexed symbol forms include statement declarations, `const X = () => {}`, named function expressions inside wrapper calls (`typedMemo(function DataTable() {})`), TS/JS class fields, and Python class-body fields (`is_dttm = Column(...)`); nested members render as `Parent › member` in `outline`.
|
|
46
|
-
|
|
47
|
-
## Traps (each cost a prior agent real time)
|
|
48
|
-
|
|
49
|
-
- **Bare-name collision.** Call references match by name, not resolved binding. When a name has several definitions, `who-calls` lists every candidate and says so in its subject line — read that section before trusting the call sites, and pass `--scope <path-fragment>` (also on `find`, `where-defined`, `impact`) to narrow to a package.
|
|
50
|
-
- **`codegraph functions` lists the CLI's own functions**, not the repository's. For repo functions use `find`. An explicit `--limit` lifts the default token budget, so a full `outline` needs no `--budget 0` incantation (an explicit `--budget` still wins).
|
|
51
|
-
- **Churn windows.** `churn --days 180` returning nothing does not mean quiet history — widen the window. Directory-scope churn ranks by raw commit count and buries small-but-critical files; for specific files use `owners`/`coupling` per file.
|
|
52
|
-
- **`tests-covering` is co-change evidence, not executed coverage** — it names the right test file to open; it cannot say the specific scenario is tested.
|
|
53
|
-
- **Coupling on a busy file has a noise tail** — partners at <15% shared commits are usually PR-cohabitation, not real coupling. Verify a partner with a read before adding it to a touch-list.
|
|
54
|
-
|
|
55
|
-
## Division of truth in this repository
|
|
56
|
-
|
|
57
|
-
`docs/` is the source of truth for **intent** — what a feature is for, its invariants, decisions, vocabulary. codegraph is the source of truth for **facts** — where things are, who calls what, what changed together, who owns what. Never copy live facts (caller counts, churn numbers) into docs as if permanent; never ask the index for intent.
|