@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.
@@ -75,6 +75,8 @@ function project() {
75
75
  agents: [] as { type: string; status: string; id?: string; description?: string }[],
76
76
  limits: [] as { kind: string; percentUsed: number }[],
77
77
  contextPercent: 0, // LEAN-3: how full the lead's context is
78
+ notified: [] as { text: string; title?: string }[], // NOTIFY-1
79
+ viewed: [] as (string | null)[], // WATCH-1
78
80
  approveExit: 0,
79
81
  mergeExit: 0,
80
82
  answer: 'Merge',
@@ -380,6 +382,8 @@ function stub(on: any, w: World) {
380
382
  });
381
383
  on('model.complete', ($: any, e: any) => {
382
384
  w.sideAsks.push({ model: e.model, prompt: e.prompt });
385
+ // LEAN-6: the loop reads the lead's reply with a small model; the test's reading answers it.
386
+ if (/Say where that reply left things/.test(e.prompt)) return { value: w.sideReply };
383
387
  return { value: { isAnswered: true, text: 'From the facts alone.', usage: null } };
384
388
  });
385
389
  // Claude Code's own settings hooks beneath: none answer a permission denial.
@@ -408,6 +412,15 @@ function stub(on: any, w: World) {
408
412
  value: { startedAt: 0, context: { percent: w.contextPercent }, rateLimits: w.limits },
409
413
  }));
410
414
  on('session.measure', ($: any, e: any) => ({ changed: e.changed }));
415
+ // NOTIFY-1: the person's notification channel; WATCH-1: the main view.
416
+ on('ui.notify', ($: any, e: any) => {
417
+ w.notified.push({ text: e.text, title: e.title });
418
+ return { value: { isSent: true, channel: 'iterm2' } };
419
+ });
420
+ on('ui.view', ($: any, e: any) => {
421
+ w.viewed.push(e.agentId ?? null);
422
+ return { value: {} };
423
+ });
411
424
  // A model request, answered with the usage the test gave it.
