@warren-dean/code-kit 0.11.1 → 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.
@@ -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) => ['in progress', 'review', 'sent back'].includes(s.state));
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' },
@@ -715,6 +722,10 @@ export function bandLines({
715
722
  nextStep = null,
716
723
  combined = null,
717
724
  team = null,
725
+ questions = [],
726
+ brief = [],
727
+ denied = [],
728
+ reports = [],
718
729
  }) {
719
730
  const lines = [];
720
731
  // MISSION-1: the goal, now, next and the agents at work, above what needs the person.
@@ -725,18 +736,39 @@ export function bandLines({
725
736
  if (!state || state.kind !== 'ok') return lines;
726
737
  // HOLD-2: calls held for the person, oldest first, with the time left on the oldest.
727
738
  if (held.length) {
728
- const [call] = [...held].sort((a, b) => a.since - b.since);
739
+ const calls = [...held].sort((a, b) => a.since - b.since);
740
+ const [call] = calls;
729
741
  const left = timeLeft(call.since + call.minutes * 60000 - now);
730
742
  lines.push({
731
743
  kind: 'held',
732
744
  text: `${held.length > 1 ? `${held.length} calls held: ` : ''}${call.who} wants to ${heldVerb(call)} · ${left}`,
733
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
+ })),
734
751
  actions: [
735
752
  { id: 'approveHeld', label: 'Approve' },
736
753
  { id: 'refuseHeld', label: 'Refuse' },
737
754
  ],
738
755
  });
739
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
+ }
740
772
  // LOOP-3: a lane stalled past its restarts waits for the person.
741
773
  for (const f of loop?.flagged ?? [])
742
774
  lines.push({
@@ -778,8 +810,28 @@ export function bandLines({
778
810
  text: `${plural(asks.length, 'approval', 'approvals')} waiting`,
779
811
  request: asks[0].request,
780
812
  files: asks[0].files,
813
+ // CARD-8: each approval is its own decision in the Decisions tab.
814
+ asks,
781
815
  actions: [{ id: 'approve', label: 'Approve…' }],
782
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
+ });
783
835
  // DECIDE-1: reviewing a lane's work is the lead's. Only with the loop off, where the person runs the
784
836
  // build themselves, does a finished story wait in the band for their Review or Merge; otherwise the
785
837
  // loop prompts the lead (or, under propose, offers the step with Go).
@@ -839,6 +891,18 @@ export function bandLines({
839
891
  });
840
892
  // TEAM-7, TEAM-9: what of the team needs this person.
841
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
+ });
842
906
  // LOOP-7: the milestone is done; LOOP-6: the loop's own state, with Pause or Resume.
843
907
  if (loop?.state === 'done')
844
908
  lines.push({
@@ -880,6 +944,10 @@ export function band(lines, { Box, Text, Button }, onAction) {
880
944
  usage: 'yellow',
881
945
  paused: 'yellow',
882
946
  facts: 'yellow',
947
+ question: 'cyan',
948
+ brief: 'cyan',
949
+ denied: 'red',
950
+ report: 'magenta',
883
951
  };
884
952
  const LIVE = { quiet: 'yellow', stalled: 'red' };
885
953
  const LABEL = 8;
@@ -1025,6 +1093,50 @@ export function approvePane(
1025
1093
  });
1026
1094
  }
1027
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
+
1028
1140
  /** What a merge (or another act run through the CLI) reported. */
1029
1141
  export function resultPane(result, { Box, Text }) {
1030
1142
  if (!result) return Text({ dimColor: true, children: ['Nothing to show.'] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warren-dean/code-kit",
3
- "version": "0.11.1",
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",
@@ -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`), and how hands-off the build is (`harness.autonomy`);
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.
@@ -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."
@@ -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 …`.
@@ -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: