@warren-dean/code-kit 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +8 -0
- package/README.md +2 -1
- package/bin/code-kit.mjs +77 -3
- package/hooks/guard-bash.mjs +5 -0
- package/hooks/lib/next.mjs +13 -3
- package/hooks/mod/decisions.mjs +1 -1
- package/hooks/mod/loop.mjs +7 -3
- package/hooks/mod/register.test.ts +20 -7
- package/hooks/mod/view.mjs +5 -7
- package/package.json +1 -1
|
@@ -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.8.
|
|
12
|
+
"version": "0.8.1",
|
|
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.8.
|
|
3
|
+
"version": "0.8.1",
|
|
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,14 @@
|
|
|
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.8.1 (2026-10-11)
|
|
6
|
+
|
|
7
|
+
Fixes for projects set up before 0.8.0.
|
|
8
|
+
|
|
9
|
+
- **No first merge for you here either.** A project set up before 0.8.0 has no code-kit rules on `main`, so its first merge still waited on you. Now the lead puts the rules you approved onto `main` itself (`code-kit queue rules`, a step the loop takes), without switching branches, and then merges the queue under them.
|
|
10
|
+
- **Waiting on the merge queue isn't a broken plan.** Stories that depend on branches waiting to merge were reported as a dependency cycle, sending the build back to spec design. `next` now says they wait for the merge.
|
|
11
|
+
- The band's key for decisions reads "d: Why, and what each choice does", not "d: d Why".
|
|
12
|
+
|
|
5
13
|
## 0.8.0 (2026-10-11)
|
|
6
14
|
|
|
7
15
|
You decide what's built and the rules; the lead delivers it. Only real decisions reach you, each with the story behind it, and the whole build reads as one story from your request to now.
|
package/README.md
CHANGED
|
@@ -274,7 +274,7 @@ With Claude Code 2.1.287 or later, code-kit's mod shows the project in the sessi
|
|
|
274
274
|
|
|
275
275
|
- **The band** sits above the prompt. While the build is moving (the lead mid-turn, an agent at work, the loop started) it opens with the mission: **goal**, the milestone being built and how many of its stories are done; **now**, what the lead is doing and the loop's state, with **Pause** or **Resume**; **next**, the step the loop takes next; and **agents**, a line per agent at work with its current tool and how long since. With Context Graph installed, a line says when a lane edited files without understanding them, and only then. Beneath, labelled **you**, what only you can decide, each with the story it's for ("for ST-4 Booking form (BOOK-1)"), and **d** ("Why, and what each choice does") opens it as a card in the Decisions tab; usage and the plan's pause are labelled **note**. While the loop runs, a lane's finished work is the lead's to review and merge, so it never waits here; with the loop off, Review and Merge are offered to you. With nothing happening and nothing waiting, the band is gone. It shows:
|
|
276
276
|
- an agent's call held for you: installing a new package, writing or committing a protected file, or a lane changing the kit. Rather than stopping the agent, the call waits while the band asks ("web wants to install dayjs · 1:53"). **Approve** opens the confirmation and, once you confirm, the same call runs; **Refuse**, or no answer within `harness.hold.minutes` (2 unless set), refuses it as before. Under `autonomy: autonomous`, a call within `approvals.delegate` is approved as delegated without asking. Nothing is held under `claude -p` or the SDK, where nobody can answer;
|
|
277
|
-
- the lead loop, under `harness.autonomy`. `/code-kit:init` asks you how hands-off you want it. Until you've chosen, the loop's 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. Once the lead has finished a turn, the loop keeps the build moving: with the lead idle and nothing typed in the prompt, it asks `code-kit next` and prompts the lead to dispatch the ready stories, review a finished one, or build its own, never the same step twice while nothing has changed. A step the lead was prompted with twice in a row and that is still next goes to you instead, in the band: "Merge lead/st-1: still next after 2 prompts. Ask the lead again?" with **Go**. **Pause** and **Resume** stop and start it. Under `propose`, each step waits in the band for **Go**; under `off`, the loop does nothing. A lane agent with no tool call for `harness.stall.nudgeMinutes` (5) is messaged, unless its work is done (it handed back, or its branch is in review or the merge queue); at `stall.restartMinutes` (10) the lead is asked to stop it and dispatch its story again, at most `stall.maxRestarts` (2) times, and then the band flags the lane. When every story is done, the band says so and the loop stops. With `harness.agents.reviewer.on`, a finished branch is first reviewed in the background by code-kit's reviewer agent, which the lead starts: it runs `verify` and the review checklist, read-only, and grades each finding blocker, concern or nit. The lead's review prompt then carries the findings, and a blocker means the branch is sent back. The pane shows each review, running with its clock or what it found. If the reviewer hasn't reported within 15 minutes, or the plan's 5-hour use is past `harness.background.pauseAtPercent`, the lead reviews without it. A branch the lead's review passes joins the merge queue (`code-kit queue`). Branches merge one at a time, in the order they passed, each verified against the base as it is then: the lead merges where `approvals.delegate.merge` is on under `autonomous`, otherwise the band shows the head with **Merge** for you. Who may merge is read from the config committed on the base, so a branch can't grant itself merges. There's no first merge for you: setting up commits the rules you approved straight onto the base. A merged lane's worktree is removed when it holds nothing uncommitted. A head that conflicts or fails verify goes back to its lane. The pane lists the queue, and lanes whose branch waits in it read `queued`;
|
|
277
|
+
- the lead loop, under `harness.autonomy`. `/code-kit:init` asks you how hands-off you want it. Until you've chosen, the loop's 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. Once the lead has finished a turn, the loop keeps the build moving: with the lead idle and nothing typed in the prompt, it asks `code-kit next` and prompts the lead to dispatch the ready stories, review a finished one, or build its own, never the same step twice while nothing has changed. A step the lead was prompted with twice in a row and that is still next goes to you instead, in the band: "Merge lead/st-1: still next after 2 prompts. Ask the lead again?" with **Go**. **Pause** and **Resume** stop and start it. Under `propose`, each step waits in the band for **Go**; under `off`, the loop does nothing. A lane agent with no tool call for `harness.stall.nudgeMinutes` (5) is messaged, unless its work is done (it handed back, or its branch is in review or the merge queue); at `stall.restartMinutes` (10) the lead is asked to stop it and dispatch its story again, at most `stall.maxRestarts` (2) times, and then the band flags the lane. When every story is done, the band says so and the loop stops. With `harness.agents.reviewer.on`, a finished branch is first reviewed in the background by code-kit's reviewer agent, which the lead starts: it runs `verify` and the review checklist, read-only, and grades each finding blocker, concern or nit. The lead's review prompt then carries the findings, and a blocker means the branch is sent back. The pane shows each review, running with its clock or what it found. If the reviewer hasn't reported within 15 minutes, or the plan's 5-hour use is past `harness.background.pauseAtPercent`, the lead reviews without it. A branch the lead's review passes joins the merge queue (`code-kit queue`). Branches merge one at a time, in the order they passed, each verified against the base as it is then: the lead merges where `approvals.delegate.merge` is on under `autonomous`, otherwise the band shows the head with **Merge** for you. Who may merge is read from the config committed on the base, so a branch can't grant itself merges. There's no first merge for you: setting up commits the rules you approved straight onto the base, and in a project set up before that, the lead puts them there itself (`code-kit queue rules`) before its first merge. A merged lane's worktree is removed when it holds nothing uncommitted. A head that conflicts or fails verify goes back to its lane. The pane lists the queue, and lanes whose branch waits in it read `queued`;
|
|
278
278
|
- approvals agents have asked for: **Approve…** opens a confirmation with a reason prefilled from the request, which you keep or rewrite;
|
|
279
279
|
- stories finished and waiting for review: **Review** asks the lead to run `/code-kit:review` on the branch, and **Merge** runs `verify`, checks included, then merges if it passes;
|
|
280
280
|
- a finish check failing in this session: **Lanes** opens the pane;
|
|
@@ -346,6 +346,7 @@ code-kit settings [--json] # the harness setti
|
|
|
346
346
|
code-kit settings set <key> <value> --reason "…" # change one, as the person: written to the config and logged as a kit approval
|
|
347
347
|
code-kit queue [--json] # the merge queue: branches whose review passed, in order
|
|
348
348
|
code-kit queue add|drop <branch> # the lead queues a branch its review passed, or takes it out
|
|
349
|
+
code-kit queue rules # the lead puts the approved rules onto a base that has none yet (a project set up before 0.8.0)
|
|
349
350
|
code-kit queue merge --delegated|--person # merge the head, verified against the base as it is now; a conflict or failed verify sends it back to its lane
|
|
350
351
|
code-kit loop [--json] # whether the person paused the lead loop; other tools' background work pauses with it
|
|
351
352
|
code-kit loop pause|resume # the person's act, as Pause and Resume in the band
|
package/bin/code-kit.mjs
CHANGED
|
@@ -336,9 +336,82 @@ function configAt(ref) {
|
|
|
336
336
|
return config ?? null;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
/**
|
|
340
|
+
* SETUP-2: a project set up before setup committed its rules onto the base has none there, so nothing
|
|
341
|
+
* could be merged by the lead. `queue rules` commits the rules as committed on this branch (the ones the
|
|
342
|
+
* person approved) onto the base, as setup now does, without checking the base out: a commit of the base
|
|
343
|
+
* with those files changed, the base moved to it. Only while the base has no rules, and only the lead.
|
|
344
|
+
*/
|
|
345
|
+
function rulesOntoBase(root, config) {
|
|
346
|
+
const into = intoName ?? config.branches.protected[0];
|
|
347
|
+
const git = (args, opts = {}) =>
|
|
348
|
+
execFileSync('git', ['-C', root, ...args], { encoding: 'utf8', ...opts }).trim();
|
|
349
|
+
const head = git(['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
350
|
+
if (head === into)
|
|
351
|
+
die(
|
|
352
|
+
`You're on ${into}: commit ${file} here as setup does (\`git add ${file} && git commit\`), which the hooks allow while ${into} has no rules.`,
|
|
353
|
+
);
|
|
354
|
+
if (configAt(into))
|
|
355
|
+
return out(
|
|
356
|
+
`${into} already has code-kit's rules committed; the merge queue merges under them.`,
|
|
357
|
+
);
|
|
358
|
+
if (!configAt('HEAD'))
|
|
359
|
+
die(
|
|
360
|
+
`${head} has no ${file} committed. Commit the rules the person approved first, then run this again.`,
|
|
361
|
+
);
|
|
362
|
+
const used = git(['worktree', 'list', '--porcelain'])
|
|
363
|
+
.split('\n')
|
|
364
|
+
.some((l) => l === `branch refs/heads/${into}`);
|
|
365
|
+
if (used)
|
|
366
|
+
die(
|
|
367
|
+
`${into} is checked out in a worktree, so moving it here would leave that checkout behind. Commit ${file} in that checkout instead (the hooks allow it while ${into} has no rules).`,
|
|
368
|
+
);
|
|
369
|
+
// The rules, and the baseline of existing violations they were approved with.
|
|
370
|
+
const files = [file, BASELINE_FILE].filter((f) => {
|
|
371
|
+
try {
|
|
372
|
+
execFileSync('git', ['-C', root, 'cat-file', '-e', `HEAD:${f}`], { stdio: 'ignore' });
|
|
373
|
+
return true;
|
|
374
|
+
} catch {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
const index = join(tmpdir(), `code-kit-rules-${process.pid}`);
|
|
379
|
+
const env = { ...process.env, GIT_INDEX_FILE: index };
|
|
380
|
+
try {
|
|
381
|
+
git(['read-tree', into], { env });
|
|
382
|
+
for (const f of files)
|
|
383
|
+
git(
|
|
384
|
+
['update-index', '--add', '--cacheinfo', `100644,${git(['rev-parse', `HEAD:${f}`])},${f}`],
|
|
385
|
+
{
|
|
386
|
+
env,
|
|
387
|
+
},
|
|
388
|
+
);
|
|
389
|
+
const tree = git(['write-tree'], { env });
|
|
390
|
+
const before = git(['rev-parse', into]);
|
|
391
|
+
const commit = git(
|
|
392
|
+
[
|
|
393
|
+
'commit-tree',
|
|
394
|
+
tree,
|
|
395
|
+
'-p',
|
|
396
|
+
before,
|
|
397
|
+
'-m',
|
|
398
|
+
`code-kit's rules onto ${into}, as the person approved them on ${head}\n\nFrom now on ${into}'s committed rules decide who merges into it.`,
|
|
399
|
+
],
|
|
400
|
+
{ env },
|
|
401
|
+
);
|
|
402
|
+
git(['update-ref', `refs/heads/${into}`, commit, before]);
|
|
403
|
+
} finally {
|
|
404
|
+
rmSync(index, { force: true });
|
|
405
|
+
}
|
|
406
|
+
const now = configAt(into);
|
|
407
|
+
out(
|
|
408
|
+
`Committed ${files.join(' and ')} onto ${into}, from ${head}. ${now?.approvals.delegate?.merge ? 'The lead merges the queue from now on (code-kit queue merge --delegated).' : `${into}'s rules don't delegate merges, so the person merges the queue.`}`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
339
412
|
/** Why the first merge into `into`, which has no config committed yet, is the person's. */
|
|
340
413
|
const firstMerge = (into, branch) =>
|
|
341
|
-
`${into} has no ${file} committed yet, so it delegates nothing
|
|
414
|
+
`${into} has no ${file} committed yet, so it delegates nothing. Put the approved rules onto it first: \`node "${process.argv[1]}" queue rules\`. Then merge ${branch} again; from then on ${into}'s committed rules decide who merges.`;
|
|
342
415
|
|
|
343
416
|
/** Why a merge didn't happen: `refused` (the rules), `verify` (problems) or `conflict`. */
|
|
344
417
|
class MergeStop extends Error {
|
|
@@ -527,6 +600,7 @@ function queue() {
|
|
|
527
600
|
const place = waiting(readQueue(root)).length;
|
|
528
601
|
return out(`Queued ${branch} to merge${place > 1 ? `, ${place - 1} ahead of it` : ', next'}.`);
|
|
529
602
|
}
|
|
603
|
+
if (sub === 'rules') return rulesOntoBase(root, config);
|
|
530
604
|
if (sub === 'merge') {
|
|
531
605
|
const byPerson = mergeMode();
|
|
532
606
|
const entries = readQueue(root);
|
|
@@ -561,7 +635,7 @@ function queue() {
|
|
|
561
635
|
}
|
|
562
636
|
if (sub !== undefined)
|
|
563
637
|
die(
|
|
564
|
-
'code-kit queue [--json] | queue add <branch> | queue drop <branch> | queue merge --delegated|--person',
|
|
638
|
+
'code-kit queue [--json] | queue add <branch> | queue drop <branch> | queue rules | queue merge --delegated|--person',
|
|
565
639
|
);
|
|
566
640
|
const entries = readQueue(root);
|
|
567
641
|
if (flag('--json')) return out(JSON.stringify(entries, null, 2));
|
|
@@ -1194,7 +1268,7 @@ function usage() {
|
|
|
1194
1268
|
' | stops [--session id] [--json] | sent-back <branch> [--reason "…"]\n' +
|
|
1195
1269
|
' | settings [--json] | settings set <key> <value> --reason "…" [--via pane]\n' +
|
|
1196
1270
|
' | hold <id> [--minutes N] | hold <id> --answer approved|refused\n' +
|
|
1197
|
-
' | queue [--json] | queue add|drop <branch> | queue merge --delegated|--person\n' +
|
|
1271
|
+
' | queue [--json] | queue add|drop <branch> | queue rules | queue merge --delegated|--person\n' +
|
|
1198
1272
|
' | loop [--json] | loop pause|resume\n' +
|
|
1199
1273
|
' | map <story> [--json] | trail [--json] | journal [--json]\n' +
|
|
1200
1274
|
' | story <id> [--json] | timeline [--json]',
|
package/hooks/guard-bash.mjs
CHANGED
|
@@ -74,6 +74,7 @@ const packages = addedPackages(cmd);
|
|
|
74
74
|
const approves = runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+approve\b`));
|
|
75
75
|
const delegatedMerge = runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+(?:queue\s+)?merge\b`));
|
|
76
76
|
const queues = runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+queue\s+(?:add|drop)\b`));
|
|
77
|
+
const rulesOntoBase = runs(cmd, new RegExp(String.raw`^${CODE_KIT}\s+queue\s+rules\b`));
|
|
77
78
|
const commits = runs(cmd, new RegExp(String.raw`^${GIT}commit\b`));
|
|
78
79
|
if (error) {
|
|
79
80
|
if (commits) block(`Blocked: ${error}\nFix the config before committing.`);
|
|
@@ -174,6 +175,10 @@ if (queues && actor.kind !== 'lead')
|
|
|
174
175
|
block(
|
|
175
176
|
`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}`,
|
|
176
177
|
);
|
|
178
|
+
if (rulesOntoBase && actor.kind !== 'lead')
|
|
179
|
+
block(
|
|
180
|
+
`Blocked: only the lead puts code-kit's rules onto the protected branch. Finish your story and commit it on your branch; the lead takes it from there.\nCommand: ${cmd}`,
|
|
181
|
+
);
|
|
177
182
|
if (delegatedMerge && actor.kind !== 'lead')
|
|
178
183
|
block(
|
|
179
184
|
`Blocked: only the lead merges into a protected branch. Finish your story and commit it on your branch; the lead reviews and merges it.\nCommand: ${cmd}`,
|
package/hooks/lib/next.mjs
CHANGED
|
@@ -180,11 +180,15 @@ export function nextStep(root, base) {
|
|
|
180
180
|
// the person does, and always the first merge, which brings code-kit's rules onto the base.
|
|
181
181
|
const delegation = queued.length ? mergeDelegation(root, config) : { delegated: false };
|
|
182
182
|
const leadMerges = delegation.delegated && config.harness.autonomy === 'autonomous';
|
|
183
|
+
// SETUP-2: a base with no rules committed (a project set up before setup committed them there): the
|
|
184
|
+
// lead puts the approved rules onto it, and the queue merges under them from then on.
|
|
183
185
|
if (queued.length && delegation.first)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
+
return result(
|
|
187
|
+
'rules',
|
|
188
|
+
`${queued[0]} passed review and is next to merge, but ${delegation.into} has no code-kit rules committed yet. Put the rules the person approved onto it (code-kit queue rules); then the queue merges under them.`,
|
|
189
|
+
{ args: delegation.into },
|
|
186
190
|
);
|
|
187
|
-
|
|
191
|
+
if (queued.length && !leadMerges)
|
|
188
192
|
attention.push(
|
|
189
193
|
`${queued.join(', ')} passed review and ${queued.length === 1 ? 'waits' : 'wait'} for the person to merge (code-kit queue merge --person, or Merge in the code-kit band).`,
|
|
190
194
|
);
|
|
@@ -227,6 +231,12 @@ export function nextStep(root, base) {
|
|
|
227
231
|
'wait',
|
|
228
232
|
`${building.join(', ')} ${building.length === 1 ? 'is' : 'are'} being built; nothing else can start until one finishes. A lane that stopped without finishing is dispatched again.`,
|
|
229
233
|
);
|
|
234
|
+
// Stories waiting on branches in the merge queue wait for the merge, not on a broken plan.
|
|
235
|
+
if (queued.length)
|
|
236
|
+
return result(
|
|
237
|
+
'wait',
|
|
238
|
+
`${queued.join(', ')} passed review and ${queued.length === 1 ? 'waits' : 'wait'} to be merged; the stories that depend on ${queued.length === 1 ? 'it' : 'them'} start after.`,
|
|
239
|
+
);
|
|
230
240
|
if (unbuildable.length)
|
|
231
241
|
return result(
|
|
232
242
|
'spec-design',
|
package/hooks/mod/decisions.mjs
CHANGED
|
@@ -50,7 +50,7 @@ function whyOf(line, check) {
|
|
|
50
50
|
if (line.kind === 'proposal') return 'You asked to approve each step before the lead takes it.';
|
|
51
51
|
if (line.kind === 'queue')
|
|
52
52
|
return check?.firstMerge
|
|
53
|
-
? 'main has no rules committed yet
|
|
53
|
+
? 'The loop is off, so merging is yours. main has no rules committed yet: code-kit queue rules puts them there.'
|
|
54
54
|
: "Your rules don't let the lead merge into main, so merges are yours.";
|
|
55
55
|
if (line.kind === 'review') return 'The loop is off, so reviewing finished work is yours.';
|
|
56
56
|
return null;
|
package/hooks/mod/loop.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// existing skills, and the hooks judge every call as before.
|
|
4
4
|
|
|
5
5
|
/** The steps the loop hands the lead; the rest (spec-design, init, check) wait for the person. */
|
|
6
|
-
const LEAD_STEPS = new Set(['dispatch', 'review', 'lead', 'merge']);
|
|
6
|
+
const LEAD_STEPS = new Set(['dispatch', 'review', 'lead', 'merge', 'rules']);
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* The prompt for a step from `code-kit next --json` (LOOP-1), or null when it isn't the lead's to
|
|
@@ -19,6 +19,9 @@ export function leadPrompt(step, { cli = 'code-kit' } = {}) {
|
|
|
19
19
|
return ids.length
|
|
20
20
|
? `Merge ${ids[0]}, next in the merge queue: run node "${cli}" queue merge --delegated. It verifies the branch against the base as it is now, and sends it back to its lane if it conflicts or fails.`
|
|
21
21
|
: null;
|
|
22
|
+
// SETUP-2: the base has no rules yet; the lead puts the approved ones there, then merges as usual.
|
|
23
|
+
if (step.step === 'rules')
|
|
24
|
+
return `Put code-kit's approved rules onto ${ids[0] ?? 'the base'}: run node "${cli}" queue rules. It commits the rules as committed on this branch, without switching branches; after it, merge the queue as usual.`;
|
|
22
25
|
if (step.step === 'dispatch')
|
|
23
26
|
return ids.length
|
|
24
27
|
? `Dispatch ${ids.join(', ')}: run /code-kit:dispatch ${ids.join(' ')}.`
|
|
@@ -40,8 +43,9 @@ export const stepKey = (step) => `${step.step}:${step.args ?? ''}`;
|
|
|
40
43
|
export function stepLabel(step) {
|
|
41
44
|
const ids = (step.args ?? '').split(/\s+/).filter(Boolean).join(', ');
|
|
42
45
|
const verb =
|
|
43
|
-
{ dispatch: 'Dispatch', review: 'Review', lead: 'Build', merge: 'Merge' }[
|
|
44
|
-
|
|
46
|
+
{ dispatch: 'Dispatch', review: 'Review', lead: 'Build', merge: 'Merge', rules: 'Rules onto' }[
|
|
47
|
+
step.step
|
|
48
|
+
] ?? step.step;
|
|
45
49
|
return ids ? `${verb} ${ids}` : verb;
|
|
46
50
|
}
|
|
47
51
|
|
|
@@ -2066,18 +2066,31 @@ test('MQ-2 where the person merges, the head of the queue waits in the band for
|
|
|
2066
2066
|
expect(w.opened).toContain(RESULT);
|
|
2067
2067
|
});
|
|
2068
2068
|
|
|
2069
|
-
test(
|
|
2069
|
+
test("SETUP-2 with no rules on the base yet, the lead puts them there under autonomy: the band doesn't ask the person to merge", async ($, on) => {
|
|
2070
2070
|
const w = project();
|
|
2071
2071
|
(w.check as any).firstMerge = true;
|
|
2072
2072
|
w.queue = [{ branch: 'lead/st-1', passed: '2026-10-10T00:00:00Z', state: 'waiting' }];
|
|
2073
|
+
w.next = { step: 'rules', args: 'main', then: [] };
|
|
2073
2074
|
await start($, on, w);
|
|
2074
2075
|
const ui = await bandUi($);
|
|
2075
|
-
expect(
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2076
|
+
expect(await ui.find({ type: 'Text', text: /next to merge/ })).toBeUndefined();
|
|
2077
|
+
await ui.unmount();
|
|
2078
|
+
});
|
|
2079
|
+
|
|
2080
|
+
test("SETUP-2 with the loop off, the merge is the person's, and its card says how the rules get onto main", async ($, on) => {
|
|
2081
|
+
const w = project();
|
|
2082
|
+
(w.check as any).firstMerge = true;
|
|
2083
|
+
(w.check as any).harness = { autonomy: 'off' };
|
|
2084
|
+
w.queue = [{ branch: 'lead/st-1', passed: '2026-10-10T00:00:00Z', state: 'waiting' }];
|
|
2085
|
+
await start($, on, w);
|
|
2086
|
+
const band = await bandUi($);
|
|
2087
|
+
expect(await band.find({ type: 'Text', text: 'lead/st-1 is next to merge' })).toBeDefined();
|
|
2088
|
+
await press($, 'band-decide');
|
|
2089
|
+
await band.unmount();
|
|
2090
|
+
const ui = await pane($, PANE);
|
|
2091
|
+
expect((await ui.find({ key: 'decision-card' }))?.text).toMatch(
|
|
2092
|
+
/code-kit queue rules puts them there/,
|
|
2093
|
+
);
|
|
2081
2094
|
await ui.unmount();
|
|
2082
2095
|
});
|
|
2083
2096
|
|
package/hooks/mod/view.mjs
CHANGED
|
@@ -798,15 +798,13 @@ export function bandLines({
|
|
|
798
798
|
});
|
|
799
799
|
// MQ-2: where the lead doesn't merge, the head of the queue waits for the person's Merge.
|
|
800
800
|
const next = queue.find((e) => ['waiting', 'merging'].includes(e.state));
|
|
801
|
-
const
|
|
802
|
-
|
|
801
|
+
const autonomous = (check?.harness?.autonomy ?? 'autonomous') === 'autonomous';
|
|
802
|
+
// SETUP-2: with no rules on the base yet, the lead puts them there first and then merges.
|
|
803
|
+
const leadMerges = autonomous && (check?.delegatesMerge || check?.firstMerge);
|
|
803
804
|
if (next && !leadMerges)
|
|
804
805
|
lines.push({
|
|
805
806
|
kind: 'queue',
|
|
806
|
-
|
|
807
|
-
text: check?.firstMerge
|
|
808
|
-
? `${next.branch} is next to merge: the first merge is yours, then the lead merges`
|
|
809
|
-
: `${next.branch} is next to merge`,
|
|
807
|
+
text: `${next.branch} is next to merge`,
|
|
810
808
|
branch: next.branch,
|
|
811
809
|
actions: [{ id: 'mergeQueue', label: 'Merge' }],
|
|
812
810
|
});
|
|
@@ -931,7 +929,7 @@ export function band(lines, { Box, Text, Button }, onAction) {
|
|
|
931
929
|
Text({ children: [''.padEnd(LABEL)] }),
|
|
932
930
|
Button({
|
|
933
931
|
key: 'band-decide',
|
|
934
|
-
label: '
|
|
932
|
+
label: 'Why, and what each choice does',
|
|
935
933
|
hotkey: 'd',
|
|
936
934
|
plain: true,
|
|
937
935
|
onPress: () => onAction('decide', first),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warren-dean/code-kit",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
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",
|