@warren-dean/code-kit 0.20.0 → 0.22.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +24 -0
- package/README.md +75 -50
- package/bin/code-kit.mjs +364 -25
- package/hooks/guard-agent.mjs +36 -0
- package/hooks/guard-bash.mjs +67 -13
- package/hooks/guard-paths.mjs +14 -2
- package/hooks/hooks.json +20 -0
- package/hooks/lane-audit.mjs +38 -1
- package/hooks/lib/adapters/hub/client.mjs +15 -0
- package/hooks/lib/checks.mjs +5 -1
- package/hooks/lib/config.mjs +149 -6
- package/hooks/lib/decisionstore.mjs +89 -0
- package/hooks/lib/harness.mjs +7 -0
- package/hooks/lib/hook.mjs +7 -0
- package/hooks/lib/instructions.mjs +46 -13
- package/hooks/lib/laneclaims.mjs +89 -0
- package/hooks/lib/layers.mjs +11 -0
- package/hooks/lib/loop-state.mjs +20 -2
- package/hooks/lib/next.mjs +34 -1
- package/hooks/lib/plan.mjs +3 -1
- package/hooks/lib/questions.mjs +23 -5
- package/hooks/lib/regression.mjs +2 -1
- package/hooks/lib/rules.mjs +20 -13
- package/hooks/lib/schedule.mjs +86 -0
- package/hooks/lib/sessions.mjs +223 -0
- package/hooks/lib/snapshot.mjs +80 -2
- package/hooks/lib/standards.mjs +450 -0
- package/hooks/lib/team.mjs +9 -2
- package/hooks/lib/verify.mjs +23 -1
- package/hooks/mod/decisions.mjs +1 -28
- package/hooks/mod/loop.mjs +8 -0
- package/hooks/mod/mission.mjs +21 -19
- package/hooks/mod/register.mjs +117 -40
- package/hooks/mod/register.test.ts +248 -73
- package/hooks/mod/review.mjs +1 -0
- package/hooks/mod/view.mjs +98 -47
- package/hooks/post-edit-check.mjs +8 -1
- package/hooks/session.mjs +24 -0
- package/hooks/stop-check.mjs +23 -3
- package/package.json +1 -1
- package/skills/decide/SKILL.md +1 -1
- package/skills/dispatch/SKILL.md +2 -2
- package/skills/init/SKILL.md +17 -3
- package/skills/lead/SKILL.md +28 -0
- package/skills/next/SKILL.md +2 -2
- package/skills/review/SKILL.md +2 -1
- package/skills/spec-design/SKILL.md +10 -7
- package/templates/agent.md +1 -0
- package/templates/claude-md.md +5 -1
- package/templates/plan.md +2 -1
package/hooks/mod/view.mjs
CHANGED
|
@@ -256,7 +256,7 @@ export function lanesPane(
|
|
|
256
256
|
: l.branch
|
|
257
257
|
? ` · ${l.branch}`
|
|
258
258
|
: ''
|
|
259
|
-
}`
|
|
259
|
+
}${l.story.slice ? ` · slice ${l.story.slice}` : ''}`
|
|
260
260
|
: l.name === LEAD
|
|
261
261
|
? 'the main session'
|
|
262
262
|
: (l.agent ?? '');
|
|
@@ -728,13 +728,25 @@ export function bandLines({
|
|
|
728
728
|
team = null,
|
|
729
729
|
questions = [],
|
|
730
730
|
brief = [],
|
|
731
|
-
denied = [],
|
|
732
731
|
reports = [],
|
|
732
|
+
working = false,
|
|
733
|
+
task = null,
|
|
734
|
+
elsewhere = [],
|
|
733
735
|
}) {
|
|
734
736
|
const lines = [];
|
|
735
|
-
// MISSION-1: the goal, now, next and the agents at work, above what needs the person.
|
|
736
|
-
|
|
737
|
+
// MISSION-1: the goal, now, next and the agents at work, above what needs the person. SESS-3: a working
|
|
738
|
+
// session's band holds only its own: no mission, queue, reviews or team.
|
|
739
|
+
const mission = working ? [] : missionLines({ state, loop, leadBusy, live, nextStep, combined });
|
|
737
740
|
lines.push(...mission);
|
|
741
|
+
// SESS-4: a working session's task, or that it records one before changing code.
|
|
742
|
+
if (working)
|
|
743
|
+
lines.push({
|
|
744
|
+
kind: 'task',
|
|
745
|
+
text: task
|
|
746
|
+
? `your task: ${task.title}${task.paths?.length ? ` · leasing ${task.paths.join(', ')}` : ''}`
|
|
747
|
+
: 'a working session: record your task before changing code (code-kit task "…" --touches …)',
|
|
748
|
+
actions: [],
|
|
749
|
+
});
|
|
738
750
|
if (notice)
|
|
739
751
|
lines.push({ kind: 'notice', text: notice, actions: [{ id: 'dismiss', label: 'Dismiss' }] });
|
|
740
752
|
if (!state || state.kind !== 'ok') return lines;
|
|
@@ -758,31 +770,15 @@ export function bandLines({
|
|
|
758
770
|
],
|
|
759
771
|
});
|
|
760
772
|
}
|
|
761
|
-
//
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
lines.push({
|
|
765
|
-
kind: 'denied',
|
|
766
|
-
text: `${denied.length > 1 ? `${denied.length} calls refused by Claude Code: ` : 'Claude Code refused '}${d.who}'s ${d.what}`,
|
|
767
|
-
denial: d,
|
|
768
|
-
denials: denied,
|
|
769
|
-
actions: [
|
|
770
|
-
...(d.command ? [{ id: 'runDenied', label: 'Put it in the prompt' }] : []),
|
|
771
|
-
{ id: 'deniedDone', label: 'Done it' },
|
|
772
|
-
{ id: 'deniedNo', label: 'Not now' },
|
|
773
|
-
],
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
// LOOP-3: a lane stalled past its restarts waits for the person.
|
|
773
|
+
// OWN-2: a call Claude Code's own permission check refused is the Lanes pane's activity, not a
|
|
774
|
+
// decision: the agent finds another way, or the lead asks the person (`code-kit ask`).
|
|
775
|
+
// LOOP-3, WHO-4: a lane stalled past its restarts is the lead's to sort out; the person sees a note.
|
|
777
776
|
for (const f of loop?.flagged ?? [])
|
|
778
777
|
lines.push({
|
|
779
778
|
kind: 'stalled',
|
|
780
|
-
text: `${f.lane} stalled ${f.count} times on ${f.story}`,
|
|
779
|
+
text: `${f.lane} stalled ${f.count} times on ${f.story}: the lead decides how it goes on`,
|
|
781
780
|
stall: f,
|
|
782
|
-
actions: [
|
|
783
|
-
{ id: 'lanes', label: 'Lanes' },
|
|
784
|
-
{ id: 'resumeStall', label: 'Resume' },
|
|
785
|
-
],
|
|
781
|
+
actions: [{ id: 'lanes', label: 'Lanes' }],
|
|
786
782
|
});
|
|
787
783
|
// LOOP-5: under autonomy propose, each step waits for Go.
|
|
788
784
|
if (loop?.proposal?.first)
|
|
@@ -797,15 +793,20 @@ export function bandLines({
|
|
|
797
793
|
{ id: 'loopOff', label: 'Off' },
|
|
798
794
|
],
|
|
799
795
|
});
|
|
796
|
+
else if (loop?.proposal?.repeated)
|
|
797
|
+
// LOOP-8, WHO-4: a step the lead was asked for three times and didn't take is a note, not a decision.
|
|
798
|
+
lines.push({
|
|
799
|
+
kind: 'stuck',
|
|
800
|
+
text: `${loop.proposal.label}: still next after ${loop.proposal.repeated} prompts to the lead`,
|
|
801
|
+
proposal: loop.proposal,
|
|
802
|
+
actions: [{ id: 'go', label: 'Ask again' }],
|
|
803
|
+
});
|
|
800
804
|
else if (loop?.proposal)
|
|
801
805
|
lines.push({
|
|
802
806
|
kind: 'proposal',
|
|
803
|
-
|
|
804
|
-
text: loop.proposal.repeated
|
|
805
|
-
? `${loop.proposal.label}: still next after ${loop.proposal.repeated} prompts. Ask the lead again?`
|
|
806
|
-
: `${loop.proposal.label}?`,
|
|
807
|
+
text: `${loop.proposal.label}?`,
|
|
807
808
|
proposal: loop.proposal,
|
|
808
|
-
actions: [{ id: 'go', label:
|
|
809
|
+
actions: [{ id: 'go', label: 'Go' }],
|
|
809
810
|
});
|
|
810
811
|
// LOOP-11, LOOP-12: what the loop read in the lead's reply holds it until the person says.
|
|
811
812
|
const waitingLine = waitingOf(loop?.waiting);
|
|
@@ -833,7 +834,7 @@ export function bandLines({
|
|
|
833
834
|
actions: [{ id: 'openDecision', label: 'Answer…' }],
|
|
834
835
|
});
|
|
835
836
|
// ASK-4: the brief's open questions, the person's to answer.
|
|
836
|
-
if (brief.length)
|
|
837
|
+
if (brief.length && !working)
|
|
837
838
|
lines.push({
|
|
838
839
|
kind: 'brief',
|
|
839
840
|
text: `${plural(brief.length, 'open question', 'open questions')} in your brief: ${brief[0].question}`,
|
|
@@ -848,7 +849,7 @@ export function bandLines({
|
|
|
848
849
|
const inReview = loopOff
|
|
849
850
|
? state.lanes.filter((l) => l.state === 'in review').map((l) => l.story)
|
|
850
851
|
: [];
|
|
851
|
-
if (inReview.length) {
|
|
852
|
+
if (inReview.length && !working) {
|
|
852
853
|
const [story] = inReview;
|
|
853
854
|
const ids = inReview.map((s) => s.id).join(', ');
|
|
854
855
|
lines.push(
|
|
@@ -891,7 +892,7 @@ export function bandLines({
|
|
|
891
892
|
const autonomous = (check?.harness?.autonomy ?? 'autonomous') === 'autonomous';
|
|
892
893
|
// SETUP-2: with no rules on the base yet, the lead puts them there first and then merges.
|
|
893
894
|
const leadMerges = autonomous && (check?.delegatesMerge || check?.firstMerge);
|
|
894
|
-
if (next && !leadMerges)
|
|
895
|
+
if (next && !leadMerges && !working)
|
|
895
896
|
lines.push({
|
|
896
897
|
kind: 'queue',
|
|
897
898
|
text: `${next.branch} is next to merge`,
|
|
@@ -899,9 +900,9 @@ export function bandLines({
|
|
|
899
900
|
actions: [{ id: 'mergeQueue', label: 'Merge' }],
|
|
900
901
|
});
|
|
901
902
|
// TEAM-7, TEAM-9: what of the team needs this person.
|
|
902
|
-
lines.push(...teamLines(team, check?.lanes ?? {}));
|
|
903
|
+
if (!working) lines.push(...teamLines(team, check?.lanes ?? {}));
|
|
903
904
|
// REPORT-3: a bug report for code-kit an agent drafted, for the person to read and send, or not.
|
|
904
|
-
if (reports.length)
|
|
905
|
+
if (reports.length && !working)
|
|
905
906
|
lines.push({
|
|
906
907
|
kind: 'report',
|
|
907
908
|
text: `${reports.length > 1 ? `${reports.length} bug reports for code-kit to send: ` : 'A bug report for code-kit to send: '}${reports[0].title}`,
|
|
@@ -922,11 +923,14 @@ export function bandLines({
|
|
|
922
923
|
// With the mission rows drawn, the loop's state and its Pause or Resume are on the `now` row.
|
|
923
924
|
else if (mission.length) {
|
|
924
925
|
// nothing more
|
|
925
|
-
} else if (loop?.state === 'elsewhere')
|
|
926
|
-
//
|
|
926
|
+
} else if (loop?.state === 'elsewhere' || loop?.state === 'unled')
|
|
927
|
+
// SESS-2: another session leads the project, or none does; this one never prompts its lead.
|
|
927
928
|
lines.push({
|
|
928
929
|
kind: 'loop',
|
|
929
|
-
text:
|
|
930
|
+
text:
|
|
931
|
+
loop.state === 'elsewhere'
|
|
932
|
+
? 'the lead is in another session'
|
|
933
|
+
: 'no session leads the project: the loop waits for one',
|
|
930
934
|
actions: [{ id: 'leadHere', label: 'Lead from here' }],
|
|
931
935
|
});
|
|
932
936
|
else if (loop?.state === 'paused')
|
|
@@ -937,6 +941,13 @@ export function bandLines({
|
|
|
937
941
|
text: `loop on${loop.last ? ` · last: ${loop.last}` : ''}`,
|
|
938
942
|
actions: [{ id: 'pause', label: 'Pause' }],
|
|
939
943
|
});
|
|
944
|
+
// OWN-1: another session's decisions are answered there: here, only how many.
|
|
945
|
+
for (const e of elsewhere)
|
|
946
|
+
lines.push({
|
|
947
|
+
kind: 'elsewhere',
|
|
948
|
+
text: `${plural(e.count, 'decision', 'decisions')} in session ${e.member}, answered there`,
|
|
949
|
+
actions: [],
|
|
950
|
+
});
|
|
940
951
|
// BAND-4: a digit for every action, in the order they're drawn.
|
|
941
952
|
let digit = 0;
|
|
942
953
|
for (const line of lines)
|
|
@@ -969,9 +980,10 @@ export function waitingOf(waiting) {
|
|
|
969
980
|
waiting,
|
|
970
981
|
actions: [{ id: 'carryOn', label: 'Carry on' }],
|
|
971
982
|
};
|
|
983
|
+
// WHO-4: a step the lead said it couldn't take is a note: the loop asks again once the project moves.
|
|
972
984
|
if (waiting.kind === 'blocked')
|
|
973
985
|
return {
|
|
974
|
-
kind: '
|
|
986
|
+
kind: 'stuck',
|
|
975
987
|
text: `${waiting.label}: the lead didn't take it${waiting.reason ? `: ${waiting.reason}` : ''}`,
|
|
976
988
|
waiting,
|
|
977
989
|
actions: [{ id: 'askAgain', label: 'Ask again' }],
|
|
@@ -1006,8 +1018,8 @@ export function band(lines, { Box, Text, Button }, onAction) {
|
|
|
1006
1018
|
facts: 'yellow',
|
|
1007
1019
|
question: 'cyan',
|
|
1008
1020
|
brief: 'cyan',
|
|
1009
|
-
denied: 'red',
|
|
1010
1021
|
report: 'magenta',
|
|
1022
|
+
stuck: 'yellow',
|
|
1011
1023
|
};
|
|
1012
1024
|
const LIVE = { quiet: 'yellow', stalled: 'red' };
|
|
1013
1025
|
const LABEL = 8;
|
|
@@ -1516,7 +1528,17 @@ const CHOICES = {
|
|
|
1516
1528
|
'compact.milestone': ['ask', 'auto', 'off'],
|
|
1517
1529
|
};
|
|
1518
1530
|
const shown = (v) =>
|
|
1519
|
-
v === null || v === ''
|
|
1531
|
+
v === null || v === ''
|
|
1532
|
+
? "the session's"
|
|
1533
|
+
: v === true
|
|
1534
|
+
? 'on'
|
|
1535
|
+
: v === false
|
|
1536
|
+
? 'off'
|
|
1537
|
+
: v === 'lead'
|
|
1538
|
+
? 'the lead'
|
|
1539
|
+
: v === 'person'
|
|
1540
|
+
? 'you'
|
|
1541
|
+
: String(v);
|
|
1520
1542
|
|
|
1521
1543
|
/**
|
|
1522
1544
|
* The Settings view: each setting from `code-kit settings --json` with a control, and, once one is
|
|
@@ -1524,7 +1546,8 @@ const shown = (v) =>
|
|
|
1524
1546
|
*/
|
|
1525
1547
|
export function settingsView(rows, pending, { Box, Text, Select, Input, Button }, handlers) {
|
|
1526
1548
|
const control = (r) => {
|
|
1527
|
-
const listed =
|
|
1549
|
+
const listed =
|
|
1550
|
+
r.choices ?? CHOICES[r.key] ?? (typeof r.default === 'boolean' ? [true, false] : null);
|
|
1528
1551
|
// A value the list doesn't name (a model given by its id) is still shown, as the one chosen.
|
|
1529
1552
|
const choices =
|
|
1530
1553
|
listed && !listed.map(String).includes(String(r.value ?? '')) ? [...listed, r.value] : listed;
|
|
@@ -1544,7 +1567,7 @@ export function settingsView(rows, pending, { Box, Text, Select, Input, Button }
|
|
|
1544
1567
|
onSubmit: (v) => handlers.onChoose(r.key, v.trim()),
|
|
1545
1568
|
});
|
|
1546
1569
|
};
|
|
1547
|
-
const
|
|
1570
|
+
const rowView = (r) =>
|
|
1548
1571
|
Box({
|
|
1549
1572
|
key: `setting-${r.key}`,
|
|
1550
1573
|
flexDirection: 'column',
|
|
@@ -1553,7 +1576,15 @@ export function settingsView(rows, pending, { Box, Text, Select, Input, Button }
|
|
|
1553
1576
|
flexDirection: 'row',
|
|
1554
1577
|
columnGap: 2,
|
|
1555
1578
|
children: [
|
|
1556
|
-
Box({
|
|
1579
|
+
Box({
|
|
1580
|
+
width: 34,
|
|
1581
|
+
children: [
|
|
1582
|
+
Text({
|
|
1583
|
+
bold: true,
|
|
1584
|
+
children: [r.section === 'Approvals' ? r.key.replace(/^approvals\./, '') : r.key],
|
|
1585
|
+
}),
|
|
1586
|
+
],
|
|
1587
|
+
}),
|
|
1557
1588
|
control(r),
|
|
1558
1589
|
...(r.value !== r.default
|
|
1559
1590
|
? [Text({ dimColor: true, children: [`default ${shown(r.default)}`] })]
|
|
@@ -1570,8 +1601,26 @@ export function settingsView(rows, pending, { Box, Text, Select, Input, Button }
|
|
|
1570
1601
|
}),
|
|
1571
1602
|
Text({ dimColor: true, children: [` ${r.about}`] }),
|
|
1572
1603
|
],
|
|
1573
|
-
})
|
|
1574
|
-
|
|
1604
|
+
});
|
|
1605
|
+
// WHO-3: the harness, then who decides each approval.
|
|
1606
|
+
const section = (name) => rows.filter((r) => (r.section ?? 'Harness') === name);
|
|
1607
|
+
const approvals = section('Approvals');
|
|
1608
|
+
const rowsView = [
|
|
1609
|
+
...section('Harness').map(rowView),
|
|
1610
|
+
...(approvals.length
|
|
1611
|
+
? [
|
|
1612
|
+
Text({ key: 'approvals-heading', bold: true, color: 'cyan', children: ['APPROVALS'] }),
|
|
1613
|
+
Text({
|
|
1614
|
+
key: 'approvals-about',
|
|
1615
|
+
dimColor: true,
|
|
1616
|
+
children: [
|
|
1617
|
+
"Who decides what a lane can't do on its own. The lead decides what's technical; you keep product, design, CI and the kit.",
|
|
1618
|
+
],
|
|
1619
|
+
}),
|
|
1620
|
+
...approvals.map(rowView),
|
|
1621
|
+
]
|
|
1622
|
+
: []),
|
|
1623
|
+
];
|
|
1575
1624
|
const confirm = pending
|
|
1576
1625
|
? [
|
|
1577
1626
|
Box({
|
|
@@ -1627,7 +1676,9 @@ export function settingsView(rows, pending, { Box, Text, Select, Input, Button }
|
|
|
1627
1676
|
Box({ flexDirection: 'column', children: rowsView }),
|
|
1628
1677
|
Text({
|
|
1629
1678
|
dimColor: true,
|
|
1630
|
-
children: [
|
|
1679
|
+
children: [
|
|
1680
|
+
'Lanes, paths, layers and which paths are protected change through /code-kit:init.',
|
|
1681
|
+
],
|
|
1631
1682
|
}),
|
|
1632
1683
|
],
|
|
1633
1684
|
});
|
|
@@ -7,8 +7,10 @@ import { join } from 'node:path';
|
|
|
7
7
|
import { matchesAny } from './lib/glob.mjs';
|
|
8
8
|
import { loadBaseline, newProblems } from './lib/baseline.mjs';
|
|
9
9
|
import { block, start } from './lib/hook.mjs';
|
|
10
|
-
import { layerProblems } from './lib/layers.mjs';
|
|
10
|
+
import { brokenLayerRule, layerProblems } from './lib/layers.mjs';
|
|
11
|
+
import { noteFor } from './lib/standards.mjs';
|
|
11
12
|
import { projectWorktrees, relativeTo, worktreeRoot } from './lib/rules.mjs';
|
|
13
|
+
import { noteWrite } from './lib/sessions.mjs';
|
|
12
14
|
|
|
13
15
|
const { input, project, config, error } = start();
|
|
14
16
|
const target = input.tool_input?.file_path;
|
|
@@ -19,10 +21,15 @@ const root = found && projectWorktrees(project).includes(found) ? found : null;
|
|
|
19
21
|
if (!root) process.exit(0); // another repository, or Claude's memory: not this project's code
|
|
20
22
|
const rel = relativeTo(root, target);
|
|
21
23
|
if (!existsSync(join(root, rel))) process.exit(0);
|
|
24
|
+
// SESS-5: in the checkout every session shares, which session wrote the file, for a blocked git to name.
|
|
25
|
+
if (root === projectWorktrees(project)[0]) noteWrite(project, input.session_id, rel);
|
|
22
26
|
|
|
23
27
|
// Violations recorded in the baseline (brownfield code as it was adopted) don't block; new ones do.
|
|
24
28
|
const layers = layerProblems(rel, readFileSync(join(root, rel), 'utf8'), config);
|
|
25
29
|
const problems = newProblems(rel, layers, loadBaseline(project));
|
|
30
|
+
// STD-4: a layer rule a project standard names says which standard it is.
|
|
31
|
+
const note = problems.length ? noteFor(root, config, problems.map(brokenLayerRule)) : '';
|
|
32
|
+
if (note) problems.push(note.trim());
|
|
26
33
|
const quoted = `'${rel.replace(/'/g, `'\\''`)}'`;
|
|
27
34
|
for (const check of config.postEdit) {
|
|
28
35
|
if (!matchesAny(rel, check.files) || matchesAny(rel, check.exclude)) continue;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SessionStart and SessionEnd (docs/specs/23-the-team.md): a session starting is recorded as a member
|
|
3
|
+
// of the team, the lead session when no other live session leads (SESS-2); as it ends, it leads nothing
|
|
4
|
+
// more, and the lanes its agents had claimed are free again (SCHED-1). Its task stays, stale (SESS-4).
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { loadConfig } from './lib/config.mjs';
|
|
7
|
+
import { releaseSession } from './lib/laneclaims.mjs';
|
|
8
|
+
import { endSession, startSession } from './lib/sessions.mjs';
|
|
9
|
+
import { projectDir } from './lib/rules.mjs';
|
|
10
|
+
|
|
11
|
+
let input = {};
|
|
12
|
+
try {
|
|
13
|
+
input = JSON.parse(readFileSync(0, 'utf8') || '{}');
|
|
14
|
+
} catch {
|
|
15
|
+
// no input: nothing to do
|
|
16
|
+
}
|
|
17
|
+
const project = projectDir();
|
|
18
|
+
const { config } = loadConfig(project);
|
|
19
|
+
const session = input.session_id ?? null;
|
|
20
|
+
if (config && session && process.argv[2] === 'start') startSession(project, session);
|
|
21
|
+
if (config && session && process.argv[2] === 'end') {
|
|
22
|
+
endSession(project, session);
|
|
23
|
+
releaseSession(project, session);
|
|
24
|
+
}
|
package/hooks/stop-check.mjs
CHANGED
|
@@ -7,13 +7,26 @@ import { join } from 'node:path';
|
|
|
7
7
|
import { loadBaseline, newProblems } from './lib/baseline.mjs';
|
|
8
8
|
import { runChecks } from './lib/checks.mjs';
|
|
9
9
|
import { block, start } from './lib/hook.mjs';
|
|
10
|
-
import { layerProblems } from './lib/layers.mjs';
|
|
10
|
+
import { brokenLayerRule, layerProblems } from './lib/layers.mjs';
|
|
11
|
+
import { noteFor } from './lib/standards.mjs';
|
|
11
12
|
import { actorFor, actorRoot, currentBranch, reportPath } from './lib/rules.mjs';
|
|
12
13
|
import { storyOfBranch } from './lib/teamfacts.mjs';
|
|
14
|
+
import { releaseLane } from './lib/laneclaims.mjs';
|
|
13
15
|
import { auditProblems, changedFiles } from './lane-audit.mjs';
|
|
14
16
|
|
|
15
17
|
const { input, project, config, error } = start();
|
|
16
18
|
|
|
19
|
+
// SCHED-1: a lane agent whose finish goes through frees its lane for the next story. A refused finish
|
|
20
|
+
// (exit 2) keeps it: the agent carries on.
|
|
21
|
+
const finishingLane =
|
|
22
|
+
input.agent_id && config
|
|
23
|
+
? Object.entries(config.lanes).find(([, l]) => l.agent === input.agent_type)?.[0]
|
|
24
|
+
: null;
|
|
25
|
+
if (finishingLane)
|
|
26
|
+
process.on('exit', (code) => {
|
|
27
|
+
if (code === 0) releaseLane(project, finishingLane, { session: input.session_id ?? null });
|
|
28
|
+
});
|
|
29
|
+
|
|
17
30
|
// Stop hooks share one `stop_hook_active` flag, so another plugin's block would make this check skip
|
|
18
31
|
// itself. Instead this hook remembers its own last block per session and agent. The same problem
|
|
19
32
|
// reported MAX_REPEATS times in a row means no progress: say so and let the finish through rather than
|
|
@@ -84,10 +97,17 @@ const layers = changed
|
|
|
84
97
|
newProblems(f, layerProblems(f, readFileSync(join(root, f), 'utf8'), config), baseline),
|
|
85
98
|
);
|
|
86
99
|
if (layers.length)
|
|
87
|
-
fail(
|
|
100
|
+
fail(
|
|
101
|
+
`Imports break the layer rules (.claude/code-kit.json › layers):${noteFor(root, config, layers.map(brokenLayerRule))}`,
|
|
102
|
+
layers.join('\n'),
|
|
103
|
+
);
|
|
88
104
|
|
|
89
105
|
const { failure } = runChecks(config, changed, root, actor);
|
|
90
|
-
if (failure)
|
|
106
|
+
if (failure)
|
|
107
|
+
fail(
|
|
108
|
+
`${failure.title}${noteFor(root, config, { kind: 'check', name: failure.check })}`,
|
|
109
|
+
failure.output,
|
|
110
|
+
);
|
|
91
111
|
|
|
92
112
|
// A lane hands back committed work on its own branch; the lead reviews and merges it.
|
|
93
113
|
if (actor.kind === 'lane' && changed.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warren-dean/code-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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",
|
package/skills/decide/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: The lead decides what a lane asked for and couldn't do itself (a ne
|
|
|
5
5
|
|
|
6
6
|
# Decide a lane's request
|
|
7
7
|
|
|
8
|
-
The person sets the guardrails: the specs, the
|
|
8
|
+
The person is the product owner and sets the guardrails: the specs, the screens' visual design, CI and the pipelines, the kit (the rules in `.claude/code-kit.json`), and product questions the spec doesn't answer, plus anything with production, cost or strategy implications. Everything technical is yours by default: contracts, migrations, packages, merges into the base, and a weakened test on a lane's branch once you've reviewed it (`code-kit approve tests --lane <lane> --delegated --story ST-n --reason "…"`); on your own branch a weakened test is the person's. Inside them, you run the team. A lane that needs something it can't do itself asks you, and you decide on the merits. Passing a request to the person because you could is not deciding: they'd approve it by default, and the check would be gone.
|
|
9
9
|
|
|
10
10
|
The CLI is `node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs"`.
|
|
11
11
|
|
package/skills/dispatch/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dispatch
|
|
3
|
-
description: Hand stories from the plan to their lane agents,
|
|
3
|
+
description: Hand stories from the plan to their lane agents, one per lane, every free lane at once. Picks the ready stories, checks each is buildable, briefs each lane agent with its story, requirements and acceptance criteria, and starts it on its own branch and worktree. Use when the lead is ready to start building, asks what to work on next, or runs /code-kit:dispatch [story IDs, a milestone, or "next"].
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Dispatch stories to lanes
|
|
@@ -34,7 +34,7 @@ Then check:
|
|
|
34
34
|
- **No open question blocks it.** If a spec's open questions touch a cited requirement, don't dispatch the story; list the question for the person.
|
|
35
35
|
- **The contracts exist.** If the story needs an API schema, event or database contract that isn't written yet, that's the lead's work first. Write it (the lead owns contracts), commit it on the lead's branch, then dispatch. A lane never invents a contract.
|
|
36
36
|
- **The lane is right.** Run `who` on the paths the story will touch. If they belong to two lanes, split the story or sequence it; don't hand one lane another's files.
|
|
37
|
-
- **
|
|
37
|
+
- **One story per lane.** A lane runs one story at a time across the whole project, every session included: two agents in one lane build on the same files in their worktrees and conflict at merge. Different lanes run in parallel; that's what the plan's dependencies are for. `code-kit next` offers at most one story for each free lane, the open slice's first (`**Slice:**`), and starting a second agent for a lane that's running one is refused, naming the story and the session that has it. `code-kit lanes` lists what each lane is running. If a lane's agent stopped without finishing (it was stopped, or its session ended), `code-kit lanes release <lane>` frees the lane for its next story.
|
|
38
38
|
|
|
39
39
|
Commit everything the lanes need first. Each lane branches from the lead's branch as committed, so uncommitted work isn't there.
|
|
40
40
|
|
package/skills/init/SKILL.md
CHANGED
|
@@ -17,7 +17,7 @@ The CLI is `node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs"`, run from the project
|
|
|
17
17
|
- **No config, and the docs describe the architecture** → **New from docs** (sections 1–3).
|
|
18
18
|
- **No config, and the docs are thin or missing** → **Brownfield** (sections 1–3, with the brownfield steps). The code is the main source, and the docs fill in what they can.
|
|
19
19
|
|
|
20
|
-
- **No config, no code and no docs** (just an idea) → stop and suggest `/code-kit:spec-design` first. It writes the brief, specs, architecture, principles and plan this skill works from.
|
|
20
|
+
- **No config, no code and no docs** (just an idea) → stop and suggest `/code-kit:spec-design` first. It writes the brief, specs, architecture, standards, principles and plan this skill works from.
|
|
21
21
|
|
|
22
22
|
Say which one applies and why before you start.
|
|
23
23
|
|
|
@@ -74,11 +74,23 @@ Write `.claude/code-kit.draft.json`. Follow `${CLAUDE_PLUGIN_ROOT}/README.md`
|
|
|
74
74
|
- **`postEdit`:** the project's existing per-file linters and formatters, with `{file}`.
|
|
75
75
|
- **`checks`:** the typecheck, test, architecture and database commands, each scoped by `files`. Only include commands that pass on the current code, or ones the person chose to keep.
|
|
76
76
|
- **`harness.autonomy`:** ask the person how hands-off they want the build. With the code-kit mod (Claude Code 2.1.287 or later), once the plan has stories, a lead loop can keep the work moving between their prompts: it prompts the lead to dispatch ready stories, review finished ones and merge them where merges are delegated. Offer the three choices in plain words: `autonomous` (it keeps going on its own; they can pause it from the band), `propose` (each step waits in the band for their Go), or `off`. Put their answer in the draft as `"harness": { "autonomy": "…" }`. If they'd rather decide later, leave it out: the loop then asks them in the band before its first step. With `autonomous`, the lead owns delivery, so also draft `"merge": true` in `approvals.delegate`: the lead merges a reviewed branch once every check passes.
|
|
77
|
-
- **`approvals.delegate`, who decides what (HELM-1):** the person
|
|
77
|
+
- **`approvals.delegate`, who decides what (HELM-1, WHO-1):** the person is the product owner; the lead decides everything technical, so lanes ask the lead, never the person. With no `approvals.delegate`, that's the default: the lead approves new packages (only when the story's requirements need them and they fit the spec, the principles and the architecture; npm packages must also pass the registry limits on licence, popularity and age), every protected change except the specs (`spec`), the screens' designs (`design`), CI (`ci`) and the kit, merges into the base, and weakened tests on a lane's branch after its review. Show the person the split in two lines, what they keep and what the lead decides, and ask if they want to keep anything else. Only if they do, draft `approvals.delegate` with exactly what the lead keeps (`{ "protected": [...], "merge": true, "tests": true, "dependencies": {} }`, leaving out what they keep), or `false` to keep everything. They can change it later in the Settings tab's Approvals section.
|
|
78
78
|
- **`harness.agents.lanes.model`:** ask which model the lane agents run on. Each lane agent builds one story inside its own paths, with its requirements and acceptance criteria in its brief, and the hooks, `verify` and the lead's review catch what it gets wrong, so Sonnet usually does the work well for much less than the session's model; recommend `sonnet`, and say the lead, the reviewer and the security reviewer keep the session's model. The mod sets it as each lane agent starts; a lane's agent file with its own `model:` keeps that. Put their answer in `harness` as `"agents": { "lanes": { "model": "sonnet" } }`, or leave it out for the session's model.
|
|
79
79
|
- **`harness.report.on`:** ask whether agents may draft bug reports for code-kit itself when code-kit gets in their way (a refusal that shouldn't have happened, a crash). Each report holds only code-kit's own facts (its version, the rule, what it said, the command), never the project's code, paths or data, and waits in their Decisions tab: they read it and send it to code-kit's public GitHub under their own account, or discard it. Off unless they say yes; put `"report": { "on": true }` in `harness` if they do.
|
|
80
|
+
- **`docs.standards`:** `docs/standards.md` unless the project keeps its standards elsewhere (see Standards, below).
|
|
80
81
|
- **`approvals.lead`:** ask the person whether they'll ever approve from a phone or another chat-only client, where a `!` command arrives as plain text. If so, set it to `true`: the lead then records approvals they give in chat (`code-kit approve`), and runs the `shell.block` commands marked `person: true` when they say so. Mark a `shell.block` command `person: true` only if the person wants to be able to have the lead run it on their say-so. Leave the setting out if the lead will run unattended.
|
|
81
82
|
|
|
83
|
+
### Standards (spec 22)
|
|
84
|
+
|
|
85
|
+
After the layers, suggest the project's standards: named engineering rules every agent builds to, each graded and saying how firmly code-kit applies it. Standards are guardrails, and guardrails are the person's: suggest them, and write nothing until the person has approved, edited or dropped each one.
|
|
86
|
+
|
|
87
|
+
- **Format.** Each standard is a `### <id> <title>` heading in `docs/standards.md`, followed by its fields, one per line: `level` (required or recommended), `applied` (enforced, checked or advice), `layers` (layer names, or all), `why` (one or two sentences), `source` (where it came from) and, for an enforced one, `enforced by`. The id's prefix is its category: `PRI-` principles (judgement, such as single responsibility; never enforced), `PAT-` patterns (how a kind of problem is solved here), `ARC-` architecture (what may depend on what, where things live), `CON-` conventions (naming, layout, tests' shape), `POL-` policies (what must or must never happen). The title is one sentence an agent can act on.
|
|
88
|
+
- **How firmly.** Mark a standard `enforced` only where a rule the draft already has refuses the change, and name it: `enforced by: layers (domain may not import infrastructure)`, `layers (web may not import the package pg)`, `protected (<approval>)`, `check (<name>)` or `shell (<words of its why>)`. code-kit then names the standard in that rule's refusal. Everything else review can judge is `checked`; the rest is `advice`.
|
|
89
|
+
- **With specs**, suggest them from the architecture, the principles, the specs' threat models and the brief's decision log, each citing its source (`source: the threat model in docs/specs/03-payments.md`). A threat model saying card numbers are never stored is a `POL-` standard; a layer rule the architecture sets is an `ARC-` standard enforced by that layer rule. The engineering rules in `docs/principles.md` move into standards; principles keeps the definition of done, the commands and the privacy and residency targets.
|
|
90
|
+
- **With no specs but code**, infer them from the code: the import graph (`graph`) for layering and import directions; naming and folder layout; the data-access pattern (repositories, direct queries, an ORM); the formatter's and linter's configs; the tests' shape and where they live. Count, don't guess: each inferred standard carries its evidence (`source: the code: 41 of 43 modules reach the database through *Repository classes`), names the files that don't follow it, and starts as `recommended`. A pattern the code follows inconsistently (3 of 5 services through repositories, 2 querying directly) isn't a standard yet: put it to the person as a choice, with both counts, and write the way they pick.
|
|
91
|
+
- **Protect the file.** Add `{ "glob": "docs/standards.md", "approval": "standards", "why": "the project's standards" }` to `protected`, before any broader glob that also covers `docs/`. The lead may approve adding a recommended standard if the person puts `standards` in `approvals.delegate.protected`; removing a standard, or weakening a required one, is always the person's, whatever the config delegates.
|
|
92
|
+
- **Check it** once the config is written: `code-kit standards` reports a duplicate id, an unknown category, an enforced principle, or an `enforced by` that names no rule the config has. Each lane agent's rules then list the standards for its layers, one line each, and CLAUDE.md lists them all.
|
|
93
|
+
|
|
82
94
|
Prove the draft:
|
|
83
95
|
|
|
84
96
|
```bash
|
|
@@ -124,6 +136,7 @@ Show the person:
|
|
|
124
136
|
3. protected paths, blocked and restricted commands, and checks (including any left out because they fail today);
|
|
125
137
|
and whether the lead may record approvals given in chat (`approvals.lead`), how hands-off the build is (`harness.autonomy`), the lane agents' model (`harness.agents.lanes.model`), and whether agents may draft bug reports for code-kit (`harness.report.on`);
|
|
126
138
|
4. the project skills, and which lane agents use each;
|
|
139
|
+
and the standards you suggest, each with its category, level, how it's applied and its source (for inferred ones, the evidence and the files that don't follow it), with the choices where the code is inconsistent;
|
|
127
140
|
5. the active adapters and what each adds, from `adapters`. A companion tool that's installed but not set up here (its init skill is in your skills list, for example `/context-graph:init` with no `.ctx/` folder): add `"adapters": { "<name>": true }` to the draft and say it will be set up before any code is written;
|
|
128
141
|
6. every **question**: where the docs or the code are silent, ambiguous or contradictory. Don't guess; a wrong owner or layer blocks real work.
|
|
129
142
|
|
|
@@ -155,13 +168,14 @@ Re-run whenever the build has moved on: new apps or packages, folders that moved
|
|
|
155
168
|
|
|
156
169
|
1. **Re-read** the docs and the repository as in section 1, including the brownfield steps if the code has grown past the docs.
|
|
157
170
|
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
|
|
171
|
+
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 <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.
|
|
159
172
|
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`:
|
|
160
173
|
- project skills for the recipes in the docs (or, without recipes, the patterns the code follows), listed on the lane agents;
|
|
161
174
|
- 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);
|
|
162
175
|
- the QA lane, its agent and its checks, when there are screens or journeys across lanes;
|
|
163
176
|
- the security reviewer (`.claude/agents/security-reviewer.md`), unless the product handles no sign-in, personal data, payments, outside input or secrets; and, as a proposal for the person, a threat model and security requirements for each spec that lacks them (spec-design's section 4);
|
|
164
177
|
- `"adapters": { "<name>": true }` for a companion that's installed but not set up, which is then set up before any code.
|
|
178
|
+
- project standards, when the project has no `docs/standards.md`: suggest them as section 2's Standards describes, moving the engineering rules out of `docs/principles.md`, and protect the file.
|
|
165
179
|
3. **Show the difference:**
|
|
166
180
|
|
|
167
181
|
```bash
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lead
|
|
3
|
+
description: Make this Claude Code session the project's lead session, where code-kit's loop runs and the plan's stories are dispatched from, when several sessions work one project. Says which session leads now and what moving the lead does, then gives the person the command that moves it. Use when the person says this session should lead, asks which session leads, or runs /code-kit:lead.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lead from this session
|
|
7
|
+
|
|
8
|
+
Several Claude Code sessions can work one project at once. One is the lead session: code-kit's loop runs there, and the plan's stories are dispatched from it. The others are working sessions: they stay quiet (no loop prompts, no other session's decisions), and each records its ad hoc task, with the paths it leases, before it changes code.
|
|
9
|
+
|
|
10
|
+
The CLI is `node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs"`, run from the project root.
|
|
11
|
+
|
|
12
|
+
1. See who leads now:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" who
|
|
16
|
+
node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" sessions
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. If this session already leads, say so and stop.
|
|
20
|
+
3. Otherwise, tell the person what moving the lead does: this session starts running the loop and dispatching the plan's stories, and the session that led becomes a working session, which stops prompting its lead. Choosing the lead is theirs, so give them the command to run themselves (the hooks refuse it from an agent), or point them to **Lead from here** in the code-kit band:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
! node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" sessions lead
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
4. Once they have, run `who` again and confirm this session leads.
|
|
27
|
+
|
|
28
|
+
A working session doesn't need this to work: it records its task (`code-kit task "<what>" --touches "<globs>"`), works in a worktree of its own, and `code-kit task done` when it's finished.
|
package/skills/next/SKILL.md
CHANGED
|
@@ -44,9 +44,9 @@ Run the named skill with the Skill tool: `code-kit:<step>`, with `args` (and the
|
|
|
44
44
|
|
|
45
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.
|
|
46
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
|
|
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 <lane>` (that lane's 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.
|
|
48
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.
|
|
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.
|
|
49
|
+
- **`wait`:** say which stories are being built and on which branches, and which ready stories wait for their lane (a lane runs one story at a time), 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.
|
|
50
50
|
- **`done`:** give the `status` summary in a few lines, then offer to run spec-design for the next milestone or feature.
|
|
51
51
|
|
|
52
52
|
## 3. Keep going until the person is needed
|
package/skills/review/SKILL.md
CHANGED
|
@@ -49,6 +49,7 @@ Read the story and every requirement it cites. Then start from what's already be
|
|
|
49
49
|
- **The spec-check report matches:** its table has no row still `missing` or `conflicts` without an answer, and its questions were answered, not guessed.
|
|
50
50
|
- **Only the story:** changes beyond the story (refactors elsewhere, other stories' requirements) are sent back, or split into their own story. A new dependency is yours to judge, as in `/code-kit:decide`: the story's requirements need it, nothing the project has does the job, and it fits the principles and the layer. Only what crosses a guardrail (the spec, a contract, the architecture) is the person's call.
|
|
51
51
|
- **Contracts respected:** the code uses the contracts as written. A lane that needed a contract change should have asked, not worked around it.
|
|
52
|
+
- **Standards kept:** `code-kit standards --for <changed files>` lists the project's standards for those files' layers. Check each changed file against the ones marked `checked` (the hooks already refuse what an `enforced` one names). A broken required standard sends the branch back, naming the standard's id and where; a broken recommended one is a concern; advice is at most a nit.
|
|
52
53
|
|
|
53
54
|
## 4. The security review
|
|
54
55
|
|
|
@@ -78,7 +79,7 @@ Report the table, `verify`'s result, the security review's findings (or that non
|
|
|
78
79
|
|
|
79
80
|
Branches merge from the queue one at a time, in the order their reviews passed, each verified against the base as it is then. Where `approvals.delegate.merge` is on and the harness's autonomy is `autonomous`, you merge the head: `code-kit queue merge --delegated` (code-kit's loop asks you when it's its turn). Otherwise the person merges it, with Merge in the code-kit band or `code-kit queue merge --person`. The queue records the commit your review passed and merges exactly that commit. A branch that moves on after your review leaves the queue: review it again, then queue it again. A head that no longer merges cleanly, or fails verify against the moved base, is sent back to its lane on its own; dispatch the fix as for any send-back. When the branch is merged, remove its worktree.
|
|
80
81
|
|
|
81
|
-
- **Send back.** List each problem, where it is, and what "fixed" looks like. Record the send-back, so `status` and `next` read the branch as waiting for its fix rather than finished:
|
|
82
|
+
- **Send back.** List each problem, where it is, and what "fixed" looks like, with the requirement or the standard's id it breaks. Record the send-back, so `status` and `next` read the branch as waiting for its fix rather than finished:
|
|
82
83
|
|
|
83
84
|
```bash
|
|
84
85
|
node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" sent-back <branch> --reason "<the problems, in one line>"
|