@warren-dean/code-kit 0.10.0 → 0.11.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 +9 -0
- package/README.md +30 -28
- package/bin/code-kit.mjs +90 -2
- package/hooks/guard-bash.mjs +8 -0
- package/hooks/lib/team.mjs +62 -7
- package/hooks/mod/decisions.mjs +3 -0
- package/hooks/mod/panes.mjs +42 -49
- package/hooks/mod/register.mjs +104 -12
- package/hooks/mod/register.test.ts +255 -15
- package/hooks/mod/teamview.mjs +469 -74
- package/hooks/mod/view.mjs +14 -2
- package/package.json +1 -1
- package/skills/init/SKILL.md +2 -1
- package/skills/team-init/SKILL.md +59 -0
package/hooks/mod/register.mjs
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
import { leadPrompt, nudgeText, restartPrompt, stallDue, stepKey, stepLabel } from './loop.mjs';
|
|
49
49
|
import { goalOf, nextText } from './mission.mjs';
|
|
50
50
|
import { contextOf, decisionCards } from './decisions.mjs';
|
|
51
|
+
import { DEFAULT_TEAM_UI, TEAM_ID, teamFrame } from './teamview.mjs';
|
|
51
52
|
import { glowOf, mapStoryOf, touchOf } from './codemap.mjs';
|
|
52
53
|
import { harnessSettings } from '../lib/harness.mjs';
|
|
53
54
|
import {
|
|
@@ -67,7 +68,8 @@ let reviewTurn = null; // 'next' until the lead's review turn starts, then its i
|
|
|
67
68
|
let approving = null; // the open Approve… confirmation: { request, reason, error }
|
|
68
69
|
let result = null; // what the last merge reported
|
|
69
70
|
let notice = null; // why the person's last act failed
|
|
70
|
-
let teamSince = null; // TEAM-6: since you last looked, as read when the Team tab opened
|
|
71
|
+
let teamSince = null; // TEAM-6: since you last looked, as read when the Team pane's tab opened
|
|
72
|
+
let teamUi = { ...DEFAULT_TEAM_UI }; // TEAM-7: the Team pane's own state
|
|
71
73
|
let boardSynced = -Infinity; // TEAM-12: when the board was last synced
|
|
72
74
|
let projectRead = -Infinity; // MAP-5: when the whole project's map was last read
|
|
73
75
|
const BOARD_EVERY_MS = 10 * 60000;
|
|
@@ -176,6 +178,14 @@ export function register(on) {
|
|
|
176
178
|
immediate: true,
|
|
177
179
|
})
|
|
178
180
|
.catch(unavailable('harness'));
|
|
181
|
+
await $.command
|
|
182
|
+
.register({
|
|
183
|
+
name: 'team',
|
|
184
|
+
description:
|
|
185
|
+
"code-kit's team: who's at work on what, claims, intents, drift and the team's setup",
|
|
186
|
+
immediate: true,
|
|
187
|
+
})
|
|
188
|
+
.catch(unavailable('team'));
|
|
179
189
|
rateLimits = (await $.session.usage().catch(() => null))?.rateLimits ?? [];
|
|
180
190
|
const cli = `${$.plugin.root}/bin/code-kit.mjs`;
|
|
181
191
|
const cwd = e.cwd ?? (await $.session.cwd());
|
|
@@ -267,8 +277,8 @@ export function register(on) {
|
|
|
267
277
|
}
|
|
268
278
|
}
|
|
269
279
|
if (paneUi.tab === 'map') trailData = (await json('trail')) ?? trailData;
|
|
270
|
-
// TEAM-6: since you last looked, read once each time
|
|
271
|
-
if (
|
|
280
|
+
// TEAM-6: since you last looked, read once each time its tab opens, which marks it read.
|
|
281
|
+
if (teamUi.tab === 'activity' && model.check?.team && !teamSince)
|
|
272
282
|
teamSince = (await json('team', 'since')) ?? { people: {} };
|
|
273
283
|
// JOURNAL-1, DECIDE-2: the lineage, for the Journal tab and the decisions' cards.
|
|
274
284
|
const deciding = held.size > 0 || (model.requests?.open ?? []).length > 0;
|
|
@@ -412,6 +422,8 @@ export function register(on) {
|
|
|
412
422
|
// TRACE-3: the map follows the specs too.
|
|
413
423
|
specs ? await listing(specs) : '',
|
|
414
424
|
await stamp('.claude/state/requests.jsonl'),
|
|
425
|
+
// TEAM-7: a live provider (the hub's helper) rewrites the team's state as events arrive.
|
|
426
|
+
await stamp('.claude/state/team.json'),
|
|
415
427
|
await stamp('.claude/state/merge-queue.json'),
|
|
416
428
|
await listing('.claude/approvals'),
|
|
417
429
|
await listing('.claude/state/stop-blocks'),
|
|
@@ -578,6 +590,22 @@ export function register(on) {
|
|
|
578
590
|
await act.reload();
|
|
579
591
|
},
|
|
580
592
|
keepClaim: (line) => act.teamAct(['team', 'keep', line.claim.story], 'Keeping the claim'),
|
|
593
|
+
// TEAM-9: the tech lead hands a claim, with its lease, to someone else.
|
|
594
|
+
reassignTo: async (story, handle) => {
|
|
595
|
+
const to = handle.trim().replace(/^@/, '');
|
|
596
|
+
if (!to) return;
|
|
597
|
+
teamUi = { ...teamUi, reassign: null, handle: '' };
|
|
598
|
+
await act.teamAct(['team', 'reassign', story, to], `Reassigning ${story}`);
|
|
599
|
+
},
|
|
600
|
+
// TEAM-13: the team's setup is changed through its init, by the lead, with the person.
|
|
601
|
+
teamSetup: async () => {
|
|
602
|
+
await $.prompt
|
|
603
|
+
.submit({ text: "Run /code-kit:team-init: I want to change this project's team setup." })
|
|
604
|
+
.catch((err) => {
|
|
605
|
+
notice = `Couldn't ask the lead: ${err?.message ?? err}`;
|
|
606
|
+
});
|
|
607
|
+
$.ui.invalidate('ui.render');
|
|
608
|
+
},
|
|
581
609
|
releaseClaim: (line) =>
|
|
582
610
|
act.teamAct(['team', 'release', line.claim.story], 'Releasing the claim'),
|
|
583
611
|
// TEAM-10: agreed, the lead records it where agents read it, through a pull request.
|
|
@@ -599,11 +627,43 @@ export function register(on) {
|
|
|
599
627
|
['intend', 'drop', String(line.intent.id), '--reason', 'dropped in the code-kit pane'],
|
|
600
628
|
'Dropping the intent',
|
|
601
629
|
),
|
|
602
|
-
// TEAM-7: the Team
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
630
|
+
// TEAM-7: the Team pane, opened from the band (on what the line is about) or /team (which closes
|
|
631
|
+
// it when it's open).
|
|
632
|
+
team: async (line, { toggle = false } = {}) => {
|
|
633
|
+
const open = (await $.ui.panes()).some((p) => p.id === TEAM_ID);
|
|
634
|
+
if (open && toggle) {
|
|
635
|
+
await $.ui.close({ id: TEAM_ID });
|
|
636
|
+
return {};
|
|
637
|
+
}
|
|
638
|
+
if (toggle || !model.state) await act.reload();
|
|
639
|
+
if (model.state?.kind === 'none') return { text: NOT_CODE_KIT };
|
|
640
|
+
if (!model.check?.team)
|
|
641
|
+
return {
|
|
642
|
+
text: "This project isn't set up as a team: one developer, no claims or owners. To share it with a team, run /code-kit:team-init.",
|
|
643
|
+
};
|
|
644
|
+
const tab =
|
|
645
|
+
line?.kind === 'teamIntent'
|
|
646
|
+
? 'intents'
|
|
647
|
+
: line?.kind === 'teamDrift'
|
|
648
|
+
? 'drift'
|
|
649
|
+
: ['teamStale', 'teamCollision'].includes(line?.kind)
|
|
650
|
+
? 'claims'
|
|
651
|
+
: line
|
|
652
|
+
? 'people'
|
|
653
|
+
: teamUi.tab;
|
|
654
|
+
teamUi = { ...teamUi, tab };
|
|
655
|
+
// Opening it reads since-you-last-looked again, which marks it read.
|
|
656
|
+
if (tab !== 'activity' || !open) teamSince = null;
|
|
657
|
+
if (!open) {
|
|
658
|
+
const placed = await $.ui.open({
|
|
659
|
+
id: TEAM_ID,
|
|
660
|
+
title: 'Team',
|
|
661
|
+
focus: true,
|
|
662
|
+
closeOnEscape: true,
|
|
663
|
+
});
|
|
664
|
+
if (!placed?.isPlaced && placed?.reason)
|
|
665
|
+
return { text: `The Team pane is waiting: ${placed.reason}` };
|
|
666
|
+
}
|
|
607
667
|
await act.readMaps();
|
|
608
668
|
$.ui.invalidate('ui.render');
|
|
609
669
|
return {};
|
|
@@ -1089,7 +1149,7 @@ export function register(on) {
|
|
|
1089
1149
|
await act.stalls();
|
|
1090
1150
|
await act.animate();
|
|
1091
1151
|
const now = await act.fingerprint();
|
|
1092
|
-
const paneOpen = (await $.ui.panes()).some((p) => p.id
|
|
1152
|
+
const paneOpen = (await $.ui.panes()).some((p) => [PANE_ID, TEAM_ID].includes(p.id));
|
|
1093
1153
|
quiet += 1;
|
|
1094
1154
|
if (now === seen && quiet < (paneOpen ? 5 : 30)) return;
|
|
1095
1155
|
quiet = 0;
|
|
@@ -1113,6 +1173,9 @@ export function register(on) {
|
|
|
1113
1173
|
text: `code-kit is still starting; try /${name} again in a moment.`,
|
|
1114
1174
|
});
|
|
1115
1175
|
on('command.run', { command: 'lanes' }, async ($, e) => (act ? act.lanes() : starting('lanes')));
|
|
1176
|
+
on('command.run', { command: 'team' }, async ($, e) =>
|
|
1177
|
+
act ? act.team(null, { toggle: true }) : starting('team'),
|
|
1178
|
+
);
|
|
1116
1179
|
on('command.run', { command: 'harness' }, async ($, e) =>
|
|
1117
1180
|
act ? act.harness() : starting('harness'),
|
|
1118
1181
|
);
|
|
@@ -1539,8 +1602,6 @@ export function register(on) {
|
|
|
1539
1602
|
needs: linesNow(now).filter((l) => !l.mission),
|
|
1540
1603
|
cards: cardsNow(now),
|
|
1541
1604
|
journal: journalNow(),
|
|
1542
|
-
team: model.team ?? null,
|
|
1543
|
-
teamSince,
|
|
1544
1605
|
now,
|
|
1545
1606
|
live: liveNow(now),
|
|
1546
1607
|
held: [...held.values()],
|
|
@@ -1567,7 +1628,6 @@ export function register(on) {
|
|
|
1567
1628
|
$.ui.resolve(e),
|
|
1568
1629
|
{
|
|
1569
1630
|
onTab: (tab) => {
|
|
1570
|
-
if (tab !== 'team') teamSince = null;
|
|
1571
1631
|
paneUi = { ...paneUi, tab };
|
|
1572
1632
|
$.ui.invalidate('ui.render');
|
|
1573
1633
|
act?.readMaps().then(() => $.ui.invalidate('ui.render'));
|
|
@@ -1704,6 +1764,38 @@ export function register(on) {
|
|
|
1704
1764
|
}),
|
|
1705
1765
|
);
|
|
1706
1766
|
|
|
1767
|
+
// TEAM-7: the Team pane.
|
|
1768
|
+
on('ui.render', { component: 'Pane', requestId: TEAM_ID }, async ($, e) =>
|
|
1769
|
+
teamFrame(
|
|
1770
|
+
{
|
|
1771
|
+
team: model.team ?? null,
|
|
1772
|
+
since: teamSince,
|
|
1773
|
+
config: model.check?.team ? { ...model.check.team, lanes: model.check.lanes ?? {} } : null,
|
|
1774
|
+
ui: teamUi,
|
|
1775
|
+
},
|
|
1776
|
+
$.ui.resolve(e),
|
|
1777
|
+
{
|
|
1778
|
+
onTab: (tab) => {
|
|
1779
|
+
if (tab !== 'activity') teamSince = null;
|
|
1780
|
+
teamUi = { ...teamUi, tab, reassign: null, handle: '' };
|
|
1781
|
+
$.ui.invalidate('ui.render');
|
|
1782
|
+
act?.readMaps().then(() => $.ui.invalidate('ui.render'));
|
|
1783
|
+
},
|
|
1784
|
+
onNeed: (id, line) => act?.[id]?.(line),
|
|
1785
|
+
onReassign: (story) => {
|
|
1786
|
+
teamUi = { ...teamUi, reassign: story, handle: '' };
|
|
1787
|
+
$.ui.invalidate('ui.render');
|
|
1788
|
+
},
|
|
1789
|
+
onHandle: (handle) => {
|
|
1790
|
+
teamUi = { ...teamUi, handle };
|
|
1791
|
+
$.ui.invalidate('ui.render');
|
|
1792
|
+
},
|
|
1793
|
+
onReassignTo: (story, handle) => act?.reassignTo(story, handle),
|
|
1794
|
+
onSetup: () => act?.teamSetup(),
|
|
1795
|
+
},
|
|
1796
|
+
),
|
|
1797
|
+
);
|
|
1798
|
+
|
|
1707
1799
|
on('ui.render', { component: 'Pane', requestId: RESULT_ID }, async ($, e) =>
|
|
1708
1800
|
resultPane(result, $.ui.resolve(e)),
|
|
1709
1801
|
);
|
|
@@ -8,6 +8,7 @@ const PANE = 'code-kit-lanes';
|
|
|
8
8
|
const APPROVE = 'code-kit-approve';
|
|
9
9
|
const RESULT = 'code-kit-result';
|
|
10
10
|
const SETTINGS = 'code-kit-settings';
|
|
11
|
+
const TEAM = 'code-kit-team';
|
|
11
12
|
|
|
12
13
|
/** A project as the stubs report it; tests change it to move the world on. */
|
|
13
14
|
function project() {
|
|
@@ -200,7 +201,7 @@ function stub(on: any, w: World) {
|
|
|
200
201
|
w.acts.push([...argv.slice(2)]);
|
|
201
202
|
return ran(0, JSON.stringify(w.teamSince));
|
|
202
203
|
}
|
|
203
|
-
if (sub === 'team' && ['keep', 'release', 'board'].includes(argv[3])) {
|
|
204
|
+
if (sub === 'team' && ['keep', 'release', 'board', 'reassign'].includes(argv[3])) {
|
|
204
205
|
w.acts.push([...argv.slice(2)]);
|
|
205
206
|
return ran(0, '');
|
|
206
207
|
}
|
|
@@ -430,6 +431,7 @@ const bandUi = ($: any) =>
|
|
|
430
431
|
const press = ($: any, key: string, requestId?: string) =>
|
|
431
432
|
$.ui.press({ plugin: 'code-kit', key, ...(requestId ? { requestId } : {}) });
|
|
432
433
|
const lanes = ($: any) => $.command.run({ command: 'lanes', args: '' });
|
|
434
|
+
const teamCmd = ($: any) => $.command.run({ command: 'team', args: '' });
|
|
433
435
|
const dayjs = {
|
|
434
436
|
at: '2026-10-06T00:00:00.000Z',
|
|
435
437
|
actor: 'web-engineer',
|
|
@@ -3017,7 +3019,32 @@ test("TEAM-8 when the shared repository can't be reached the band says so as a n
|
|
|
3017
3019
|
await band.unmount();
|
|
3018
3020
|
});
|
|
3019
3021
|
|
|
3020
|
-
test('TEAM-
|
|
3022
|
+
test('TEAM-7 /team opens the Team pane on its people: each with their lanes, claims, pulls and intents; /team again closes it', async ($, on) => {
|
|
3023
|
+
const w = project();
|
|
3024
|
+
w.team = teamOf('sam');
|
|
3025
|
+
(w.check as any).team = { techLead: 'alex' };
|
|
3026
|
+
await start($, on, w);
|
|
3027
|
+
await teamCmd($);
|
|
3028
|
+
expect(w.opened).toEqual([TEAM]);
|
|
3029
|
+
const ui = await pane($, TEAM);
|
|
3030
|
+
expect((await ui.find({ key: 'team-header' }))?.text).toMatch(
|
|
3031
|
+
/tech lead alex[\s\S]*you are sam · own web[\s\S]*2 claimed · 1 intent open · 1 pull request/,
|
|
3032
|
+
);
|
|
3033
|
+
expect((await ui.find({ key: 'team-person-alex' }))?.text).toMatch(/alex · tech lead/);
|
|
3034
|
+
expect((await ui.find({ key: 'team-person-sam' }))?.text).toMatch(
|
|
3035
|
+
/sam · you · owns web[\s\S]*ST-4 · stale since Tuesday/,
|
|
3036
|
+
);
|
|
3037
|
+
expect((await ui.find({ key: 'team-person-kim' }))?.text).toMatch(
|
|
3038
|
+
/#14 Booking list \(web\/st-9\)[\s\S]*intends: Split the booking form/,
|
|
3039
|
+
);
|
|
3040
|
+
// GitHub's own provider has no drift checks, so no Drift tab.
|
|
3041
|
+
expect(await ui.find({ key: 'team-tab-drift' })).toBeUndefined();
|
|
3042
|
+
await ui.unmount();
|
|
3043
|
+
await teamCmd($);
|
|
3044
|
+
expect(w.closed).toEqual([TEAM]);
|
|
3045
|
+
});
|
|
3046
|
+
|
|
3047
|
+
test('TEAM-6 since you last looked is read when its tab opens, which marks it read, and again each time it opens', async ($, on) => {
|
|
3021
3048
|
const w = project();
|
|
3022
3049
|
w.team = teamOf('sam');
|
|
3023
3050
|
w.teamSince = {
|
|
@@ -3027,28 +3054,147 @@ test('TEAM-6 and TEAM-7 the Team tab: since you last looked, then each person wi
|
|
|
3027
3054
|
};
|
|
3028
3055
|
(w.check as any).team = { techLead: 'alex' };
|
|
3029
3056
|
await start($, on, w);
|
|
3030
|
-
await
|
|
3031
|
-
const ui = await pane($,
|
|
3032
|
-
|
|
3033
|
-
|
|
3057
|
+
await teamCmd($);
|
|
3058
|
+
const ui = await pane($, TEAM);
|
|
3059
|
+
const reads = () => w.acts.filter((a) => a[0] === 'team' && a[1] === 'since').length;
|
|
3060
|
+
expect(reads()).toBe(0);
|
|
3061
|
+
expect((await ui.find({ key: 'team-tab-activity' }))?.props.hotkey).toBe('2');
|
|
3062
|
+
await press($, 'team-tab-activity', TEAM);
|
|
3034
3063
|
expect(w.acts).toContainEqual(['team', 'since', '--json']);
|
|
3035
3064
|
expect((await ui.find({ key: 'team-since' }))?.text).toMatch(
|
|
3036
|
-
/kim
|
|
3037
|
-
);
|
|
3038
|
-
expect((await ui.find({ key: 'team-person-alex' }))?.text).toMatch(/alex · tech lead/);
|
|
3039
|
-
expect((await ui.find({ key: 'team-person-sam' }))?.text).toMatch(
|
|
3040
|
-
/sam · you · owns web[\s\S]*ST-4 · stale since Tuesday/,
|
|
3065
|
+
/kim[\s\S]*pushed 2 commits to web\/st-9/,
|
|
3041
3066
|
);
|
|
3042
|
-
|
|
3043
|
-
|
|
3067
|
+
await press($, 'team-tab-people', TEAM);
|
|
3068
|
+
await press($, 'team-tab-activity', TEAM);
|
|
3069
|
+
expect(reads()).toBe(2);
|
|
3070
|
+
await ui.unmount();
|
|
3071
|
+
});
|
|
3072
|
+
|
|
3073
|
+
test("TEAM-9 the Claims tab: Keep and Release on yours, and nothing on others'", async ($, on) => {
|
|
3074
|
+
const w = project();
|
|
3075
|
+
w.team = teamOf('sam');
|
|
3076
|
+
(w.check as any).team = { techLead: 'alex' };
|
|
3077
|
+
await start($, on, w);
|
|
3078
|
+
await teamCmd($);
|
|
3079
|
+
const ui = await pane($, TEAM);
|
|
3080
|
+
await press($, 'team-tab-claims', TEAM);
|
|
3081
|
+
expect((await ui.find({ key: 'team-claims-ST-4' }))?.text).toMatch(
|
|
3082
|
+
/ST-4[\s\S]*sam \(you\)[\s\S]*stale since Tuesday[\s\S]*leases apps\/web\/booking\/\*\*/,
|
|
3044
3083
|
);
|
|
3045
|
-
await
|
|
3084
|
+
expect(await ui.find({ key: 'team-release-ST-9' })).toBeUndefined();
|
|
3085
|
+
expect(await ui.find({ key: 'team-reassign-ST-4' })).toBeUndefined();
|
|
3086
|
+
await press($, 'team-keep-ST-4', TEAM);
|
|
3087
|
+
expect(w.acts).toContainEqual(['team', 'keep', 'ST-4']);
|
|
3088
|
+
await press($, 'team-release-ST-4', TEAM);
|
|
3046
3089
|
expect(w.acts).toContainEqual(['team', 'release', 'ST-4']);
|
|
3047
3090
|
await ui.unmount();
|
|
3048
3091
|
});
|
|
3049
3092
|
|
|
3050
|
-
test(
|
|
3093
|
+
test("TEAM-9 the tech lead releases or reassigns anyone's claim from the Claims tab", async ($, on) => {
|
|
3051
3094
|
const w = project();
|
|
3095
|
+
w.team = teamOf('alex');
|
|
3096
|
+
(w.check as any).team = { techLead: 'alex' };
|
|
3097
|
+
await start($, on, w);
|
|
3098
|
+
await teamCmd($);
|
|
3099
|
+
const ui = await pane($, TEAM);
|
|
3100
|
+
await press($, 'team-tab-claims', TEAM);
|
|
3101
|
+
await press($, 'team-release-ST-9', TEAM);
|
|
3102
|
+
expect(w.acts).toContainEqual(['team', 'release', 'ST-9']);
|
|
3103
|
+
expect(await ui.find({ key: 'team-reassign-to-ST-9' })).toBeUndefined();
|
|
3104
|
+
await press($, 'team-reassign-ST-9', TEAM);
|
|
3105
|
+
expect(await ui.find({ key: 'team-reassign-to-ST-9' })).toBeDefined();
|
|
3106
|
+
await $.ui.input({ plugin: 'code-kit', key: 'team-reassign-to-ST-9', text: '@sam' });
|
|
3107
|
+
expect(w.acts).toContainEqual(['team', 'reassign', 'ST-9', 'sam']);
|
|
3108
|
+
expect(await ui.find({ key: 'team-reassign-to-ST-9' })).toBeUndefined();
|
|
3109
|
+
await ui.unmount();
|
|
3110
|
+
});
|
|
3111
|
+
|
|
3112
|
+
test('TEAM-5 the Intents tab lists the open intents, with Agree and Drop for the tech lead', async ($, on) => {
|
|
3113
|
+
const w = project();
|
|
3114
|
+
w.team = teamOf('alex');
|
|
3115
|
+
(w.check as any).team = { techLead: 'alex' };
|
|
3116
|
+
await start($, on, w);
|
|
3117
|
+
await teamCmd($);
|
|
3118
|
+
const ui = await pane($, TEAM);
|
|
3119
|
+
await press($, 'team-tab-intents', TEAM);
|
|
3120
|
+
expect((await ui.find({ key: 'team-intents-12' }))?.text).toMatch(
|
|
3121
|
+
/#12[\s\S]*Split the booking form[\s\S]*kim[\s\S]*touches apps\/web\/\*\*/,
|
|
3122
|
+
);
|
|
3123
|
+
await press($, 'team-drop-12', TEAM);
|
|
3124
|
+
expect(w.acts).toContainEqual([
|
|
3125
|
+
'intend',
|
|
3126
|
+
'drop',
|
|
3127
|
+
'12',
|
|
3128
|
+
'--reason',
|
|
3129
|
+
'dropped in the code-kit pane',
|
|
3130
|
+
]);
|
|
3131
|
+
await ui.unmount();
|
|
3132
|
+
});
|
|
3133
|
+
|
|
3134
|
+
test('TEAM-5 a developer sees the intents without Agree or Drop', async ($, on) => {
|
|
3135
|
+
const w = project();
|
|
3136
|
+
w.team = teamOf('sam');
|
|
3137
|
+
(w.check as any).team = { techLead: 'alex' };
|
|
3138
|
+
await start($, on, w);
|
|
3139
|
+
await teamCmd($);
|
|
3140
|
+
const ui = await pane($, TEAM);
|
|
3141
|
+
await press($, 'team-tab-intents', TEAM);
|
|
3142
|
+
expect(await ui.find({ key: 'team-intents-12' })).toBeDefined();
|
|
3143
|
+
expect(await ui.find({ key: 'team-agree-12' })).toBeUndefined();
|
|
3144
|
+
await ui.unmount();
|
|
3145
|
+
});
|
|
3146
|
+
|
|
3147
|
+
test("TEAM-13 the Setup tab shows the team's config and what's still missing, and asks the lead to change it", async ($, on) => {
|
|
3148
|
+
const w = project();
|
|
3149
|
+
w.team = {
|
|
3150
|
+
...teamOf('sam'),
|
|
3151
|
+
provider: {
|
|
3152
|
+
name: 'GitHub',
|
|
3153
|
+
live: false,
|
|
3154
|
+
detail: 'claims are refs on origin, intents are discussions in "Intents"',
|
|
3155
|
+
},
|
|
3156
|
+
setup: [
|
|
3157
|
+
'Discussions are off for this repository. Turn them on in its Settings › Features, add a "Intents" category.',
|
|
3158
|
+
],
|
|
3159
|
+
};
|
|
3160
|
+
(w.check as any).team = {
|
|
3161
|
+
techLead: 'alex',
|
|
3162
|
+
staleDays: 3,
|
|
3163
|
+
intents: 'Intents',
|
|
3164
|
+
provider: 'github',
|
|
3165
|
+
owners: { web: ['sam'] },
|
|
3166
|
+
};
|
|
3167
|
+
await start($, on, w);
|
|
3168
|
+
await teamCmd($);
|
|
3169
|
+
const ui = await pane($, TEAM);
|
|
3170
|
+
expect((await ui.find({ key: 'team-provider' }))?.text).toBe(
|
|
3171
|
+
'Through GitHub: claims are refs on origin, intents are discussions in "Intents"',
|
|
3172
|
+
);
|
|
3173
|
+
expect((await ui.find({ key: 'team-setup-count' }))?.text).toBe(
|
|
3174
|
+
'1 thing to set up: see Setup (6).',
|
|
3175
|
+
);
|
|
3176
|
+
await press($, 'team-tab-setup', TEAM);
|
|
3177
|
+
expect((await ui.find({ key: 'team-config' }))?.text).toMatch(
|
|
3178
|
+
/Tech lead\s+alex[\s\S]*Provider\s+GitHub[\s\S]*Lane web\s+sam[\s\S]*Unowned lanes\s+api, core/,
|
|
3179
|
+
);
|
|
3180
|
+
expect((await ui.find({ key: 'team-todo' }))?.text).toMatch(/Discussions are off/);
|
|
3181
|
+
await press($, 'team-setup', TEAM);
|
|
3182
|
+
expect(w.prompts.at(-1)).toMatch(/\/code-kit:team-init/);
|
|
3183
|
+
await ui.unmount();
|
|
3184
|
+
});
|
|
3185
|
+
|
|
3186
|
+
test('TEAM-13 without a team, /team says how to set one up, and opens nothing', async ($, on) => {
|
|
3187
|
+
const w = project();
|
|
3188
|
+
await start($, on, w);
|
|
3189
|
+
const said = await teamCmd($);
|
|
3190
|
+
expect(said.text).toMatch(/isn't set up as a team[\s\S]*\/code-kit:team-init/);
|
|
3191
|
+
expect(w.opened).toEqual([]);
|
|
3192
|
+
});
|
|
3193
|
+
|
|
3194
|
+
test('TEAM-7 the Lanes pane has no Team tab: the team has its own pane', async ($, on) => {
|
|
3195
|
+
const w = project();
|
|
3196
|
+
w.team = teamOf('sam');
|
|
3197
|
+
(w.check as any).team = { techLead: 'alex' };
|
|
3052
3198
|
await start($, on, w);
|
|
3053
3199
|
await lanes($);
|
|
3054
3200
|
const ui = await pane($, PANE);
|
|
@@ -3056,6 +3202,100 @@ test('TEAM-7 without a team there is no Team tab', async ($, on) => {
|
|
|
3056
3202
|
await ui.unmount();
|
|
3057
3203
|
});
|
|
3058
3204
|
|
|
3205
|
+
test('a live provider says so in the Team pane, by its own name, and has a Drift tab', async ($, on) => {
|
|
3206
|
+
const w = project();
|
|
3207
|
+
w.team = {
|
|
3208
|
+
...teamOf('kim'),
|
|
3209
|
+
provider: {
|
|
3210
|
+
name: 'the code-kit teams hub',
|
|
3211
|
+
live: true,
|
|
3212
|
+
account: 'kim',
|
|
3213
|
+
detail: 'claims, leases and intents as they happen',
|
|
3214
|
+
},
|
|
3215
|
+
findings: [],
|
|
3216
|
+
};
|
|
3217
|
+
(w.check as any).team = { techLead: 'alex' };
|
|
3218
|
+
await start($, on, w);
|
|
3219
|
+
await teamCmd($);
|
|
3220
|
+
const ui = await pane($, TEAM);
|
|
3221
|
+
expect((await ui.find({ key: 'team-provider' }))?.text).toBe(
|
|
3222
|
+
'Through the code-kit teams hub · live · signed in as kim: claims, leases and intents as they happen',
|
|
3223
|
+
);
|
|
3224
|
+
await press($, 'team-tab-drift', TEAM);
|
|
3225
|
+
expect(
|
|
3226
|
+
await ui.find({ type: 'Text', text: 'No drift found on the branches at work.' }),
|
|
3227
|
+
).toBeDefined();
|
|
3228
|
+
await ui.unmount();
|
|
3229
|
+
});
|
|
3230
|
+
|
|
3231
|
+
test("with a live provider (the hub), the band says when it fell back to GitHub, and names drift on your work; the Team tab shows who's at work and the drift found", async ($, on) => {
|
|
3232
|
+
const w = project();
|
|
3233
|
+
const at = new Date().toISOString();
|
|
3234
|
+
w.team = {
|
|
3235
|
+
...teamOf('kim'),
|
|
3236
|
+
fallback: "The code-kit teams hub can't be reached.",
|
|
3237
|
+
sessions: [
|
|
3238
|
+
{
|
|
3239
|
+
login: 'kim',
|
|
3240
|
+
branch: 'web/st-9',
|
|
3241
|
+
agents: [{ type: 'web-engineer', story: 'ST-9', tool: 'Edit' }],
|
|
3242
|
+
seen: at,
|
|
3243
|
+
away: false,
|
|
3244
|
+
},
|
|
3245
|
+
{ login: 'sam', branch: 'api/st-4', agents: [], seen: at, away: true },
|
|
3246
|
+
],
|
|
3247
|
+
findings: [
|
|
3248
|
+
{
|
|
3249
|
+
id: 1,
|
|
3250
|
+
branch: 'web/st-9',
|
|
3251
|
+
by: 'kim',
|
|
3252
|
+
conflictsWith: 'spec docs/specs/02-booking.md',
|
|
3253
|
+
severity: 'blocker',
|
|
3254
|
+
text: 'BOOK-2 now holds prices in cents.',
|
|
3255
|
+
at,
|
|
3256
|
+
},
|
|
3257
|
+
{
|
|
3258
|
+
id: 2,
|
|
3259
|
+
branch: 'api/st-4',
|
|
3260
|
+
by: 'sam',
|
|
3261
|
+
conflictsWith: 'branch web/st-9',
|
|
3262
|
+
severity: 'note',
|
|
3263
|
+
text: 'only a note',
|
|
3264
|
+
at,
|
|
3265
|
+
},
|
|
3266
|
+
],
|
|
3267
|
+
};
|
|
3268
|
+
(w.check as any).team = { techLead: 'alex' };
|
|
3269
|
+
await start($, on, w);
|
|
3270
|
+
const band = await bandUi($);
|
|
3271
|
+
expect(
|
|
3272
|
+
await band.find({
|
|
3273
|
+
type: 'Text',
|
|
3274
|
+
text: "team: the team's provider can't be reached, so claims are checked through GitHub until it's back",
|
|
3275
|
+
}),
|
|
3276
|
+
).toBeDefined();
|
|
3277
|
+
expect(
|
|
3278
|
+
await band.find({ type: 'Text', text: 'drift on web/st-9: BOOK-2 now holds prices in cents.' }),
|
|
3279
|
+
).toBeDefined();
|
|
3280
|
+
expect(await band.find({ type: 'Text', text: /only a note/ })).toBeUndefined();
|
|
3281
|
+
// The drift line's Team button (the second: the collision's comes first) opens the Team pane on the drift.
|
|
3282
|
+
await press($, 'band-team-2', 'above-prompt');
|
|
3283
|
+
expect(w.opened).toEqual([TEAM]);
|
|
3284
|
+
const ui = await pane($, TEAM);
|
|
3285
|
+
expect((await ui.find({ key: 'team-provider' }))?.text).toMatch(
|
|
3286
|
+
/can't be reached \(The code-kit teams hub can't be reached\.\): claims are checked through GitHub/,
|
|
3287
|
+
);
|
|
3288
|
+
expect((await ui.find({ key: 'team-drift-web/st-9' }))?.text).toMatch(
|
|
3289
|
+
/web\/st-9 · kim[\s\S]*blocker · spec docs\/specs\/02-booking\.md: BOOK-2 now holds prices in cents\./,
|
|
3290
|
+
);
|
|
3291
|
+
await press($, 'team-tab-people', TEAM);
|
|
3292
|
+
expect((await ui.find({ key: 'team-person-kim' }))?.text).toMatch(
|
|
3293
|
+
/at work on web\/st-9: web-engineer on ST-9 \(Edit\)[\s\S]*drift on web\/st-9 \(blocker\): BOOK-2 now holds prices in cents\./,
|
|
3294
|
+
);
|
|
3295
|
+
expect((await ui.find({ key: 'team-person-sam' }))?.text).toMatch(/away \(last seen/);
|
|
3296
|
+
await ui.unmount();
|
|
3297
|
+
});
|
|
3298
|
+
|
|
3059
3299
|
test('TEAM-12 with a board, the mod syncs it in the background, at most every 10 minutes', async ($, on) => {
|
|
3060
3300
|
const w = project();
|
|
3061
3301
|
w.team = teamOf('sam');
|