412
425
  on('turn.step', async function* ($: any, e: any) {
413
426
  return {
@@ -2134,7 +2147,9 @@ test('LOOP-1 nothing is submitted mid-turn, or while the person has a draft, or
2134
2147
  expect(w.prompts).toEqual(['Dispatch ST-9: run /code-kit:dispatch ST-9.']);
2135
2148
  await leadTurn($, 't-3');
2136
2149
  await settle(clock);
2137
- expect(w.prompts).toHaveLength(1);
2150
+ // LOOP-13: the turn left ST-9 where it stood, so it's parked, and the next step goes instead.
2151
+ expect(w.prompts.filter((p: string) => p.startsWith('Dispatch ST-9'))).toHaveLength(1);
2152
+ expect(w.prompts.at(-1)).toMatch(/^Build ST-6, ST-7 yourself/);
2138
2153
  });
2139
2154
 
2140
2155
  test("LOOP-1 review and the lead's own stories are prompted with their skill", async ($, on) => {
@@ -2299,6 +2314,60 @@ test(
2299
2314
  },
2300
2315
  );
2301
2316
 
2317
+ test('LEAN-5 a lane agent past its context limit is told to wrap up, and its story goes on with a fresh agent', async ($, on) => {
2318
+ const w = project();
2319
+ w.next = { step: 'wait', args: '', then: [] };
2320
+ w.agents = [
2321
+ { id: 'a1', type: 'web-engineer', description: 'ST-4 booking form', status: 'running' },
2322
+ ];
2323
+ const clock = await start($, on, w);
2324
+ await clock.advance(2000);
2325
+ await request($, 'a1', 100);
2326
+ expect(w.sent).toEqual([]);
2327
+ // 270k of context, past the 250k limit: told once, however many requests follow.
2328
+ await request($, 'a1', 300);
2329
+ await request($, 'a1', 320);
2330
+ expect(w.sent).toHaveLength(1);
2331
+ expect(w.sent[0].text).toMatch(/^Your context is past 250k tokens/);
2332
+ await $.turn.complete({
2333
+ turnId: 't-a1',
2334
+ agentId: 'a1',
2335
+ answer: 'ST-4: committed, report written.',
2336
+ durationMs: 1,
2337
+ isAborted: false,
2338
+ } as any);
2339
+ await leadTurn($, 't-2');
2340
+ await settle(clock);
2341
+ expect(w.prompts.at(-1)).toMatch(
2342
+ /^web-engineer handed ST-4 back at its context limit, with its work committed on web\/st-4\. Carry the story on with a fresh agent: run node ".*" worktrees release web\/st-4/,
2343
+ );
2344
+ expect(w.prompts.at(-1)).toMatch(
2345
+ /Read your earlier report first: \.claude\/state\/reports\/web_st-4\.md/,
2346
+ );
2347
+ });
2348
+
2349
+ test("LEAN-4 past compact.at the lead's context is compacted between turns, without asking", async ($, on) => {
2350
+ const w = project();
2351
+ const compacted: string[] = [];
2352
+ on('session.compact', ($: any, e: any) => {
2353
+ compacted.push(e.instructions);
2354
+ return {
2355
+ messages: [{ role: 'user', text: 'Summary: ST-4 in progress on web/st-4.', toolUses: [] }],
2356
+ };
2357
+ });
2358
+ w.contextPercent = 35;
2359
+ const clock = await start($, on, w);
2360
+ await leadTurn($);
2361
+ await settle(clock);
2362
+ expect(compacted).toEqual([]);
2363
+ w.contextPercent = 46;
2364
+ await leadTurn($, 't-2');
2365
+ await settle(clock);
2366
+ expect(compacted).toHaveLength(1);
2367
+ expect(compacted[0]).toMatch(/^The lead's context is compacted between turns/);
2368
+ expect(w.asked.filter((a) => /Compact/.test(a.question))).toEqual([]);
2369
+ });
2370
+
2302
2371
  test('LOOP-3 an agent whose work is done is never nudged or restarted: it handed back, or its branch waits in the merge queue', async ($, on) => {
2303
2372
  const w = project();
2304
2373
  w.next = { step: 'wait', args: '', then: [] };
@@ -2464,7 +2533,9 @@ test("LOOP-11 a reply that waits on the person holds the loop; the dialog asks t
2464
2533
  await leadSays($, 't-2', 'Before I dispatch ST-9: should its prices include GST?');
2465
2534
  await settle(clock);
2466
2535
  // The side call read the reply over the lead's own conversation, with the step it answered.
2467
- expect(w.sideAsks.at(-1).fork).toBe(true);
2536
+ // LEAN-6: read by a small model from the reply itself, not a fork over the lead's conversation.
2537
+ expect(w.sideAsks.at(-1).fork).toBeUndefined();
2538
+ expect(w.sideAsks.at(-1).model).toBe('sonnet');
2468
2539
  expect(w.sideAsks.at(-1).prompt).toMatch(/prompted you: "Dispatch ST-9/);
2469
2540
  expect(w.sideAsks.at(-1).prompt).toMatch(/should its prices include GST\?/);
2470
2541
  expect(w.asked.at(-1)).toEqual({
@@ -2528,7 +2599,8 @@ test("LEAN-3 when a milestone finishes, the person is asked to compact the lead'
2528
2599
  ...w.stories.map((s) => ({ ...s, milestone: 'M1' })),
2529
2600
  ];
2530
2601
  const compacted: string[] = [];
2531
- w.contextPercent = 62;
2602
+ // Under compact.at (40%), so only the milestone brings the question.
2603
+ w.contextPercent = 30;
2532
2604
  on('session.compact', ($: any, e: any) => {
2533
2605
  compacted.push(e.instructions);
2534
2606
  // A compaction leaves the summary in the conversation's place.
@@ -2548,6 +2620,7 @@ test("LEAN-3 when a milestone finishes, the person is asked to compact the lead'
2548
2620
  w.refs += ' m1 refs/heads/main-moved\n';
2549
2621
  await clock.advance(2000);
2550
2622
  w.answer = 'Compact now';
2623
+ w.contextPercent = 62;
2551
2624
  await leadTurn($, 't-2');
2552
2625
  await settle(clock);
2553
2626
  expect(w.asked.at(-1)?.question).toMatch(
@@ -2566,6 +2639,39 @@ test("LEAN-3 when a milestone finishes, the person is asked to compact the lead'
2566
2639
  expect(compacted).toHaveLength(1);
2567
2640
  });
2568
2641
 
2642
+ test("LOOP-13 a review the lead's turn didn't move isn't prompted again while other lanes commit, whatever the reply reads as", async ($, on) => {
2643
+ const w = project();
2644
+ w.stories.push({
2645
+ id: 'ST-160',
2646
+ title: 'Content security policy',
2647
+ lane: 'web',
2648
+ state: 'in review',
2649
+ branch: 'web/st-160',
2650
+ waitingOn: [],
2651
+ });
2652
+ w.refs += ' p1 refs/heads/web/st-160\n';
2653
+ w.next = { step: 'review', args: 'ST-160', then: [] };
2654
+ w.sideReply = unread;
2655
+ const clock = await start($, on, w);
2656
+ await leadTurn($);
2657
+ await settle(clock);
2658
+ expect(w.prompts).toEqual(['Review ST-160: run /code-kit:review ST-160.']);
2659
+ // The lead says it waits, the reading comes back done, and lanes keep committing.
2660
+ w.sideReply = outcome({ outcome: 'done' });
2661
+ for (const [i, turn] of ['t-2', 't-3', 't-4', 't-5'].entries()) {
2662
+ w.refs += ` x${i} refs/heads/core/st-${i}\n`;
2663
+ await leadSays($, turn, 'ST-160 waits on ST-165; nothing to review yet.');
2664
+ await settle(clock);
2665
+ w.sideReply = unread;
2666
+ }
2667
+ expect(w.prompts).toHaveLength(1);
2668
+ // Its own branch moves: it's prompted again.
2669
+ w.refs = w.refs.replace(' p1 refs/heads/web/st-160', ' p2 refs/heads/web/st-160');
2670
+ await leadTurn($, 't-6');
2671
+ await settle(clock);
2672
+ expect(w.prompts).toHaveLength(2);
2673
+ });
2674
+
2569
2675
  test('LOOP-13 stories the lead says wait on other work are parked, each until what it waits on moves', async ($, on) => {
2570
2676
  const w = project();
2571
2677
  w.stories.push(
@@ -2635,10 +2741,17 @@ test('LOOP-13 stories the lead says wait on other work are parked, each until wh
2635
2741
  await leadTurn($, 't-3');
2636
2742
  await settle(clock);
2637
2743
  expect(w.prompts).toHaveLength(asked);
2638
- // The story building on ST-158 commits: ST-158 wakes, and ST-172 still waits.
2744
+ // The story building on ST-158 commits: that alone doesn't wake it.
2639
2745
  w.refs = w.refs.replace(' s2 refs/heads/api/st-164', ' s9 refs/heads/api/st-164');
2640
2746
  await leadTurn($, 't-4');
2641
2747
  await settle(clock);
2748
+ expect(w.prompts).toHaveLength(asked);
2749
+ // It finishes: ST-158 wakes, and ST-172 still waits.
2750
+ w.stories = w.stories.map((s: any) => (s.id === 'ST-164' ? { ...s, state: 'in review' } : s));
2751
+ w.refs += ' s10 refs/heads/api/st-164-done\n';
2752
+ await clock.advance(2000);
2753
+ await leadTurn($, 't-5');
2754
+ await settle(clock);
2642
2755
  expect(w.prompts.at(-1)).toBe('Review ST-158: run /code-kit:review ST-158.');
2643
2756
  expect(w.prompts).toHaveLength(asked + 1);
2644
2757
  });
@@ -2850,6 +2963,17 @@ test('SET-4 with no lane model set, lane agents start as Claude Code decides', a
2850
2963
  expect(w.spawned[0].model ?? null).toBe(null);
2851
2964
  });
2852
2965
 
2966
+ test("LEAN-7 the security reviewer starts on the reviewers' model, Sonnet unless set, and its own file's model wins", async ($, on) => {
2967
+ const w = project();
2968
+ await start($, on, w);
2969
+ await dispatch($, 'security-reviewer');
2970
+ await dispatch($, 'security-reviewer', 'opus');
2971
+ w.agentFiles['/work/.claude/agents/security-reviewer.md'] =
2972
+ '---\nname: security-reviewer\nmodel: opus\n---\n';
2973
+ await dispatch($, 'security-reviewer');
2974
+ expect(w.spawned.map((e) => e.model ?? null)).toEqual(['sonnet', 'opus', null]);
2975
+ });
2976
+
2853
2977
  // --- the reviewer -----------------------------------------------------------------------------------
2854
2978
 
2855
2979
  /** ST-4 finished on web/st-4, the reviewer on. */
@@ -3351,6 +3475,10 @@ test('VIEW-6 tabs on 1 to 7, j and k move between lanes, and the selected lane h
3351
3475
  await press($, 'lane-next', PANE);
3352
3476
  await press($, 'lane-next', PANE);
3353
3477
  expect((await ui.find({ key: 'lane-web' }))?.text).toMatch(/^›/);
3478
+ // WATCH-1: w puts the lane agent's transcript in the main view.
3479
+ expect((await ui.find({ key: 'act-watch' }))?.props.hotkey).toBe('w');
3480
+ await press($, 'act-watch', PANE);
3481
+ expect(w.viewed).toEqual(['a1']);
3354
3482
  expect((await ui.find({ key: 'act-nudge' }))?.props.hotkey).toBe('n');
3355
3483
  await press($, 'act-nudge', PANE);
3356
3484
  expect(w.sent).toEqual([
@@ -3366,6 +3494,30 @@ test('VIEW-6 tabs on 1 to 7, j and k move between lanes, and the selected lane h
3366
3494
  await ui.unmount();
3367
3495
  });
3368
3496
 
3497
+ test('NOTIFY-1 a decision new since the last look raises one notification; what waited before, and notify.on false, raise none', async ($, on) => {
3498
+ const w = project();
3499
+ w.requests = [dayjs];
3500
+ const clock = await start($, on, w);
3501
+ await clock.advance(2000);
3502
+ // The approval waiting when the session first looked isn't news.
3503
+ expect(w.notified).toEqual([]);
3504
+ w.questions = [question()];
3505
+ w.refs += ' q1 refs/heads/web/st-4-q\n';
3506
+ await clock.advance(2000);
3507
+ await clock.advance(2000);
3508
+ expect(w.notified).toHaveLength(1);
3509
+ expect(w.notified[0].title).toBe('code-kit: the lead asks you');
3510
+ await clock.advance(2000);
3511
+ expect(w.notified).toHaveLength(1);
3512
+ // Turned off, a new decision raises nothing.
3513
+ (w.check as any).harness = { notify: { on: false } };
3514
+ w.questions = [question(), question({ id: 'Q-2', question: 'Which payment provider?' })];
3515
+ w.refs += ' q2 refs/heads/web/st-4-q2\n';
3516
+ await clock.advance(2000);
3517
+ await clock.advance(2000);
3518
+ expect(w.notified).toHaveLength(1);
3519
+ });
3520
+
3369
3521
  test('VIEW-6 s sends the selected story back, with the reason the person gives', async ($, on) => {
3370
3522
  const w = project();
3371
3523
  w.stories[0] = { ...w.stories[0], state: 'review' };
@@ -19,6 +19,9 @@ const { input, project, config, error } = start();
19
19
  // reported MAX_REPEATS times in a row means no progress: say so and let the finish through rather than
20
20
  // loop. A different problem means progress, and counts from one again.
21
21
  const MAX_REPEATS = 3;
22
+ // LEAN-1: the most a lane's hand-back holds.
23
+ const HAND_BACK_LINES = 10;
24
+ const HAND_BACK_CHARS = 1200;
22
25
  const memory = join(
23
26
  project,
24
27
  '.claude/state/stop-blocks',
@@ -97,9 +100,17 @@ if (actor.kind === 'lane' && changed.length) {
97
100
  // LEAN-1: the lead's context gets a short hand-back; the detail waits in a file it reads when it reviews.
98
101
  if (actor.kind === 'lane' && storyOfBranch(currentBranch(root))) {
99
102
  const report = reportPath(root);
100
- if (!existsSync(join(root, report)))
103
+ // Written in the lane's worktree, or by its absolute path in the main checkout: either is found.
104
+ if (!existsSync(join(root, report)) && !existsSync(join(project, report)))
101
105
  blockCounted(
102
106
  `Write your full report to ${report} before you finish: what you did and why, the spec section, the commands you ran with their output, what's left, and anything you need decided (with the choices, and any text you'd have pasted, such as a migration you can't write yet). Then finish with a short hand-back of at most ten lines: the story and branch, done or blocked, any decision the lead must make, and that report's path. Don't paste output or code into the hand-back; the lead reads the file when it reviews.`,
103
107
  );
108
+ // The hand-back is what reaches the lead's context: a few lines, the detail left in the report.
109
+ const handBack = String(input.last_assistant_message ?? '').trim();
110
+ const lines = handBack.split('\n').filter((l) => l.trim()).length;
111
+ if (lines > HAND_BACK_LINES || handBack.length > HAND_BACK_CHARS)
112
+ blockCounted(
113
+ `Your hand-back is ${lines} lines and ${handBack.length} characters, and all of it goes into the lead's context. Finish again with at most ${HAND_BACK_LINES} short lines: the story and branch, done or blocked, any decision the lead must make, and ${report}. Don't restate the report: the lead reads it when it reviews.`,
114
+ );
104
115
  }
105
116
  passed();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warren-dean/code-kit",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Keeps AI coding agents inside your project's rules: lanes, layer rules, protected paths and proof before finish, enforced by Claude Code hooks and in CI",
5
5
  "license": "MIT",
6
6
  "author": "Warren Langeveldt",
@@ -48,6 +48,8 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" team claim ST-<n> --lease "<glob>,
48
48
 
49
49
  The lease is the paths the story will create or change: its lane's folders for this feature, and any contract it changes. A claim someone else holds is refused with who holds it; pick another story or ask them. Stories in a lane another person owns are theirs to dispatch. Making the story's branch also claims it, and each commit on it adds what it changed to the lease; merging it ends the claim.
50
50
 
51
+ One agent per story: never send a running lane agent a second story, or a send-back. A story that goes on (sent back, or handed back at its context limit) gets a fresh agent: first run `node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" worktrees release <branch>`, which keeps the branch's commits and moves its report to the main checkout, then start the agent as below with the brief and "Read your earlier report first: `.claude/state/reports/<branch with / as _>.md` in the main checkout". A long-lived agent rereads its whole context on every step, which is most of what a build costs.
52
+
51
53
  Start each story with the Agent tool: `subagent_type` is the lane's agent, `isolation: "worktree"`, and run them in the background, with a `description` that starts with the story's id ("ST-12 booking form"): the loop reads it to know the story is in flight, and won't ask for it again while the agent works. Start every story in the batch in the same message so they run in parallel.
52
54
 
53
55
  The brief is the agent's whole context. Write it so the agent needs nothing else:
@@ -102,7 +102,8 @@ Draft, alongside the config:
102
102
  - its layers and what they may depend on;
103
103
  - the specs it reads;
104
104
  - the command that proves its work;
105
- - its lane rules.
105
+ - its lane rules: the rules the docs set for this lane, and anything the project adds to code-kit's own (its principles and architecture docs, its own words for what's banned). These are the project's, so they stay outside code-kit's part.
106
+ - Leave out the template's `Always:` list: it's code-kit's part, which `code-kit instructions --write` adds once the config is written, between its markers.
106
107
  - If an agent already exists, keep its content and add only what's missing.
107
108
  - Drop the spec-check lines if the config has no `docs.specs`.
108
109
  - **A security reviewer:** write `.claude/agents/security-reviewer.md` from `${CLAUDE_PLUGIN_ROOT}/templates/security-reviewer.md`, in the same approval as the lane agents, whenever the product handles sign-in, personal data, payments, input from outside (forms, APIs, webhooks, uploads) or secrets. That's almost every product; leave it out only for one with none of these, such as a static site or a local tool with no accounts, and say why. It is a reviewer, not a lane: no entry in `lanes`, no paths, and only read-only tools (Read, Grep, Glob, and Bash for commands that change nothing), so the hooks treat it as read-only too. Fill it in from the docs (or the code, for brownfield):
@@ -111,7 +112,7 @@ Draft, alongside the config:
111
112
  - the privacy and data residency targets in the principles;
112
113
  - the project's audit commands (for example `npm audit --omit=dev`, `pip-audit`), each run once now; leave out one that isn't installed, and say so.
113
114
  - If the agent already exists, keep its content and add only what's missing.
114
- - **CLAUDE.md:** add the section from `${CLAUDE_PLUGIN_ROOT}/templates/claude-md.md`.
115
+ - **CLAUDE.md:** code-kit's section is rendered from the config, not written by hand: once the config is written, `node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" instructions --write` adds it between its markers (`<!-- code-kit:begin vN -->` … `<!-- code-kit:end -->`). Anything the project wants to say beyond it (its layer order in words, its docs, its own rules) goes in a section of its own, outside the markers, so a later code-kit never touches it.
115
116
  - **Project skills, with the agents** (recipes like "new screen", "new migration" or "new endpoint"): one per recipe in the architecture's `## Recipes`, or any other doc that describes one step by step. Draft them now, alongside the agents, and show them in the same approval; the agents and their skills are one piece of work. Brownfield code without recipes in its docs: propose recipes from the patterns the code already follows (how its existing screens, endpoints or migrations are built), as drafts the person approves. Never write an empty one.
116
117
  - Write each as `.claude/skills/<name>/SKILL.md`, saying in its description which recipe in which doc it comes from, so an update can find it again.
117
118
  - List it under **Skills to use** on every lane agent whose paths the recipe touches, not only one: a recipe that adds a migration and the endpoint that uses it belongs to both lanes. A recipe only the lead follows goes in the CLAUDE.md section instead.
@@ -154,6 +155,7 @@ Re-run whenever the build has moved on: new apps or packages, folders that moved
154
155
 
155
156
  1. **Re-read** the docs and the repository as in section 1, including the brownfield steps if the code has grown past the docs.
156
157
  2. **Draft** the whole config again into `.claude/code-kit.draft.json`, starting from the live one. Keep everything that's still right, so the diff shows only real changes. If the live config has no `harness.autonomy`, ask the person about it as in section 2.
158
+ Bring code-kit's own instructions up to date: run `code-kit instructions`. Parts between markers are rewritten with `--write`. A part written before the markers (CLAUDE.md's `## Enforced, not advised` section, an agent's `Always:` list) is merged once: replace it with what `code-kit instructions show lead` or `show lane` prints, and move every line the project wrote itself into a section of its own outside the markers. Show the person the difference with the rest.
157
159
  A config without `"setup": 3` predates what setup now includes; add whichever of these the project is missing, as section 2 and 3 describe, and set `"setup": 3`:
158
160
  - project skills for the recipes in the docs (or, without recipes, the patterns the code follows), listed on the lane agents;
159
161
  - the design gate for every lane that owns screens, with its register; and design stories in the plan for screens not yet built (ask the person before adding stories);
@@ -26,6 +26,7 @@ It returns `step`, `why`, `args`, `then` (steps that can follow straight after)
26
26
  | Config, but no specs, plan or stories | `spec-design` |
27
27
  | A companion the config wants, not set up yet | `setup`: run that tool's init skill (`args`), before code |
28
28
  | An older setup missing what init now adds, nothing in flight | `update`: re-run init, before any new story |
29
+ | code-kit's instructions behind this code-kit | `instructions`: bring CLAUDE.md and the agents up to date |
29
30
  | A lane waits on a request you decide | `decide`: approve it, or answer with an alternative |
30
31
  | Stories finished on their branches | `review`, then `dispatch` what's ready |
31
32
  | Design stories ready (`**Lane:** design`) | `design`, then `dispatch` what needs no design |
@@ -43,6 +44,7 @@ Run the named skill with the Skill tool: `code-kit:<step>`, with `args` (and the
43
44
 
44
45
  - **`setup`:** run the skill named in `args` (for example `context-graph:init`); it's another plugin's, so call it by that full name.
45
46
  - **`update`:** run `code-kit:init`. `why` names what the setup is missing (the design gate, design stories, a QA lane, a security reviewer); init adds those, shows the person the difference, and sets `"setup": 3`. Until then no new story starts; work already started goes on to review and merge.
47
+ - **`instructions`:** there's no skill to run. With `args` `write`, run `code-kit instructions --write`. With `merge`, some files were written before code-kit marked its part: run `code-kit instructions` to list them, replace each old part with what `code-kit instructions show lead` (CLAUDE.md) or `show lane` (an agent file) prints, keep every line the project wrote itself outside the markers, then run `--write` for the rest. Commit it on a branch and merge it like your own stories.
46
48
  - **`lead`:** there's no skill to run. Build each story yourself, on the branch `lead/st-<n>`: spec-check it first if it cites requirements, do the work, commit, then run `code-kit:review` on the branch like any other. A spike's result goes in the docs it names.
47
49
  - **`wait`:** say which stories are being built and on which branches, then stop. When a lane agent finishes, its notification arrives in this session; run this skill again then. If a story has been in progress with no agent running (the session ended, or the agent stopped without committing), dispatch it again.
48
50
  - **`done`:** give the `status` summary in a few lines, then offer to run spec-design for the next milestone or feature.
@@ -38,7 +38,7 @@ Any problem it reports means the branch goes back. Don't fix a lane's code yours
38
38
 
39
39
  ## 3. Check the work against the story
40
40
 
41
- Read the story, every requirement it cites, and the full diff (`git diff <base>...<branch>`). Read each changed file in full where the diff alone doesn't show the behaviour. Then, for each requirement:
41
+ Read the story and every requirement it cites. Then start from what's already been worked out, not the whole diff: the lane's report (`.claude/state/reports/<branch with / as _>.md`, in its worktree or the main checkout), the reviewer's findings when the background reviewer ran, and `git diff --stat <base>...<branch>`. Open the diff one file at a time (`git diff <base>...<branch> -- <file>`) for the files each requirement depends on and the files a finding names, and a changed file in full only where its diff doesn't show the behaviour. Everything you open stays in your context for the rest of the build, so open what the verdict needs. Then, for each requirement:
42
42
 
43
43
  | Requirement | Acceptance criterion | Built (where) | Test (which) | Verdict |
44
44
  | ----------- | -------------------- | ------------- | ------------ | ------- |
@@ -84,7 +84,7 @@ Report the table, `verify`'s result, the security review's findings (or that non
84
84
  node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" sent-back <branch> --reason "<the problems, in one line>"
85
85
  ```
86
86
 
87
- Send the list to the same lane agent if it's still running (SendMessage), or dispatch the story again with the list added to the brief. Set the story's status to `in progress`.
87
+ Dispatch the story again to a fresh agent with the list added to the brief (`/code-kit:dispatch`, which first releases the branch's worktree), never to the agent that built it: its context is already large. Set the story's status to `in progress`.
88
88
 
89
89
  - **Ask the person.** When the right answer is a product or scope decision (the spec is silent, or the lane found a real conflict), put the question to the person with your recommendation. Don't merge around it.
90
90
 
@@ -12,7 +12,7 @@ The project's specs are in the folder named by `docs.specs` in `.claude/code-kit
12
12
  1. **Find the section.** The task should name its requirement IDs (`BOOK-4`, headings in the specs) or a spec section. If it doesn't, search the specs folder for the domain nouns. Read the whole section, not just the matching lines, plus every section it references.
13
13
  2. **Read the principles.** Re-read the parts of `docs.principles` that apply: layers, ports and providers, events, testing and the rules for agents. Note this lane's layer rules from `layers` in the config.
14
14
  3. **Make a checklist.** One line per requirement: behaviour, fields, error cases, permissions, states, tests.
15
- 4. **Compare with the code.** For each line, search for where it's implemented, then read those files in full.
15
+ 4. **Compare with the code.** For each line, search for where it's implemented, then read what implements it: the functions and types the requirement touches, not every file around them.
16
16
  5. **Report** as a table:
17
17
 
18
18
  | Requirement | Status (done / partial / missing / conflicts) | Where | Note |
@@ -18,8 +18,8 @@ Lane rules:
18
18
  Always:
19
19
 
20
20
  - Work on the branch the brief names (`<lane>/st-<n>`). Start every task with the spec-check skill and save its report to .claude/state/spec-check/<branch>.md; the path guard blocks code until it exists.
21
- - Follow {{docs.principles}}, including the rules for AI agents. Hooks enforce ownership, layers and the project's checks; if one blocks you, fix the cause, never work around it.
22
- - Read every file you change in full, plus its imports and its callers.
21
+ - Follow `{{docs.principles}}`, including the rules for AI agents. Hooks enforce ownership, layers and the project's checks; if one blocks you, fix the cause, never work around it.
22
+ - Read every file you change in full. Of what it imports and what calls it, read what your change needs: the definitions it uses and the call sites it changes. A context tool's fresh card for a file stands for reading it whole; read the source where the card is missing, stale or not enough.
23
23
  - Search before you build; reuse existing services, providers, components and schemas.
24
24
  - Write only in your owned paths. For anything else, write a short change request for the lead and stop.
25
25
  - You answer to the lead, never the person. A question the spec doesn't settle (a product choice, UX the spec is silent on, an account): write it for the lead, with the choices and what each does, and carry on with what doesn't depend on it, or stop. The lead decides, or asks the person.
@@ -28,7 +28,9 @@ Always:
28
28
  - Prove each acceptance criterion with a test whose title names the requirement ID, e.g. `BOOK-4 …`.
29
29
  - Fixing a bug: first write a test that fails because of the bug, through the code that's already there, then fix it. `verify` runs your test without the fix, and it must fail on an assertion, not because it calls something the fix adds.
30
30
  - Never weaken a test to get to green: no deleting, skipping or focusing tests, cutting cases or assertions, or rewriting snapshots. The commit is refused without the person's approval. Fix the code; if the test itself is wrong, say why in a change request for the lead.
31
+ - Run the narrowest tests that prove each step, with failures only in the output (a quiet reporter, or `| tail -40` on failure); run the full suite once, before you commit. Long output stays in your context and is paid for on every step after it.
31
32
  - Commit your work on your branch before finishing; the stop hook runs the checks and requires it.
32
33
  - Don't message the lead while you work: it hears from you once, when you finish. If you're blocked on something only the lead or the person can settle, finish and say so.
34
+ - One story per agent. If you're asked to wrap up because your context is large, finish the step you're on, commit, write your report and hand back: a fresh agent carries the story on from your report.
33
35
  - Before you finish, write your full report to `.claude/state/reports/<branch>.md` (the branch with `/` as `_`, as for spec-check): what you did and why, the spec section, the commands you ran with their output, what's left, and anything you need decided, with the choices and any text you'd otherwise paste. The stop hook requires it.
34
36
  - Finish with a short hand-back, at most ten lines: the story and branch, done or blocked, any decision the lead must make, and the report's path. No output or code in it: the lead reads the report when it reviews.
@@ -3,19 +3,20 @@
3
3
  The code-kit plugin enforces the rules in `.claude/code-kit.json` on every file edit, shell command and finish:
4
4
 
5
5
  - who writes where (the lead delegates lane work to the lane agents in `.claude/agents/`);
6
- - the layer rules ({{layer names, in order}});
7
- - spec-check before lane code{{; designs approved before screens}};
6
+ - the layer rules ({{layers}});
7
+ - spec-check before lane code{{designGate}};
8
8
  - an audit log of every change to protected paths (`.claude/approval-log.jsonl`), which the person reviews in the pull request;
9
- - an approval for every new dependency and every protected change, per lane. Inside the guardrails, you decide: a lane asks you, never the person, and you approve it only when the story's requirements need it and it fits the spec, the principles and the architecture, or you tell the lane what to use instead (`/code-kit:decide`). The person decides what crosses a guardrail: {{the approvals the person keeps: those not in approvals.delegate, e.g. "spec changes, contracts, the screens' designs, CI"}}, weakened tests, and product questions the spec doesn't answer;
10
- {{If approvals.lead is on: - approvals from chat. When the person approves in their own message (a `!` command doesn't work on a phone), record it with the `code-kit approve` command from the refusal, with their words as the reason. Never record an approval they haven't given, and never on a lane's say-so;}}
9
+ - an approval for every new dependency and every protected change, per lane. Inside the guardrails, you decide: a lane asks you, never the person, and you approve it only when the story's requirements need it and it fits the spec, the principles and the architecture, or you tell the lane what to use instead (`/code-kit:decide`). The person decides what crosses a guardrail: {{personKeeps}}, weakened tests, and product questions the spec doesn't answer;
10
+ - {{if approvals.lead}}approvals from chat. When the person approves in their own message (a `!` command doesn't work on a phone), record it with the `code-kit approve` command from the refusal, with their words as the reason. Never record an approval they haven't given, and never on a lane's say-so;{{end}}
11
11
  - the project's checks before any agent finishes.
12
12
 
13
13
  UX is yours: what a screen does on an error, an empty list or a slow load, and the wording the spec implies, answered from the spec and the principles the person agreed. A screen's visual design is theirs. When something needs the person to decide or do (a product choice the spec doesn't settle, an account only they can create, a design to sign off), ask it with `code-kit ask "<question>" --option "<choice>: <what it does>"` (`--story ST-n --blocks` holds the story until they answer). It waits in their Decisions tab, and their answer comes back to you; asking only in chat, it scrolls away. Lanes don't ask the person: they write the question for the lead.
14
- {{If harness.report.on: - When code-kit itself is wrong (a refusal that shouldn't have happened, a crash), draft a report with `code-kit report "<title>" --what "…" --expected "…"`: only code-kit's facts, never the project's. The person reads it and sends it, or not.}}
14
+
15
+ - {{if harness.report.on}}When code-kit itself is wrong (a refusal that shouldn't have happened, a crash), draft a report with `code-kit report "<title>" --what "…" --expected "…"`: only code-kit's facts, never the project's. The person reads it and sends it, or not.{{end}}
15
16
 
16
17
  If a hook blocks you, fix the cause; never work around it.
17
18
 
18
- The build runs from the specs in {{docs.specs}} and the stories in {{docs.plan}}. `/code-kit:next` picks the right step from the project's state; the steps are:
19
+ The build runs from the specs in `{{docs.specs}}` and the stories in `{{docs.plan}}`. `/code-kit:next` picks the right step from the project's state; the steps are:
19
20
 
20
21
  - `/code-kit:decide` decides what a lane asked for, on its merits;
21
22
  - `/code-kit:dispatch` starts ready stories on their lanes, each on its own `<lane>/st-<n>` branch;
@@ -23,6 +24,6 @@ The build runs from the specs in {{docs.specs}} and the stories in {{docs.plan}}
23
24
  - `/code-kit:status` shows what's done, in review, ready and blocked, and what's untested;
24
25
  - `/code-kit:spec-design` changes the specs when a decision changes. It updates the spec, the decision log and the plan together.
25
26
 
26
- {{If the CI workflow is installed: pull requests run `code-kit verify`, and it must pass before a person merges to a protected branch.}}
27
+ {{if ci}}Pull requests run `code-kit verify`, and it must pass before a person merges to a protected branch.{{end}}
27
28
 
28
29
  Changing the rules means editing `.claude/code-kit.json` (the lead only), then running the `code-kit:check` skill.