@warren-dean/code-kit 0.17.0 → 0.19.0

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.
@@ -9,7 +9,7 @@
9
9
  "name": "code-kit",
10
10
  "source": "./",
11
11
  "description": "Keeps AI coding agents inside your project's rules, from idea to merged code: spec design, lanes, layer rules, protected paths with an approval log, spec-check and design gates, proof-before-finish checks, dispatch, review, build status and CI verification, configured per project from its docs",
12
- "version": "0.17.0",
12
+ "version": "0.19.0",
13
13
  "category": "development",
14
14
  "tags": ["guardrails", "architecture", "lanes", "layers", "spec-driven", "ci", "multi-agent"],
15
15
  "license": "MIT"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-kit",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Keeps AI coding agents inside your project's rules, from idea to merged code: spec design, lanes, layer rules, protected paths with an approval log, spec-check and design gates, proof-before-finish checks, dispatch, review, build status and CI verification, configured per project from its docs",
5
5
  "author": {
6
6
  "name": "Warren Langeveldt"
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  What changed in each release of code-kit, newest first. Versions follow the plugin manifest and the npm package `@warren-dean/code-kit`. Each release is also on [GitHub Releases](https://github.com/warrendeanlangeveldt/code-kit/releases).
4
4
 
5
+ ## 0.19.0 (2026-10-19)
6
+
7
+ A build uses far fewer tokens. In two days of two builds, output was under 1% of the tokens: nearly all of it was agents rereading large contexts on every request.
8
+
9
+ - Each lane agent builds one story. A send-back, or a story carried on, goes to a fresh agent, never to the one that built it. Lane agents had reached 900k to 970k tokens of context over hundreds of requests.
10
+ - A lane agent past `agents.lanes.contextLimit` (250,000 tokens by default) is told to finish its step, commit, write its report and hand back. The loop then has the lead carry the story on with a fresh agent that reads that report first.
11
+ - `code-kit worktrees` lists the project's worktrees. `worktrees release <branch>` frees one for a fresh agent, keeping the branch's commits and moving its report to the main checkout, and `worktrees prune` removes every one that's merged and clean. One project had 11 such leftovers, about 4.5 GB; `code-kit next` now points them out.
12
+ - The lead's context is compacted between turns once it's past `compact.at` (40% of its window by default), without asking. Waiting for a milestone wasn't enough: one lead reached 968k tokens first.
13
+ - The loop reads the lead's replies with Sonnet, from the reply itself. Before, it forked the lead's whole conversation on the lead's own model at the end of every turn.
14
+ - Lane agents read every file they change in full, but of its imports and callers only what the change needs; a fresh Context Graph card counts as reading a file. They run the narrowest tests with failures only in the output, and the full suite once before committing. The lead's review starts from the lane's report, the reviewer's findings and the diff's summary.
15
+ - `agents.reviewer.model` now defaults to Sonnet and covers the security reviewer too, unless its agent file names a model.
16
+ - The lane rules changed, so `code-kit next` asks the lead to bring code-kit's instructions up to date.
17
+
18
+ ## 0.18.0 (2026-10-17)
19
+
20
+ code-kit's instructions to your agents now keep up with code-kit, it tells you when something needs you, and the fixes found by checking two live builds.
21
+
22
+ - Re-running `/code-kit:init` never updated code-kit's section of CLAUDE.md or the lane agents' `Always:` rules, so the rules from 0.16.0 and 0.17.0 never reached projects already set up. `code-kit instructions` now renders both from the templates and your config, between `<!-- code-kit:begin vN -->` and `<!-- code-kit:end -->` markers, and says whether each file is current, behind or written before the markers. `--write` brings marked parts up to date and never touches anything outside them. A part written before the markers, often tailored by an earlier init, is merged once by the lead, keeping your own lines. While any is behind, `code-kit next` says so before any other step and the loop asks the lead to update them. Init writes them with the same command.
23
+ - With Claude Code 2.1.301 or later, code-kit raises a system notification when something new needs you, such as an approval, a question from the lead, a stalled lane or a merge that's yours: "code-kit: the lead asks you". Each decision is notified once, and `notify.on` turns it off.
24
+ - **w** on a working lane in the Lanes pane puts that lane agent's transcript in the main view (Claude Code 2.1.301 or later).
25
+ - The loop no longer prompts the same story over and over. A story waiting on another wakes only when that story changes state, not on each of its commits, and a story the lead's turn left where it stood is parked whatever the reply said. In one build, "Review ST-160" had been prompted 31 times.
26
+ - A lane's hand-back of more than ten lines or 1,200 characters is refused by the stop hook, which also finds the report in the main checkout.
27
+ - Lanes may write scratch files in Claude Code's own temporary folder, and `git diff`, `log` and `show` may read the audit log's history.
28
+
5
29
  ## 0.17.0 (2026-10-17)
6
30
 
7
31
  The lead's context stays lean: lanes report to a file, and the lead's context is compacted when a milestone finishes.
package/README.md CHANGED
@@ -326,15 +326,18 @@ What waits for you there:
326
326
 
327
327
  Each button has a digit: type it in an empty prompt to press it. The band keeps one line per kind ("2 approvals waiting"); the Decisions tab says how many of each kind, then lists a short card for each decision, five at a time (**j** and **k** move), with the selected one in full below.
328
328
 
329
+ - **Notifications.** When something new needs you (an approval, a question from the lead, the lead waiting on you, a stalled lane, a call Claude Code refused, a step or a merge that's yours), code-kit raises a system notification through your notification channel, once per decision, from the session that runs the loop: "code-kit: the lead asks you". What was already waiting when the session started isn't announced. It needs Claude Code 2.1.301 or later; turn it off with `notify.on`.
330
+
329
331
  ### The lead loop
330
332
 
331
333
  The loop keeps the build moving between your prompts, under `harness.autonomy`. `/code-kit:init` asks you how hands-off you want it. Until you've chosen, its first step waits in the band ("code-kit can keep the build moving between your prompts") with **Keep going**, **Ask each time** and **Off**, and your answer is recorded as your setting.
332
334
 
333
335
  - **Each step.** Once the lead has finished a turn, with the lead idle and nothing typed in the prompt, the loop asks `code-kit next` and prompts the lead to dispatch the ready stories, review a finished one, or build its own. It never prompts the same step twice while nothing has changed, and never a story an agent is still on: one its lane agent is building, or a reviewer is reviewing, waits until that agent finishes ("waiting: ST-10 is still with its agent").
334
336
  - **It reads the lead's reply first.** Before prompting again, the loop reads the lead's last reply with a side call over the lead's own conversation. A reply that waits on you (a question, a design to look at, an approval), or a turn you stopped, holds the loop until you answer or speak to the lead: the band says what the lead waits on, and its question is asked in Claude Code's own question dialog. A step the lead didn't take waits in the band with its reason and **Ask again**.
335
- - **Stories waiting on other work.** When the lead says a story isn't due yet because it waits on work still going (a paired story a lane is building, a rebuild, a check running), the loop parks that story and leaves it out of every step, so a step naming it with others prompts only the others. It wakes when what it waits on moves: its own branch, the stories the lead named, or the unfinished stories that depend on it. Other lanes' commits don't wake it, and after 30 minutes with nothing moving the lead is asked once more.
337
+ - **Stories waiting on other work.** When the lead says a story isn't due yet because it waits on work still going (a paired story a lane is building, a rebuild, a check running), the loop parks that story and leaves it out of every step, so a step naming it with others prompts only the others. It wakes when its own branch or state moves, or when a story it waits on (one the lead named, or an unfinished one building on it) changes state; that story's commits along the way don't wake it, nor do other lanes'. A story the lead's turn left exactly where it stood is parked too, whatever the reply said. After 30 minutes with nothing moving, the lead is asked once more.
336
338
  - **A step that doesn't move.** One the lead was prompted with twice in a row that is still next goes to you instead: "Merge lead/st-1: still next after 2 prompts. Ask the lead again?" with **Ask again**.
337
- - **A lean lead.** A lane writes its full report, with its commands and their output, to `.claude/state/reports/<branch>.md`, and hands the lead a few lines naming it; the stop hook won't let a lane on a story branch finish without it, and the lead reads the file when it reviews. Lanes don't message the lead while they work. When a milestone finishes, the lead's context is compacted before the next one (`harness.compact.milestone`: you're asked by default, or it's done at once): the summary keeps what's in flight and what waits on you, and drops the finished stories' detail.
339
+ - **A lean lead.** A lane writes its full report, with its commands and their output, to `.claude/state/reports/<branch>.md`, and hands the lead a few lines naming it; the stop hook won't let a lane on a story branch finish without it, or with a hand-back of more than ten lines, and the lead reads the file when it reviews. Lanes don't message the lead while they work. When a milestone finishes, the lead's context is compacted before the next one (`harness.compact.milestone`: you're asked by default, or it's done at once): the summary keeps what's in flight and what waits on you, and drops the finished stories' detail.
340
+ - **Token use.** A long-lived agent rereads its whole context on every request, which is most of what a build costs. So each lane agent builds one story; one past `agents.lanes.contextLimit` is told to wrap up, and the lead carries its story on with a fresh agent that reads its report (`code-kit worktrees release` frees the branch). The lead's context is compacted between turns once it's past `compact.at`. The loop reads the lead's replies with Sonnet, and the reviewers run on Sonnet by default.
338
341
  - **Designs.** A design story is handed to the lead once a session; while you and the lead design it, the loop leaves it to you.
339
342
  - **One session runs it.** The first session whose loop acts runs it for the project. Another session you open there, for any reason, never prompts its lead: its band reads "the loop runs in another session", with **Lead from here** to move the loop to it.
340
343
  - **Modes.** **Pause** and **Resume** stop and start it. Under `propose`, each step waits for **Go**; under `off`, the loop does nothing. When every story is done, the band says so and the loop stops.
@@ -357,7 +360,7 @@ The loop keeps the build moving between your prompts, under `harness.autonomy`.
357
360
  - **Trail**: every requirement in the specs as a cell by spec, its glyph and colour saying whether it's done and tested, done but untested, in progress, todo, without a story or removed, with a legend counting each. **h**/**l** and **j**/**k** move between cells and specs (or pick one from the list), showing the requirement's stories, their branches, the code they changed and the tests that name it. Beneath the cells, the spec's trail drawn as a flow, each requirement to its stories, code and tests, with a missing link dashed in red.
358
361
  - **Queue**: the merge queue.
359
362
  - **Usage**.
360
- - **Keys.** **j** and **k** move between lanes, and the selected lane's acts are on **a** (approve), **r** (review), **s** (send back, with your reason), **m** (merge), **n** (nudge its agent) and **x** (stop it); **p** pauses or resumes the loop, and **f** filters the lanes by name, agent or story.
363
+ - **Keys.** **j** and **k** move between lanes, and the selected lane's acts are on **a** (approve), **r** (review), **s** (send back, with your reason), **m** (merge), **w** (watch: its agent's own transcript in the main view, on Claude Code 2.1.301 or later; the tasks list brings the conversation back), **n** (nudge its agent) and **x** (stop it); **p** pauses or resumes the loop, and **f** filters the lanes by name, agent or story.
361
364
  - **A story.** **Enter** (or **o**) opens the selected lane's story: its requirements beside its spec-check report, the reviewer's findings by severity (each a way into the diff), `verify`'s problems, its usage and loop steps, and its diff against the base, file by file, with the acts its state allows. **b** or Escape goes back to the lanes.
362
365
 
363
366
  ### Other views
@@ -417,6 +420,8 @@ Owner backend lane / backend-engineer → jobs lane / jobs-engineer: 14 file(s),
417
420
 
418
421
  Lanes never change silently: only when you approve the diff.
419
422
 
423
+ code-kit's own instructions to your agents, its section of CLAUDE.md and each lane agent's `Always:` rules, are kept between markers and rendered from your config, so a newer code-kit can bring them up to date without touching anything you wrote around them. `code-kit instructions` says where each stands, and `--write` updates them. When they're behind, `code-kit next` says so before any other step and the loop asks the lead to update them. A setup from before the markers is merged once, by the lead, keeping your own lines.
424
+
420
425
  ## The CLI
421
426
 
422
427
  Run from the project root, as `code-kit <command>` once installed (`npm i -g @warren-dean/code-kit`), or `npx @warren-dean/code-kit <command>`. Add `--config .claude/code-kit.draft.json` to any command to read a draft instead. In a session, the `/code-kit:check` skill runs `check` and `unowned` for you. `next` also works in a project without code-kit, or outside a git repository.
@@ -432,6 +437,8 @@ code-kit verify [--base ref] [--branch name] [--no-checks] [--json] # a branch'
432
437
  code-kit security [--base ref] [--branch name] [--json] # whether a branch needs the security review, and why: sign-in, permissions, outside input, personal data, payments, secrets, dependencies
433
438
  code-kit status [--base ref] [--json] # stories and requirements against the spec
434
439
  code-kit next [--base ref] [--json] # the step to take now, from the project's state
440
+ code-kit worktrees [--json] | worktrees release <branch> | worktrees prune # the project's worktrees; free one for a fresh agent on its story, or remove every merged and clean one, keeping their reports
441
+ code-kit instructions [--write] [--json] | instructions show lead|lane # code-kit's part of CLAUDE.md and the lane agents: where each stands, brought up to date, or printed
435
442
  code-kit trace <path>... [--json] # what a file is for: requirements, lane, layer rules
436
443
  code-kit merge <branch> --delegated|--person [--into branch] # merge a branch that passes verify: the lead where approvals.delegate.merge is on, or the person
437
444
  code-kit requests [--json] # approval requests still waiting, numbered, each the lead's or the person's to decide, the approvals in force, and every approval given
@@ -477,33 +484,33 @@ code-kit approve <name>... --reason "…" [--lane name] [--delegated [--story ST
477
484
 
478
485
  `.claude/code-kit.json`. Globs use `*` (one segment), `**` (any depth), `**/` (zero or more folders), `?` and `{a,b}`. Patterns under `shell` are regular expressions.
479
486
 
480
- | Field | Required | What it is |
481
- | ------------------------------ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
482
- | `version` | yes | `1` |
483
- | `docs.specs` | no | Folder of specs. With it, a lane needs a spec-check report before writing code on a branch |
484
- | `docs.principles`, `docs.plan` | no | The engineering principles and the implementation plan |
485
- | `docs.decisions` | no | Where agreed intents are recorded as numbered decision records (`code-kit intend record`); default `docs/decisions` |
486
- | `lead.paths` | yes | What the main session writes. `.claude/**` is always added. |
487
- | `lead.outside` | no | Paths outside the repository the lead may write, e.g. `../shared-lib/**`. Claude's memory and scratchpad are always included. |
488
- | `anyActor` | no | Paths every writer may change (e.g. the lockfile), read-only agents included. `.claude/state/**` is always included. An entry may be `{ "glob", "reviewer" }` to name the lane (or `"lead"`) that reviews changes to it; otherwise the lead reviews them, and `who` and `trace` say so. |
489
- | `lanes.<name>` | yes (may be `{}`) | `{ agent, paths, exclude?, owners? }`: the agent name maps a subagent to its lane; `owners` (GitHub handles) are the people whose sessions work it, in a team (needs `team`). A worktree with a `.lane` file holding the lane name makes the main session that lane. |
490
- | `protected[]` | no | `{ glob, approval, why }`: logged on every commit. Anyone but the lead needs an approval: the lead's where `approvals.delegate.protected` names it, otherwise a person's (see [Approvals](#approvals)). `.claude/**` is always protected as `kit`. |
491
- | `designGate` | no | `{ register, screens: [{ glob, not? }], howTo }`: screen files wait until the register (`{ "screens": [{ "file": … }] }`) lists them. Init sets it for every lane that owns screens; `/code-kit:design` adds approved designs |
492
- | `layers[]` | no | `{ name, paths, mayImport, denyPackages? }`: the first layer whose paths match a file owns it. A layer may always import itself. |
493
- | `importAliases` | no | `{ "@app/domain": "packages/domain/src" }`: resolves non-relative imports to repository paths |
494
- | `shell.block[]` | no | `{ pattern, why, person?, command? }`: never run. `person: true` marks a person's act, which the lead may do for them with `approvals.lead` on. `command: true` matches the pattern from the start of each command the line runs, so a heredoc or quoted text that mentions it isn't refused |
495
- | `shell.restricted[]` | no | `{ pattern, lanes, why }`: only the lead and these lanes |
496
- | `postEdit[]` | no | `{ files, exclude?, run }`: after each edit of a matching file. `{file}` is the file. Exit 127 (tool not installed) is skipped. |
497
- | `tests[]` | no | Globs naming the test files, for the weakened-test and fix rules. Default: the usual conventions (`*.test.*`, `*.spec.*`, `__tests__/`, `test/`, `tests/`, `e2e/`, `*_test.go`, `test_*.py`). |
498
- | `regression.prepare` | no | A command run in a fresh checkout before a fix's tests run there without the fix, such as `npm ci`. Without it, the checkout's `node_modules` and virtualenvs are linked in. |
499
- | `checks[]` | no | `{ name, files, exclude?, run[], each?, owners?, ifMissing?, timeoutSeconds? }`: before finishing, when matching files changed. `each` runs the commands in every changed folder matching that glob. `owners` limits who may change the files (the lead always may). `ifMissing: "fail"` makes a missing tool an error (the default is to skip the check, which `verify` and a merge list as not run). |
500
- | `adapters` | no | `{ "<adapter>": false }` switches off an adapter that would otherwise be detected; `true` says the project wants that tool, so until it's set up here `next` gives its setup before any code. See [Adapters](#adapters). |
501
- | `approvals.lead` | no | `true` lets the lead act on what the person says in chat, for when `!` isn't available (on a phone, say): it records their approvals with `code-kit approve`, and runs `shell.block` commands marked `person`. Default `false`. See [Approvals](#approvals). |
502
- | `approvals.delegate` | no | `{ protected?: [approval names], merge?: true, dependencies?: { licences?, minWeeklyDownloads?, maxMonthsSinceRelease?, allowInstallScripts?: true \| false \| [kinds] } }`: what the lead decides, inside the guardrails you keep: the protected approvals it grants, and packages it approves for a story, with `code-kit approve --delegated`; with `merge`, merges into a protected branch with `code-kit merge --delegated`. Lanes ask the lead for these, never you. npm packages are also checked against the registry; the defaults are MIT, Apache-2.0, BSD and ISC, 10,000 weekly downloads, a release within 18 months, and no install scripts. See [Approvals](#approvals). |
503
- | `setup` | no | The version of init's setup the project has (3 now). Without it, or lower, `next` names what the project is missing (the design gate, design stories before screens, a QA lane, a security reviewer) and starts no new story until re-running init adds it |
504
- | `team` | no | Preview. For several people in one repository: `{ techLead, staleDays?, intents?, board?, provider? }`. `techLead` is the GitHub handle of the person who owns the plan, specs and contracts; `staleDays` is how many working days without a push make a claim stale (default 3); `intents` is the discussion category intents go in (default `Intents`; create it once in the repository's Settings › Discussions); `board` is `{ owner, number }`, a GitHub Project the plan is mirrored onto; `provider` is `"github"` (default) or `{ command, fallback? }` for another team provider, such as the code-kit teams hub; with `fallback: "github"`, claims go through GitHub whenever that provider can't be reached. See docs/specs/16-teams.md. |
505
- | `branches.protected` | no | Default `["main", "master"]` |
506
- | `harness` | no | What the mod's harness does in the session: `autonomy` (`autonomous`, `propose` or `off`; default `autonomous`), `hold.minutes` (how long a call waiting on your approval is held, 0 to 10; default 2), `stall.nudgeMinutes` and `stall.restartMinutes` (default 5 and 10), `stall.maxRestarts` (default 2), `agents.reviewer.on` (default `false`) and `agents.reviewer.model` (default the session's), `agents.lanes.model` (the model each lane agent starts on, unless the lead's call or the lane's agent file names one; default the session's), `background.pauseAtPercent` (default 80), `compact.milestone` (when a milestone finishes, compact the lead's context: `ask`, `auto` or `off`; default `ask`) and `report.on` (agents draft bug reports for code-kit, which you send or discard; default `false`). Change them with `/harness` or `code-kit settings set`. |
487
+ | Field | Required | What it is |
488
+ | ------------------------------ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
489
+ | `version` | yes | `1` |
490
+ | `docs.specs` | no | Folder of specs. With it, a lane needs a spec-check report before writing code on a branch |
491
+ | `docs.principles`, `docs.plan` | no | The engineering principles and the implementation plan |
492
+ | `docs.decisions` | no | Where agreed intents are recorded as numbered decision records (`code-kit intend record`); default `docs/decisions` |
493
+ | `lead.paths` | yes | What the main session writes. `.claude/**` is always added. |
494
+ | `lead.outside` | no | Paths outside the repository the lead may write, e.g. `../shared-lib/**`. Claude's memory and scratchpad are always included. |
495
+ | `anyActor` | no | Paths every writer may change (e.g. the lockfile), read-only agents included. `.claude/state/**` is always included. An entry may be `{ "glob", "reviewer" }` to name the lane (or `"lead"`) that reviews changes to it; otherwise the lead reviews them, and `who` and `trace` say so. |
496
+ | `lanes.<name>` | yes (may be `{}`) | `{ agent, paths, exclude?, owners? }`: the agent name maps a subagent to its lane; `owners` (GitHub handles) are the people whose sessions work it, in a team (needs `team`). A worktree with a `.lane` file holding the lane name makes the main session that lane. |
497
+ | `protected[]` | no | `{ glob, approval, why }`: logged on every commit. Anyone but the lead needs an approval: the lead's where `approvals.delegate.protected` names it, otherwise a person's (see [Approvals](#approvals)). `.claude/**` is always protected as `kit`. |
498
+ | `designGate` | no | `{ register, screens: [{ glob, not? }], howTo }`: screen files wait until the register (`{ "screens": [{ "file": … }] }`) lists them. Init sets it for every lane that owns screens; `/code-kit:design` adds approved designs |
499
+ | `layers[]` | no | `{ name, paths, mayImport, denyPackages? }`: the first layer whose paths match a file owns it. A layer may always import itself. |
500
+ | `importAliases` | no | `{ "@app/domain": "packages/domain/src" }`: resolves non-relative imports to repository paths |
501
+ | `shell.block[]` | no | `{ pattern, why, person?, command? }`: never run. `person: true` marks a person's act, which the lead may do for them with `approvals.lead` on. `command: true` matches the pattern from the start of each command the line runs, so a heredoc or quoted text that mentions it isn't refused |
502
+ | `shell.restricted[]` | no | `{ pattern, lanes, why }`: only the lead and these lanes |
503
+ | `postEdit[]` | no | `{ files, exclude?, run }`: after each edit of a matching file. `{file}` is the file. Exit 127 (tool not installed) is skipped. |
504
+ | `tests[]` | no | Globs naming the test files, for the weakened-test and fix rules. Default: the usual conventions (`*.test.*`, `*.spec.*`, `__tests__/`, `test/`, `tests/`, `e2e/`, `*_test.go`, `test_*.py`). |
505
+ | `regression.prepare` | no | A command run in a fresh checkout before a fix's tests run there without the fix, such as `npm ci`. Without it, the checkout's `node_modules` and virtualenvs are linked in. |
506
+ | `checks[]` | no | `{ name, files, exclude?, run[], each?, owners?, ifMissing?, timeoutSeconds? }`: before finishing, when matching files changed. `each` runs the commands in every changed folder matching that glob. `owners` limits who may change the files (the lead always may). `ifMissing: "fail"` makes a missing tool an error (the default is to skip the check, which `verify` and a merge list as not run). |
507
+ | `adapters` | no | `{ "<adapter>": false }` switches off an adapter that would otherwise be detected; `true` says the project wants that tool, so until it's set up here `next` gives its setup before any code. See [Adapters](#adapters). |
508
+ | `approvals.lead` | no | `true` lets the lead act on what the person says in chat, for when `!` isn't available (on a phone, say): it records their approvals with `code-kit approve`, and runs `shell.block` commands marked `person`. Default `false`. See [Approvals](#approvals). |
509
+ | `approvals.delegate` | no | `{ protected?: [approval names], merge?: true, dependencies?: { licences?, minWeeklyDownloads?, maxMonthsSinceRelease?, allowInstallScripts?: true \| false \| [kinds] } }`: what the lead decides, inside the guardrails you keep: the protected approvals it grants, and packages it approves for a story, with `code-kit approve --delegated`; with `merge`, merges into a protected branch with `code-kit merge --delegated`. Lanes ask the lead for these, never you. npm packages are also checked against the registry; the defaults are MIT, Apache-2.0, BSD and ISC, 10,000 weekly downloads, a release within 18 months, and no install scripts. See [Approvals](#approvals). |
510
+ | `setup` | no | The version of init's setup the project has (3 now). Without it, or lower, `next` names what the project is missing (the design gate, design stories before screens, a QA lane, a security reviewer) and starts no new story until re-running init adds it |
511
+ | `team` | no | Preview. For several people in one repository: `{ techLead, staleDays?, intents?, board?, provider? }`. `techLead` is the GitHub handle of the person who owns the plan, specs and contracts; `staleDays` is how many working days without a push make a claim stale (default 3); `intents` is the discussion category intents go in (default `Intents`; create it once in the repository's Settings › Discussions); `board` is `{ owner, number }`, a GitHub Project the plan is mirrored onto; `provider` is `"github"` (default) or `{ command, fallback? }` for another team provider, such as the code-kit teams hub; with `fallback: "github"`, claims go through GitHub whenever that provider can't be reached. See docs/specs/16-teams.md. |
512
+ | `branches.protected` | no | Default `["main", "master"]` |
513
+ | `harness` | no | What the mod's harness does in the session: `autonomy` (`autonomous`, `propose` or `off`; default `autonomous`), `hold.minutes` (how long a call waiting on your approval is held, 0 to 10; default 2), `stall.nudgeMinutes` and `stall.restartMinutes` (default 5 and 10), `stall.maxRestarts` (default 2), `agents.reviewer.on` (default `false`) and `agents.reviewer.model` (code-kit's reviewer and the security reviewer; default `sonnet`), `agents.lanes.model` (the model each lane agent starts on, unless the lead's call or the lane's agent file names one; default the session's), `background.pauseAtPercent` (default 80), `compact.at` (compact the lead's context between turns once it's past this share of its window; default 40, 0 for never), `compact.milestone` (when a milestone finishes, compact the lead's context: `ask`, `auto` or `off`; default `ask`), `agents.lanes.contextLimit` (the context a lane agent may reach before it hands its story to a fresh agent; default 250,000 tokens), `notify.on` (a system notification when something new needs you; default `true`) and `report.on` (agents draft bug reports for code-kit, which you send or discard; default `false`). Change them with `/harness` or `code-kit settings set`. |
507
514
 
508
515
  Add `.claude/approvals/`, `.claude/state/` and `.claude/code-kit.draft.json` to `.gitignore`, and `.claude/approval-log.jsonl merge=union` to `.gitattributes`, so merging branches that both logged changes keeps every entry. Commit `.claude/code-kit.baseline.json` if the project has one.
509
516
 
package/bin/code-kit.mjs CHANGED
@@ -33,8 +33,9 @@
33
33
  // the harness settings; set is the person's own change
34
34
  // record approvals a person gave in chat (with approvals.lead on, the lead runs it)
35
35
  // --config <file> reads a draft (.claude/code-kit.draft.json) instead of .claude/code-kit.json.
36
- import { execFileSync } from 'node:child_process';
36
+ import { execFileSync, spawnSync } from 'node:child_process';
37
37
  import {
38
+ copyFileSync,
38
39
  existsSync,
39
40
  mkdirSync,
40
41
  mkdtempSync,
@@ -77,6 +78,13 @@ import {
77
78
  import { storyTimes } from '../hooks/lib/timeline.mjs';
78
79
  import { PROJECT, projectMap, storyMap, trail } from '../hooks/lib/storymap.mjs';
79
80
  import { journal } from '../hooks/lib/journal.mjs';
81
+ import {
82
+ INSTRUCTIONS_VERSION,
83
+ agentRules,
84
+ instructionsState,
85
+ leadSection,
86
+ writeInstructions,
87
+ } from '../hooks/lib/instructions.mjs';
80
88
  import { dequeue, enqueue, lastMerged, readQueue, settle, waiting } from '../hooks/lib/queue.mjs';
81
89
  import { SETTINGS, parseSetting, settingOf, withSetting } from '../hooks/lib/harness.mjs';
82
90
  import { claimLead, readLoop, releaseLead, writeLoop } from '../hooks/lib/loop-state.mjs';
@@ -1884,8 +1892,124 @@ function loopCommand() {
1884
1892
  );
1885
1893
  }
1886
1894
 
1895
+ /**
1896
+ * ONB-8: code-kit's own instructions (the lead's section of CLAUDE.md, each lane agent's "Always:" rules):
1897
+ * where each stands, `--write` to bring the marked ones up to date, `--show lead|lane` to print one.
1898
+ */
1899
+ function instructionsCommand() {
1900
+ const { config } = load();
1901
+ const root = resolve('.');
1902
+ if (rest[0] === 'show') {
1903
+ if (!['lead', 'lane'].includes(rest[1])) die('code-kit instructions show lead|lane');
1904
+ return out(rest[1] === 'lead' ? leadSection(root, config) : agentRules(root, config));
1905
+ }
1906
+ const changed = flag('--write') ? writeInstructions(root, config) : [];
1907
+ const files = instructionsState(root, config);
1908
+ if (flag('--json'))
1909
+ return out(
1910
+ JSON.stringify(
1911
+ {
1912
+ version: INSTRUCTIONS_VERSION,
1913
+ files: files.map(({ file, kind, state, version }) => ({ file, kind, state, version })),
1914
+ written: changed,
1915
+ },
1916
+ null,
1917
+ 2,
1918
+ ),
1919
+ );
1920
+ const said = {
1921
+ current: 'up to date',
1922
+ stale: 'from an older code-kit or config; --write brings it up to date',
1923
+ missing: 'none yet; --write adds it',
1924
+ legacy: 'written before code-kit marked its part; merge it once, by hand (below)',
1925
+ };
1926
+ if (changed.length) out(`Brought up to date: ${changed.join(', ')}.`);
1927
+ for (const f of files) out(` ${f.file}: ${said[f.state]}`);
1928
+ if (files.some((f) => f.state === 'legacy'))
1929
+ out(
1930
+ '\nTo merge one: replace its old code-kit part (in CLAUDE.md from `## Enforced, not advised` to the line about changing the rules; in an agent file from `Always:` to the end of its list) with what `code-kit instructions show lead` or `show lane` prints. Keep every line the project wrote itself (its own rules, its layer order, its docs) outside the markers, under a heading of its own, so a later refresh never touches them.',
1931
+ );
1932
+ }
1933
+
1934
+ /** LEAN-6: a worktree of the project, with its branch, story, report and whether it's merged and clean. */
1935
+ function worktreeList(config) {
1936
+ const root = resolve('.');
1937
+ const run = (...a) => spawnSync('git', a, { cwd: root, encoding: 'utf8' }).stdout?.trim() ?? '';
1938
+ const from = findBase(config, false);
1939
+ const blocks = run('worktree', 'list', '--porcelain').split('\n\n').filter(Boolean);
1940
+ return blocks.slice(1).map((b) => {
1941
+ const path = /^worktree (.+)$/m.exec(b)?.[1];
1942
+ const branch = /^branch refs\/heads\/(.+)$/m.exec(b)?.[1] ?? null;
1943
+ const report = branch ? `.claude/state/reports/${branch.replace(/[^\w.-]+/g, '_')}.md` : null;
1944
+ const status = spawnSync('git', ['-C', path, 'status', '--porcelain'], { encoding: 'utf8' });
1945
+ return {
1946
+ path,
1947
+ branch,
1948
+ story: branch ? storyOfBranch(branch) : null,
1949
+ merged: Boolean(
1950
+ branch &&
1951
+ from &&
1952
+ spawnSync('git', ['merge-base', '--is-ancestor', branch, from], { cwd: root }).status === 0,
1953
+ ),
1954
+ clean: status.status === 0 && !status.stdout.trim(),
1955
+ report: report && existsSync(join(path, report)) ? report : null,
1956
+ };
1957
+ });
1958
+ }
1959
+
1960
+ /** LEAN-6: releases a worktree: its report kept in the main checkout, the worktree removed, its branch kept. */
1961
+ function releaseWorktree(w) {
1962
+ if (!w.clean)
1963
+ die(
1964
+ `${w.branch ?? w.path} has uncommitted changes; nothing was removed. Commit them on the branch, or have its agent do so, then release it.`,
1965
+ );
1966
+ if (w.report) {
1967
+ mkdirSync(join(resolve('.'), '.claude/state/reports'), { recursive: true });
1968
+ copyFileSync(join(w.path, w.report), join(resolve('.'), w.report));
1969
+ }
1970
+ const removed = spawnSync('git', ['worktree', 'remove', w.path], {
1971
+ cwd: resolve('.'),
1972
+ encoding: 'utf8',
1973
+ });
1974
+ if (removed.status !== 0) die(`git worktree remove failed: ${removed.stderr.trim()}`);
1975
+ return `${w.branch ?? w.path}${w.report ? ` (its report kept at ${w.report})` : ''}`;
1976
+ }
1977
+
1978
+ /**
1979
+ * LEAN-6: `worktrees` lists the project's worktrees; `release <branch>` frees one for a fresh agent on the
1980
+ * same story; `prune` releases every one whose branch is merged and that holds nothing uncommitted.
1981
+ */
1982
+ function worktreesCommand() {
1983
+ const { config } = load();
1984
+ const list = worktreeList(config);
1985
+ if (rest[0] === 'release') {
1986
+ const w = list.find((x) => x.branch === rest[1] || x.path === rest[1]);
1987
+ if (!w) die(`No worktree has ${rest[1] ?? '<branch>'} checked out.`);
1988
+ return out(`Released ${releaseWorktree(w)}. The branch and its commits stay.`);
1989
+ }
1990
+ if (rest[0] === 'prune') {
1991
+ const done = list.filter((w) => w.merged && w.clean);
1992
+ if (!done.length) return out('No worktree is both merged and clean; nothing was removed.');
1993
+ for (const w of done) out(`Released ${releaseWorktree(w)}.`);
1994
+ return;
1995
+ }
1996
+ if (flag('--json')) return out(JSON.stringify(list, null, 2));
1997
+ if (!list.length) return out('No worktrees besides the main checkout.');
1998
+ for (const w of list)
1999
+ out(
2000
+ ` ${w.branch ?? '(detached)'}${w.story ? ` · ${w.story}` : ''}: ${w.merged ? 'merged' : 'not merged'}, ${w.clean ? 'clean' : 'uncommitted changes'} ${w.path}`,
2001
+ );
2002
+ const prunable = list.filter((w) => w.merged && w.clean).length;
2003
+ if (prunable)
2004
+ out(
2005
+ `\n${prunable} merged and clean: \`code-kit worktrees prune\` removes them, keeping their reports.`,
2006
+ );
2007
+ }
2008
+
1887
2009
  const commands = {
1888
2010
  check,
2011
+ worktrees: worktreesCommand,
2012
+ instructions: instructionsCommand,
1889
2013
  loop: loopCommand,
1890
2014
  approve: () => (rest.length ? approve(rest) : usage()),
1891
2015
  merge: () => (rest.length === 1 ? merge(rest[0]) : usage()),
@@ -1922,6 +2046,8 @@ function usage() {
1922
2046
  ' | verify [--base ref] [--branch name] [--no-checks] | status [--base ref] [--json]\n' +
1923
2047
  ' | security [--base ref] [--branch name] [--json]\n' +
1924
2048
  ' | next [--base ref] [--json] | adapters | trace <path>... [--json]\n' +
2049
+ ' | instructions [--write] [--json] | instructions show lead|lane\n' +
2050
+ ' | worktrees [--json] | worktrees release <branch> | worktrees prune\n' +
1925
2051
  ' | approve <name>... --reason "…" [--lane name] [--config file]\n' +
1926
2052
  ' | merge <branch> --delegated|--person [--into branch] | requests [--json] | requests drop <n>|all\n' +
1927
2053
  ' | ask "<question>" [--story ST-n] [--blocks] [--why "…"] [--option "Label: effect"]... | ask [--json]\n' +
@@ -58,6 +58,21 @@ const RUNS = [
58
58
  for (const [re, why] of RUNS) {
59
59
  if (runs(cmd, re, { quoted: false })) block(`Blocked: ${why}\nCommand: ${cmd}`);
60
60
  }
61
+ /**
62
+ * Whether `cmd` only reads git's history, with what it prints piped into read-only filters: `git diff
63
+ * main...HEAD -- .claude/approval-log.jsonl`, `git log -p -- … | head`. No redirect, no tee, nothing else.
64
+ */
65
+ function gitReadsOnly(cmd) {
66
+ const code = codeOf(cmd);
67
+ if (/>|\btee\b|`|\$\(/.test(code)) return false;
68
+ const parts = commandsOf(cmd);
69
+ return (
70
+ parts.length > 0 &&
71
+ new RegExp(String.raw`^${GIT}(?:diff|log|show|blame)\b`).test(parts[0].code) &&
72
+ parts.slice(1).every((c) => /^(?:grep|head|tail|wc|cat|sort|uniq|less)\b/.test(c.code))
73
+ );
74
+ }
75
+
61
76
  // These read the command whole: a pipe spans commands, and an approval or the log can be written
62
77
  // through a quoted path.
63
78
  const ALWAYS = [
@@ -73,8 +88,8 @@ const ALWAYS = [
73
88
  ],
74
89
  [
75
90
  /approval-log\.jsonl/,
76
- "The approval audit log is appended only by the commit hook; nobody edits it. A shell command may not name it, even to read it; read it with the Read tool, which can't write.",
77
- cmd,
91
+ "The approval audit log is appended only by the commit hook; nobody edits it. A shell command may not name it except to read its history with git (git diff, log or show, piped only into grep, head, tail or wc); read the file itself with the Read tool, which can't write.",
92
+ gitReadsOnly(cmd) ? '' : cmd,
78
93
  ],
79
94
  ];
80
95
  for (const [re, why, text] of ALWAYS) {
@@ -221,6 +236,22 @@ if (runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+settings\s+set\b`)))
221
236
  block(
222
237
  `Blocked: changing the harness settings is the person's own act, from the code-kit pane (/harness) or their terminal. Tell them what you'd change and why, and stop.\nCommand: ${cmd}`,
223
238
  );
239
+ // LEAN-6: releasing or pruning worktrees is the lead's, which knows which stories are still at work.
240
+ if (
241
+ runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+worktrees\s+(?:release|prune)\b`)) &&
242
+ actor.kind !== 'lead'
243
+ )
244
+ block(
245
+ `Blocked: releasing worktrees is the lead's, which knows which stories are still at work. Finish your story and hand back.\nCommand: ${cmd}`,
246
+ );
247
+ // ONB-8: code-kit's instructions are the lead's to bring up to date, like the rest of the kit.
248
+ if (
249
+ runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+instructions\b.*\s--write\b`)) &&
250
+ actor.kind !== 'lead'
251
+ )
252
+ block(
253
+ `Blocked: code-kit's instructions in CLAUDE.md and the agent files are the lead's to update. Tell the lead they're behind, and carry on.\nCommand: ${cmd}`,
254
+ );
224
255
  if (queues && actor.kind !== 'lead')
225
256
  block(
226
257
  `Blocked: only the lead puts branches in the merge queue, after its review passes them. Finish your story and commit it on your branch; the lead reviews it.\nCommand: ${cmd}`,
@@ -47,12 +47,13 @@ export const SETTINGS = {
47
47
  about: 'A background reviewer for each finished branch',
48
48
  },
49
49
  'agents.reviewer.model': {
50
- default: null,
50
+ default: 'sonnet',
51
51
  problem: (v) =>
52
52
  v === null || (typeof v === 'string' && v.length > 0)
53
53
  ? null
54
54
  : `must be a model (${MODELS.join(', ')}, or an id), or null for the session's`,
55
- about: "The reviewer's model; none means the session's",
55
+ about:
56
+ "The reviewers' model (code-kit's reviewer and the security reviewer); none means the session's",
56
57
  },
57
58
  'agents.lanes.model': {
58
59
  default: null,
@@ -63,18 +64,38 @@ export const SETTINGS = {
63
64
  about:
64
65
  "The lane agents' model, set as each is dispatched; none means the session's. A lane's agent file with its own model keeps it",
65
66
  },
67
+ 'agents.lanes.contextLimit': {
68
+ default: 250000,
69
+ problem: (v) =>
70
+ Number.isInteger(v) && v >= 0 ? null : 'must be a whole number of tokens, 0 for no limit',
71
+ about:
72
+ "The context a lane agent may reach before it's asked to wrap up and hand its story to a fresh agent; 0 for no limit",
73
+ },
66
74
  'background.pauseAtPercent': {
67
75
  default: 80,
68
76
  problem: (v) =>
69
77
  Number.isFinite(v) && v > 0 && v <= 100 ? null : 'must be a percentage from 1 to 100',
70
78
  about: "The plan's 5-hour use at which background agents pause",
71
79
  },
80
+ 'compact.at': {
81
+ default: 40,
82
+ problem: (v) =>
83
+ Number.isFinite(v) && v >= 0 && v <= 100 ? null : 'must be a percentage from 0 (off) to 100',
84
+ about:
85
+ "How full the lead's context may get, as a share of its window, before it's compacted between turns; 0 for never",
86
+ },
72
87
  'compact.milestone': {
73
88
  default: 'ask',
74
89
  problem: (v) => (COMPACT.includes(v) ? null : `must be one of ${COMPACT.join(', ')}`),
75
90
  about:
76
91
  "When a milestone finishes, compact the lead's context before the next: ask you, do it, or leave it",
77
92
  },
93
+ 'notify.on': {
94
+ default: true,
95
+ problem: (v) => (typeof v === 'boolean' ? null : 'must be true or false'),
96
+ about:
97
+ 'A system notification when something new needs you: an approval, a question, a stalled lane',
98
+ },
78
99
  'report.on': {
79
100
  default: false,
80
101
  problem: (v) => (typeof v === 'boolean' ? null : 'must be true or false'),