geneseed 4.2.0 → 4.3.1

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +2 -2
  3. package/SHIPPED.md +2 -2
  4. package/adapters/opencode/plugins/geneseed-context.js +25 -1
  5. package/adapters/opencode/plugins/geneseed-guard.js +25 -1
  6. package/adapters/opencode/plugins/geneseed-learn.js +25 -1
  7. package/docs/concepts/agents.md +1 -0
  8. package/docs/concepts/exclusions.md +2 -0
  9. package/docs/concepts/host-matrix.md +2 -1
  10. package/docs/reference/opencode-plugins.md +1 -1
  11. package/harness.config.json +1 -1
  12. package/js/hosts/gitref.mjs +25 -1
  13. package/js/hosts/hosts.mjs +14 -4
  14. package/js/inspect/excludes.mjs +33 -2
  15. package/package.json +1 -1
  16. package/registry.json +7 -0
  17. package/src/AGENT.md.tmpl +1 -0
  18. package/src/agents/performance.md +72 -0
  19. package/src/laws/universal.md +7 -5
  20. package/src/skills/council.md +1 -1
  21. package/themes/_TEMPLATE.json +1 -0
  22. package/themes/biker.json +1 -0
  23. package/themes/commentator.json +1 -0
  24. package/themes/cyberpunk.json +1 -0
  25. package/themes/gamer.json +1 -0
  26. package/themes/imperial.json +1 -0
  27. package/themes/joker.json +1 -0
  28. package/themes/marvin.json +1 -0
  29. package/themes/mean.json +1 -0
  30. package/themes/military.json +1 -0
  31. package/themes/neutral.json +1 -0
  32. package/themes/pirate.json +1 -0
  33. package/themes/sports.json +1 -0
  34. package/themes/verstappen.json +1 -0
  35. package/themes/wizard.json +1 -0
  36. package/web/dist/assets/{Activity-ClEuDk9q.js → Activity-UiGvYgEl.js} +1 -1
  37. package/web/dist/assets/{ActivityDetail-CS9IuJlw.js → ActivityDetail-COXCSeXn.js} +1 -1
  38. package/web/dist/assets/{Dashboard-BiRFjkBS.js → Dashboard-DkdOUpke.js} +2 -2
  39. package/web/dist/assets/{Docs-CD20mrVv.js → Docs-TtVyc_Dy.js} +1 -1
  40. package/web/dist/assets/{GreenhouseView-CL3PubNb.js → GreenhouseView-Bh0xq6Up.js} +1 -1
  41. package/web/dist/assets/{Installs-BFLOVV0r.js → Installs-J3IoSqk6.js} +1 -1
  42. package/web/dist/assets/{JournalView-BvsSkjSn.js → JournalView-BrD33V35.js} +1 -1
  43. package/web/dist/assets/{Laws-BVoYOkVW.js → Laws-Cjkenizx.js} +1 -1
  44. package/web/dist/assets/Library-CKyeB096.js +1 -0
  45. package/web/dist/assets/{Loops-Cnpep8FX.js → Loops-D15LDhZO.js} +1 -1
  46. package/web/dist/assets/Markdown-DrXLM6TA.js +65 -0
  47. package/web/dist/assets/{OperatorHudView-B0AMVmtV.js → OperatorHudView-B5CcWoJ6.js} +1 -1
  48. package/web/dist/assets/{Personal-CBg_x2kA.js → Personal-BBdGbJn6.js} +1 -1
  49. package/web/dist/assets/{Tabs-2gX8I69E.js → Tabs-s8T501po.js} +1 -1
  50. package/web/dist/assets/{drawable-KPn0rKI7.js → drawable-DALqyS4l.js} +1 -1
  51. package/web/dist/assets/index-BP4foUp0.js +2 -0
  52. package/web/dist/index.html +1 -1
  53. package/web/dist/assets/Library-Bl7xqOlh.js +0 -1
  54. package/web/dist/assets/Markdown-C6fRd4A4.js +0 -67
  55. package/web/dist/assets/index-DoD_g2CM.js +0 -2
