k0-board 0.4.0 → 0.5.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/skills/k0-changelog/SKILL.md +2 -2
- package/.claude/skills/k0-discuss/SKILL.md +105 -0
- package/.claude/skills/k0-discuss/references/rounds.md +280 -0
- package/.claude/skills/k0-epic/SKILL.md +196 -0
- package/.claude/skills/k0-links/.claude-plugin/plugin.json +6 -0
- package/.claude/skills/k0-links/hooks/hooks.json +1 -0
- package/.claude/skills/k0-links/hooks/links.js +128 -0
- package/.claude/skills/k0-links/hooks/register.js +204 -0
- package/.claude/skills/k0-next/SKILL.md +82 -0
- package/.claude/skills/k0-order/SKILL.md +123 -0
- package/.claude/skills/k0-plan/SKILL.md +156 -0
- package/.claude/skills/k0-split/SKILL.md +169 -0
- package/.claude/skills/k0-story/SKILL.md +108 -0
- package/.claude/skills/k0-ultracode/SKILL.md +251 -0
- package/.claude/skills/k0-ultracode/references/agent-brief.md +71 -0
- package/.claude/skills/k0-ultracode/references/modes.md +49 -0
- package/.claude/skills/k0-verify/SKILL.md +219 -0
- package/.claude/skills/k0-whatsnew/SKILL.md +95 -0
- package/.claude/skills/k0-work/SKILL.md +212 -0
- package/CHANGELOG.md +361 -1
- package/README.md +931 -111
- package/bin/k0-board.js +79 -11
- package/menubar/K0MenuBar.swift +35 -5
- package/package.json +13 -1
- package/platform/contract.js +30 -5
- package/platform/darwin/terminal.js +230 -56
- package/platform/index.js +2 -1
- package/platform/linux/index.js +3 -0
- package/platform/linux/tray.py +10 -10
- package/platform/shared/command.js +19 -3
- package/platform/shared/tmux.js +32 -14
- package/platform/win32/index.js +5 -2
- package/platform/win32/terminal.js +24 -8
- package/platform/win32/tray.ps1 +13 -13
- package/server/backlog.js +618 -0
- package/server/changelog.js +13 -13
- package/server/db.js +1800 -146
- package/server/files.js +60 -21
- package/server/git.js +53 -14
- package/server/idle.js +15 -13
- package/server/index.js +1251 -186
- package/server/launcher.js +122 -14
- package/server/machine.js +10 -1
- package/server/mirror.js +1048 -0
- package/server/prices.js +59 -0
- package/server/projects.js +57 -12
- package/server/servers.js +22 -1
- package/server/sessions.js +5 -5
- package/server/settings.js +25 -2
- package/server/update.js +324 -0
- package/server/usage.js +642 -0
- package/server/watcher.js +67 -13
- package/server/worktree.js +600 -0
- package/server/writer.js +98 -26
- package/web/awake.js +62 -0
- package/web/base.css +4 -0
- package/web/board.css +396 -16
- package/web/board.js +1182 -152
- package/web/changelog.js +3 -3
- package/web/files.js +73 -16
- package/web/index.html +109 -12
- package/web/list.css +536 -0
- package/web/list.js +1015 -0
- package/web/mentions.js +27 -9
- package/web/recency.js +10 -10
- package/web/units.js +1 -1
- package/web/usage.css +169 -0
- package/web/usage.html +78 -0
- package/web/usage.js +447 -0
- package/web/view.js +22 -3
- package/web/whatsnew.css +211 -0
- package/web/whatsnew.html +49 -0
- package/web/whatsnew.js +339 -0
package/server/watcher.js
CHANGED
|
@@ -27,18 +27,49 @@ export function transcriptPath(cwd, sessionId) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* Where the transcript of a session really is.
|
|
31
|
+
*
|
|
32
|
+
* Usually under the slug of the directory the session was started in — but a session that moved
|
|
33
|
+
* into a worktree writes under the worktree's slug from then on, and the story only knows the
|
|
34
|
+
* repository. So the hints are tried first, cheaply, and when none of them has the file the whole
|
|
35
|
+
* projects directory is read once: one `readdir` per rename, never on the per-second scan.
|
|
36
|
+
*/
|
|
37
|
+
export function findTranscript(sessionId, ...cwds) {
|
|
38
|
+
for (const cwd of cwds) {
|
|
39
|
+
if (!cwd) continue
|
|
40
|
+
const file = transcriptPath(cwd, sessionId)
|
|
41
|
+
if (fs.existsSync(file)) return file
|
|
42
|
+
}
|
|
43
|
+
let dirs
|
|
44
|
+
try {
|
|
45
|
+
dirs = fs.readdirSync(PROJECTS_DIR)
|
|
46
|
+
} catch {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
for (const d of dirs) {
|
|
50
|
+
const file = path.join(PROJECTS_DIR, d, `${sessionId}.jsonl`)
|
|
51
|
+
if (fs.existsSync(file)) return file
|
|
52
|
+
}
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Changes the name of a session that has already ended, so that renaming a story makes the new
|
|
31
58
|
* name show up in the list of sessions you can resume.
|
|
32
59
|
*
|
|
33
60
|
* These are the two lines Claude Code writes for itself at the end of the transcript on every
|
|
34
61
|
* turn, and the last one wins: all this does is write another copy.
|
|
35
62
|
*
|
|
36
63
|
* Only on a closed session: if the process is alive it holds the file open for appending and
|
|
37
|
-
* would put the old name back on the next turn. There,
|
|
64
|
+
* would put the old name back on the next turn. There, `/rename` typed into its window takes care
|
|
65
|
+
* of it — `renameLive` in launcher.js — and the `-n` of Resume does the rest.
|
|
66
|
+
*
|
|
67
|
+
* `story` is the flat row: the session's id, and the two places its transcript is likeliest to be.
|
|
38
68
|
*/
|
|
39
|
-
export function renameSession(
|
|
40
|
-
const
|
|
41
|
-
|
|
69
|
+
export function renameSession(story, name) {
|
|
70
|
+
const sessionId = story.session_id
|
|
71
|
+
const file = findTranscript(sessionId, story.work_path, story.project_path)
|
|
72
|
+
if (!file) return false
|
|
42
73
|
const line = (o) => JSON.stringify({ ...o, sessionId }) + '\n'
|
|
43
74
|
fs.appendFileSync(
|
|
44
75
|
file,
|
|
@@ -162,6 +193,22 @@ export function forgetSession(sessionId) {
|
|
|
162
193
|
scans.delete(sessionId)
|
|
163
194
|
}
|
|
164
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Lets go of every session that is not running any more.
|
|
198
|
+
*
|
|
199
|
+
* `forgetSession` above is the deliberate goodbye — a story deleted, a terminal closed by hand —
|
|
200
|
+
* and a session that simply ends is none of those, so nothing ever said goodbye to it. One entry
|
|
201
|
+
* stayed behind per session this server had ever watched, each holding the tail of a transcript
|
|
202
|
+
* and the tools it was waiting on; on a board that has seen a few hundred of them, that was most
|
|
203
|
+
* of what the process was holding on to at rest. It had grown to 479 against 11 really alive.
|
|
204
|
+
*
|
|
205
|
+
* Losing the offset of a session that comes back is not a loss: the next scan reads the file from
|
|
206
|
+
* the top, which is what the first scan of any session does anyway.
|
|
207
|
+
*/
|
|
208
|
+
export function forgetDeadSessions(alive) {
|
|
209
|
+
for (const sessionId of scans.keys()) if (!alive.has(sessionId)) scans.delete(sessionId)
|
|
210
|
+
}
|
|
211
|
+
|
|
165
212
|
/**
|
|
166
213
|
* Something is really running behind this status. It is the one question two different places
|
|
167
214
|
* ask: whether a status that survives the death of a process would be a lie, and whether a
|
|
@@ -170,23 +217,30 @@ export function forgetSession(sessionId) {
|
|
|
170
217
|
export const busy = (status) => status === 'WORKING' || status === 'PLANNING'
|
|
171
218
|
|
|
172
219
|
/**
|
|
173
|
-
* Translates Claude Code's signals into the board's statuses.
|
|
220
|
+
* Translates Claude Code's signals into the board's five live statuses.
|
|
174
221
|
* Returns { status, alive }.
|
|
222
|
+
*
|
|
223
|
+
* Five, not seven: `BACKLOG` and `COMPLETED` were never things a session does — one means the
|
|
224
|
+
* story has no session at all, the other means you have ticked it off — and they are the story's
|
|
225
|
+
* business now, on the other axis. What is left here is what the process is actually doing.
|
|
175
226
|
*/
|
|
176
|
-
export function deriveStatus(
|
|
177
|
-
|
|
178
|
-
|
|
227
|
+
export function deriveStatus(story, live) {
|
|
228
|
+
// Ticked off. The terminal was closed when you said Done, so whatever the session was in the
|
|
229
|
+
// middle of, it is not in the middle of it any more — and until the process really goes, the
|
|
230
|
+
// post-it must not go on breathing.
|
|
231
|
+
if (story.completed_at) return { status: story.session_status || 'IDLE', alive: false }
|
|
232
|
+
if (!story.session_id) return { status: story.session_status || 'IDLE', alive: false }
|
|
179
233
|
|
|
180
|
-
const session = live.get(
|
|
234
|
+
const session = live.get(story.session_id)
|
|
181
235
|
if (!session) {
|
|
182
236
|
// Process dead: the session can still be resumed. We keep the last status only if it said
|
|
183
237
|
// something about you (an open question, a plan left sitting there); WORKING and PLANNING
|
|
184
238
|
// would be a lie, nothing is grinding away any more.
|
|
185
|
-
const
|
|
186
|
-
return { status:
|
|
239
|
+
const last = story.session_status || 'IDLE'
|
|
240
|
+
return { status: busy(last) ? 'IDLE' : last, alive: false }
|
|
187
241
|
}
|
|
188
242
|
|
|
189
|
-
const t = scanTranscript(session.cwd ||
|
|
243
|
+
const t = scanTranscript(session.cwd || story.project_path, story.session_id)
|
|
190
244
|
|
|
191
245
|
switch (session.status) {
|
|
192
246
|
case 'busy':
|