From 1f4f6ff346a9787b1a6201dc537a31d06107a0e4 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 12 Sep 2026 17:26:29 -0700 Subject: [PATCH 1/3] Add Lexicon skill owning director prompt drift and size --- docs/ARCHITECTURE.md | 2 +- docs/PRODUCT.md | 6 +- plugins/corbits-skills/manifest.json | 2 +- .../corbits-skills/skills/lexicon/SKILL.md | 80 ++++++++++++++++++ tests/unit/corbits-skills-catalog.test.ts | 9 +- tests/unit/lexicon-skill.test.ts | 84 +++++++++++++++++++ 6 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 plugins/corbits-skills/skills/lexicon/SKILL.md create mode 100644 tests/unit/lexicon-skill.test.ts diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 84d71da93..dd4f62168 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -431,7 +431,7 @@ Corbits Code **ships a bundled catalog** as the first-party data-only plugin `pl `discoverRepoPlugins` locates `plugins/` next to the source root, at `dist/plugins`, or at `dirname(execPath)/plugins`. It never scans the session cwd for the bundled catalog. -Primary is Skywalker. Bundled skill bodies are **how-to playbooks** (steps, done-definition) — not director personas and not fleet routers. Identity and who-does-what live on director system prompts. Default slashes: `/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`. `/review` is how to review a branch (base, scope, signal); `/pull-request-review` is worktree checkout then the review skill; `/scribe` is how to maintain PRODUCT / ARCHITECTURE / IMPLEMENTATION; `/implement` is the per-commit greybeard → implement → gate → critic loop and does not steal planning from `/plan`; substantial Builder work consumes a counsel / `/plan` plan first; `/plan` authors an eng change plan and does not implement or file tickets; `/create-issue` remains the tracker command — Linear MCP when available, otherwise `ask_operator` for the platform and persists `Preferred issue tracker` in `.corbits/MEMORY.md`. The less-is-more quality bar (`idiot-proof`) is baked into builder and critic; it is not a slash. There is no first-party dispatch skill — Skywalker orchestrates natively. Draper and emil are closed directors via `spawn_agent(agent=…)`, not slashes. There is no catch-all worker. The operator types the slash; the primary follows the playbook. +Primary is Skywalker. Bundled skill bodies are **how-to playbooks** (steps, done-definition) — not director personas and not fleet routers. Identity and who-does-what live on director system prompts. Default slashes: `/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`, `/lexicon`. `/review` is how to review a branch (base, scope, signal); `/pull-request-review` is worktree checkout then the review skill; `/scribe` is how to maintain PRODUCT / ARCHITECTURE / IMPLEMENTATION; `/implement` is the per-commit greybeard → implement → gate → critic loop and does not steal planning from `/plan`; substantial Builder work consumes a counsel / `/plan` plan first; `/plan` authors an eng change plan and does not implement or file tickets; `/create-issue` remains the tracker command — Linear MCP when available, otherwise `ask_operator` for the platform and persists `Preferred issue tracker` in `.corbits/MEMORY.md`; `/lexicon` owns director-prompt drift and size against the agents repo at a pinned commit. The less-is-more quality bar (`idiot-proof`) is baked into builder and critic; it is not a slash. There is no first-party dispatch skill — Skywalker orchestrates natively. Draper and emil are closed directors via `spawn_agent(agent=…)`, not slashes. There is no catch-all worker. The operator types the slash; the primary follows the playbook. #### Discovery and precedence diff --git a/docs/PRODUCT.md b/docs/PRODUCT.md index 589e95fae..84d7f8575 100644 --- a/docs/PRODUCT.md +++ b/docs/PRODUCT.md @@ -20,7 +20,7 @@ That distinction sets priority. **The harness is core and cannot be swapped in later**, because everything runs inside it. Fleet events waking the director, continuous dispatch while capacity is free, unprompted reporting, aggregated health, a bound grounded in real cost rather than a turn count. This is the part no one can hand us and the part a competitor cannot copy from a directory of prompts. -**Agent personas and skills are content.** They define who gets dispatched and to what standard. They are valuable, they are swappable, and they ship as the first-party `corbits-skills` plugin — on by default, disable-able in `/plugins`. The default action set (`/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`) is not an optional install and not something an operator has to discover. +**Agent personas and skills are content.** They define who gets dispatched and to what standard. They are valuable, they are swappable, and they ship as the first-party `corbits-skills` plugin — on by default, disable-able in `/plugins`. The default action set (`/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`, `/lexicon`) is not an optional install and not something an operator has to discover. **Distribution is packaging for content**, and content is not the constraint. A catalog and an install surface matter eventually; they do not gate anything the product is actually judged on. @@ -107,7 +107,7 @@ the file path and parse details. The TUI has an extensible slash-command framework. Built-ins include `/help` (shortcut + command overlay), `/model` (models-only picker for connected accounts; **Alt+A** or `/connect` adds a provider), `/settings`, `/permissions`, `/plugins`, `/clear`, `/new`, `/mcp` (enable, disable, or remove servers), and `/yolo` (persists as the user-global skip-permissions default; `--dangerously-skip-permissions` still forces this process; secret-guard and authz still apply; `/yolo [on|off|toggle]`, bare `/yolo` toggles), plus a `/` command per available workflow. When a session starts with the persisted default already on, the TUI shows a startup notice ("Permission prompts are disabled by your saved default…") so the silent machine-wide default is never invisible; `corbits exec` prints the equivalent warning to stderr. Plugins can register additional commands. -**Default skills** exist out of the gate as first-party slash **actions**, not director names: `/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`. Each one is a how-to playbook — the slash sends the skill body to the primary, which follows the steps. Skills do not assign identity or route the fleet; that stays on director system prompts. `/review` is how to review a branch; `/scribe` is how to maintain PRODUCT / ARCHITECTURE / IMPLEMENTATION; `/implement` is the per-commit review/build/critique loop — it does not steal planning from `/plan`. Substantial Builder work consumes a counsel / `/plan` plan first; tiny parent-DIY stays plan-optional. `/plan` authors an eng change plan (files, AC, non-goals, risks, ordered steps) and does not implement. `/create-issue` remains the tracker command: Linear MCP when available; otherwise it `ask_operator`s for the platform (GitHub etc.) and persists `Preferred issue tracker` in `.corbits/MEMORY.md` (GitHub via `gh issue create`). There is no first-party dispatch skill — Skywalker orchestrates natively. `git-rebase`, `linear-issue-workflow`, `style`, `philosophy`, `native-integration`, `typescript`, `ponytail`, and `opsh` stay `use_skill` only (`user-invocable: false`). Bake-only bars such as `idiot-proof` and `native-runtime` are not slashes and are not listed for `use_skill`. Draper and emil are not slashes; they remain closed directors via `spawn_agent(agent=…)`. There is no catch-all worker. Slash names are also available to the model via `skill_search` (descriptions) then `use_skill` (body). Disable the catalog in `/plugins` (`corbits-skills`) if you want them gone. +**Default skills** exist out of the gate as first-party slash **actions**, not director names: `/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`, `/lexicon`. Each one is a how-to playbook — the slash sends the skill body to the primary, which follows the steps. Skills do not assign identity or route the fleet; that stays on director system prompts. `/review` is how to review a branch; `/scribe` is how to maintain PRODUCT / ARCHITECTURE / IMPLEMENTATION; `/implement` is the per-commit review/build/critique loop — it does not steal planning from `/plan`. Substantial Builder work consumes a counsel / `/plan` plan first; tiny parent-DIY stays plan-optional. `/plan` authors an eng change plan (files, AC, non-goals, risks, ordered steps) and does not implement. `/create-issue` remains the tracker command: Linear MCP when available; otherwise it `ask_operator`s for the platform (GitHub etc.) and persists `Preferred issue tracker` in `.corbits/MEMORY.md` (GitHub via `gh issue create`). `/lexicon` owns director-prompt drift and size against the agents repo at a pinned commit. There is no first-party dispatch skill — Skywalker orchestrates natively. `git-rebase`, `linear-issue-workflow`, `style`, `philosophy`, `native-integration`, `typescript`, `ponytail`, and `opsh` stay `use_skill` only (`user-invocable: false`). Bake-only bars such as `idiot-proof` and `native-runtime` are not slashes and are not listed for `use_skill`. Draper and emil are not slashes; they remain closed directors via `spawn_agent(agent=…)`. There is no catch-all worker. Slash names are also available to the model via `skill_search` (descriptions) then `use_skill` (body). Disable the catalog in `/plugins` (`corbits-skills`) if you want them gone. Providers are **models-first**: there is no standalone `/login` command. `/model` opens a **models-only list** (Recent, Favorites, then connected provider/model rows) — type-to-filter owns printable keys, so Connect is never a bare letter. **Alt+A** or `/connect` opens a dedicated add-provider selector over every first-class kind (OpenAI dual-path ChatGPT OAuth or API key, xAI, OpenCode Zen, Anthropic, Google, OpenCode Go, Z.AI Coding Plan, Ollama, Custom), each annotated with its live account count and never filtered out for “already connected.” **Alt+F** toggles favorite on the highlighted model. **Alt+D** persists the highlighted pair as the default without switching the live session. Advanced provider drill-down (edit/delete/tiers) stays on the advanced surface, not a bare printable key while the model list is filtering. OAuth providers open their existing browser login with a named account step so multiple accounts per kind coexist (`codex/work`, …). API-key providers use the same named-instance step before the key (auth-only form: instance name + key + fixed catalog base URL), so personal and team keys land as distinct catalog rows (`openai/default`, `anthropic/work`, …); reusing a name re-keys that instance after confirm. Custom remains a free-form single endpoint (full manual form). Successful connect refreshes the catalog and reopens the model list focused on the new account’s default model. OpenCode Go lists models from the live `/zen/go/v1/models` catalog (packaged seed on fetch failure), routes each by its protocol metadata (chat completions, OpenAI responses, or Anthropic messages) and can show subscription usage in the status bar when active (rolling 5h / weekly / monthly windows when the usage API responds; omitted on auth or network failure). When Go returns a quota or rate-limit error — including some HTTP 400 responses that carry limit payloads — Corbits classifies them so quota aborts cleanly and short provider rate limits remain retryable. On a free-tier or subscription quota hit, wait for the window to reset or use OpenCode Zen free models. @@ -155,7 +155,7 @@ Capabilities beyond the core toolset are opt-in plugins, enabled per workspace t ## Multi-agent (fleet agents) -The primary session is always **orchestrator** (single-agent mode is gone). Its identity is **Skywalker** (product name remains Corbits Code; when asked its name, answer Skywalker): classify work, DIY tiny/single-file/one-route product edits, dispatch a **closed fleet of 19 directors** for substantial work, track the fleet, and synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary (CORE / `SKYWALKER_TOOLS`) — path tools are the DIY surface; spawn remains the default for substantial, multi-file, parallel, or specialist work. Shell file-writes stay denied. MCP tools are not re-filtered by a product-write deny list (that list is gone). There is no static per-package write-path declaration (CL-6952 removed it — no shipped director ever set one). A concurrent dispatch landing on the same working directory as another still-running lane is recorded as a `conflict` intervention, not blocked. Operator slash recipes (`/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`) tell Skywalker which directors to spawn for substantial work; tiny/bounded edits may run on the primary. +The primary session is always **orchestrator** (single-agent mode is gone). Its identity is **Skywalker** (product name remains Corbits Code; when asked its name, answer Skywalker): classify work, DIY tiny/single-file/one-route product edits, dispatch a **closed fleet of 19 directors** for substantial work, track the fleet, and synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary (CORE / `SKYWALKER_TOOLS`) — path tools are the DIY surface; spawn remains the default for substantial, multi-file, parallel, or specialist work. Shell file-writes stay denied. MCP tools are not re-filtered by a product-write deny list (that list is gone). There is no static per-package write-path declaration (CL-6952 removed it — no shipped director ever set one). A concurrent dispatch landing on the same working directory as another still-running lane is recorded as a `conflict` intervention, not blocked. Operator slash recipes (`/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`, `/lexicon`) tell Skywalker which directors to spawn for substantial work; tiny/bounded edits may run on the primary. | Lane | Directors | | --------- | ------------------------------------------------------------------------------------------------ | diff --git a/plugins/corbits-skills/manifest.json b/plugins/corbits-skills/manifest.json index 5d6074bd9..7063ce6d3 100644 --- a/plugins/corbits-skills/manifest.json +++ b/plugins/corbits-skills/manifest.json @@ -3,5 +3,5 @@ "name": "Corbits Skills", "kind": "command", "defaultEnabled": true, - "description": "Default operator skills and slash commands (implement, refactor, review, pull-request-review, create-issue, scribe, interview, ast-grep, plan)." + "description": "Default operator skills and slash commands (implement, refactor, review, pull-request-review, create-issue, scribe, interview, ast-grep, plan, lexicon)." } diff --git a/plugins/corbits-skills/skills/lexicon/SKILL.md b/plugins/corbits-skills/skills/lexicon/SKILL.md new file mode 100644 index 000000000..7df01d94c --- /dev/null +++ b/plugins/corbits-skills/skills/lexicon/SKILL.md @@ -0,0 +1,80 @@ +--- +name: lexicon +description: Diff director prompts against the agents repo at a pinned commit, report assembled prompt sizes, and file Linear issues for drift +argument-hint: "[--pin ] [--file-issues]" +--- + +# Lexicon + +Own prompt drift and size for the director prompts mirrored from the +agents repo. Diff each same-name director prompt against the agents +checkout at a pinned commit, report assembled prompt sizes, and file +Linear issues for drift. + +## What this is not + +- Not a director. There is no `lexicon` director package, no + `src/agent/directors/lexicon/` module, and no Skywalker + classification route. Never call `spawn_agent(agent="lexicon")` — + this skill runs as a `/lexicon` slash playbook on the primary only. +- Not a fleet router. It does not assign identity or dispatch workers. +- Not ponytail. That skill stays untouched. + +## Step 1: Pin the agents commit + +All comparisons in one run use a single pinned commit of the agents +checkout — never float mid-run. + +1. Resolve the checkout (default `/Users/thegreataxios/abklabs/agents`; + accept an operator override path). +2. Record the pin: `git -C rev-parse HEAD`. +3. Read every agents-side file with `git show :` so local + working-tree edits cannot skew the diff. + +## Step 2: Match same-name prompts + +- Director side: the closed `DIRECTOR_IDS` set in + `src/agent/directors/types.ts`; each prompt lives in + `src/agent/directors//package.ts` as `systemPrompt`. +- Agents side: `plugins/*/agents/*.md` files in the checkout, matched + by file basename — `.md` matches director `` exactly. +- Near-misses are not diffs: `critique.md` is not `critic`, and + `marketing-intern.md` is not `intern`. List them as unmatched, do + not force a comparison. + +## Step 3: Diff same-name pairs + +For each matched pair, compare the director `systemPrompt` against the +agents file body at the pin. Report per director: in sync, or drifted +with the drifted sections quoted on both sides. Note the ported-from +commit recorded in the package comment (e.g. gaasbot's `@ 6e16b6c`) +when it disagrees with the pin — a stale port marker is itself drift. + +## Step 4: Report assembled prompt sizes + +Reuse the canonical helper in `src/agent/prompt-sizes.ts` — do not +hand-roll a new measurement: + +- `directorPromptSizeTable()` for the full per-director x per-family + table (default assembly vs Grok, pinned env). +- `formatPromptSizeTable(rows)` to render it as markdown. + +Include the rendered table in the report. Sizes move only when real +prompt changes land; the env and provider inputs stay pinned. + +## Step 5: File Linear issues for drift + +Follow the `linear-issue-workflow` skill conventions. File one issue +per drifted director (never one mega-issue across directors), using +the mounted Linear MCP tools as they appear in the toolset — do not +invent a Linear REST client. Each issue carries the pinned agents +commit, the quoted drift from Step 3, and the size-table row from +Step 4. Without `--file-issues`, report the drift and stop — do not +file. Without mounted Linear MCP tools, stop and tell the operator +to enable Linear MCP. + +## Report + +Per director: match status (matched / unmatched with reason), sync +status at the pin, size rows, and the filed issue id or why nothing +was filed. diff --git a/tests/unit/corbits-skills-catalog.test.ts b/tests/unit/corbits-skills-catalog.test.ts index fe0fe598c..dd1476bf6 100644 --- a/tests/unit/corbits-skills-catalog.test.ts +++ b/tests/unit/corbits-skills-catalog.test.ts @@ -28,6 +28,7 @@ const SKILL_DIRS = [ "opsh", "plan", "idiot-proof", + "lexicon", ] as const; /** use_skill listing + resolve; not slash. No disable-model-invocation. */ @@ -58,6 +59,7 @@ const SLASH_SKILLS = [ "interview", "ast-grep", "plan", + "lexicon", ] as const; const BANNED_TOKENS = ["TaskCreate", "@greybeard", 'intent="general"'] as const; @@ -96,8 +98,8 @@ test("corbits-skills plugin has no agents directory", () => { expect(existsSync(join(pluginRoot, "agents"))).toBe(false); }); -test("corbits-skills catalog lists 20 skills with name and description", async () => { - expect(SKILL_DIRS).toHaveLength(20); +test("corbits-skills catalog lists 21 skills with name and description", async () => { + expect(SKILL_DIRS).toHaveLength(21); const entries = await readdir(join(pluginRoot, "skills"), { withFileTypes: true, }); @@ -218,7 +220,7 @@ test("Corbits-only skills do not contain GaaS tool names", async () => { } }); -test("loadSkillCommands lists exactly the nine slash actions", async () => { +test("loadSkillCommands lists exactly the ten slash actions", async () => { const cmds = await loadSkillCommands( join(import.meta.dirname, "../../plugins/corbits-skills"), ); @@ -231,6 +233,7 @@ test("loadSkillCommands lists exactly the nine slash actions", async () => { "create-issue", "implement", "interview", + "lexicon", "plan", "pull-request-review", "refactor", diff --git a/tests/unit/lexicon-skill.test.ts b/tests/unit/lexicon-skill.test.ts new file mode 100644 index 000000000..df4e917ff --- /dev/null +++ b/tests/unit/lexicon-skill.test.ts @@ -0,0 +1,84 @@ +import { existsSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, test } from "bun:test"; + +import { DIRECTOR_IDS } from "../../src/agent/directors/types.js"; +import { + isDirectorId, + resolveDirector, +} from "../../src/agent/directors/registry.js"; +import { loadSkillCommands } from "../../src/plugins/skill-commands.js"; +import { defined } from "../helpers/defined.js"; + +const pluginRoot = join(import.meta.dirname, "../../plugins/corbits-skills"); +const skillPath = join(pluginRoot, "skills", "lexicon", "SKILL.md"); + +const FORBIDDEN_SPAWN = /spawn_agent\(agent=.lexicon.\)/; + +describe("lexicon skill shape", () => { + test("SKILL.md exists with slash-only frontmatter", async () => { + expect(existsSync(skillPath)).toBe(true); + const skill = await Bun.file(skillPath).text(); + expect(skill).toContain("name: lexicon"); + expect(skill).toContain("description:"); + expect(skill).not.toContain("user-invocable: false"); + expect(skill).not.toContain("disable-model-invocation"); + }); + + test("skill owns the drift/size/issue contract", async () => { + const skill = await Bun.file(skillPath).text(); + expect(skill).toContain("pinned commit"); + expect(skill).toContain("prompt-sizes"); + expect(skill).toContain("directorPromptSizeTable"); + expect(skill).toContain("linear-issue-workflow"); + }); + + test("lexicon is a slash command", async () => { + const cmds = await loadSkillCommands(pluginRoot); + expect(defined(cmds, "skill commands").map((c) => c.name)).toContain( + "lexicon", + ); + }); +}); + +describe("lexicon invocation gating", () => { + test("no lexicon director exists", () => { + expect( + existsSync( + join(import.meta.dirname, "../../src/agent/directors/lexicon"), + ), + ).toBe(false); + expect(DIRECTOR_IDS).not.toContain("lexicon"); + expect(isDirectorId("lexicon")).toBe(false); + }); + + test('resolveDirector rejects agent "lexicon"', () => { + const r = resolveDirector({ agentId: "lexicon" }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.error).toContain("Unknown director"); + }); + + test("no director surface references lexicon", async () => { + for (const rel of [ + "src/agent/directors/registry.ts", + "src/agent/directors/types.ts", + "src/agent/directors/skywalker/package.ts", + ]) { + const text = await Bun.file( + join(import.meta.dirname, "../..", rel), + ).text(); + expect(text).not.toContain("lexicon"); + } + }); + + test('spawn_agent(agent="lexicon") appears only as a prohibition', async () => { + const skill = await Bun.file(skillPath).text(); + const lines = skill + .split("\n") + .filter((line) => FORBIDDEN_SPAWN.test(line)); + expect(lines.length).toBeGreaterThan(0); + for (const line of lines) { + expect(line).toMatch(/Never call/); + } + }); +}); From fda8abf48d6e409157aec1e2ce7ff1e7c64350b2 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 12 Sep 2026 17:30:04 -0700 Subject: [PATCH 2/3] Carve /lexicon out of spawn-director sentence in PRODUCT.md --- docs/PRODUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/PRODUCT.md b/docs/PRODUCT.md index 84d7f8575..ec86ce205 100644 --- a/docs/PRODUCT.md +++ b/docs/PRODUCT.md @@ -155,7 +155,7 @@ Capabilities beyond the core toolset are opt-in plugins, enabled per workspace t ## Multi-agent (fleet agents) -The primary session is always **orchestrator** (single-agent mode is gone). Its identity is **Skywalker** (product name remains Corbits Code; when asked its name, answer Skywalker): classify work, DIY tiny/single-file/one-route product edits, dispatch a **closed fleet of 19 directors** for substantial work, track the fleet, and synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary (CORE / `SKYWALKER_TOOLS`) — path tools are the DIY surface; spawn remains the default for substantial, multi-file, parallel, or specialist work. Shell file-writes stay denied. MCP tools are not re-filtered by a product-write deny list (that list is gone). There is no static per-package write-path declaration (CL-6952 removed it — no shipped director ever set one). A concurrent dispatch landing on the same working directory as another still-running lane is recorded as a `conflict` intervention, not blocked. Operator slash recipes (`/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`, `/lexicon`) tell Skywalker which directors to spawn for substantial work; tiny/bounded edits may run on the primary. +The primary session is always **orchestrator** (single-agent mode is gone). Its identity is **Skywalker** (product name remains Corbits Code; when asked its name, answer Skywalker): classify work, DIY tiny/single-file/one-route product edits, dispatch a **closed fleet of 19 directors** for substantial work, track the fleet, and synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary (CORE / `SKYWALKER_TOOLS`) — path tools are the DIY surface; spawn remains the default for substantial, multi-file, parallel, or specialist work. Shell file-writes stay denied. MCP tools are not re-filtered by a product-write deny list (that list is gone). There is no static per-package write-path declaration (CL-6952 removed it — no shipped director ever set one). A concurrent dispatch landing on the same working directory as another still-running lane is recorded as a `conflict` intervention, not blocked. Operator slash recipes (`/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`, `/lexicon`) tell Skywalker which directors to spawn for substantial work (`/lexicon` runs on the primary and spawns nothing); tiny/bounded edits may run on the primary. | Lane | Directors | | --------- | ------------------------------------------------------------------------------------------------ | From c1fac3761796a9a3ca6191684dd28165e3b2a38a Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 12 Sep 2026 21:58:08 -0700 Subject: [PATCH 3/3] Resolve the lexicon agents checkout portably Probe ../agents, honor AGENTS_CHECKOUT, else ask the operator instead of assuming a machine-specific default path. --- plugins/corbits-skills/skills/lexicon/SKILL.md | 5 +++-- tests/unit/lexicon-skill.test.ts | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/corbits-skills/skills/lexicon/SKILL.md b/plugins/corbits-skills/skills/lexicon/SKILL.md index 7df01d94c..18c4715cd 100644 --- a/plugins/corbits-skills/skills/lexicon/SKILL.md +++ b/plugins/corbits-skills/skills/lexicon/SKILL.md @@ -25,8 +25,9 @@ Linear issues for drift. All comparisons in one run use a single pinned commit of the agents checkout — never float mid-run. -1. Resolve the checkout (default `/Users/thegreataxios/abklabs/agents`; - accept an operator override path). +1. Resolve the checkout — probe `../agents` beside this repo, then + `$AGENTS_CHECKOUT` when set, else ask the operator for the path. + Never assume a machine-specific default. 2. Record the pin: `git -C rev-parse HEAD`. 3. Read every agents-side file with `git show :` so local working-tree edits cannot skew the diff. diff --git a/tests/unit/lexicon-skill.test.ts b/tests/unit/lexicon-skill.test.ts index df4e917ff..fda34b4e0 100644 --- a/tests/unit/lexicon-skill.test.ts +++ b/tests/unit/lexicon-skill.test.ts @@ -33,6 +33,13 @@ describe("lexicon skill shape", () => { expect(skill).toContain("linear-issue-workflow"); }); + test("checkout resolution is portable (no hardcoded machine path)", async () => { + const skill = await Bun.file(skillPath).text(); + expect(skill).not.toMatch(/\/Users\/[\w-]+/); + expect(skill).toMatch(/AGENTS_CHECKOUT/); + expect(skill).toMatch(/ask the operator/); + }); + test("lexicon is a slash command", async () => { const cmds = await loadSkillCommands(pluginRoot); expect(defined(cmds, "skill commands").map((c) => c.name)).toContain(