@warren-dean/code-kit 0.19.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 +10 -0
- package/README.md +4 -1
- package/bin/code-kit.mjs +145 -4
- 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/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/register.mjs +35 -0
- package/hooks/mod/register.test.ts +45 -4
- 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/team-init/SKILL.md +3 -3
|
@@ -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.
|
|
12
|
+
"version": "0.20.0",
|
|
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.
|
|
3
|
+
"version": "0.20.0",
|
|
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,16 @@
|
|
|
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.20.0 (2026-10-21)
|
|
6
|
+
|
|
7
|
+
A team provider can now receive a report of a project's facts, and a hosted provider's adapter is in preview.
|
|
8
|
+
|
|
9
|
+
- A team provider can receive a report of the project: each story's stage and when it changed, milestones and their targets, send-backs, the requirement trail, approvals, token use by kind of agent, and the project's rules. A report never holds code, diffs or prompts; its fields are a fixed list, and anything else is refused before it's sent. With GitHub as the provider, nothing is sent anywhere.
|
|
10
|
+
- `code-kit team report --dry-run` shows exactly what would be sent, and `team.report: false` turns reports off.
|
|
11
|
+
- A milestone in the plan can have a target date: `**Target:** 2026-11-30` under its heading.
|
|
12
|
+
- A hosted provider's adapter is in preview. It runs only for a project whose config names one, connects through code-kit's own hooks with GitHub as its fallback, and doesn't need a Discussions category for intents.
|
|
13
|
+
- A decision record now names whoever agreed the intent, not always the tech lead.
|
|
14
|
+
|
|
5
15
|
## 0.19.0 (2026-10-19)
|
|
6
16
|
|
|
7
17
|
A build uses far fewer tokens. In two days of two builds, output was under 1% of the tokens: nearly all of it was agents rereading large contexts on every request.
|
package/README.md
CHANGED
|
@@ -468,6 +468,9 @@ code-kit team claim <story> [--lease globs] | team release <story> # claim a s
|
|
|
468
468
|
code-kit team keep <story> | team reassign <story> <handle> # keep your stale claim (its clock starts again); the tech lead hands a claim to someone else
|
|
469
469
|
code-kit team codeowners [--write] # .github/CODEOWNERS from the config: the tech lead for their paths, each lane's owners for its paths
|
|
470
470
|
code-kit team board # mirror the plan onto the team's GitHub Project: each story with Lane, Claimed by, Stale and State
|
|
471
|
+
code-kit team report [--dry-run] [--json] # report the project's facts to the team provider; --dry-run prints exactly what would be sent (spec 21)
|
|
472
|
+
code-kit team provider signin|signout|status|connect # a hosted team provider the config names (preview): sign this machine in, connect the repository
|
|
473
|
+
code-kit team pulse --session <id> # used by the mod with a hosted provider: the session's agents and token use, on stdin, and the report when something changed
|
|
471
474
|
code-kit intend "<what and why>" [--touches globs] # raise a change of direction before any code, as a discussion in the repository's Intents category
|
|
472
475
|
code-kit intend agree|drop|duplicate <id> [--reason "…"] # the tech lead's answer, from their terminal or the pane
|
|
473
476
|
code-kit intend record <id> # write an agreed intent as a numbered decision record in docs.decisions, for a pull request
|
|
@@ -508,7 +511,7 @@ code-kit approve <name>... --reason "…" [--lane name] [--delegated [--story ST
|
|
|
508
511
|
| `approvals.lead` | no | `true` lets the lead act on what the person says in chat, for when `!` isn't available (on a phone, say): it records their approvals with `code-kit approve`, and runs `shell.block` commands marked `person`. Default `false`. See [Approvals](#approvals). |
|
|
509
512
|
| `approvals.delegate` | no | `{ protected?: [approval names], merge?: true, dependencies?: { licences?, minWeeklyDownloads?, maxMonthsSinceRelease?, allowInstallScripts?: true \| false \| [kinds] } }`: what the lead decides, inside the guardrails you keep: the protected approvals it grants, and packages it approves for a story, with `code-kit approve --delegated`; with `merge`, merges into a protected branch with `code-kit merge --delegated`. Lanes ask the lead for these, never you. npm packages are also checked against the registry; the defaults are MIT, Apache-2.0, BSD and ISC, 10,000 weekly downloads, a release within 18 months, and no install scripts. See [Approvals](#approvals). |
|
|
510
513
|
| `setup` | no | The version of init's setup the project has (3 now). Without it, or lower, `next` names what the project is missing (the design gate, design stories before screens, a QA lane, a security reviewer) and starts no new story until re-running init adds it |
|
|
511
|
-
| `team` | no | Preview. For several people in one repository: `{ techLead, staleDays?, intents?, board?, provider? }`. `techLead` is the GitHub handle of the person who owns the plan, specs and contracts; `staleDays` is how many working days without a push make a claim stale (default 3); `intents` is the discussion category intents go in (default `Intents`; create it once in the repository's Settings › Discussions); `board` is `{ owner, number }`, a GitHub Project the plan is mirrored onto; `provider` is `"github"` (default)
|
|
514
|
+
| `team` | no | Preview. For several people in one repository: `{ techLead, staleDays?, intents?, board?, provider?, report? }`. `techLead` is the GitHub handle of the person who owns the plan, specs and contracts; `staleDays` is how many working days without a push make a claim stale (default 3); `intents` is the discussion category intents go in (default `Intents`; create it once in the repository's Settings › Discussions); `board` is `{ owner, number }`, a GitHub Project the plan is mirrored onto; `provider` is `"github"` (default), `{ command, fallback? }` for another team provider, or, in preview, a hosted provider's adapter as `{ "<name>": "<https address>" }`, which keeps the intents (Discussions only when `intents` is set) and has GitHub as its fallback; with `fallback: "github"`, claims go through GitHub whenever that provider can't be reached; `report` (default true) is whether the project reports its facts (stories' stages, send-backs, the requirement trail, approvals, token use and the rules; never code) to a provider that isn't GitHub. See docs/specs/16-teams.md and docs/specs/21-hub-adapter-and-reports.md. |
|
|
512
515
|
| `branches.protected` | no | Default `["main", "master"]` |
|
|
513
516
|
| `harness` | no | What the mod's harness does in the session: `autonomy` (`autonomous`, `propose` or `off`; default `autonomous`), `hold.minutes` (how long a call waiting on your approval is held, 0 to 10; default 2), `stall.nudgeMinutes` and `stall.restartMinutes` (default 5 and 10), `stall.maxRestarts` (default 2), `agents.reviewer.on` (default `false`) and `agents.reviewer.model` (code-kit's reviewer and the security reviewer; default `sonnet`), `agents.lanes.model` (the model each lane agent starts on, unless the lead's call or the lane's agent file names one; default the session's), `background.pauseAtPercent` (default 80), `compact.at` (compact the lead's context between turns once it's past this share of its window; default 40, 0 for never), `compact.milestone` (when a milestone finishes, compact the lead's context: `ask`, `auto` or `off`; default `ask`), `agents.lanes.contextLimit` (the context a lane agent may reach before it hands its story to a fresh agent; default 250,000 tokens), `notify.on` (a system notification when something new needs you; default `true`) and `report.on` (agents draft bug reports for code-kit, which you send or discard; default `false`). Change them with `/harness` or `code-kit settings set`. |
|
|
514
517
|
|
package/bin/code-kit.mjs
CHANGED
|
@@ -57,7 +57,20 @@ import {
|
|
|
57
57
|
} from '../hooks/lib/config.mjs';
|
|
58
58
|
import { diffConfigs, ownershipMoves } from '../hooks/lib/diff.mjs';
|
|
59
59
|
import { CODE, importsOf, layerOf, layerProblems, targetOf } from '../hooks/lib/layers.mjs';
|
|
60
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
ADAPTERS,
|
|
62
|
+
activeAdapters,
|
|
63
|
+
adapterFacts,
|
|
64
|
+
providerAdapterOf,
|
|
65
|
+
} from '../hooks/lib/adapters/index.mjs';
|
|
66
|
+
import {
|
|
67
|
+
EVENT_PARTS,
|
|
68
|
+
USAGE_DIR,
|
|
69
|
+
buildSnapshot,
|
|
70
|
+
mainCheckout,
|
|
71
|
+
snapshotHash,
|
|
72
|
+
snapshotProblems,
|
|
73
|
+
} from '../hooks/lib/snapshot.mjs';
|
|
61
74
|
import { nextStep } from '../hooks/lib/next.mjs';
|
|
62
75
|
import { discardReport, draftReport, readReports, sendReport } from '../hooks/lib/report.mjs';
|
|
63
76
|
import {
|
|
@@ -92,6 +105,7 @@ import {
|
|
|
92
105
|
claimChanges,
|
|
93
106
|
storyOfBranch,
|
|
94
107
|
syncBoard,
|
|
108
|
+
readCache,
|
|
95
109
|
teamOf,
|
|
96
110
|
teamProvider,
|
|
97
111
|
whoami,
|
|
@@ -172,6 +186,7 @@ const FLAGS = [
|
|
|
172
186
|
'--off',
|
|
173
187
|
'--blocks',
|
|
174
188
|
'--take',
|
|
189
|
+
'--dry-run',
|
|
175
190
|
];
|
|
176
191
|
const flag = (name) => args.includes(name);
|
|
177
192
|
const writeBaseline = flag('--write');
|
|
@@ -242,7 +257,7 @@ function check() {
|
|
|
242
257
|
if (team) {
|
|
243
258
|
const me = whoami(resolve('.'));
|
|
244
259
|
out(
|
|
245
|
-
`Team: ${team.techLead} is the tech lead; a claim is stale after ${team.staleDays} working day${team.staleDays === 1 ? '' : 's'} without a push; provider ${team.provider === 'github' ? 'GitHub' : team.provider.command}.`,
|
|
260
|
+
`Team: ${team.techLead} is the tech lead; a claim is stale after ${team.staleDays} working day${team.staleDays === 1 ? '' : 's'} without a push; provider ${team.provider === 'github' ? 'GitHub' : (team.provider.name ?? team.provider.command)}.`,
|
|
246
261
|
);
|
|
247
262
|
for (const [lane, owners] of Object.entries(team.owners))
|
|
248
263
|
out(`Lane ${lane} is owned by ${owners.join(', ')}`);
|
|
@@ -1461,8 +1476,12 @@ function teamCommand() {
|
|
|
1461
1476
|
die(
|
|
1462
1477
|
'This project has no "team" in its config, so it has no claims or owners. Add "team": { "techLead": "<GitHub handle>" } to share them.',
|
|
1463
1478
|
);
|
|
1479
|
+
// HUBR-6: a hosted provider's own commands (sign in, connect, status), through its adapter.
|
|
1480
|
+
if (rest[0] === 'provider') return providerCommand(raw);
|
|
1464
1481
|
const provider = teamProvider(resolve('.'), team);
|
|
1465
1482
|
const [sub, story] = rest;
|
|
1483
|
+
if (sub === 'report') return teamReport(team, provider);
|
|
1484
|
+
if (sub === 'pulse') return pulseCommand(raw, team, provider);
|
|
1466
1485
|
const holderWords = (h) =>
|
|
1467
1486
|
`${h.story} is claimed by ${h.by} (${ago(h.at)}). Ask them, or the tech lead (${team.techLead}), to release it.`;
|
|
1468
1487
|
if (sub === 'claim') {
|
|
@@ -1577,7 +1596,7 @@ function teamCommand() {
|
|
|
1577
1596
|
}
|
|
1578
1597
|
if (sub !== undefined)
|
|
1579
1598
|
die(
|
|
1580
|
-
'code-kit team [--json] [--fresh] | team init … | team since [--json] [--peek] | team claim|keep|release <story> | team reassign <story> <handle>',
|
|
1599
|
+
'code-kit team [--json] [--fresh] | team init … | team since [--json] [--peek] | team claim|keep|release <story> | team reassign <story> <handle> | team report [--dry-run] [--json]',
|
|
1581
1600
|
);
|
|
1582
1601
|
const state = provider.state({ fresh: flag('--fresh') });
|
|
1583
1602
|
if (flag('--json')) return out(JSON.stringify(state, null, 2));
|
|
@@ -1608,6 +1627,128 @@ function teamCommand() {
|
|
|
1608
1627
|
out(` #${p.number} ${p.title} (${p.by}, ${p.branch}${p.draft ? ', draft' : ''})`);
|
|
1609
1628
|
}
|
|
1610
1629
|
|
|
1630
|
+
// --- what a project reports (docs/specs/21-hub-adapter-and-reports.md) ----------------------------
|
|
1631
|
+
|
|
1632
|
+
const REPORT_SENT = '.claude/state/report-sent.json';
|
|
1633
|
+
const REPORT_MINUTES = 10;
|
|
1634
|
+
|
|
1635
|
+
/** HUBR-2: the snapshot, from what status, timeline, trail, the journal and the config already know. */
|
|
1636
|
+
function snapshotNow(team) {
|
|
1637
|
+
const { config } = load();
|
|
1638
|
+
if (!config.docs?.specs || !config.docs?.plan) return null;
|
|
1639
|
+
const from = base(config, false);
|
|
1640
|
+
const status = buildStatus('.', config, from, tracked());
|
|
1641
|
+
const claims = (readCache(resolve('.')) ?? {}).claims ?? [];
|
|
1642
|
+
return buildSnapshot({
|
|
1643
|
+
root: resolve('.'),
|
|
1644
|
+
config,
|
|
1645
|
+
status,
|
|
1646
|
+
base: from,
|
|
1647
|
+
claims,
|
|
1648
|
+
version: JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version,
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
/**
|
|
1653
|
+
* HUBR-3: sends the snapshot when what happened changed (stages, approvals, send-backs, merges), or when
|
|
1654
|
+
* only usage changed and ten minutes have passed; never the same snapshot twice. A provider that can't
|
|
1655
|
+
* be reached is tried again with the next change. `{ sent, why }`.
|
|
1656
|
+
*/
|
|
1657
|
+
function sendSnapshot(team, provider, snapshot, { force = false } = {}) {
|
|
1658
|
+
if (team.provider === 'github') return { sent: false, why: 'GitHub keeps no reports.' };
|
|
1659
|
+
if (!team.report) return { sent: false, why: 'team.report is false.' };
|
|
1660
|
+
const problems = snapshotProblems(snapshot);
|
|
1661
|
+
if (problems.length) return { sent: false, why: `The report isn't valid: ${problems[0]}.` };
|
|
1662
|
+
const file = join(mainCheckout(resolve('.')), REPORT_SENT);
|
|
1663
|
+
let last = {};
|
|
1664
|
+
try {
|
|
1665
|
+
last = JSON.parse(readFileSync(file, 'utf8'));
|
|
1666
|
+
} catch {
|
|
1667
|
+
// nothing sent yet
|
|
1668
|
+
}
|
|
1669
|
+
const events = snapshotHash(snapshot, EVENT_PARTS);
|
|
1670
|
+
const all = snapshotHash(snapshot);
|
|
1671
|
+
const due =
|
|
1672
|
+
force ||
|
|
1673
|
+
events !== last.events ||
|
|
1674
|
+
(all !== last.all && Date.now() - (last.at ?? 0) >= REPORT_MINUTES * 60_000);
|
|
1675
|
+
if (!due) return { sent: false, why: 'Nothing new to report.' };
|
|
1676
|
+
const r = provider.report(snapshot);
|
|
1677
|
+
if (!r || r.ok === false)
|
|
1678
|
+
return { sent: false, why: r?.why ?? 'The provider refused the report.' };
|
|
1679
|
+
mkdirSync(join(mainCheckout(resolve('.')), '.claude/state'), { recursive: true });
|
|
1680
|
+
writeFileSync(file, `${JSON.stringify({ events, all, at: Date.now() })}\n`);
|
|
1681
|
+
return { sent: true };
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
/** HUBR-2, HUBR-4: `team report` sends what's new; `--dry-run` prints exactly what would be sent. */
|
|
1685
|
+
function teamReport(team, provider) {
|
|
1686
|
+
const snapshot = snapshotNow(team);
|
|
1687
|
+
if (!snapshot)
|
|
1688
|
+
die(
|
|
1689
|
+
'A report needs docs.specs and docs.plan in the config. The spec-design skill writes both.',
|
|
1690
|
+
);
|
|
1691
|
+
if (flag('--dry-run')) return out(JSON.stringify(snapshot, null, 2));
|
|
1692
|
+
const r = sendSnapshot(team, provider, snapshot, { force: true });
|
|
1693
|
+
if (flag('--json')) return out(JSON.stringify(r, null, 2));
|
|
1694
|
+
out(
|
|
1695
|
+
r.sent
|
|
1696
|
+
? `Reported this project's facts to ${team.provider.name ?? 'the team provider'}.`
|
|
1697
|
+
: r.why,
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/**
|
|
1702
|
+
* The mod's pulse (HUBR-3, HUBR-6), every 30 seconds while a session runs, only for a hosted provider:
|
|
1703
|
+
* stdin is `{ agents, usage }`, the session's agents by tool and branch and its token ledger (USE-1).
|
|
1704
|
+
* Keeps both for the provider's client and the report, and reports when something new happened.
|
|
1705
|
+
*/
|
|
1706
|
+
async function pulseCommand(raw, team, provider) {
|
|
1707
|
+
const hosted = providerAdapterOf(raw.team?.provider);
|
|
1708
|
+
if (!hosted || hosted.problem || !sessionId) return;
|
|
1709
|
+
let input = {};
|
|
1710
|
+
try {
|
|
1711
|
+
let raw = '';
|
|
1712
|
+
for await (const chunk of process.stdin) raw += chunk;
|
|
1713
|
+
input = raw.trim() ? JSON.parse(raw) : {};
|
|
1714
|
+
} catch {
|
|
1715
|
+
input = {};
|
|
1716
|
+
}
|
|
1717
|
+
const root = resolve('.');
|
|
1718
|
+
const id = sessionId.replace(/[^\w-]/g, '');
|
|
1719
|
+
if (input.agents) hosted.adapter.presence(root, id, input.agents);
|
|
1720
|
+
if (input.usage && typeof input.usage === 'object') {
|
|
1721
|
+
const dir = join(mainCheckout(root), USAGE_DIR);
|
|
1722
|
+
mkdirSync(dir, { recursive: true });
|
|
1723
|
+
writeFileSync(
|
|
1724
|
+
join(dir, `${id}.json`),
|
|
1725
|
+
`${JSON.stringify({ at: Date.now(), entries: input.usage })}\n`,
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
// Building the snapshot reads git: at most once a minute.
|
|
1729
|
+
const built = join(mainCheckout(root), '.claude/state/report-built.json');
|
|
1730
|
+
let at = 0;
|
|
1731
|
+
try {
|
|
1732
|
+
at = JSON.parse(readFileSync(built, 'utf8')).at ?? 0;
|
|
1733
|
+
} catch {
|
|
1734
|
+
// never built
|
|
1735
|
+
}
|
|
1736
|
+
if (Date.now() - at < 60_000) return;
|
|
1737
|
+
writeFileSync(built, `${JSON.stringify({ at: Date.now() })}\n`);
|
|
1738
|
+
const snapshot = snapshotNow(team);
|
|
1739
|
+
if (snapshot) sendSnapshot(team, provider, snapshot);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
/** HUBR-6: `team provider signin|signout|status|connect|drift-model …`, run by the provider's client. */
|
|
1743
|
+
function providerCommand(raw) {
|
|
1744
|
+
const hosted = providerAdapterOf(raw.team?.provider);
|
|
1745
|
+
if (!hosted || hosted.problem)
|
|
1746
|
+
die(
|
|
1747
|
+
"This team's provider isn't a hosted one: claims and intents go through GitHub, and there's nothing to sign in to.",
|
|
1748
|
+
);
|
|
1749
|
+
process.exit(hosted.adapter.run(rest.slice(1), hosted.url));
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1611
1752
|
/**
|
|
1612
1753
|
* TEAM-13: `team init` sets the project up as a team's, or (`--off`) as one developer's again. Without
|
|
1613
1754
|
* `--write` it shows the team section it would write; with it, it writes the config and says what the
|
|
@@ -1671,7 +1812,7 @@ function teamInit(raw) {
|
|
|
1671
1812
|
if (flag('--json'))
|
|
1672
1813
|
return out(JSON.stringify({ written: true, ...section, me: state.me ?? null, setup }, null, 2));
|
|
1673
1814
|
out(
|
|
1674
|
-
`Wrote the team to ${file}: ${team.techLead} is the tech lead; a claim is stale after ${team.staleDays} working day${team.staleDays === 1 ? '' : 's'} without a push; intents go in "${team.intents}".`,
|
|
1815
|
+
`Wrote the team to ${file}: ${team.techLead} is the tech lead; a claim is stale after ${team.staleDays} working day${team.staleDays === 1 ? '' : 's'} without a push; ${team.intents ? `intents go in "${team.intents}"` : `${team.provider.name ?? 'the provider'} keeps intents`}.`,
|
|
1675
1816
|
);
|
|
1676
1817
|
for (const [lane, owners] of Object.entries(team.owners))
|
|
1677
1818
|
out(`Lane ${lane}: ${owners.join(', ')}`);
|
package/hooks/hooks.json
CHANGED
|
@@ -46,6 +46,28 @@
|
|
|
46
46
|
]
|
|
47
47
|
}
|
|
48
48
|
],
|
|
49
|
+
"SessionStart": [
|
|
50
|
+
{
|
|
51
|
+
"hooks": [
|
|
52
|
+
{
|
|
53
|
+
"type": "command",
|
|
54
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/team-session.mjs\" start",
|
|
55
|
+
"timeout": 10
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"SessionEnd": [
|
|
61
|
+
{
|
|
62
|
+
"hooks": [
|
|
63
|
+
{
|
|
64
|
+
"type": "command",
|
|
65
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/team-session.mjs\" end",
|
|
66
|
+
"timeout": 10
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
],
|
|
49
71
|
"Stop": [
|
|
50
72
|
{
|
|
51
73
|
"hooks": [
|