@warren-dean/code-kit 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/hooks/lib/team.mjs +101 -9
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"name": "code-kit",
|
|
10
10
|
"source": "./",
|
|
11
11
|
"description": "Keeps AI coding agents inside your project's rules, from idea to merged code: spec design, lanes, layer rules, protected paths with an approval log, spec-check and design gates, proof-before-finish checks, dispatch, review, build status and CI verification, configured per project from its docs",
|
|
12
|
-
"version": "0.9.
|
|
12
|
+
"version": "0.9.1",
|
|
13
13
|
"category": "development",
|
|
14
14
|
"tags": ["guardrails", "architecture", "lanes", "layers", "spec-driven", "ci", "multi-agent"],
|
|
15
15
|
"license": "MIT"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-kit",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Keeps AI coding agents inside your project's rules, from idea to merged code: spec design, lanes, layer rules, protected paths with an approval log, spec-check and design gates, proof-before-finish checks, dispatch, review, build status and CI verification, configured per project from its docs",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Warren Langeveldt"
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
What changed in each release of code-kit, newest first. Versions follow the plugin manifest and the npm package `@warren-dean/code-kit`. Each release is also on [GitHub Releases](https://github.com/warrendeanlangeveldt/code-kit/releases).
|
|
4
4
|
|
|
5
|
+
## 0.9.1 (2026-10-12)
|
|
6
|
+
|
|
7
|
+
Team mode, run against a real GitHub repository. It's still a preview.
|
|
8
|
+
|
|
9
|
+
- **Since you last looked names pull requests again.** GitHub's activity feed now leaves the title out of pull request events, so they read "undefined". Titles, drafts and merges now come from the pull requests themselves.
|
|
10
|
+
- **Pushes show up straight away, with how many commits.** GitHub's feed listed pushes late or not at all. code-kit now reads them from git after a fetch, counting only each branch's own commits.
|
|
11
|
+
- **The board stays as it is on a new machine.** The first `code-kit team board` on another computer rewrote fields that were already right. It now reads the board's values first and changes only what differs.
|
|
12
|
+
|
|
5
13
|
## 0.9.0 (2026-10-11)
|
|
6
14
|
|
|
7
15
|
Onboarding in the right order, a QA lane, maps that work on any project, and team mode as a preview.
|
package/README.md
CHANGED
|
@@ -316,7 +316,7 @@ For several people in one repository, each with their own agents. Add `"team": {
|
|
|
316
316
|
- **Stale claims** (nothing pushed for three working days) are flagged, never released on their own: the holder keeps or releases it, and the tech lead can reassign it.
|
|
317
317
|
- **GitHub does its part.** `code-kit team codeowners --write` writes CODEOWNERS from the config, and `"board": { "owner", "number" }` mirrors the plan onto a GitHub Project.
|
|
318
318
|
|
|
319
|
-
It's a preview: it's tested end to end against a stand-in for GitHub,
|
|
319
|
+
It's a preview: it's tested end to end against a stand-in for GitHub, and has been run once against a real private repository (claims, leases, intents as discussions, decision records, since-you-last-looked and the Project board). Spec: `docs/specs/16-teams.md`.
|
|
320
320
|
|
|
321
321
|
## Enforce it in CI
|
|
322
322
|
|
package/hooks/lib/team.mjs
CHANGED
|
@@ -272,6 +272,53 @@ function githubProvider(root, team) {
|
|
|
272
272
|
sha ? `${sha}:${CLAIMS}/${story}` : `:${CLAIMS}/${story}`,
|
|
273
273
|
]);
|
|
274
274
|
const me = () => whoami(root);
|
|
275
|
+
/**
|
|
276
|
+
* TEAM-6: commits on the shared repository's branches since `at`, by the person who made them (their
|
|
277
|
+
* GitHub handle from a noreply address, otherwise their name): [{ at, by, kind: 'pushed', text }].
|
|
278
|
+
*/
|
|
279
|
+
const pushesSince = (at) => {
|
|
280
|
+
if (!ok(git(['fetch', '--prune', '--quiet', 'origin']))) return [];
|
|
281
|
+
const out = [];
|
|
282
|
+
const branches = git(['for-each-ref', '--format=%(refname:lstrip=3)', 'refs/remotes/origin/'])
|
|
283
|
+
.stdout.split('\n')
|
|
284
|
+
.filter((b) => b && b !== 'HEAD');
|
|
285
|
+
// A branch's own commits, not the default branch's it started from.
|
|
286
|
+
const head = git(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']).stdout.trim();
|
|
287
|
+
const main = head
|
|
288
|
+
? head.replace(/^origin\//, '')
|
|
289
|
+
: branches.includes('main')
|
|
290
|
+
? 'main'
|
|
291
|
+
: 'master';
|
|
292
|
+
for (const branch of branches) {
|
|
293
|
+
const log = git([
|
|
294
|
+
'log',
|
|
295
|
+
`refs/remotes/origin/${branch}`,
|
|
296
|
+
...(branch !== main && branches.includes(main)
|
|
297
|
+
? ['--not', `refs/remotes/origin/${main}`]
|
|
298
|
+
: []),
|
|
299
|
+
'--no-merges',
|
|
300
|
+
`--since=@${Math.floor(at / 1000)}`,
|
|
301
|
+
'--format=%ae%x00%an%x00%ct',
|
|
302
|
+
]).stdout;
|
|
303
|
+
const byPerson = new Map();
|
|
304
|
+
for (const line of log.split('\n').filter(Boolean)) {
|
|
305
|
+
const [email, name, unix] = line.split('\0');
|
|
306
|
+
// git's --since is to the second; the last read may have been later in the same second.
|
|
307
|
+
if (Number(unix) * 1000 <= at) continue;
|
|
308
|
+
const who = email.match(/^(?:\d+\+)?([^@]+)@users\.noreply\.github\.com$/)?.[1] ?? name;
|
|
309
|
+
const seen = byPerson.get(who) ?? { n: 0, at: 0 };
|
|
310
|
+
byPerson.set(who, { n: seen.n + 1, at: Math.max(seen.at, Number(unix) * 1000) });
|
|
311
|
+
}
|
|
312
|
+
for (const [who, { n, at: when }] of byPerson)
|
|
313
|
+
out.push({
|
|
314
|
+
at: when,
|
|
315
|
+
by: who,
|
|
316
|
+
kind: 'pushed',
|
|
317
|
+
text: `pushed ${n} commit${n === 1 ? '' : 's'} to ${branch}`,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return out;
|
|
321
|
+
};
|
|
275
322
|
|
|
276
323
|
const provider = {
|
|
277
324
|
whoami: () => ({ login: me() }),
|
|
@@ -449,9 +496,35 @@ function githubProvider(root, team) {
|
|
|
449
496
|
} catch {
|
|
450
497
|
return null;
|
|
451
498
|
}
|
|
499
|
+
// The pull requests themselves, for what GitHub's trimmed events leave out.
|
|
500
|
+
const listed = JSON.parse(
|
|
501
|
+
gh([
|
|
502
|
+
'pr',
|
|
503
|
+
'list',
|
|
504
|
+
'--state',
|
|
505
|
+
'all',
|
|
506
|
+
'--limit',
|
|
507
|
+
'100',
|
|
508
|
+
'--json',
|
|
509
|
+
'number,title,isDraft,mergedAt',
|
|
510
|
+
]) ?? '[]',
|
|
511
|
+
);
|
|
512
|
+
const pulls = Object.fromEntries(
|
|
513
|
+
listed.map((x) => [
|
|
514
|
+
x.number,
|
|
515
|
+
{ title: x.title, draft: x.isDraft, merged: Boolean(x.mergedAt) },
|
|
516
|
+
]),
|
|
517
|
+
);
|
|
518
|
+
// Pushes from git itself: GitHub's feed publishes them late, if at all, and without counts.
|
|
519
|
+
const pushes = pushesSince(at);
|
|
452
520
|
const found = discussions();
|
|
453
521
|
return [
|
|
454
|
-
...activityOf(
|
|
522
|
+
...activityOf(
|
|
523
|
+
events.filter((e) => e.type !== 'PushEvent'),
|
|
524
|
+
at,
|
|
525
|
+
pulls,
|
|
526
|
+
),
|
|
527
|
+
...pushes,
|
|
455
528
|
...discussionActivity(found?.intents ?? [], at, team.techLead),
|
|
456
529
|
].sort((a, b) => a.at - b.at);
|
|
457
530
|
},
|
|
@@ -683,7 +756,7 @@ export function teamRefusal({ actor, rel, branch, config, me, state }) {
|
|
|
683
756
|
* TEAM-6: GitHub's repository events since `at` (ms) as plain sentences: [{ at, by, kind, text }],
|
|
684
757
|
* oldest first. Events code-kit doesn't speak about (stars, forks, wiki edits) are left out.
|
|
685
758
|
*/
|
|
686
|
-
export function activityOf(events, at) {
|
|
759
|
+
export function activityOf(events, at, pulls = {}) {
|
|
687
760
|
const out = [];
|
|
688
761
|
for (const e of Array.isArray(events) ? events : []) {
|
|
689
762
|
const when = Date.parse(e.created_at);
|
|
@@ -692,20 +765,31 @@ export function activityOf(events, at) {
|
|
|
692
765
|
const p = e.payload ?? {};
|
|
693
766
|
const add = (kind, text) => out.push({ at: when, by, kind, text });
|
|
694
767
|
if (e.type === 'PushEvent' && String(p.ref).startsWith('refs/heads/')) {
|
|
695
|
-
|
|
696
|
-
|
|
768
|
+
// GitHub's events no longer always carry the commit count: say it only when they do.
|
|
769
|
+
const n = p.size ?? p.commits?.length ?? null;
|
|
770
|
+
add(
|
|
771
|
+
'pushed',
|
|
772
|
+
`pushed ${n === null ? '' : `${n} commit${n === 1 ? '' : 's'} `}to ${p.ref.slice(11)}`,
|
|
773
|
+
);
|
|
697
774
|
} else if (e.type === 'CreateEvent' && p.ref_type === 'branch')
|
|
698
775
|
add('branched', `started ${p.ref}`);
|
|
699
776
|
else if (e.type === 'PullRequestEvent') {
|
|
777
|
+
// GitHub's events are trimmed to the pull request's number and branches; its title, draft and
|
|
778
|
+
// merged come from the pull requests themselves (`pulls`, by number), where the event lacks them.
|
|
700
779
|
const pr = p.pull_request ?? {};
|
|
701
|
-
const
|
|
780
|
+
const known = pulls[pr.number ?? p.number] ?? {};
|
|
781
|
+
const title = pr.title ?? known.title;
|
|
782
|
+
const name = `#${pr.number ?? p.number}${title ? ` ${title}` : ''}`;
|
|
783
|
+
const draft = pr.draft ?? known.draft;
|
|
784
|
+
const merged = pr.merged ?? known.merged;
|
|
702
785
|
if (p.action === 'opened' || p.action === 'reopened')
|
|
703
|
-
add('opened', `opened ${
|
|
704
|
-
else if (p.action === 'closed' &&
|
|
786
|
+
add('opened', `opened ${draft ? 'draft ' : ''}pull request ${name}`);
|
|
787
|
+
else if (p.action === 'closed' && merged) add('merged', `merged ${name}`);
|
|
705
788
|
else if (p.action === 'closed') add('closed', `closed ${name} without merging`);
|
|
706
789
|
else if (p.action === 'ready_for_review') add('opened', `marked ${name} ready for review`);
|
|
707
790
|
} else if (e.type === 'PullRequestReviewEvent') {
|
|
708
791
|
const pr = p.pull_request ?? {};
|
|
792
|
+
const title = pr.title ?? pulls[pr.number]?.title;
|
|
709
793
|
const state = String(p.review?.state ?? '').toLowerCase();
|
|
710
794
|
const how =
|
|
711
795
|
state === 'approved'
|
|
@@ -713,7 +797,7 @@ export function activityOf(events, at) {
|
|
|
713
797
|
: state === 'changes_requested'
|
|
714
798
|
? 'asked for changes on'
|
|
715
799
|
: 'reviewed';
|
|
716
|
-
add('reviewed', `${how} #${pr.number} ${
|
|
800
|
+
add('reviewed', `${how} #${pr.number}${title ? ` ${title}` : ''}`);
|
|
717
801
|
}
|
|
718
802
|
}
|
|
719
803
|
return out.sort((a, b) => a.at - b.at);
|
|
@@ -799,7 +883,15 @@ export function syncBoard(root, team, stories, claims) {
|
|
|
799
883
|
for (const item of json(['item-list', String(number), ...at, '--limit', '1000']).items ??
|
|
800
884
|
[]) {
|
|
801
885
|
const id = String(item.title ?? item.content?.title ?? '').split(' ')[0];
|
|
802
|
-
|
|
886
|
+
// gh gives each item's field values under the field's name in lower case: what's there already
|
|
887
|
+
// isn't written again.
|
|
888
|
+
if (isStoryId(id) && !cache.items[id])
|
|
889
|
+
cache.items[id] = {
|
|
890
|
+
id: item.id,
|
|
891
|
+
values: Object.fromEntries(
|
|
892
|
+
BOARD_FIELDS.map((f) => [f, String(item[f.toLowerCase()] ?? '')]),
|
|
893
|
+
),
|
|
894
|
+
};
|
|
803
895
|
}
|
|
804
896
|
}
|
|
805
897
|
const byStory = new Map(claims.map((c) => [c.story, c]));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warren-dean/code-kit",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Keeps AI coding agents inside your project's rules: lanes, layer rules, protected paths and proof before finish, enforced by Claude Code hooks and in CI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Warren Langeveldt",
|