k0-board 0.3.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.
Files changed (82) hide show
  1. package/.claude/skills/k0-changelog/SKILL.md +2 -2
  2. package/.claude/skills/k0-discuss/SKILL.md +105 -0
  3. package/.claude/skills/k0-discuss/references/rounds.md +280 -0
  4. package/.claude/skills/k0-epic/SKILL.md +196 -0
  5. package/.claude/skills/k0-links/.claude-plugin/plugin.json +6 -0
  6. package/.claude/skills/k0-links/hooks/hooks.json +1 -0
  7. package/.claude/skills/k0-links/hooks/links.js +128 -0
  8. package/.claude/skills/k0-links/hooks/register.js +204 -0
  9. package/.claude/skills/k0-next/SKILL.md +82 -0
  10. package/.claude/skills/k0-order/SKILL.md +123 -0
  11. package/.claude/skills/k0-plan/SKILL.md +156 -0
  12. package/.claude/skills/k0-split/SKILL.md +169 -0
  13. package/.claude/skills/k0-story/SKILL.md +108 -0
  14. package/.claude/skills/k0-ultracode/SKILL.md +251 -0
  15. package/.claude/skills/k0-ultracode/references/agent-brief.md +71 -0
  16. package/.claude/skills/k0-ultracode/references/modes.md +49 -0
  17. package/.claude/skills/k0-verify/SKILL.md +219 -0
  18. package/.claude/skills/k0-whatsnew/SKILL.md +95 -0
  19. package/.claude/skills/k0-work/SKILL.md +212 -0
  20. package/CHANGELOG.md +426 -1
  21. package/README.md +1021 -119
  22. package/bin/k0-board.js +81 -11
  23. package/menubar/K0MenuBar.swift +35 -5
  24. package/package.json +13 -1
  25. package/platform/contract.js +37 -6
  26. package/platform/darwin/index.js +1 -0
  27. package/platform/darwin/shell.js +4 -1
  28. package/platform/darwin/terminal.js +230 -56
  29. package/platform/index.js +3 -1
  30. package/platform/linux/index.js +14 -0
  31. package/platform/linux/shell.js +10 -1
  32. package/platform/linux/tray.py +10 -10
  33. package/platform/shared/command.js +19 -3
  34. package/platform/shared/tmux.js +32 -14
  35. package/platform/win32/index.js +6 -2
  36. package/platform/win32/shell.js +4 -1
  37. package/platform/win32/terminal.js +24 -8
  38. package/platform/win32/tray.ps1 +13 -13
  39. package/server/backlog.js +618 -0
  40. package/server/changelog.js +13 -13
  41. package/server/db.js +1800 -146
  42. package/server/files.js +156 -26
  43. package/server/git.js +53 -14
  44. package/server/idle.js +15 -13
  45. package/server/index.js +1286 -189
  46. package/server/launcher.js +122 -14
  47. package/server/machine.js +10 -1
  48. package/server/mirror.js +1048 -0
  49. package/server/prices.js +59 -0
  50. package/server/projects.js +57 -12
  51. package/server/servers.js +22 -1
  52. package/server/sessions.js +5 -5
  53. package/server/settings.js +25 -2
  54. package/server/update.js +324 -0
  55. package/server/usage.js +642 -0
  56. package/server/watcher.js +67 -13
  57. package/server/worktree.js +600 -0
  58. package/server/writer.js +98 -26
  59. package/web/awake.js +62 -0
  60. package/web/base.css +4 -0
  61. package/web/board.css +404 -16
  62. package/web/board.js +1182 -152
  63. package/web/changelog.js +3 -3
  64. package/web/conf.js +118 -0
  65. package/web/files.css +206 -1
  66. package/web/files.html +10 -0
  67. package/web/files.js +533 -75
  68. package/web/fuzzy.js +73 -21
  69. package/web/index.html +109 -12
  70. package/web/json.js +145 -0
  71. package/web/list.css +536 -0
  72. package/web/list.js +1015 -0
  73. package/web/mentions.js +27 -9
  74. package/web/recency.js +10 -10
  75. package/web/units.js +1 -1
  76. package/web/usage.css +169 -0
  77. package/web/usage.html +78 -0
  78. package/web/usage.js +447 -0
  79. package/web/view.js +22 -3
  80. package/web/whatsnew.css +211 -0
  81. package/web/whatsnew.html +49 -0
  82. 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
- * Changes the name of a session that has already ended, so that renaming a card makes the new
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, the `-n` of Resume takes care of it.
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(cwd, sessionId, name) {
40
- const file = transcriptPath(cwd, sessionId)
41
- if (!fs.existsSync(file)) return false
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(card, live) {
177
- if (card.completed_at) return { status: 'COMPLETED', alive: false }
178
- if (!card.session_id) return { status: 'BACKLOG', alive: false }
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(card.session_id)
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 stale = busy(card.status) || card.status === 'BACKLOG'
186
- return { status: stale ? 'IDLE' : card.status, alive: false }
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 || card.project_path, card.session_id)
243
+ const t = scanTranscript(session.cwd || story.project_path, story.session_id)
190
244
 
191
245
  switch (session.status) {
192
246
  case 'busy':