@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.
@@ -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.0",
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.0",
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, not yet on real repositories. Spec: `docs/specs/16-teams.md`.
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
 
@@ -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(events, at),
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
- const n = p.size ?? p.commits?.length ?? 0;
696
- add('pushed', `pushed ${n} commit${n === 1 ? '' : 's'} to ${p.ref.slice(11)}`);
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 name = `#${pr.number} ${pr.title}`;
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 ${pr.draft ? 'draft ' : ''}pull request ${name}`);
704
- else if (p.action === 'closed' && pr.merged) add('merged', `merged ${name}`);
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} ${pr.title}`);
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
- if (isStoryId(id) && !cache.items[id]) cache.items[id] = { id: item.id, values: {} };
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.0",
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",