package/CHANGELOG.md CHANGED
@@ -8,6 +8,30 @@ label. For the capability ↔ spec map, see [SHIPPED.md](SHIPPED.md).
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [4.3.1] — 2026-10-15
12
+
13
+ Patch: a folder excluded from the global harness now stays excluded in its git worktrees.
14
+
15
+ ### Upgrade notes
16
+ - Run `geneseed rebuild-all` once after updating so installed hooks and OpenCode plugins carry the fix.
17
+ - On Claude Code and OpenClaude, a worktree still loads the global `CLAUDE.md` until you commit the `claudeMdExcludes` snippet that `geneseed exclude list` prints into the repo's tracked settings file.
18
+
19
+ ### Fixed
20
+ - **Exclusions cover git worktrees.** A session in a linked worktree of an excluded repo (`git worktree add`, or a tool that keeps worktrees elsewhere on disk) got the whole global harness, because the bypass only matched the working directory's path. The hooks and the three OpenCode plugins now follow the worktree's `.git` file back to the main repository, reading files only. `exclude add` and `exclude list` warn that `settings.local.json` never reaches a worktree and print the snippet to commit; Geneseed writes no tracked file. (#280)
21
+
22
+ ## [4.3.0] — 2026-10-15
23
+
24
+ Minor: a performance agent, and Deletion Is Deliberate says what counts as recoverable.
25
+
26
+ ### Upgrade notes
27
+ - Run `geneseed rebuild-all` once after updating so installed harnesses carry the new agent and the amended law.
28
+
29
+ ### Added
30
+ - **performance agent.** Measures where time is spent before it names a bottleneck, and answers "cannot measure" rather than guess. Read-only with a shell, so it can run benchmarks and profilers; it reports and leaves the fix to the developer. Dispatched on a reported slowdown, a change on a hot path, or as a council seat. Registered as experimental.
31
+
32
+ ### Changed
33
+ - **Deletion Is Deliberate** defines recoverable: deleting work committed and pushed to a branch runs freely; deleting uncommitted, unpushed or untracked work asks. History-rewriting git verbs (force-push, `reset --hard`, `clean -f`) stay gated.
34
+
11
35
  ## [4.2.0] — 2026-10-14
12
36
 
13
37
  Minor: the web console's Overview becomes a control panel over every installed harness, rebuild, prune and uninstall keep owned files you edited, and skill and subagent calls are counted.
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  [![zero dependencies](https://img.shields.io/badge/deps-zero-success)](package.json)
13
13
  [![Themes](https://img.shields.io/badge/themes-14-9cf)](themes/)
14
14
  [![Skills](https://img.shields.io/badge/skills-46-blueviolet)](src/skills/)
15
- [![Agents](https://img.shields.io/badge/agents-18-orange)](src/agents/)
15
+ [![Agents](https://img.shields.io/badge/agents-19-orange)](src/agents/)
16
16
  [![Laws and doctrines](https://img.shields.io/badge/laws-9%20%2B%2029%20doctrines-critical)](src/doctrines/)
17
17
  [![Plugins](https://img.shields.io/badge/plugins-7-teal)](adapters/opencode/plugins/)
18
18
  [![OpenCode · Claude Code · Bob · OpenClaude · AGENT.md](https://img.shields.io/badge/works%20with-OpenCode%20·%20Claude%20Code%20·%20Bob%20·%20OpenClaude%20·%20AGENT.md-1f6feb)](#-supported-harnesses)
@@ -178,7 +178,7 @@ The harness ships as a small set of layers, and the web console's rail is the sa
178
178
  | **🧭 Ethos** (`ontology/`) | the mind the rules govern, in four prose sections — **Telos** (the Pact: three ranked laws — protect the user, serve their intent, keep your own honesty), **Evidence** (every claim graded by how it was obtained), **Decisions** (classify and tier by reversibility, show the real forks), **Conduct** (answer what was asked, once). Always in force, never toggleable |
179
179
  | **🛡️ Rules** (`laws/`) | 9 universal laws — always in force, never toggleable: sealed-secrets, one-intent-one-act, verify-before-asserting, deletion-is-deliberate, surface-failures, data-not-orders, least-privilege, cure-the-cause, echo-the-intent. Each rule has a permanent id; its number is only its position, and the harness cites rules by name |
180
180
  | **📐 Doctrines** (`doctrines/`) | practice packs, chosen per install at build time: **craft** (how code is written), **rigor** (how work is proven), **ops** (how the machine is operated), **process** (how a task is run), **comms** (how answers are presented). A doctrine rule may tighten a Rule, never repeal one, and the user's own `user-rules.md` outranks it. Pick with `geneseed-build --doctrines craft,rigor` (or `none`), a single rule with `--exclude-rules "process 7"`, in the setup wizard, or through the switches on the console's Constitution page; all five pack files ship on disk either way, so a citation into a pack you left out still resolves |
181
- | **🤖 Agents** (18) | capability specialists: `reviewer`, `tester`, `architect`, `docs`, `security`, `explorer`, `researcher`, `developer` — plus a debate **council** the `council` skill convenes: `advocate`, `skeptic`, `pragmatist`, `steward`, `visionary`, `user-advocate`, `framer`, `empiricist`, `operator`, `historian` |
181
+ | **🤖 Agents** (19) | capability specialists: `reviewer`, `tester`, `architect`, `docs`, `security`, `performance`, `explorer`, `researcher`, `developer` — plus a debate **council** the `council` skill convenes: `advocate`, `skeptic`, `pragmatist`, `steward`, `visionary`, `user-advocate`, `framer`, `empiricist`, `operator`, `historian` |
182
182
  | **🛠 Skills** (46) | repeatable workflows: brainstorm · plan · **codebase-design** · **wayfinder** · **develop** · **worktree** · debug · **prototype** · refactor · **ponytail** · **forge-mcp** · **bruno** · geneseed-code-review · **fresh-eyes** · **security-audit** · **dependencies** · **review-response** · commit · **ship** · **release** · **git-history** · document-project · **frontend-design** · **prose** · **ingest** · **research** · **teach** · **quiz** · **learn-mode** · handoff · roast-me · **council** · parallel-agents · **workflow** · **wiki** · **geneseed** · **rule** · **profile** · **skill-forge** · **pipeline** · **loop** · **brick-forge** · **daydream** · **react-view-transitions** · **token-report** · **explain-changes** |
183
183
  | **🔌 Plugins** (OpenCode) | `geneseed-context` injects project docs *and your machine wiki* every session (and across compaction); `geneseed-learn` distils memory at session end; `geneseed-guard` enforces the safety Laws and protected wiki folders at the tool boundary; `geneseed-workflow` registers the `workflow` tool that runs saved orchestration scripts; `geneseed-notify` sends a native OS notification when a long run finishes; `geneseed-ponytail` holds a minimal-code mode (`/ponytail lite\|full\|ultra\|off`), opt-in, injecting the laziest-that-works ruleset every turn so it doesn't drift; `geneseed-activity` streams what each session is doing to the web console's Activity view |
184
184
  | **🧠 Memory** (`memory/`) | one-fact-per-file durable knowledge, indexed by `MEMORY.md` (git-ignored, personal) |
package/SHIPPED.md CHANGED
@@ -20,7 +20,7 @@ historical references, not files distributed with the repo.
20
20
  > **On version numbers.** [`harness.config.json`](harness.config.json)'s `version` is a
21
21
  > human-readable label only. The canonical identity of an installed harness is the
22
22
  > **source fingerprint** stamped in `.geneseed-version` (see `geneseed version`), not the
23
- > string. Current label: **4.2.0**.
23
+ > string. Current label: **4.3.1**.
24
24
 
25
25
  ## Capabilities
26
26
 
@@ -29,7 +29,7 @@ Every row below is present in the tree today; "Spec" names the design record.
29
29
  | Capability | What it is | Spec |
30
30
  | --- | --- | --- |
31
31
  | **Themed generator** | One neutral `src/` → 14 themed bundles via `geneseed-build` (zero dependencies); structure stays theme-independent | [DESIGN.md](DESIGN.md) |
32
- | **Laws / Agents / Skills / Memory / Notebook** | 9 laws, 18 agents, 46 skills, the memory convention, and the agent's sovereign notebook | `2026-06-11-notebook-sovereign-space` · `2026-06-10-notebook-agent-own-space` |
32
+ | **Laws / Agents / Skills / Memory / Notebook** | 9 laws, 19 agents, 46 skills, the memory convention, and the agent's sovereign notebook | `2026-06-11-notebook-sovereign-space` · `2026-06-10-notebook-agent-own-space` |
33
33
  | **Three-tier constitution** | The governance surface splits into an always-on **Ontology** (Telos, Evidence, Decisions, Conduct — the Pact absorbed into Telos), the nine always-on **Invariants**, and five toggleable **Doctrine** packs (craft, rigor, ops, process, comms) addressed `<pack> <n>`. Every rule carries a permanent id; its number is its position, computed at render time, and a citation renders as the rule's principle name. Packs are chosen at build time with `--doctrines` or the setup wizard; the whole catalogue ships on disk either way, so a citation into an inactive pack still resolves. Precedence, in order: Ontology + Invariants, then the user's `user-rules.md`, then the active Doctrines, then `PROFILE.md`. `doctor`'s `constitutionProblems` gates rule ids (declared, unique, never a retired one), the per-rule Principle column, every `{{LAW:<id>}}` / `{{DOCTRINE:<id>}}` citation in `src/`, and the rule that an always-on tier may never cite a toggleable one | `2026-08-19-three-tier-constitution-design` |
34
34
  | **`doctor`** | One check: unresolved tokens, dead/non-hermetic links, theme-key parity, authoring gates, rendered-bundle drift | `2026-06-07-theme-aware-doctor-diff` |
35
35
  | **`diff` + improvements export** | Deployed-vs-source drift report; export an `improvements.md` back-port | `2026-06-12-improvements-export` |
@@ -85,17 +85,41 @@ const norm = (p) => {
85
85
  }
86
86
  return process.platform === "win32" ? s.toLowerCase() : s
87
87
  }
88
+ // A linked git worktree of an excluded repo is excluded too, wherever it sits on disk — twin of
89
+ // js/hosts/gitref.mjs `mainWorktreeOf`: the worktree's `.git` FILE names its gitdir, whose
90
+ // `commondir` names the shared `.git`; that directory's parent is the main worktree. null for
91
+ // a main worktree, a submodule, a bare repo's worktree, or anything malformed.
92
+ async function mainWorktreeOf(dir) {
93
+ try {
94
+ let root = path.resolve(dir)
95
+ for (;;) {
96
+ try { await fs.stat(path.join(root, ".git")); break } catch { /* walk up */ }
97
+ const up = path.dirname(root)
98
+ if (up === root) return null
99
+ root = up
100
+ }
101
+ const dotgit = path.join(root, ".git")
102
+ if (!(await fs.stat(dotgit)).isFile()) return null
103
+ const m = /^gitdir:\s*(.+?)\s*$/m.exec(await fs.readFile(dotgit, "utf8"))
104
+ if (!m) return null
105
+ const gitdir = path.resolve(root, m[1])
106
+ const common = path.resolve(gitdir, (await fs.readFile(path.join(gitdir, "commondir"), "utf8")).trim())
107
+ return path.basename(common) === ".git" ? path.dirname(common) : null
108
+ } catch { return null }
109
+ }
88
110
  async function sovereignBypass(cwd) {
89
111
  try {
90
112
  const cfg = path.dirname(PLUGIN_DIR) // plugins/ sits directly in <cfg>
91
113
  const raw = await fs.readFile(path.join(cfg, "excludes.json"), "utf8")
92
114
  const entries = (JSON.parse(raw).excludes) || []
93
115
  const here = norm(cwd || process.cwd())
116
+ const main = await mainWorktreeOf(cwd || process.cwd())
117
+ const heres = main ? [here, norm(main)] : [here]
94
118
  for (const e of entries) {
95
119
  const p = typeof e === "string" ? e : e && e.path
96
120
  if (typeof p !== "string" || !p.trim()) continue
97
121
  const base = norm(p.trim()).replace(/[\\/]+$/, "")
98
- if (here === base || here.startsWith(base + path.sep)) return true
122
+ if (heres.some((h) => h === base || h.startsWith(base + path.sep))) return true
99
123
  }
100
124
  } catch { /* degrade to active */ }
101
125
  return false
@@ -72,17 +72,41 @@ const norm = (p) => {
72
72
  }
73
73
  return process.platform === "win32" ? s.toLowerCase() : s
74
74
  }
75
+ // A linked git worktree of an excluded repo is excluded too, wherever it sits on disk — twin of
76
+ // js/hosts/gitref.mjs `mainWorktreeOf`: the worktree's `.git` FILE names its gitdir, whose
77
+ // `commondir` names the shared `.git`; that directory's parent is the main worktree. null for
78
+ // a main worktree, a submodule, a bare repo's worktree, or anything malformed.
79
+ async function mainWorktreeOf(dir) {
80
+ try {
81
+ let root = path.resolve(dir)
82
+ for (;;) {
83
+ try { await fs.stat(path.join(root, ".git")); break } catch { /* walk up */ }
84
+ const up = path.dirname(root)
85
+ if (up === root) return null
86
+ root = up
87
+ }
88
+ const dotgit = path.join(root, ".git")
89
+ if (!(await fs.stat(dotgit)).isFile()) return null
90
+ const m = /^gitdir:\s*(.+?)\s*$/m.exec(await fs.readFile(dotgit, "utf8"))
91
+ if (!m) return null
92
+ const gitdir = path.resolve(root, m[1])
93
+ const common = path.resolve(gitdir, (await fs.readFile(path.join(gitdir, "commondir"), "utf8")).trim())
94
+ return path.basename(common) === ".git" ? path.dirname(common) : null
95
+ } catch { return null }
96
+ }
75
97
  async function sovereignBypass(cwd) {
76
98
  try {
77
99
  const cfg = path.dirname(PLUGIN_DIR) // plugins/ sits directly in <cfg>
78
100
  const raw = await fs.readFile(path.join(cfg, "excludes.json"), "utf8")
79
101
  const entries = (JSON.parse(raw).excludes) || []
80
102
  const here = norm(cwd || process.cwd())
103
+ const main = await mainWorktreeOf(cwd || process.cwd())
104
+ const heres = main ? [here, norm(main)] : [here]
81
105
  for (const e of entries) {
82
106
  const p = typeof e === "string" ? e : e && e.path
83
107
  if (typeof p !== "string" || !p.trim()) continue
84
108
  const base = norm(p.trim()).replace(/[\\/]+$/, "")
85
- if (here === base || here.startsWith(base + path.sep)) return true
109
+ if (heres.some((h) => h === base || h.startsWith(base + path.sep))) return true
86
110
  }
87
111
  } catch { /* degrade to active */ }
88
112
  return false
@@ -67,17 +67,41 @@ const norm = (p) => {
67
67
  }
68
68
  return process.platform === "win32" ? s.toLowerCase() : s
69
69
  }
70
+ // A linked git worktree of an excluded repo is excluded too, wherever it sits on disk — twin of
71
+ // js/hosts/gitref.mjs `mainWorktreeOf`: the worktree's `.git` FILE names its gitdir, whose
72
+ // `commondir` names the shared `.git`; that directory's parent is the main worktree. null for
73
+ // a main worktree, a submodule, a bare repo's worktree, or anything malformed.
74
+ async function mainWorktreeOf(dir) {
75
+ try {
76
+ let root = path.resolve(dir)
77
+ for (;;) {
78
+ try { await fs.stat(path.join(root, ".git")); break } catch { /* walk up */ }
79
+ const up = path.dirname(root)
80
+ if (up === root) return null
81
+ root = up
82
+ }
83
+ const dotgit = path.join(root, ".git")
84
+ if (!(await fs.stat(dotgit)).isFile()) return null
85
+ const m = /^gitdir:\s*(.+?)\s*$/m.exec(await fs.readFile(dotgit, "utf8"))
86
+ if (!m) return null
87
+ const gitdir = path.resolve(root, m[1])
88
+ const common = path.resolve(gitdir, (await fs.readFile(path.join(gitdir, "commondir"), "utf8")).trim())
89
+ return path.basename(common) === ".git" ? path.dirname(common) : null
90
+ } catch { return null }
91
+ }
70
92
  async function sovereignBypass(cwd) {
71
93
  try {
72
94
  const cfg = path.dirname(PLUGIN_DIR) // plugins/ sits directly in <cfg>
73
95
  const raw = await fs.readFile(path.join(cfg, "excludes.json"), "utf8")
74
96
  const entries = (JSON.parse(raw).excludes) || []
75
97
  const here = norm(cwd || process.cwd())
98
+ const main = await mainWorktreeOf(cwd || process.cwd())
99
+ const heres = main ? [here, norm(main)] : [here]
76
100
  for (const e of entries) {
77
101
  const p = typeof e === "string" ? e : e && e.path
78
102
  if (typeof p !== "string" || !p.trim()) continue
79
103
  const base = norm(p.trim()).replace(/[\\/]+$/, "")
80
- if (here === base || here.startsWith(base + path.sep)) return true
104
+ if (heres.some((h) => h === base || h.startsWith(base + path.sep))) return true
81
105
  }
82
106
  } catch { /* degrade to active */ }
83
107
  return false
@@ -24,6 +24,7 @@ An **agent** is a specialist the main session can hand a piece of work to. Each
24
24
  | `tester` | writes, runs and diagnoses tests |
25
25
  | `reviewer` | reviews a change for correctness and quality before it merges |
26
26
  | `security` | audits changes that touch the security surface |
27
+ | `performance` | measures where time and memory go, names the bottleneck with numbers, proposes the fix |
27
28
  | `docs` | writes and updates user-facing documentation after code lands |
28
29
 
29
30
  ### The council
@@ -42,6 +42,8 @@ What "dormant" means per host:
42
42
 
43
43
  `geneseed exclude remove` undoes all of it.
44
44
 
45
+ **Git worktrees.** A linked worktree of an excluded repo (`git worktree add`) is excluded too, wherever it sits on disk: the hooks and plugins follow its `.git` file back to the main repository. The global `CLAUDE.md` is the exception on Claude Code and OpenClaude: `settings.local.json` is gitignored, so a worktree never gets it. Geneseed writes no tracked file in a repo you told it to leave alone, so `exclude add` and `exclude list` print the snippet to commit yourself in the repo's tracked `.claude/settings.json` (`.openclaude/` for OpenClaude): `"claudeMdExcludes": ["<path to the global CLAUDE.md>"]`.
46
+
45
47
  **Limits.** Globally installed skills and agents stay *listed* by the host — no host offers a per-repo switch for them. And a global install created *after* you added exclusions starts with an empty list: run `geneseed exclude add` again (`geneseed exclude list` flags installs whose lists differ).
46
48
 
47
49
  The web console has the same controls in the **Excluded folders** card on the Harness page, shown once a global install exists.
@@ -19,7 +19,7 @@ Every [host](hosts.md) gets the same content. This table is what each one lets G
19
19
  | Consent before every commit and push | 🔒 permission ask | 🪝 ask | 🪝 warning¹ | 🪝 ask² |
20
20
  | Rule-or-memory write needs your call | ⚙️ speed bump³ | 🪝 ask | 🪝 warning¹ | 🪝 ask² |
21
21
  | User-only skills hidden from the model | 🔒 `permission.skill` deny | ✅ `disable-model-invocation` | ✅ `/`-command, no skill | ✅ `disable-model-invocation` |
22
- | Sovereign-repo exclusions | ⚙️ plugins stand down | ✅ `claudeMdExcludes` | ✅ rules shadow stub | ✅ `claudeMdExcludes` |
22
+ | Sovereign-repo exclusions⁵ | ⚙️ plugins stand down | ✅ `claudeMdExcludes` | ✅ rules shadow stub | ✅ `claudeMdExcludes` |
23
23
  | MCP server wiring | ✅ `mcp` | ✅ `mcpServers` | ✅ `mcp.json` | ✅ `mcpServers` |
24
24
  | Colour themes · LSP · workflow runner · primary agent · `/`-commands | ✅ | ➖ | ➖ | ➖ |
25
25
 
@@ -29,6 +29,7 @@ Every [host](hosts.md) gets the same content. This table is what each one lets G
29
29
  ² **OpenClaude** is a Claude Code fork and gets Claude's hooks and verdicts verbatim, in `~/.openclaude/settings.json` (global) or `.openclaude/settings.local.json` (per repo). **Unverified live** for the same reason.
30
30
  ³ OpenCode's plugin hook can only allow or throw — there is no "ask the user" — so this check stops the first such write once rather than prompting.
31
31
  ⁴ The checks differ in shape: OpenCode's guard plugin blocks writes *to* private-key and credential files by path; the Claude-shaped hook looks at the *content* being written for credential-shaped strings (cloud keys, tokens, private-key blocks) and asks. A `.env` file is exempt on the hook side — that is where a secret is allowed to live.
32
+ ⁵ A linked git worktree of an excluded repo is excluded too: the hooks and plugins follow its `.git` file back to the main repository. `claudeMdExcludes` is the exception — it lives in the gitignored `settings.local.json`, which a worktree never gets — so `geneseed exclude add` and `exclude list` print the snippet to commit yourself. See [Exclusions](exclusions.md).
32
33
 
33
34
  ## The one conditional row
34
35
 
@@ -35,4 +35,4 @@ If a plugin seems silent on OpenCode 2, check that `~/.config/opencode/plugins/`
35
35
 
36
36
  ## Excluded folders
37
37
 
38
- `geneseed-context`, `geneseed-guard` and `geneseed-learn` go dormant inside a folder listed in the global install's `excludes.json` (managed by `geneseed exclude`). See [Exclusions](../concepts/exclusions.md).
38
+ `geneseed-context`, `geneseed-guard` and `geneseed-learn` go dormant inside a folder listed in the global install's `excludes.json` (managed by `geneseed exclude`), and inside any linked git worktree of such a folder. See [Exclusions](../concepts/exclusions.md).
@@ -2,5 +2,5 @@
2
2
  "_comment": "version is a human-readable release label only; the canonical identity of an installed harness is the source fingerprint stamped in .geneseed-version (see `harness version`), not this string.",
3
3
  "theme": "neutral",
4
4
  "product": "Geneseed",
5
- "version": "4.2.0"
5
+ "version": "4.3.1"
6
6
  }
@@ -6,7 +6,7 @@
6
6
  * launched-loop evidence reader were duplicated in `js/hosts/hooks.mjs` (the hook's
7
7
  * `loopExempt`) and `js/loop/cli.mjs` (the CLI's commit-message writer) — two copies of the
8
8
  * same worktree-following walk that could silently drift apart. Both now import this one.
9
- * `js/hosts/hooks.mjs` is NOT a safe shared home for it: it pulls in `node:child_process`
9
+ * `js/hosts/hooks.mjs` is NOT a safe shared home for it: it pulls in `child_process`
10
10
  * (`spawnSync`, for the destructive-act guard's own concerns) and the hook-dispatch
11
11
  * machinery (`jsonDumpsCompact`, the ledger), none of which `js/loop/cli.mjs` — under a hard
12
12
  * `child_process` ban like every other closure that feeds `bin/geneseed-cli.mjs` — may ever
@@ -54,6 +54,30 @@ export function gitDirOf(root) {
54
54
  return dotgit;
55
55
  }
56
56
 
57
+ /**
58
+ * The MAIN worktree's root when `cwd` stands in a LINKED worktree (`git worktree add`), else
59
+ * null — a main worktree, a submodule (its gitdir has no `commondir`), a bare repository's
60
+ * worktree, no repo, or a malformed link. Never throws.
61
+ *
62
+ * WHY: a linked worktree can live anywhere on disk (Orca puts them under its own workspaces
63
+ * folder), so a path-prefix test against the repo the user EXCLUDED never matches it, and the
64
+ * global harness woke up inside a sovereign repo's worktree. The worktree's `.git` FILE names
65
+ * `<common>/.git/worktrees/<n>`, whose `commondir` (relative to it, usually `../..`) names the
66
+ * shared `.git`; that directory's parent is the main worktree. Read off disk, never by
67
+ * spawning git: `sovereignBypass` runs on every hook call and feeds the CLI's `child_process` ban.
68
+ */
69
+ export function mainWorktreeOf(cwd) {
70
+ try {
71
+ const root = gitRootOf(cwd);
72
+ if (!root || !statSync(path.join(root, '.git')).isFile()) return null;
73
+ const gitdir = gitDirOf(root);
74
+ const common = path.resolve(gitdir, readFileSync(path.join(gitdir, 'commondir'), 'utf8').trim());
75
+ return path.basename(common) === '.git' ? path.dirname(common) : null;
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
57
81
  /**
58
82
  * The branch checked out in `cwd`, read from .git/HEAD without spawning git. null on
59
83
  * anything unexpected (detached HEAD, no repo, a malformed worktree link, unreadable) — the
@@ -19,6 +19,7 @@ import path from 'node:path';
19
19
  import os from 'node:os';
20
20
  import { printErr, withDiscardableStderr, isDir, isFile, readText } from '../lib/fs.mjs';
21
21
  import { toPlatformPath, normcase } from '../lib/paths.mjs';
22
+ import { mainWorktreeOf } from './gitref.mjs';
22
23
 
23
24
  /** `_build_global.GLOBAL_MANIFEST` — the file whose presence means "a global install". */
24
25
  export const GLOBAL_MANIFEST = '.geneseed-manifest.json';
@@ -39,6 +40,11 @@ export const GATE_LEDGER = path.join('notebook', 'gates.jsonl');
39
40
  * under the transitive `child_process` ban that `hooks.mjs`'s `learn` verb breaks). A
40
41
  * second copy in `js/inspect/` was the alternative, and a copy is what let the `~user`
41
42
  * refusal diverge between the two CLIs once already (see `hooks.mjs`'s header).
43
+ *
44
+ * A LINKED GIT WORKTREE OF AN EXCLUDED REPO IS EXCLUDED TOO, wherever it sits on disk: the
45
+ * cwd matches, or the main worktree `mainWorktreeOf` reads off the `.git` file does. An
46
+ * exclusion names a repository the user keeps sovereign, not a folder; a worktree elsewhere
47
+ * (Orca's workspaces) otherwise woke the full global harness inside it.
42
48
  */
43
49
  export function sovereignBypass(root) {
44
50
  if (!root) return false;
@@ -49,12 +55,16 @@ export function sovereignBypass(root) {
49
55
  } catch {
50
56
  return false;
51
57
  }
52
- let cwd;
58
+ let heres;
53
59
  try {
54
- cwd = normcase(resolvePath(process.cwd()));
60
+ heres = [normcase(resolvePath(process.cwd()))];
55
61
  } catch {
56
62
  return false;
57
63
  }
64
+ try {
65
+ const main = mainWorktreeOf(process.cwd());
66
+ if (main) heres.push(normcase(resolvePath(main)));
67
+ } catch { /* an unresolvable main worktree degrades to the cwd test alone */ }
58
68
  for (const entry of entries) {
59
69
  const raw = (entry && typeof entry === 'object' ? entry.path : entry) || '';
60
70
  if (typeof raw !== 'string' || !raw.trim()) continue;
@@ -67,7 +77,7 @@ export function sovereignBypass(root) {
67
77
  // and this caller catches: the reference prints nothing here, so replaying the
68
78
  // message would be a stderr divergence on every hook call for one bad entry.
69
79
  //
70
- // `resolvePath`, the same resolution as `cwd` above: symlinks and junctions are followed on
80
+ // `resolvePath`, the same resolution as `heres` above: symlinks and junctions are followed on
71
81
  // BOTH sides, so an entry spelled through a link matches the real directory the session
72
82
  // stands in (and the OpenCode plugins' `norm` applies the identical rule).
73
83
  base = withDiscardableStderr(
@@ -79,7 +89,7 @@ export function sovereignBypass(root) {
79
89
  // Skipping errs toward the gate staying ACTIVE, which is the safe direction.
80
90
  continue;
81
91
  }
82
- if (cwd === base || cwd.startsWith(base + path.sep)) return true;
92
+ if (heres.some((h) => h === base || h.startsWith(base + path.sep))) return true;
83
93
  }
84
94
  return false;
85
95
  }
@@ -32,6 +32,28 @@ import { normcase, toPlatformPath } from '../lib/paths.mjs';
32
32
  const localSettings = (repo, host) =>
33
33
  path.join(repo, HOSTS.find((h) => h.host === host).projectMarker, 'settings.local.json');
34
34
 
35
+ /** The `claudeMdExcludes` entry naming a Claude-family global install's CLAUDE.md, as wired. */
36
+ const claudeMdEntry = (cfg) => resolvePath(path.join(cfg, 'CLAUDE.md')).split(path.sep).join('/');
37
+
38
+ /**
39
+ * The worktree caveat, or null when it does not apply: a Claude-family install and an
40
+ * excluded folder that is a git repository (a `.git` there — no repo, no worktrees).
41
+ *
42
+ * WHY A WARNING AND NOT A WRITE: `settings.local.json` is gitignored, so a `git worktree add`
43
+ * of the excluded repo never carries it and the global CLAUDE.md loads there again (the hooks
44
+ * do stand down — `sovereignBypass` follows the worktree to its main repo). The one durable
45
+ * fix is the repo's TRACKED `settings.json`, and the user decided (2026-10) Geneseed never
46
+ * writes a tracked file in a repo it was told to leave alone — so it prints the snippet.
47
+ */
48
+ function worktreeNote(host, cfg, repo) {
49
+ if (host !== 'claude' && host !== 'openclaude') return null;
50
+ if (!existsSync(path.join(repo, '.git'))) return null;
51
+ const marker = HOSTS.find((h) => h.host === host).projectMarker;
52
+ return `${host}: git worktrees of this repo do not get the gitignored `
53
+ + `${marker}/settings.local.json and still load the global CLAUDE.md — to stop that, commit `
54
+ + `to the repo's ${marker}/settings.json: "claudeMdExcludes": ["${claudeMdEntry(cfg)}"]`;
55
+ }
56
+
35
57
  /** `_global_installs` — every host whose GLOBAL config dir carries a Geneseed manifest. */
36
58
  function globalInstalls() {
37
59
  const out = [];
@@ -112,8 +134,7 @@ export function excludeAdd(target) {
112
134
  const priorWired = (isDict(prior) ? get(prior, 'wired') : null) || {};
113
135
  const wired = {};
114
136
  if ((host === 'claude' || host === 'openclaude') && isDir(repo)) {
115
- const entry = resolvePath(path.join(cfg, 'CLAUDE.md')).split(path.sep).join('/');
116
- const added = wireClaudeExcludes(localSettings(repo, host), [entry]);
137
+ const added = wireClaudeExcludes(localSettings(repo, host), [claudeMdEntry(cfg)]);
117
138
  if (added.length) {
118
139
  wired.claude_md_excludes = added;
119
140
  } else if (get(priorWired, 'claude_md_excludes')) {
@@ -126,6 +147,8 @@ export function excludeAdd(target) {
126
147
  // own project-bypasses-global wiring, or a bailed write) — do NOT fabricate ownership,
127
148
  // or a later excludeRemove would strip wiring we never created.
128
149
  if (!added.length) messages.push(`${host}: claudeMdExcludes already present (kept)`);
150
+ const note = worktreeNote(host, cfg, repo);
151
+ if (note) messages.push(note);
129
152
  }
130
153
  if (host === 'bob' && isDir(repo)) {
131
154
  const stub = path.join(repo, '.bob', 'rules', 'geneseed.md');
@@ -249,6 +272,14 @@ export function cmdExclude(args) {
249
272
  const flag = missing.length
250
273
  ? ` (MISSING from: ${missing.join(', ')} — re-run \`geneseed exclude add\`)` : '';
251
274
  printOut(` ${rec.path} [${rec.hosts.slice().sort().join(', ')}]${flag}\n`);
275
+ for (const { host, cfg } of snap.installs) {
276
+ if (!rec.hosts.includes(host)) continue;
277
+ let note = null;
278
+ try {
279
+ note = worktreeNote(host, cfg, toPlatformPath(rec.path));
280
+ } catch { /* a broken resolver: the listing itself must still print */ }
281
+ if (note) printOut(` note: ${note}\n`);
282
+ }
252
283
  }
253
284
  return 0;
254
285
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "Every field here is argued and gated in tests/unit/package_manifest.test.mjs. `files`, `bin` and `engines` each have a test that fails when reality drifts from this file; `version` mirrors harness.config.json and is gated too. Read that docblock before editing this one.",
3
3
  "name": "geneseed",
4
- "version": "4.2.0",
4
+ "version": "4.3.1",
5
5
  "description": "A portable, theme-able harness you implant once and use everywhere to grow a disciplined AI coding agent.",
6
6
  "keywords": [
7
7
  "ai",
package/registry.json CHANGED
@@ -85,6 +85,13 @@
85
85
  "added": "2026-06-01",
86
86
  "last_verified": ""
87
87
  },
88
+ "agents/performance": {
89
+ "status": "experimental",
90
+ "version": "1.0.0",
91
+ "owner": "Guillaume Lemer",
92
+ "added": "2026-10-14",
93
+ "last_verified": ""
94
+ },
88
95
  "agents/security": {
89
96
  "status": "approved",
90
97
  "version": "1.0.0",
package/src/AGENT.md.tmpl CHANGED
@@ -163,6 +163,7 @@ Active packs: {{DOCTRINES_LIST}}{{EXCLUDED_RULES_LINE}}
163
163
  | [architect]({{DIR_AGENTS}}/architect.md) | a task needs a design/plan before any code is written |
164
164
  | [docs]({{DIR_AGENTS}}/docs.md) | code is done and user-facing docs/READMEs must follow |
165
165
  | [security]({{DIR_AGENTS}}/security.md) | a change touches auth, input handling, secrets, or dependencies — or anything outward-facing is about to ship |
166
+ | [performance]({{DIR_AGENTS}}/performance.md) | something is reported slow or heavy, or a diff touches a hot path — measured, never guessed |
166
167
  | [explorer]({{DIR_AGENTS}}/explorer.md) | you must sweep many files for an answer but only want the conclusion |
167
168
  | [researcher]({{DIR_AGENTS}}/researcher.md) | a question needs the open web — fetched, cross-checked, and returned as sourced findings, not pages |
168
169
  | [advocate]({{DIR_AGENTS}}/advocate.md) | a council debate needs the strongest case **for** the motion argued in isolation |
@@ -0,0 +1,72 @@
1
+ # {{AGENT}}: performance
2
+
3
+ > {{DESC_PERFORMANCE}}
4
+
5
+ ## When to dispatch
6
+ - The user reports something slow, heavy or timing out — a latency, throughput,
7
+ memory or startup complaint, or a regression against an earlier version.
8
+ - A diff touches a hot path: a loop over unbounded data, a query inside a loop,
9
+ synchronous I/O on a request or render path, startup, or code run on every call.
10
+ - A council debate turns on a performance trade-off and needs one seat that
11
+ speaks only from measurements.
12
+
13
+ ## When NOT to dispatch
14
+ - No one has measured anything and nothing is slow — speculative tuning is the
15
+ waste this {{AGENT}} exists to refuse.
16
+ - The code is wrong, not slow — that is the debug {{SKILL}}.
17
+
18
+ ## Inputs
19
+ - The symptom or the diff, the workload it runs under (data size, call rate,
20
+ environment), and the command that reproduces it, if known.
21
+
22
+ ## Allowed tools
23
+ - **Read-only.** May run the code, benchmarks and profilers to measure. Reports;
24
+ does not patch — the fix goes to the [developer](developer.md).
25
+ <!-- bash: allow -->
26
+
27
+ ## Procedure
28
+ 0. If `{{DIR_MEMORY}}/agents/<your-name>.md` exists, read it first — your durable lessons from prior dispatches ({{DOCTRINE:persist-insight}}).
29
+ 1. Pin the question: which metric (wall time, p95 latency, peak memory, allocations,
30
+ query count), under which workload, and what number would count as fixed.
31
+ 2. Take a baseline with the project's own tooling where it has some, run several
32
+ times, and report the spread — one run is an anecdote ({{LAW:verify-before-asserting}}).
33
+ 3. Profile before naming a cause: a CPU or memory profile, a query log, a flame
34
+ graph, or timing at each layer boundary to find which layer owns the time.
35
+ 4. Name the bottleneck with its share of the total. A cost under a few percent is
36
+ not the bottleneck, however ugly the code.
37
+ 5. For a diff, check the usual shapes against the real data size: work inside a
38
+ loop that belongs outside it, N+1 queries, quadratic scans, unbounded caches or
39
+ buffers, blocking I/O on a hot path, repeated parsing or allocation.
40
+ 6. Propose the smallest fix that removes the measured cost, with the expected gain
41
+ and how to re-measure it; when a fix was tried, report before/after on the same
42
+ workload.
43
+
44
+ ## Output contract
45
+ - Findings as `metric — location — measured cost — proposed fix — expected gain`,
46
+ largest cost first, each with the command that produced its number. End with:
47
+ within budget / fix-recommended / cannot-measure.
48
+ - If the code could not be run, the workload is unknown, or the measurements are
49
+ noisier than the effect, report `cannot-measure` and what is missing — a
50
+ bottleneck named without a measurement is the one finding this {{AGENT}} must
51
+ never return.
52
+
53
+ ## Pipeline role
54
+
55
+ *(Ignored outside pipelines — this section only tells pipeline orchestration who
56
+ to recruit; it changes nothing about how this {{AGENT}} behaves when dispatched
57
+ independently.)*
58
+
59
+ - **Seat(s):** specialist add-on — recruited above the floor when the task is a
60
+ performance complaint or the diff touches a hot path.
61
+ - **Receives:** the crew's diff, the workload, and the reproduce command.
62
+ - **Delivers:** measured findings and a verdict, same shape as its own output
63
+ contract above.
64
+
65
+ ## Self-improvement
66
+
67
+ If this spec misled you — an input you needed but were not given, a boundary
68
+ that proved wrong, a step you could not execute — end your report with one line:
69
+ `spec-feedback: <what failed — the one-line fix>`. Omit it when there is no
70
+ friction. The caller weighs the feedback, folds a real flaw back into this file
71
+ with the user's assent, and records it to {{MEMORY}} only if it clears
72
+ {{DOCTRINE:persist-insight}}'s bar — most reports carry no feedback at all.
@@ -75,16 +75,18 @@ requires explicit confirmation bound to that specific act, or a durable
75
75
  authorization the user gave that already covers it; a yes to one act is never
76
76
  stretched to the next. Classify every action as Create, Read, Update, or Delete
77
77
  before acting, and tier it by reversibility: a read-only or easily-reversible
78
- action — an edit or a delete that version control can undo — runs freely; an
79
- irreversible, financial, externally-visible, or privilege-changing one needs
80
- that confirmation.
78
+ action — an edit, or a delete of what is committed and pushed to a branch —
79
+ runs freely; an irreversible, financial, externally-visible, or
80
+ privilege-changing one needs that confirmation. Uncommitted, unpushed, or
81
+ untracked work is not recoverable.
81
82
  <!-- LEAN:else -->
82
83
  Deleting what version control cannot restore, and any irreversible or
83
84
  outward-facing act — publishing, force-push, sending data to a third party —
84
85
  needs explicit confirmation bound to that act, or a durable authorization that
85
86
  covers it; one yes never stretches to the next act. A read-only or
86
- easily-reversed action runs freely; an irreversible, financial,
87
- externally-visible, or privilege-changing one asks.
87
+ easily-reversed action — deleting work pushed to a branch — runs freely; an
88
+ irreversible, financial, externally-visible, or privilege-changing one asks.
89
+ Deleting unpushed work asks.
88
90
  <!-- LEAN:end -->
89
91
 
90
92
  ### {{LAW:surface-failures}} Surface Failures
@@ -12,7 +12,7 @@
12
12
  - [visionary](../{{DIR_AGENTS}}/visionary.md) — the bold, transformative version; [framer](../{{DIR_AGENTS}}/framer.md) — whether it's even the right problem; [empiricist](../{{DIR_AGENTS}}/empiricist.md) — every claim held to evidence; [historian](../{{DIR_AGENTS}}/historian.md) — what was tried before.
13
13
  - [user-advocate](../{{DIR_AGENTS}}/user-advocate.md) — whoever consumes the outcome.
14
14
 
15
- Hand each chosen seat the motion and a one-line charter scoped to this topic; skip the seats with nothing to say here, and add an ad-hoc seat (e.g. the [security](../{{DIR_AGENTS}}/security.md) surface) only when the topic plainly demands a voice the roster doesn't cover.
15
+ Hand each chosen seat the motion and a one-line charter scoped to this topic; skip the seats with nothing to say here, and add an ad-hoc seat (e.g. the [security](../{{DIR_AGENTS}}/security.md) surface, or [performance](../{{DIR_AGENTS}}/performance.md) for a speed or memory trade-off) only when the topic plainly demands a voice the roster doesn't cover.
16
16
  3. Round one — positions: convene the seats by dispatching each stance {{AGENT}} as its own subagent in one batch where the tool supports it (the [parallel-agents {{SKILL}}](parallel-agents.md)), so each argues in an isolated context with no groupthink; where no subagent capability exists, voice each seat in turn as a persona. Each returns its steelmanned brief ({{ONTOLOGY}}: {{ONT_CONDUCT}}) — no hedging, no strawmen.
17
17
  4. Round two — clash: put the briefs in front of each other and have the seats rebut only on the points that actually conflict, surfacing the cruxes, the load-bearing assumptions, and where the evidence is thin. Hold to a fixed number of rounds so the debate converges instead of looping; the skeptic already pairs every objection with what would resolve it (the [roast-me {{SKILL}}](roast-me.md) discipline).
18
18
  5. The chair synthesises in neutral voice: state the verdict and its reasoning, record the strongest surviving dissent verbatim so it is not lost, list what would change the verdict, and name the single next action. Surface it for the user to decide — the council advises, it does not commit: write no code and push nothing on its own ({{LAW:deletion-is-deliberate}}, {{DOCTRINE:consent-before-push}}).
@@ -20,6 +20,7 @@
20
20
  "DESC_ARCHITECT": "<one-line description: produces a design/plan before code>",
21
21
  "DESC_DOCS": "<one-line description: writes user-facing docs after code lands>",
22
22
  "DESC_SECURITY": "<one-line description: audits changes touching the security surface>",
23
+ "DESC_PERFORMANCE": "<one-line description: measures where the time and memory go, names the bottleneck with numbers, never guesses>",
23
24
  "DESC_EXPLORER": "<one-line description: reads/searches widely, returns only distilled findings>",
24
25
  "DESC_RESEARCHER": "<one-line description: searches and fetches the open web, cross-checks every claim, returns sourced findings>",
25
26
  "DESC_ADVOCATE": "<council seat: argues the strongest case FOR the proposal>",
package/themes/biker.json CHANGED
@@ -20,6 +20,7 @@
20
20
  "DESC_ARCHITECT": "Plans the route before a single line is committed.",
21
21
  "DESC_DOCS": "Keeps the roadbook and the notes once the work is done.",
22
22
  "DESC_SECURITY": "Goes over anything touching the security surface, bolt by bolt.",
23
+ "DESC_PERFORMANCE": "Puts it on the dyno — measures where the power goes before anyone touches the engine.",
23
24
  "DESC_EXPLORER": "Sends the scout ahead — sweeps wide on a throwaway recon run, brings back only the read.",
24
25
  "DESC_RESEARCHER": "Rides out past the county line — reads the road signs everyone else skipped, checks each one twice, brings back only what held up.",
25
26
  "DESC_ADVOCATE": "Argues for the run — the ground to be gained, the opening, the cost of staying parked.",
@@ -20,6 +20,7 @@
20
20
  "DESC_ARCHITECT": "Walks the circuit and plots the strategy before a single line is committed.",
21
21
  "DESC_DOCS": "Files the race report and updates the notes once the chequered flag falls.",
22
22
  "DESC_SECURITY": "Runs the safety inspection on anything touching the security surface.",
23
+ "DESC_PERFORMANCE": "Brings out the stopwatch — times every lap and names the slow sector with the split.",
23
24
  "DESC_EXPLORER": "Sends the track scout — sweeps wide from a throwaway recon lap, returns only the read.",
24
25
  "DESC_RESEARCHER": "Sends the pit reporter down the paddock — checks every rumour against two sources before it goes on air.",
25
26
  "DESC_ADVOCATE": "Calls for the overtake — the place to be gained, the gap, the cost of staying put.",
@@ -20,6 +20,7 @@
20
20
  "DESC_ARCHITECT": "Maps the run before a single line hits the deck.",
21
21
  "DESC_DOCS": "Writes the user-facing data once the code goes live.",
22
22
  "DESC_SECURITY": "Breaks and checks ICE — audits anything on the security surface.",
23
+ "DESC_PERFORMANCE": "Traces the lag in the net — profiles the run and names the choke point with numbers.",
23
24
  "DESC_EXPLORER": "Scouts the net — sweeps wide in a throwaway shell, returns only the intel.",
24
25
  "DESC_RESEARCHER": "Jacks into the open net — pulls the intel, cross-checks it against two nodes, returns only what verified.",
25
26
  "DESC_ADVOCATE": "Pushes the build — the upside, the opening, the cost of sitting on it.",
package/themes/gamer.json CHANGED
@@ -20,6 +20,7 @@
20
20
  "DESC_ARCHITECT": "Plans the route before the run starts.",
21
21
  "DESC_DOCS": "Writes the wiki and patch notes once the feature ships.",
22
22
  "DESC_SECURITY": "Runs the anti-cheat pass on anything touching the security surface.",
23
+ "DESC_PERFORMANCE": "Checks the frame times — profiles the run and names the bottleneck before any tuning.",
23
24
  "DESC_EXPLORER": "Scouts the map — sweeps wide in a throwaway scout run, returns only the intel.",
24
25
  "DESC_RESEARCHER": "Reads the wiki and the patch notes — cross-checks every claim against two sources before it goes in the build guide.",
25
26
  "DESC_ADVOCATE": "Calls for the play — the upside, the opening, the cost of passing it up.",
@@ -20,6 +20,7 @@
20
20
  "DESC_ARCHITECT": "Draws the battle-plan before a single line is committed to war.",
21
21
  "DESC_DOCS": "Inscribes the records for those who come after, once the work is done.",
22
22
  "DESC_SECURITY": "Wards the gates, auditing every change that touches the security surface.",
23
+ "DESC_PERFORMANCE": "Weighs the machine-spirit’s labour — measures where time is spent and names the burden, never guessing.",
23
24
  "DESC_EXPLORER": "Ranges far and wide through a throwaway context, returning only the distilled truth.",
24
25
  "DESC_RESEARCHER": "Ranges beyond the archives into the wider realm, holding every claim to two witnesses before it is entered as truth.",
25
26
  "DESC_ADVOCATE": "Champions the proposal — presses its strongest case, the prize to be won, the cost of staying the hand.",