@warren-dean/code-kit 0.11.0 → 0.12.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 +20 -0
- package/README.md +17 -8
- package/bin/code-kit.mjs +173 -5
- package/hooks/guard-bash.mjs +17 -0
- package/hooks/lib/harness.mjs +6 -0
- package/hooks/lib/next.mjs +19 -2
- package/hooks/lib/plan.mjs +16 -4
- package/hooks/lib/questions.mjs +140 -0
- package/hooks/lib/report.mjs +246 -0
- package/hooks/lib/rules.mjs +262 -15
- package/hooks/mod/art.mjs +2 -2
- package/hooks/mod/decisions.mjs +119 -4
- package/hooks/mod/loop.mjs +49 -0
- package/hooks/mod/panes.mjs +3 -1
- package/hooks/mod/register.mjs +201 -3
- package/hooks/mod/register.test.ts +312 -3
- package/hooks/mod/view.mjs +159 -11
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +1 -1
- package/skills/init/SKILL.md +2 -1
- package/skills/next/SKILL.md +14 -0
- package/skills/review/SKILL.md +1 -1
- package/templates/agent.md +1 -0
- package/templates/claude-md.md +3 -0
package/hooks/mod/view.mjs
CHANGED
|
@@ -39,6 +39,7 @@ export function readyFrom(next) {
|
|
|
39
39
|
* building a branch for the story and, if it has commits, the lane's agent still at work on it;
|
|
40
40
|
* in review commits on the branch (status's `review`) and the agent no longer at work;
|
|
41
41
|
* sent back a review sent the branch back, and the lane hasn't committed its fix yet;
|
|
42
|
+
* waiting on you the lane's story waits on the person's answer (ASK-3);
|
|
42
43
|
* blocked the lane's next story waits on another;
|
|
43
44
|
* idle none of these.
|
|
44
45
|
* `status` can't tell a finished branch from one still being built, so the agent at work decides.
|
|
@@ -46,7 +47,11 @@ export function readyFrom(next) {
|
|
|
46
47
|
function laneRow(name, agent, stories, atWork) {
|
|
47
48
|
const own = stories.filter((s) => s.lane === name);
|
|
48
49
|
const active = Boolean(agent && atWork.has(agent));
|
|
49
|
-
const story = own.find((s) =>
|
|
50
|
+
const story = own.find((s) =>
|
|
51
|
+
['in progress', 'review', 'sent back', 'waiting on you'].includes(s.state),
|
|
52
|
+
);
|
|
53
|
+
if (story?.state === 'waiting on you')
|
|
54
|
+
return { name, agent, active, story, branch: story.branch, state: 'waiting on you' };
|
|
50
55
|
if (story?.state === 'sent back')
|
|
51
56
|
return { name, agent, active, story, branch: story.branch, state: 'sent back' };
|
|
52
57
|
if (story) {
|
|
@@ -107,6 +112,7 @@ export const STATE_COLOR = {
|
|
|
107
112
|
orchestrating: 'yellow',
|
|
108
113
|
'in review': 'yellow',
|
|
109
114
|
'sent back': 'magenta',
|
|
115
|
+
'waiting on you': 'cyan',
|
|
110
116
|
blocked: 'red',
|
|
111
117
|
idle: undefined,
|
|
112
118
|
};
|
|
@@ -357,6 +363,7 @@ const CARD_STATE = {
|
|
|
357
363
|
review: { label: 'in review', color: 'yellow' },
|
|
358
364
|
'in progress': { label: 'building', color: 'blue' },
|
|
359
365
|
'sent back': { label: 'sent back', color: 'magenta' },
|
|
366
|
+
'waiting on you': { label: 'waiting on you', color: 'cyan' },
|
|
360
367
|
blocked: { label: 'blocked', color: 'red' },
|
|
361
368
|
ready: { label: 'ready', color: 'green' },
|
|
362
369
|
todo: { label: 'to do', color: 'gray' },
|
|
@@ -630,6 +637,31 @@ export const APPROVE_ID = 'code-kit-approve';
|
|
|
630
637
|
export const RESULT_ID = 'code-kit-result';
|
|
631
638
|
|
|
632
639
|
/** An approval name as a person reads it: a dependency approval by its package. */
|
|
640
|
+
/**
|
|
641
|
+
* CARD-2: open requests as the decisions they are: one per approval and lane, with every file (or
|
|
642
|
+
* command) it was asked for, newest first. Approving it covers them all.
|
|
643
|
+
*/
|
|
644
|
+
export function approvalAsks(open) {
|
|
645
|
+
const asks = new Map();
|
|
646
|
+
for (const r of open ?? []) {
|
|
647
|
+
const key = `${[...r.names].sort().join(',')}|${r.lane ?? r.actor}`;
|
|
648
|
+
const ask = asks.get(key) ?? { request: r, files: [] };
|
|
649
|
+
if (!ask.files.includes(r.what)) ask.files.push(r.what);
|
|
650
|
+
asks.set(key, ask);
|
|
651
|
+
}
|
|
652
|
+
return [...asks.values()];
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/** What an approval decision covers, in a line: "contract for the platform lane: a.ts, b.ts (2 files)". */
|
|
656
|
+
export function askWords(request, files = [request?.what]) {
|
|
657
|
+
const who = request.lane ? `the ${request.lane} lane` : request.actor;
|
|
658
|
+
const list =
|
|
659
|
+
files.length > 4
|
|
660
|
+
? `${files.slice(0, 4).join(', ')} and ${files.length - 4} more`
|
|
661
|
+
: files.join(', ');
|
|
662
|
+
return `${request.names.map(approvalLabel).join(', ')} for ${who}: ${list}${files.length > 1 ? ` (${files.length} files)` : ''}`;
|
|
663
|
+
}
|
|
664
|
+
|
|
633
665
|
export function approvalLabel(name) {
|
|
634
666
|
return name.startsWith('dep-') ? name.slice(4).replaceAll('+', '/') : name;
|
|
635
667
|
}
|
|
@@ -690,6 +722,10 @@ export function bandLines({
|
|
|
690
722
|
nextStep = null,
|
|
691
723
|
combined = null,
|
|
692
724
|
team = null,
|
|
725
|
+
questions = [],
|
|
726
|
+
brief = [],
|
|
727
|
+
denied = [],
|
|
728
|
+
reports = [],
|
|
693
729
|
}) {
|
|
694
730
|
const lines = [];
|
|
695
731
|
// MISSION-1: the goal, now, next and the agents at work, above what needs the person.
|
|
@@ -700,18 +736,39 @@ export function bandLines({
|
|
|
700
736
|
if (!state || state.kind !== 'ok') return lines;
|
|
701
737
|
// HOLD-2: calls held for the person, oldest first, with the time left on the oldest.
|
|
702
738
|
if (held.length) {
|
|
703
|
-
const
|
|
739
|
+
const calls = [...held].sort((a, b) => a.since - b.since);
|
|
740
|
+
const [call] = calls;
|
|
704
741
|
const left = timeLeft(call.since + call.minutes * 60000 - now);
|
|
705
742
|
lines.push({
|
|
706
743
|
kind: 'held',
|
|
707
744
|
text: `${held.length > 1 ? `${held.length} calls held: ` : ''}${call.who} wants to ${heldVerb(call)} · ${left}`,
|
|
708
745
|
held: call,
|
|
746
|
+
// CARD-8: each held call is its own decision in the Decisions tab.
|
|
747
|
+
calls: calls.map((c) => ({
|
|
748
|
+
held: c,
|
|
749
|
+
text: `${c.who} wants to ${heldVerb(c)} · ${timeLeft(c.since + c.minutes * 60000 - now)}`,
|
|
750
|
+
})),
|
|
709
751
|
actions: [
|
|
710
752
|
{ id: 'approveHeld', label: 'Approve' },
|
|
711
753
|
{ id: 'refuseHeld', label: 'Refuse' },
|
|
712
754
|
],
|
|
713
755
|
});
|
|
714
756
|
}
|
|
757
|
+
// PERM-1: Claude Code's own permission check refused a call only the person can let through.
|
|
758
|
+
if (denied.length) {
|
|
759
|
+
const [d] = denied;
|
|
760
|
+
lines.push({
|
|
761
|
+
kind: 'denied',
|
|
762
|
+
text: `${denied.length > 1 ? `${denied.length} calls refused by Claude Code: ` : 'Claude Code refused '}${d.who}'s ${d.what}`,
|
|
763
|
+
denial: d,
|
|
764
|
+
denials: denied,
|
|
765
|
+
actions: [
|
|
766
|
+
...(d.command ? [{ id: 'runDenied', label: 'Put it in the prompt' }] : []),
|
|
767
|
+
{ id: 'deniedDone', label: 'Done it' },
|
|
768
|
+
{ id: 'deniedNo', label: 'Not now' },
|
|
769
|
+
],
|
|
770
|
+
});
|
|
771
|
+
}
|
|
715
772
|
// LOOP-3: a lane stalled past its restarts waits for the person.
|
|
716
773
|
for (const f of loop?.flagged ?? [])
|
|
717
774
|
lines.push({
|
|
@@ -745,14 +802,36 @@ export function bandLines({
|
|
|
745
802
|
actions: [{ id: 'go', label: 'Go' }],
|
|
746
803
|
});
|
|
747
804
|
// A held call's request is asked on its own line above, not again here.
|
|
748
|
-
|
|
749
|
-
|
|
805
|
+
// CARD-2: counted as decisions, not files: one approval for a lane covers every file it was asked for.
|
|
806
|
+
const asks = approvalAsks((requests?.open ?? []).filter((r) => !held.some((h) => sameAsk(h, r))));
|
|
807
|
+
if (asks.length)
|
|
750
808
|
lines.push({
|
|
751
809
|
kind: 'approvals',
|
|
752
|
-
text: `${plural(
|
|
753
|
-
request:
|
|
810
|
+
text: `${plural(asks.length, 'approval', 'approvals')} waiting`,
|
|
811
|
+
request: asks[0].request,
|
|
812
|
+
files: asks[0].files,
|
|
813
|
+
// CARD-8: each approval is its own decision in the Decisions tab.
|
|
814
|
+
asks,
|
|
754
815
|
actions: [{ id: 'approve', label: 'Approve…' }],
|
|
755
816
|
});
|
|
817
|
+
// ASK-2: what the lead asked the person and couldn't settle itself.
|
|
818
|
+
if (questions.length)
|
|
819
|
+
lines.push({
|
|
820
|
+
kind: 'question',
|
|
821
|
+
text: `${questions.length > 1 ? `${questions.length} questions from the lead: ` : 'The lead asks: '}${questions[0].question}`,
|
|
822
|
+
question: questions[0],
|
|
823
|
+
questions,
|
|
824
|
+
actions: [{ id: 'openDecision', label: 'Answer…' }],
|
|
825
|
+
});
|
|
826
|
+
// ASK-4: the brief's open questions, the person's to answer.
|
|
827
|
+
if (brief.length)
|
|
828
|
+
lines.push({
|
|
829
|
+
kind: 'brief',
|
|
830
|
+
text: `${plural(brief.length, 'open question', 'open questions')} in your brief: ${brief[0].question}`,
|
|
831
|
+
brief: brief[0],
|
|
832
|
+
briefs: brief,
|
|
833
|
+
actions: [{ id: 'openDecision', label: 'Answer…' }],
|
|
834
|
+
});
|
|
756
835
|
// DECIDE-1: reviewing a lane's work is the lead's. Only with the loop off, where the person runs the
|
|
757
836
|
// build themselves, does a finished story wait in the band for their Review or Merge; otherwise the
|
|
758
837
|
// loop prompts the lead (or, under propose, offers the step with Go).
|
|
@@ -812,6 +891,18 @@ export function bandLines({
|
|
|
812
891
|
});
|
|
813
892
|
// TEAM-7, TEAM-9: what of the team needs this person.
|
|
814
893
|
lines.push(...teamLines(team, check?.lanes ?? {}));
|
|
894
|
+
// REPORT-3: a bug report for code-kit an agent drafted, for the person to read and send, or not.
|
|
895
|
+
if (reports.length)
|
|
896
|
+
lines.push({
|
|
897
|
+
kind: 'report',
|
|
898
|
+
text: `${reports.length > 1 ? `${reports.length} bug reports for code-kit to send: ` : 'A bug report for code-kit to send: '}${reports[0].title}`,
|
|
899
|
+
report: reports[0],
|
|
900
|
+
reports,
|
|
901
|
+
actions: [
|
|
902
|
+
{ id: 'openDecision', label: 'Read it' },
|
|
903
|
+
{ id: 'discardReport', label: 'Discard' },
|
|
904
|
+
],
|
|
905
|
+
});
|
|
815
906
|
// LOOP-7: the milestone is done; LOOP-6: the loop's own state, with Pause or Resume.
|
|
816
907
|
if (loop?.state === 'done')
|
|
817
908
|
lines.push({
|
|
@@ -853,6 +944,10 @@ export function band(lines, { Box, Text, Button }, onAction) {
|
|
|
853
944
|
usage: 'yellow',
|
|
854
945
|
paused: 'yellow',
|
|
855
946
|
facts: 'yellow',
|
|
947
|
+
question: 'cyan',
|
|
948
|
+
brief: 'cyan',
|
|
949
|
+
denied: 'red',
|
|
950
|
+
report: 'magenta',
|
|
856
951
|
};
|
|
857
952
|
const LIVE = { quiet: 'yellow', stalled: 'red' };
|
|
858
953
|
const LABEL = 8;
|
|
@@ -998,6 +1093,50 @@ export function approvePane(
|
|
|
998
1093
|
});
|
|
999
1094
|
}
|
|
1000
1095
|
|
|
1096
|
+
export const ANSWER_ID = 'code-kit-answer';
|
|
1097
|
+
|
|
1098
|
+
/** ASK-2, ASK-4: the person's answer to the lead's question, or to one in their brief, typed. */
|
|
1099
|
+
export function answerPane(
|
|
1100
|
+
answering,
|
|
1101
|
+
{ Box, Text, Input, Button },
|
|
1102
|
+
{ onInput, onSubmit, onCancel },
|
|
1103
|
+
) {
|
|
1104
|
+
const { kind, question, text, error } = answering;
|
|
1105
|
+
return Box({
|
|
1106
|
+
flexDirection: 'column',
|
|
1107
|
+
rowGap: 1,
|
|
1108
|
+
children: [
|
|
1109
|
+
Text({ bold: true, children: [question] }),
|
|
1110
|
+
Text({
|
|
1111
|
+
dimColor: true,
|
|
1112
|
+
children: [
|
|
1113
|
+
kind === 'brief'
|
|
1114
|
+
? `An open question in ${answering.path}. The lead records your answer in the brief's decisions and builds on it.`
|
|
1115
|
+
: `${answering.id}, from the lead${answering.story ? `, for ${answering.story}` : ''}. Your answer is recorded and sent to the lead.`,
|
|
1116
|
+
],
|
|
1117
|
+
}),
|
|
1118
|
+
Input({
|
|
1119
|
+
key: 'answer-text',
|
|
1120
|
+
label: 'Answer',
|
|
1121
|
+
value: text,
|
|
1122
|
+
submitLabel: 'Answer',
|
|
1123
|
+
autoFocus: true,
|
|
1124
|
+
onInput,
|
|
1125
|
+
onSubmit,
|
|
1126
|
+
}),
|
|
1127
|
+
...(error ? [Text({ color: 'red', children: [error] })] : []),
|
|
1128
|
+
Box({
|
|
1129
|
+
flexDirection: 'row',
|
|
1130
|
+
columnGap: 2,
|
|
1131
|
+
children: [
|
|
1132
|
+
Button({ key: 'answer-confirm', label: 'Answer', onPress: () => onSubmit(text) }),
|
|
1133
|
+
Button({ key: 'answer-cancel', label: 'Cancel', onPress: onCancel }),
|
|
1134
|
+
],
|
|
1135
|
+
}),
|
|
1136
|
+
],
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1001
1140
|
/** What a merge (or another act run through the CLI) reported. */
|
|
1002
1141
|
export function resultPane(result, { Box, Text }) {
|
|
1003
1142
|
if (!result) return Text({ dimColor: true, children: ['Nothing to show.'] });
|
|
@@ -1129,12 +1268,13 @@ export function refusalView(card, expanded, { Box, Text, Button }, { onApprove,
|
|
|
1129
1268
|
export function approvalsText(requests) {
|
|
1130
1269
|
const open = requests?.open ?? [];
|
|
1131
1270
|
const inForce = requests?.inForce ?? [];
|
|
1271
|
+
const history = requests?.history ?? [];
|
|
1272
|
+
const asks = approvalAsks(open);
|
|
1132
1273
|
const lines = [
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
),
|
|
1274
|
+
asks.length
|
|
1275
|
+
? `${plural(asks.length, 'approval', 'approvals')} waiting:`
|
|
1276
|
+
: 'No requests waiting.',
|
|
1277
|
+
...asks.map((a) => ` ${askWords(a.request, a.files)}`),
|
|
1138
1278
|
inForce.length
|
|
1139
1279
|
? `${plural(inForce.length, 'approval', 'approvals')} in force:`
|
|
1140
1280
|
: 'No approvals in force.',
|
|
@@ -1142,6 +1282,14 @@ export function approvalsText(requests) {
|
|
|
1142
1282
|
(a) =>
|
|
1143
1283
|
` ${approvalLabel(a.name)} for ${a.lane ? `the ${a.lane} lane` : 'any agent'}, ${a.minutesLeft} min left: ${a.reason}`,
|
|
1144
1284
|
),
|
|
1285
|
+
// CARD-6: what was given, newest first, whether or not its branch has merged.
|
|
1286
|
+
history.length ? 'Approvals given, newest first:' : 'No approvals given yet.',
|
|
1287
|
+
...history
|
|
1288
|
+
.slice(0, 20)
|
|
1289
|
+
.map(
|
|
1290
|
+
(h) =>
|
|
1291
|
+
` ${new Date(h.at).toLocaleString('en-AU', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })} ${approvalLabel(h.name)} for ${h.lane ? `the ${h.lane} lane` : 'any agent'}, by ${h.by}${h.files.length ? ` (${h.files.length} file${h.files.length === 1 ? '' : 's'})` : ''}: ${h.reason}`,
|
|
1292
|
+
),
|
|
1145
1293
|
];
|
|
1146
1294
|
return lines.join('\n');
|
|
1147
1295
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warren-dean/code-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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/dispatch/SKILL.md
CHANGED
|
@@ -48,7 +48,7 @@ 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
|
-
Start each story with the Agent tool: `subagent_type` is the lane's agent, `isolation: "worktree"`, and run them in the background. Start every story in the batch in the same message so they run in parallel.
|
|
51
|
+
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
52
|
|
|
53
53
|
The brief is the agent's whole context. Write it so the agent needs nothing else:
|
|
54
54
|
|
package/skills/init/SKILL.md
CHANGED
|
@@ -74,6 +74,7 @@ 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 `approvals.delegate`: `"merge": true` (the lead merges a reviewed branch once every check passes) and `"dependencies": {}` (it approves a new package within the default limits on licence, popularity and age), so neither comes to the person. Say these are the limits; anything outside them still comes to them as a decision.
|
|
77
|
+
- **`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.
|
|
77
78
|
- **`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.
|
|
78
79
|
|
|
79
80
|
Prove the draft:
|
|
@@ -118,7 +119,7 @@ Show the person:
|
|
|
118
119
|
1. the layers and what each may depend on, and for brownfield the existing violations the baseline will record;
|
|
119
120
|
2. the lanes, their agents and their paths, and the security reviewer (or why it's left out);
|
|
120
121
|
3. protected paths, blocked and restricted commands, and checks (including any left out because they fail today);
|
|
121
|
-
and whether the lead may record approvals given in chat (`approvals.lead`),
|
|
122
|
+
and whether the lead may record approvals given in chat (`approvals.lead`), how hands-off the build is (`harness.autonomy`), and whether agents may draft bug reports for code-kit (`harness.report.on`);
|
|
122
123
|
4. the project skills, and which lane agents use each;
|
|
123
124
|
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;
|
|
124
125
|
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.
|
package/skills/next/SKILL.md
CHANGED
|
@@ -56,4 +56,18 @@ After a step finishes, run `next --json` again and carry on with the new step, w
|
|
|
56
56
|
- the step is `wait` or `done`;
|
|
57
57
|
- the same step comes back twice in a row with nothing changed. Report what's stuck instead of looping.
|
|
58
58
|
|
|
59
|
+
**Ask the person through code-kit, not only in chat.** Anything you can't settle yourself and the person has to decide or do (a product choice, an account or key only they can create, wording or a design only they can sign off, whether to keep something private) goes to their Decisions tab, where it waits until they answer instead of scrolling away:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" ask "<the question, in a sentence>" [--story ST-n --blocks] [--why "<why you can't settle it>"] --option "<choice>: <what it does>" --option "…"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- `--blocks` holds the story until they answer: `next` stops dispatching or reviewing it, and the stories that depend on it wait too. Carry on with anything else meanwhile.
|
|
66
|
+
- Their answer comes back to you as a prompt ("The person answered Q-3 …"). If they answer in chat instead, record it: `code-kit ask answer Q-3 "<their words>"`. A question that stops mattering is withdrawn: `code-kit ask withdraw Q-3 --reason "…"`.
|
|
67
|
+
- `attention` names the stories waiting on the person, and `brief` lists the brief's open questions, which they see in the same tab; when they answer one, record it in the brief's decision log and take it out of the open questions.
|
|
68
|
+
- When Claude Code itself refuses a command you need (its permission check, not a code-kit hook), the person sees it in the band with the command: say what it's for, and don't try another way round it.
|
|
69
|
+
- An approval request nobody needs any more (you went another way) is dropped: `code-kit requests drop <n> --reason "…"`, so it stops waiting for them.
|
|
70
|
+
|
|
71
|
+
**When code-kit itself is wrong** (a refusal that shouldn't have happened, a crash, a pane showing the wrong thing) and the config has `harness.report.on`, draft a report for code-kit's maintainers: `code-kit report "<what went wrong, in a few words>" --what "<what happened>" --expected "<what should have>" --command "<what you ran>" --refusal "<what code-kit said>"`. Only code-kit's own facts go in it, never the project's code or data. The person reads it and sends it, or not. Then work around it if you can.
|
|
72
|
+
|
|
59
73
|
When you stop, end with one line saying what happens next and who does it, e.g. "Next: you approve the draft config, then I dispatch ST-1 to ST-4."
|
package/skills/review/SKILL.md
CHANGED
|
@@ -57,7 +57,7 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" security --base <base> --branch <b
|
|
|
57
57
|
|
|
58
58
|
It does when the diff touches sign-in, permissions, input from outside, personal data, encryption, payments, secrets, or queries and commands built from strings, adds dependencies, or the story cites a security requirement; it says which, and where. When it does:
|
|
59
59
|
|
|
60
|
-
- Run the `security-reviewer` agent (Agent tool, `subagent_type` `security-reviewer`) with the branch, the base, the story, its requirements, the spec files it touches (their `## Threat model` and `## Security requirements`) and the reasons `security` gave. It reads only, and ends with the same findings block as code-kit's reviewer: each finding graded blocker, concern or nit, with where, the attack and the fix.
|
|
60
|
+
- Run the `security-reviewer` agent (Agent tool, `subagent_type` `security-reviewer`, its `description` naming the branch, such as "Security review web/st-12", so the loop knows the story is under review) with the branch, the base, the story, its requirements, the spec files it touches (their `## Threat model` and `## Security requirements`) and the reasons `security` gave. It reads only, and ends with the same findings block as code-kit's reviewer: each finding graded blocker, concern or nit, with where, the attack and the fix.
|
|
61
61
|
- **A blocker fails the review**, like a failing test: the branch goes back to its lane with the finding (where, the attack, the fix). Concerns are yours to weigh: send back the ones the threat model says matter, and put the rest in the report. Nits go in the report.
|
|
62
62
|
- No `security-reviewer` agent in the project: `security` says so. Review the diff yourself against the same list in `${CLAUDE_PLUGIN_ROOT}/templates/security-reviewer.md`, and tell the person that re-running `/code-kit:init` adds the agent.
|
|
63
63
|
- A background reviewer can't start agents: it lists the security review as due, as a concern with the reasons, and the lead runs it.
|
package/templates/agent.md
CHANGED
|
@@ -22,6 +22,7 @@ Always:
|
|
|
22
22
|
- Read every file you change in full, plus its imports and its callers.
|
|
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
|
+
- Something only the person can decide (a product choice, wording, an account): write the question for the lead, with the choices and what each does, and carry on with what doesn't depend on it, or stop. The lead asks the person.
|
|
25
26
|
- A new dependency needs a person's approval for your lane. If the install is refused, put the package, why it's needed and the approval command from the refusal in a change request for the lead, and stop.
|
|
26
27
|
- Build every feature end to end. No mocks, stubs, placeholder screens or "coming soon".
|
|
27
28
|
- Prove each acceptance criterion with a test whose title names the requirement ID, e.g. `BOOK-4 …`.
|
package/templates/claude-md.md
CHANGED
|
@@ -10,6 +10,9 @@ The code-kit plugin enforces the rules in `.claude/code-kit.json` on every tool
|
|
|
10
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;}}
|
|
11
11
|
- the project's checks before any agent finishes.
|
|
12
12
|
|
|
13
|
+
When something needs the person to decide or do (a product choice, an account only they can create, wording only they can 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.}}
|
|
15
|
+
|
|
13
16
|
If a hook blocks you, fix the cause; never work around it.
|
|
14
17
|
|
|
15
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:
|