@warren-dean/code-kit 0.18.0 → 0.20.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 +23 -0
- package/README.md +32 -27
- package/bin/code-kit.mjs +224 -5
- package/hooks/guard-bash.mjs +8 -0
- package/hooks/hooks.json +22 -0
- package/hooks/lib/adapters/hub/client.mjs +708 -0
- package/hooks/lib/adapters/hub/index.mjs +25 -0
- package/hooks/lib/adapters/index.mjs +35 -0
- package/hooks/lib/harness.mjs +17 -2
- package/hooks/lib/instructions.mjs +1 -1
- package/hooks/lib/next.mjs +27 -1
- package/hooks/lib/plan.mjs +24 -0
- package/hooks/lib/snapshot.mjs +352 -0
- package/hooks/lib/team.mjs +46 -7
- package/hooks/lib/teamfacts.mjs +10 -6
- package/hooks/mod/loop.mjs +20 -3
- package/hooks/mod/register.mjs +116 -7
- package/hooks/mod/register.test.ts +118 -6
- package/hooks/mod/teamview.mjs +8 -2
- package/hooks/mod/view.mjs +36 -0
- package/hooks/team-session.mjs +57 -0
- package/package.json +1 -1
- package/skills/dispatch/SKILL.md +2 -0
- package/skills/review/SKILL.md +2 -2
- package/skills/spec-check/SKILL.md +1 -1
- package/skills/team-init/SKILL.md +3 -3
- package/templates/agent.md +3 -1
package/hooks/mod/view.mjs
CHANGED
|
@@ -1676,6 +1676,42 @@ export function withUsage(ledger, who, usage) {
|
|
|
1676
1676
|
};
|
|
1677
1677
|
}
|
|
1678
1678
|
|
|
1679
|
+
/**
|
|
1680
|
+
* HUBR-2: the ledger as a report carries it: [{ story, kind, input, output, cache }], each entry's kind
|
|
1681
|
+
* (lead, lane, reviewer, security-reviewer or background) from who made it and `typeOf(key)`, the agent's
|
|
1682
|
+
* type. Tokens only: no prompt, no message.
|
|
1683
|
+
*/
|
|
1684
|
+
export function usageForReport(ledger, typeOf = () => null) {
|
|
1685
|
+
return Object.fromEntries(
|
|
1686
|
+
Object.entries(ledger).map(([id, e]) => {
|
|
1687
|
+
const type = e.key === 'lead' ? null : typeOf(e.key);
|
|
1688
|
+
const kind =
|
|
1689
|
+
e.key === 'lead'
|
|
1690
|
+
? 'lead'
|
|
1691
|
+
: e.lane
|
|
1692
|
+
? 'lane'
|
|
1693
|
+
: type === 'security-reviewer'
|
|
1694
|
+
? 'security-reviewer'
|
|
1695
|
+
: /(^|:)reviewer$/.test(type ?? '')
|
|
1696
|
+
? 'reviewer'
|
|
1697
|
+
: 'background';
|
|
1698
|
+
return [
|
|
1699
|
+
id,
|
|
1700
|
+
{ story: e.story ?? null, kind, input: e.input, output: e.output, cache: e.cache },
|
|
1701
|
+
];
|
|
1702
|
+
}),
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
/**
|
|
1707
|
+
* HUBR-6: an agent's presence, by tool name and the branch its lane works on (never what it reads or
|
|
1708
|
+
* writes): { type, tool, branch, at }.
|
|
1709
|
+
*/
|
|
1710
|
+
export function presenceOf({ agentId, type, tool, lane, lanes, at }) {
|
|
1711
|
+
const branch = lane ? (lanes?.find((l) => l.name === lane)?.branch ?? null) : null;
|
|
1712
|
+
return { type: agentId ? (type ?? null) : 'lead', tool: tool ?? null, branch, at };
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1679
1715
|
const total = (e) => e.input + e.output + e.cache;
|
|
1680
1716
|
const median = (xs) => {
|
|
1681
1717
|
const s = [...xs].sort((a, b) => a - b);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// SessionStart and SessionEnd (code-kit spec 21, HUBR-6): when the team's provider is a hosted one, its
|
|
2
|
+
// adapter holds the session's live link (the event stream and the team's state on disk) from start to
|
|
3
|
+
// end, and the session reports the project's facts once as it starts (HUBR-3). Nothing happens in a
|
|
4
|
+
// project whose team works through GitHub, or that has no team.
|
|
5
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { providerAdapterOf } from './lib/adapters/index.mjs';
|
|
10
|
+
|
|
11
|
+
let input = {};
|
|
12
|
+
try {
|
|
13
|
+
input = JSON.parse(readFileSync(0, 'utf8') || '{}');
|
|
14
|
+
} catch {
|
|
15
|
+
// no input: nothing to start
|
|
16
|
+
}
|
|
17
|
+
const root = process.env.CLAUDE_PROJECT_DIR ?? input.cwd ?? process.cwd();
|
|
18
|
+
const session = String(input.session_id ?? 'session').replace(/[^\w-]/g, '');
|
|
19
|
+
let raw = null;
|
|
20
|
+
try {
|
|
21
|
+
raw = JSON.parse(readFileSync(join(root, '.claude', 'code-kit.json'), 'utf8'));
|
|
22
|
+
} catch {
|
|
23
|
+
// not a code-kit project
|
|
24
|
+
}
|
|
25
|
+
const hosted = providerAdapterOf(raw?.team?.provider);
|
|
26
|
+
if (hosted && !hosted.problem) {
|
|
27
|
+
const { adapter, url } = hosted;
|
|
28
|
+
if (process.argv[2] === 'start') {
|
|
29
|
+
// The link lives as long as the Claude Code process that started this hook.
|
|
30
|
+
let watch = process.ppid;
|
|
31
|
+
try {
|
|
32
|
+
watch =
|
|
33
|
+
Number(
|
|
34
|
+
execFileSync('ps', ['-o', 'ppid=', '-p', String(process.ppid)], {
|
|
35
|
+
encoding: 'utf8',
|
|
36
|
+
}).trim(),
|
|
37
|
+
) || process.ppid;
|
|
38
|
+
} catch {
|
|
39
|
+
// the hook's own parent, then
|
|
40
|
+
}
|
|
41
|
+
adapter.session.start(root, session, watch, url);
|
|
42
|
+
// The project's facts as the session starts, in the background: the session doesn't wait on it.
|
|
43
|
+
if (raw.team.report !== false)
|
|
44
|
+
spawn(
|
|
45
|
+
process.execPath,
|
|
46
|
+
[
|
|
47
|
+
join(dirname(fileURLToPath(import.meta.url)), '..', 'bin', 'code-kit.mjs'),
|
|
48
|
+
'team',
|
|
49
|
+
'pulse',
|
|
50
|
+
'--session',
|
|
51
|
+
session,
|
|
52
|
+
],
|
|
53
|
+
{ cwd: root, detached: true, stdio: 'ignore' },
|
|
54
|
+
).unref();
|
|
55
|
+
}
|
|
56
|
+
if (process.argv[2] === 'end') adapter.session.end(root, session);
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warren-dean/code-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.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,6 +48,8 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" team claim ST-<n> --lease "<glob>,
|
|
|
48
48
|
|
|
49
49
|
The lease is the paths the story will create or change: its lane's folders for this feature, and any contract it changes. A claim someone else holds is refused with who holds it; pick another story or ask them. Stories in a lane another person owns are theirs to dispatch. Making the story's branch also claims it, and each commit on it adds what it changed to the lease; merging it ends the claim.
|
|
50
50
|
|
|
51
|
+
One agent per story: never send a running lane agent a second story, or a send-back. A story that goes on (sent back, or handed back at its context limit) gets a fresh agent: first run `node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" worktrees release <branch>`, which keeps the branch's commits and moves its report to the main checkout, then start the agent as below with the brief and "Read your earlier report first: `.claude/state/reports/<branch with / as _>.md` in the main checkout". A long-lived agent rereads its whole context on every step, which is most of what a build costs.
|
|
52
|
+
|
|
51
53
|
Start each story with the Agent tool: `subagent_type` is the lane's agent, `isolation: "worktree"`, and run them in the background, with a `description` that starts with the story's id ("ST-12 booking form"): the loop reads it to know the story is in flight, and won't ask for it again while the agent works. Start every story in the batch in the same message so they run in parallel.
|
|
52
54
|
|
|
53
55
|
The brief is the agent's whole context. Write it so the agent needs nothing else:
|
package/skills/review/SKILL.md
CHANGED
|
@@ -38,7 +38,7 @@ Any problem it reports means the branch goes back. Don't fix a lane's code yours
|
|
|
38
38
|
|
|
39
39
|
## 3. Check the work against the story
|
|
40
40
|
|
|
41
|
-
Read the story
|
|
41
|
+
Read the story and every requirement it cites. Then start from what's already been worked out, not the whole diff: the lane's report (`.claude/state/reports/<branch with / as _>.md`, in its worktree or the main checkout), the reviewer's findings when the background reviewer ran, and `git diff --stat <base>...<branch>`. Open the diff one file at a time (`git diff <base>...<branch> -- <file>`) for the files each requirement depends on and the files a finding names, and a changed file in full only where its diff doesn't show the behaviour. Everything you open stays in your context for the rest of the build, so open what the verdict needs. Then, for each requirement:
|
|
42
42
|
|
|
43
43
|
| Requirement | Acceptance criterion | Built (where) | Test (which) | Verdict |
|
|
44
44
|
| ----------- | -------------------- | ------------- | ------------ | ------- |
|
|
@@ -84,7 +84,7 @@ Report the table, `verify`'s result, the security review's findings (or that non
|
|
|
84
84
|
node "${CLAUDE_PLUGIN_ROOT}/bin/code-kit.mjs" sent-back <branch> --reason "<the problems, in one line>"
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
Dispatch the story again to a fresh agent with the list added to the brief (`/code-kit:dispatch`, which first releases the branch's worktree), never to the agent that built it: its context is already large. Set the story's status to `in progress`.
|
|
88
88
|
|
|
89
89
|
- **Ask the person.** When the right answer is a product or scope decision (the spec is silent, or the lane found a real conflict), put the question to the person with your recommendation. Don't merge around it.
|
|
90
90
|
|
|
@@ -12,7 +12,7 @@ The project's specs are in the folder named by `docs.specs` in `.claude/code-kit
|
|
|
12
12
|
1. **Find the section.** The task should name its requirement IDs (`BOOK-4`, headings in the specs) or a spec section. If it doesn't, search the specs folder for the domain nouns. Read the whole section, not just the matching lines, plus every section it references.
|
|
13
13
|
2. **Read the principles.** Re-read the parts of `docs.principles` that apply: layers, ports and providers, events, testing and the rules for agents. Note this lane's layer rules from `layers` in the config.
|
|
14
14
|
3. **Make a checklist.** One line per requirement: behaviour, fields, error cases, permissions, states, tests.
|
|
15
|
-
4. **Compare with the code.** For each line, search for where it's implemented, then read
|
|
15
|
+
4. **Compare with the code.** For each line, search for where it's implemented, then read what implements it: the functions and types the requirement touches, not every file around them.
|
|
16
16
|
5. **Report** as a table:
|
|
17
17
|
|
|
18
18
|
| Requirement | Status (done / partial / missing / conflicts) | Where | Note |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: team-init
|
|
3
|
-
description: Set up a code-kit project for a team of developers, change its team setup, or make it one developer's again. Asks who the tech lead is, who owns which lanes, when a claim goes stale and where intents go, shows the team section before writing it, checks GitHub is ready (gh signed in, the shared repository, the intents' discussion category),
|
|
3
|
+
description: Set up a code-kit project for a team of developers, change its team setup, or make it one developer's again. Asks who the tech lead is, who owns which lanes, when a claim goes stale and where intents go, shows the team section before writing it, checks GitHub is ready (gh signed in, the shared repository, the intents' discussion category), offers CODEOWNERS and the Project board, and connects a hosted team provider only when the config already names one. Use when the person says they work in a team, wants teammates to share the project, wants to change the tech lead or lane owners, wants to stop working as a team, or runs /code-kit:team-init. Not every developer works in a team: never set one up unasked.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Set up the team
|
|
@@ -25,7 +25,7 @@ Ask only what the config can't answer, with the current value as the default on
|
|
|
25
25
|
- **Tech lead:** whose GitHub handle owns the plan, specs, contracts and principles, agrees or drops intents, and may release or reassign anyone's claim. Default: `me`.
|
|
26
26
|
- **Lane owners:** for each lane, whose sessions build its stories (GitHub handles), or nobody, in which case whoever claims a story first builds it. `git log --format='%an' -- <lane paths> | sort | uniq -c` and CODEOWNERS suggest people, but ask for handles: names in commits aren't handles.
|
|
27
27
|
- **Stale after:** how many working days without a push before a claim is stale (default 3).
|
|
28
|
-
- **Intents category:** the GitHub Discussions category intents go in (default `Intents`).
|
|
28
|
+
- **Intents category:** the GitHub Discussions category intents go in (default `Intents`). Skip it when the config's `team.provider` names a hosted provider (`{ "<name>": "<https address>" }`) and `team.intents` isn't set: that provider keeps the intents.
|
|
29
29
|
- **Back to one developer:** confirm that claims, owners and intents stop applying, and that teammates' sessions lose them too once it's merged.
|
|
30
30
|
|
|
31
31
|
## 3. Show, then write
|
|
@@ -48,7 +48,7 @@ Each is optional; ask, and do only what they want:
|
|
|
48
48
|
|
|
49
49
|
- **CODEOWNERS** (TEAM-11): `team codeowners --write` writes `.github/CODEOWNERS` from the config, so GitHub asks the tech lead and lane owners to review. Then they require code owners' review in a ruleset on the protected branch.
|
|
50
50
|
- **A Project board** (TEAM-12): with a GitHub Project's owner and number, add `"board": { "owner": "<user or organisation>", "number": <n> }` to `team` in the config; the mod then mirrors the plan onto it.
|
|
51
|
-
- **A hosted team provider
|
|
51
|
+
- **A hosted team provider, only when the config already names one:** hosted providers are in preview, so never offer or suggest one. When `team.provider` is `{ "<name>": "<https address>" }` (set by hand), connect this machine: run `team provider status`; if it isn't signed in, run `team provider signin` in the background, give the person the code and the GitHub address it prints, and wait (they enter the code in their browser; never ask for a password or token); then `team provider connect`, which checks the provider can see the repository and adds its MCP server to `.mcp.json`. Commit `.mcp.json` with the config.
|
|
52
52
|
|
|
53
53
|
## 5. Finish
|
|
54
54
|
|
package/templates/agent.md
CHANGED
|
@@ -19,7 +19,7 @@ Always:
|
|
|
19
19
|
|
|
20
20
|
- Work on the branch the brief names (`<lane>/st-<n>`). Start every task with the spec-check skill and save its report to .claude/state/spec-check/<branch>.md; the path guard blocks code until it exists.
|
|
21
21
|
- Follow `{{docs.principles}}`, including the rules for AI agents. Hooks enforce ownership, layers and the project's checks; if one blocks you, fix the cause, never work around it.
|
|
22
|
-
- Read every file you change in full
|
|
22
|
+
- Read every file you change in full. Of what it imports and what calls it, read what your change needs: the definitions it uses and the call sites it changes. A context tool's fresh card for a file stands for reading it whole; read the source where the card is missing, stale or not enough.
|
|
23
23
|
- Search before you build; reuse existing services, providers, components and schemas.
|
|
24
24
|
- Write only in your owned paths. For anything else, write a short change request for the lead and stop.
|
|
25
25
|
- You answer to the lead, never the person. A question the spec doesn't settle (a product choice, UX the spec is silent on, an account): write it for the lead, with the choices and what each does, and carry on with what doesn't depend on it, or stop. The lead decides, or asks the person.
|
|
@@ -28,7 +28,9 @@ Always:
|
|
|
28
28
|
- Prove each acceptance criterion with a test whose title names the requirement ID, e.g. `BOOK-4 …`.
|
|
29
29
|
- Fixing a bug: first write a test that fails because of the bug, through the code that's already there, then fix it. `verify` runs your test without the fix, and it must fail on an assertion, not because it calls something the fix adds.
|
|
30
30
|
- Never weaken a test to get to green: no deleting, skipping or focusing tests, cutting cases or assertions, or rewriting snapshots. The commit is refused without the person's approval. Fix the code; if the test itself is wrong, say why in a change request for the lead.
|
|
31
|
+
- Run the narrowest tests that prove each step, with failures only in the output (a quiet reporter, or `| tail -40` on failure); run the full suite once, before you commit. Long output stays in your context and is paid for on every step after it.
|
|
31
32
|
- Commit your work on your branch before finishing; the stop hook runs the checks and requires it.
|
|
32
33
|
- Don't message the lead while you work: it hears from you once, when you finish. If you're blocked on something only the lead or the person can settle, finish and say so.
|
|
34
|
+
- One story per agent. If you're asked to wrap up because your context is large, finish the step you're on, commit, write your report and hand back: a fresh agent carries the story on from your report.
|
|
33
35
|
- Before you finish, write your full report to `.claude/state/reports/<branch>.md` (the branch with `/` as `_`, as for spec-check): what you did and why, the spec section, the commands you ran with their output, what's left, and anything you need decided, with the choices and any text you'd otherwise paste. The stop hook requires it.
|
|
34
36
|
- Finish with a short hand-back, at most ten lines: the story and branch, done or blocked, any decision the lead must make, and the report's path. No output or code in it: the lead reads the report when it reviews.
|