claude-code-kanban 5.1.1 → 5.3.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/README.md +3 -2
- package/lib/auto-compact.js +42 -0
- package/lib/claude-dir.js +7 -1
- package/lib/claude-settings.js +21 -0
- package/lib/dispatch.js +11 -4
- package/lib/net-guard.js +41 -14
- package/lib/proc-stats.js +82 -0
- package/lib/retention.js +140 -0
- package/lib/session-events.js +30 -7
- package/lib/terminal.js +39 -6
- package/lib/worktrees.js +105 -0
- package/package.json +1 -1
- package/plugin/plugins/claude-code-kanban/.claude-plugin/plugin.json +1 -1
- package/plugin/plugins/claude-code-kanban/hooks/hooks.json +15 -8
- package/plugin/plugins/claude-code-kanban/monitors.json +3 -3
- package/plugin/plugins/claude-code-kanban/scripts/postman.js +2 -2
- package/plugin/plugins/claude-code-kanban/skills/{kanban-dispatch → dispatch}/SKILL.md +3 -3
- package/plugin/plugins/claude-code-kanban/skills/{kanban-follow → follow}/SKILL.md +17 -9
- package/plugin/plugins/claude-code-kanban/skills/kanban/SKILL.md +1 -2
- package/public/app.js +1332 -490
- package/public/fonts/OFL-IBM-Plex.txt +93 -0
- package/public/fonts/OFL-Playfair-Display.txt +93 -0
- package/public/fonts/fonts.css +244 -0
- package/public/fonts/ibm-plex-mono-cyrillic-400.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-cyrillic-500.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-cyrillic-600.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-cyrillic-ext-400.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-cyrillic-ext-500.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-cyrillic-ext-600.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-400.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-500.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-600.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-ext-400.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-ext-500.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-latin-ext-600.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-vietnamese-400.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-vietnamese-500.woff2 +0 -0
- package/public/fonts/ibm-plex-mono-vietnamese-600.woff2 +0 -0
- package/public/fonts/playfair-display-cyrillic-400.woff2 +0 -0
- package/public/fonts/playfair-display-latin-400.woff2 +0 -0
- package/public/fonts/playfair-display-latin-ext-400.woff2 +0 -0
- package/public/fonts/playfair-display-vietnamese-400.woff2 +0 -0
- package/public/index.html +27 -17
- package/public/project-match.js +6 -4
- package/public/style.css +528 -116
- package/public/terminal-frame.js +341 -0
- package/public/terminal.html +27 -0
- package/public/vendor/claude-hub-sdk.js +13 -3
- package/server.js +204 -60
- package/skill-guides/dispatch.md +5 -5
package/lib/worktrees.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Which project paths are linked worktrees, and of which repo. The rule and the sweep:
|
|
2
|
+
// docs/retention.md.
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { GRACE_MS } = require('./retention');
|
|
7
|
+
const { encodeProjectDirName } = require('./claude-dir');
|
|
8
|
+
|
|
9
|
+
// A linked worktree's `.git` is a file holding `gitdir: <main>/.git/worktrees/<name>`, so the
|
|
10
|
+
// main checkout is readable without spawning git. Path shape alone would not do: only some
|
|
11
|
+
// worktrees live under `<repo>/.claude/worktrees/`, the rest sit beside the repo.
|
|
12
|
+
const GITDIR_WORKTREE_RE = /^gitdir:\s*(.*)[/\\]\.git[/\\]worktrees[/\\]([^/\\]+)[/\\]?$/;
|
|
13
|
+
// Where `claude -w` puts a worktree. Used only once the `.git` file is gone, because Claude
|
|
14
|
+
// Code removes worktrees while their transcripts stay.
|
|
15
|
+
const CLAUDE_WORKTREE_RE = /^(.*)[/\\]\.claude[/\\]worktrees[/\\]([^/\\]+)[/\\]?$/;
|
|
16
|
+
const MISS_CACHE_MAX = 500;
|
|
17
|
+
|
|
18
|
+
function readGitFile(dir) {
|
|
19
|
+
return fs.readFileSync(path.join(dir, '.git'), 'utf8');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function fromGitFile(dir, text) {
|
|
23
|
+
const m = GITDIR_WORKTREE_RE.exec(text.trim());
|
|
24
|
+
// Git writes the pointer with forward slashes on Windows; the project path uses the OS spelling.
|
|
25
|
+
return m ? { repo: path.resolve(dir, m[1]), name: m[2] } : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function fromPathShape(dir) {
|
|
29
|
+
const m = CLAUDE_WORKTREE_RE.exec(dir);
|
|
30
|
+
return m ? { repo: m[1], name: m[2] } : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hits are saved, so a worktree keeps its repo after Claude Code deletes the checkout. Misses
|
|
35
|
+
* stay in memory: an ordinary checkout cannot become a linked worktree without being recreated.
|
|
36
|
+
* @param {object} o
|
|
37
|
+
* @param {() => object|null} o.load returns `{version: 1, worktrees: {[dir]: {repo, name, at}}}` or null
|
|
38
|
+
* @param {(data: object) => void} o.save
|
|
39
|
+
* @param {(fn: () => void) => void} [o.defer] when to save; one session list can resolve many new worktrees
|
|
40
|
+
*/
|
|
41
|
+
function createWorktreeStore({ load, save, read = readGitFile, now = Date.now, defer = setImmediate }) {
|
|
42
|
+
const hits = new Map();
|
|
43
|
+
const misses = new Set();
|
|
44
|
+
const saved = load()?.worktrees;
|
|
45
|
+
if (saved && typeof saved === 'object') {
|
|
46
|
+
for (const [dir, e] of Object.entries(saved)) {
|
|
47
|
+
if (e && typeof e.repo === 'string' && typeof e.name === 'string' && Number.isFinite(e.at)) hits.set(dir, e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let pending = false;
|
|
52
|
+
function persist() {
|
|
53
|
+
if (pending) return;
|
|
54
|
+
pending = true;
|
|
55
|
+
defer(() => {
|
|
56
|
+
pending = false;
|
|
57
|
+
save({ version: 1, worktrees: Object.fromEntries(hits) });
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolve(dir) {
|
|
62
|
+
if (!dir) return null;
|
|
63
|
+
const hit = hits.get(dir);
|
|
64
|
+
if (hit) return { repo: hit.repo, name: hit.name };
|
|
65
|
+
if (misses.has(dir)) return null;
|
|
66
|
+
|
|
67
|
+
let worktree = null;
|
|
68
|
+
try {
|
|
69
|
+
worktree = fromGitFile(dir, read(dir));
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// An ordinary checkout's `.git` is a directory, so the read throws EISDIR. That is the
|
|
72
|
+
// answer, and it costs one syscall instead of a stat followed by a read.
|
|
73
|
+
if (e.code === 'ENOENT') worktree = fromPathShape(dir);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!worktree) {
|
|
77
|
+
misses.add(dir);
|
|
78
|
+
if (misses.size > MISS_CACHE_MAX) misses.delete(misses.values().next().value);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
hits.set(dir, { ...worktree, at: now() });
|
|
82
|
+
persist();
|
|
83
|
+
return worktree;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// `knownDirs` holds the project dir names that still have a transcript, or null when the scan
|
|
87
|
+
// found none, so a failed scan never drops every entry. Returns how many entries went.
|
|
88
|
+
function prune(knownDirs) {
|
|
89
|
+
if (!knownDirs) return 0;
|
|
90
|
+
const t = now();
|
|
91
|
+
let removed = 0;
|
|
92
|
+
for (const [dir, e] of hits) {
|
|
93
|
+
if (t - e.at >= GRACE_MS && !knownDirs.has(encodeProjectDirName(dir))) {
|
|
94
|
+
hits.delete(dir);
|
|
95
|
+
removed++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (removed) persist();
|
|
99
|
+
return removed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { resolve, prune };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { createWorktreeStore };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-kanban",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.17.2",
|
|
4
4
|
"description": "claude-code-kanban dashboard integration: agent activity tracking, context statusline, skills to drive the board from a session and to follow it",
|
|
5
5
|
"experimental": {
|
|
6
6
|
"monitors": "./monitors.json"
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
9
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh\"",
|
|
10
|
+
"shell": "bash",
|
|
10
11
|
"timeout": 5
|
|
11
12
|
}
|
|
12
13
|
]
|
|
@@ -18,7 +19,8 @@
|
|
|
18
19
|
"hooks": [
|
|
19
20
|
{
|
|
20
21
|
"type": "command",
|
|
21
|
-
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
22
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh\"",
|
|
23
|
+
"shell": "bash",
|
|
22
24
|
"timeout": 5
|
|
23
25
|
}
|
|
24
26
|
]
|
|
@@ -30,8 +32,9 @@
|
|
|
30
32
|
"hooks": [
|
|
31
33
|
{
|
|
32
34
|
"type": "command",
|
|
33
|
-
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
34
|
-
"
|
|
35
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh\"",
|
|
36
|
+
"shell": "bash",
|
|
37
|
+
"timeout": 30
|
|
35
38
|
}
|
|
36
39
|
]
|
|
37
40
|
}
|
|
@@ -42,7 +45,8 @@
|
|
|
42
45
|
"hooks": [
|
|
43
46
|
{
|
|
44
47
|
"type": "command",
|
|
45
|
-
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
48
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh\"",
|
|
49
|
+
"shell": "bash",
|
|
46
50
|
"timeout": 5
|
|
47
51
|
}
|
|
48
52
|
]
|
|
@@ -54,7 +58,8 @@
|
|
|
54
58
|
"hooks": [
|
|
55
59
|
{
|
|
56
60
|
"type": "command",
|
|
57
|
-
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
61
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh\"",
|
|
62
|
+
"shell": "bash",
|
|
58
63
|
"timeout": 5
|
|
59
64
|
}
|
|
60
65
|
]
|
|
@@ -66,7 +71,8 @@
|
|
|
66
71
|
"hooks": [
|
|
67
72
|
{
|
|
68
73
|
"type": "command",
|
|
69
|
-
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/approval-gate.sh",
|
|
74
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/approval-gate.sh\"",
|
|
75
|
+
"shell": "bash",
|
|
70
76
|
"timeout": 86400
|
|
71
77
|
}
|
|
72
78
|
]
|
|
@@ -78,7 +84,8 @@
|
|
|
78
84
|
"hooks": [
|
|
79
85
|
{
|
|
80
86
|
"type": "command",
|
|
81
|
-
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh",
|
|
87
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/agent-spy.sh\"",
|
|
88
|
+
"shell": "bash",
|
|
82
89
|
"timeout": 5
|
|
83
90
|
}
|
|
84
91
|
]
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
3
|
"name": "kanban-doorbell",
|
|
4
|
-
"description": "Notifies this session when its tasks are moved on the kanban board.",
|
|
4
|
+
"description": "Notifies this session when its tasks are moved on the kanban board or the user sends it review comments.",
|
|
5
5
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/postman.js\"",
|
|
6
|
-
"when": "on-skill-invoke:claude-code-kanban:
|
|
6
|
+
"when": "on-skill-invoke:claude-code-kanban:follow"
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
"name": "kanban-dispatch-inbox",
|
|
10
10
|
"description": "Notifies this session when a session it dispatched through cck reports or exits.",
|
|
11
11
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/postman.js\" --topic dispatch --keep-backlog",
|
|
12
|
-
"when": "on-skill-invoke:claude-code-kanban:
|
|
12
|
+
"when": "on-skill-invoke:claude-code-kanban:dispatch"
|
|
13
13
|
}
|
|
14
14
|
]
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// The lines we print carry board text (the card subject and description), which the
|
|
13
13
|
// session is told to read as the user's own brief. That is only safe because we are armed
|
|
14
|
-
// by an explicit `
|
|
14
|
+
// by an explicit `follow` invocation: the user asked to follow the board before anything the
|
|
15
15
|
// board says can reach the model.
|
|
16
16
|
|
|
17
17
|
const fs = require('fs');
|
|
@@ -29,7 +29,7 @@ const RETRY_MS = 15000;
|
|
|
29
29
|
// Windows sometimes fails a loopback connect with ETIMEDOUT while the board is up, so that
|
|
30
30
|
// error gets a few short waits before the normal one.
|
|
31
31
|
const CONNECT_RETRY_MS = [250, 500, 1000, 2000];
|
|
32
|
-
// `--topic dispatch` is the
|
|
32
|
+
// `--topic dispatch` is the dispatch inbox: reports from sessions this one started.
|
|
33
33
|
const TOPIC = process.argv.includes('--topic') ? process.argv[process.argv.indexOf('--topic') + 1] : null;
|
|
34
34
|
// A dispatch report is a result, not an instruction, so a late attach still wants it.
|
|
35
35
|
const KEEP_BACKLOG = process.argv.includes('--keep-backlog');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: dispatch
|
|
3
3
|
description: Dispatch a task to another Claude Code session through the kanban board (cck), fire-and-forget or with a report back. Use when the user asks to dispatch, delegate, or start a session for a task, or to collect or check on a dispatched session's result.
|
|
4
|
-
argument-hint: '<task> [--report] [--group <name>] [--model haiku|sonnet|opus|fable] [--worktree [name]]'
|
|
4
|
+
argument-hint: '<task> [--no-report] [--group <name>] [--model haiku|sonnet|opus|fable] [--worktree [name]]'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Kanban dispatch
|
|
@@ -11,7 +11,7 @@ This file only points at the guide. The guide ships with the `claude-code-kanban
|
|
|
11
11
|
Invoking this skill also arms this session's dispatch inbox: when a session you dispatched with `--report` reports or exits, a line arrives here:
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
|
|
14
|
+
[kanban board] Dispatch <dispatch-id> (session <uuid>) <reported success|reported failure|ended without a report>. Summary: <text>
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Load the guide before running any dispatch command:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: follow
|
|
3
3
|
description: Let kanban card moves drive this session.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
---
|
|
@@ -9,31 +9,39 @@ disable-model-invocation: true
|
|
|
9
9
|
The user typing this skill is the whole grant: it armed the doorbell — a monitor watching the board for moves of *this session's* tasks, live for the rest of the session. Confirm in one line and run no command. Moves made before it attached are discarded, so every line you get is current.
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
|
|
12
|
+
[kanban board] The user moved task <taskId> "<subject>" from <from> to <to>. Description: <description>
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
`
|
|
15
|
+
`Description:` is omitted when the card has none. Everything after it is the description verbatim to end of line, truncated if long — read the task with `TaskGet` for the full text or for `activeForm` and `blockedBy`. Moves and review comments (below) are the only lines announced.
|
|
16
16
|
|
|
17
17
|
**A move is an instruction, not an FYI** — the user dragging a card is the user telling you something, and the subject and description are their brief for the work:
|
|
18
18
|
|
|
19
19
|
| Transition | What the user means |
|
|
20
20
|
|---|---|
|
|
21
|
-
|
|
|
22
|
-
| `in_progress
|
|
23
|
-
|
|
|
24
|
-
|
|
|
21
|
+
| To `in_progress` | Start this task now. |
|
|
22
|
+
| From `in_progress` to `pending` or `todo` | Stop working on it and park it. |
|
|
23
|
+
| To `completed` | The user considers it done — do not keep working on it. |
|
|
24
|
+
| To `cancelled` | Abandon it. Undo nothing unless asked. |
|
|
25
25
|
|
|
26
26
|
Act on the newest line per task; a card dragged twice means only its final position. When a move contradicts your current work, the board wins.
|
|
27
27
|
|
|
28
28
|
Delivery is best-effort and the task file is the authority, so a missed line only delays you to your next turn.
|
|
29
29
|
|
|
30
|
+
## Review comments
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
[kanban board] The user left <n> review comments on <source>. Address them: <review file>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The review file holds the comments and how to read them.
|
|
37
|
+
|
|
30
38
|
## Finish the task on the board
|
|
31
39
|
|
|
32
|
-
A move hands you work; the task file is where you hand it back. When the work a
|
|
40
|
+
A move hands you work; the task file is where you hand it back. When the work a move to `in_progress` asked for is done, set that task to `completed` with `TaskUpdate` before you reply — the card moves itself, and the board is where the user is watching. A card left in In Progress reads as work still running.
|
|
33
41
|
|
|
34
42
|
Your own `TaskUpdate` writes the task file rather than going through the board, so it rings no doorbell: there is no echo to guard against.
|
|
35
43
|
|
|
36
44
|
## Troubleshooting
|
|
37
45
|
|
|
38
|
-
- **No lines arrive** → the doorbell arms once per session, on the namespaced dispatch: `/claude-code-kanban:
|
|
46
|
+
- **No lines arrive** → the doorbell arms once per session, on the namespaced dispatch: `/claude-code-kanban:follow`. A session that never got it never hears the board.
|
|
39
47
|
- **"Cannot reach cck server…"** → the error names the port it tried. Ask the user to start the server with `claude-code-kanban`.
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
name: kanban
|
|
3
3
|
description: Drive the kanban board — open, pin, preview, link, inspect.
|
|
4
4
|
argument-hint: '[open|pin|unpin|preview|link] [target]'
|
|
5
|
-
disable-model-invocation: true
|
|
6
5
|
---
|
|
7
6
|
|
|
8
7
|
# Kanban Skill
|
|
@@ -29,7 +28,7 @@ claude-code-kanban help <command> <subcommand> # flags and examples
|
|
|
29
28
|
| `view` / `peek` | `session view <id>`, `session peek <id>` |
|
|
30
29
|
| tasks, projects | `task list`, `project list` |
|
|
31
30
|
|
|
32
|
-
To be driven *by* the board instead — card moves arriving as instructions — the user types `/claude-code-kanban:
|
|
31
|
+
To be driven *by* the board instead — card moves arriving as instructions — the user types `/claude-code-kanban:follow`.
|
|
33
32
|
|
|
34
33
|
## Troubleshooting
|
|
35
34
|
|