@kynetic-ai/spec 0.1.2 → 0.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.
Files changed (510) hide show
  1. package/README.md +250 -17
  2. package/dist/acp/client.d.ts +18 -4
  3. package/dist/acp/client.d.ts.map +1 -1
  4. package/dist/acp/client.js +44 -26
  5. package/dist/acp/client.js.map +1 -1
  6. package/dist/acp/framing.d.ts +2 -2
  7. package/dist/acp/framing.d.ts.map +1 -1
  8. package/dist/acp/framing.js +37 -29
  9. package/dist/acp/framing.js.map +1 -1
  10. package/dist/acp/index.d.ts +6 -7
  11. package/dist/acp/index.d.ts.map +1 -1
  12. package/dist/acp/index.js +3 -3
  13. package/dist/acp/index.js.map +1 -1
  14. package/dist/acp/types.d.ts +5 -5
  15. package/dist/acp/types.d.ts.map +1 -1
  16. package/dist/acp/types.js +18 -18
  17. package/dist/acp/types.js.map +1 -1
  18. package/dist/agents/adapters.d.ts.map +1 -1
  19. package/dist/agents/adapters.js +24 -13
  20. package/dist/agents/adapters.js.map +1 -1
  21. package/dist/agents/index.d.ts +2 -2
  22. package/dist/agents/index.js +2 -2
  23. package/dist/agents/spawner.d.ts +4 -4
  24. package/dist/agents/spawner.d.ts.map +1 -1
  25. package/dist/agents/spawner.js +6 -6
  26. package/dist/agents/spawner.js.map +1 -1
  27. package/dist/cli/batch-context.d.ts +43 -0
  28. package/dist/cli/batch-context.d.ts.map +1 -0
  29. package/dist/cli/batch-context.js +93 -0
  30. package/dist/cli/batch-context.js.map +1 -0
  31. package/dist/cli/batch-exec.d.ts +116 -0
  32. package/dist/cli/batch-exec.d.ts.map +1 -0
  33. package/dist/cli/batch-exec.js +694 -0
  34. package/dist/cli/batch-exec.js.map +1 -0
  35. package/dist/cli/batch.d.ts +4 -2
  36. package/dist/cli/batch.d.ts.map +1 -1
  37. package/dist/cli/batch.js +15 -14
  38. package/dist/cli/batch.js.map +1 -1
  39. package/dist/cli/command-annotations.d.ts +23 -0
  40. package/dist/cli/command-annotations.d.ts.map +1 -0
  41. package/dist/cli/command-annotations.js +27 -0
  42. package/dist/cli/command-annotations.js.map +1 -0
  43. package/dist/cli/commands/agents.d.ts +46 -0
  44. package/dist/cli/commands/agents.d.ts.map +1 -0
  45. package/dist/cli/commands/agents.js +377 -0
  46. package/dist/cli/commands/agents.js.map +1 -0
  47. package/dist/cli/commands/batch.d.ts +20 -0
  48. package/dist/cli/commands/batch.d.ts.map +1 -0
  49. package/dist/cli/commands/batch.js +214 -0
  50. package/dist/cli/commands/batch.js.map +1 -0
  51. package/dist/cli/commands/clone-for-testing.d.ts +1 -1
  52. package/dist/cli/commands/clone-for-testing.d.ts.map +1 -1
  53. package/dist/cli/commands/clone-for-testing.js +37 -47
  54. package/dist/cli/commands/clone-for-testing.js.map +1 -1
  55. package/dist/cli/commands/derive.d.ts +1 -1
  56. package/dist/cli/commands/derive.d.ts.map +1 -1
  57. package/dist/cli/commands/derive.js +140 -88
  58. package/dist/cli/commands/derive.js.map +1 -1
  59. package/dist/cli/commands/doctor.d.ts +11 -0
  60. package/dist/cli/commands/doctor.d.ts.map +1 -0
  61. package/dist/cli/commands/doctor.js +152 -0
  62. package/dist/cli/commands/doctor.js.map +1 -0
  63. package/dist/cli/commands/export.d.ts +12 -0
  64. package/dist/cli/commands/export.d.ts.map +1 -0
  65. package/dist/cli/commands/export.js +134 -0
  66. package/dist/cli/commands/export.js.map +1 -0
  67. package/dist/cli/commands/help.d.ts +1 -1
  68. package/dist/cli/commands/help.d.ts.map +1 -1
  69. package/dist/cli/commands/help.js +163 -37
  70. package/dist/cli/commands/help.js.map +1 -1
  71. package/dist/cli/commands/inbox.d.ts +1 -1
  72. package/dist/cli/commands/inbox.d.ts.map +1 -1
  73. package/dist/cli/commands/inbox.js +178 -56
  74. package/dist/cli/commands/inbox.js.map +1 -1
  75. package/dist/cli/commands/index.d.ts +31 -19
  76. package/dist/cli/commands/index.d.ts.map +1 -1
  77. package/dist/cli/commands/index.js +31 -19
  78. package/dist/cli/commands/index.js.map +1 -1
  79. package/dist/cli/commands/init.d.ts +5 -1
  80. package/dist/cli/commands/init.d.ts.map +1 -1
  81. package/dist/cli/commands/init.js +108 -57
  82. package/dist/cli/commands/init.js.map +1 -1
  83. package/dist/cli/commands/item.d.ts +1 -1
  84. package/dist/cli/commands/item.d.ts.map +1 -1
  85. package/dist/cli/commands/item.js +557 -274
  86. package/dist/cli/commands/item.js.map +1 -1
  87. package/dist/cli/commands/link.d.ts +1 -1
  88. package/dist/cli/commands/link.d.ts.map +1 -1
  89. package/dist/cli/commands/link.js +55 -46
  90. package/dist/cli/commands/link.js.map +1 -1
  91. package/dist/cli/commands/log.d.ts +1 -1
  92. package/dist/cli/commands/log.d.ts.map +1 -1
  93. package/dist/cli/commands/log.js +57 -51
  94. package/dist/cli/commands/log.js.map +1 -1
  95. package/dist/cli/commands/merge-driver.d.ts +19 -0
  96. package/dist/cli/commands/merge-driver.d.ts.map +1 -0
  97. package/dist/cli/commands/merge-driver.js +398 -0
  98. package/dist/cli/commands/merge-driver.js.map +1 -0
  99. package/dist/cli/commands/meta.d.ts +1 -1
  100. package/dist/cli/commands/meta.d.ts.map +1 -1
  101. package/dist/cli/commands/meta.js +533 -399
  102. package/dist/cli/commands/meta.js.map +1 -1
  103. package/dist/cli/commands/module.d.ts +1 -1
  104. package/dist/cli/commands/module.d.ts.map +1 -1
  105. package/dist/cli/commands/module.js +30 -25
  106. package/dist/cli/commands/module.js.map +1 -1
  107. package/dist/cli/commands/plan-import.d.ts +11 -0
  108. package/dist/cli/commands/plan-import.d.ts.map +1 -0
  109. package/dist/cli/commands/plan-import.js +516 -0
  110. package/dist/cli/commands/plan-import.js.map +1 -0
  111. package/dist/cli/commands/plan.d.ts +10 -0
  112. package/dist/cli/commands/plan.d.ts.map +1 -0
  113. package/dist/cli/commands/plan.js +421 -0
  114. package/dist/cli/commands/plan.js.map +1 -0
  115. package/dist/cli/commands/ralph.d.ts +1 -1
  116. package/dist/cli/commands/ralph.d.ts.map +1 -1
  117. package/dist/cli/commands/ralph.js +1097 -169
  118. package/dist/cli/commands/ralph.js.map +1 -1
  119. package/dist/cli/commands/refs.d.ts +13 -0
  120. package/dist/cli/commands/refs.d.ts.map +1 -0
  121. package/dist/cli/commands/refs.js +283 -0
  122. package/dist/cli/commands/refs.js.map +1 -0
  123. package/dist/cli/commands/search.d.ts +1 -1
  124. package/dist/cli/commands/search.d.ts.map +1 -1
  125. package/dist/cli/commands/search.js +199 -37
  126. package/dist/cli/commands/search.js.map +1 -1
  127. package/dist/cli/commands/serve.d.ts +10 -0
  128. package/dist/cli/commands/serve.d.ts.map +1 -0
  129. package/dist/cli/commands/serve.js +491 -0
  130. package/dist/cli/commands/serve.js.map +1 -0
  131. package/dist/cli/commands/session.d.ts +25 -6
  132. package/dist/cli/commands/session.d.ts.map +1 -1
  133. package/dist/cli/commands/session.js +811 -127
  134. package/dist/cli/commands/session.js.map +1 -1
  135. package/dist/cli/commands/setup-seeding.d.ts +81 -0
  136. package/dist/cli/commands/setup-seeding.d.ts.map +1 -0
  137. package/dist/cli/commands/setup-seeding.js +292 -0
  138. package/dist/cli/commands/setup-seeding.js.map +1 -0
  139. package/dist/cli/commands/setup.d.ts +77 -3
  140. package/dist/cli/commands/setup.d.ts.map +1 -1
  141. package/dist/cli/commands/setup.js +1233 -274
  142. package/dist/cli/commands/setup.js.map +1 -1
  143. package/dist/cli/commands/shadow.d.ts +1 -1
  144. package/dist/cli/commands/shadow.d.ts.map +1 -1
  145. package/dist/cli/commands/shadow.js +70 -50
  146. package/dist/cli/commands/shadow.js.map +1 -1
  147. package/dist/cli/commands/skill-crud.d.ts +58 -0
  148. package/dist/cli/commands/skill-crud.d.ts.map +1 -0
  149. package/dist/cli/commands/skill-crud.js +753 -0
  150. package/dist/cli/commands/skill-crud.js.map +1 -0
  151. package/dist/cli/commands/skill-diff.d.ts +27 -0
  152. package/dist/cli/commands/skill-diff.d.ts.map +1 -0
  153. package/dist/cli/commands/skill-diff.js +840 -0
  154. package/dist/cli/commands/skill-diff.js.map +1 -0
  155. package/dist/cli/commands/skill-install.d.ts +53 -0
  156. package/dist/cli/commands/skill-install.d.ts.map +1 -0
  157. package/dist/cli/commands/skill-install.js +452 -0
  158. package/dist/cli/commands/skill-install.js.map +1 -0
  159. package/dist/cli/commands/skill.d.ts +20 -0
  160. package/dist/cli/commands/skill.d.ts.map +1 -0
  161. package/dist/cli/commands/skill.js +36 -0
  162. package/dist/cli/commands/skill.js.map +1 -0
  163. package/dist/cli/commands/task.d.ts +1 -1
  164. package/dist/cli/commands/task.d.ts.map +1 -1
  165. package/dist/cli/commands/task.js +569 -346
  166. package/dist/cli/commands/task.js.map +1 -1
  167. package/dist/cli/commands/tasks.d.ts +26 -1
  168. package/dist/cli/commands/tasks.d.ts.map +1 -1
  169. package/dist/cli/commands/tasks.js +227 -122
  170. package/dist/cli/commands/tasks.js.map +1 -1
  171. package/dist/cli/commands/trait.d.ts +1 -1
  172. package/dist/cli/commands/trait.d.ts.map +1 -1
  173. package/dist/cli/commands/trait.js +166 -101
  174. package/dist/cli/commands/trait.js.map +1 -1
  175. package/dist/cli/commands/triage.d.ts +7 -0
  176. package/dist/cli/commands/triage.d.ts.map +1 -0
  177. package/dist/cli/commands/triage.js +569 -0
  178. package/dist/cli/commands/triage.js.map +1 -0
  179. package/dist/cli/commands/util.d.ts +7 -0
  180. package/dist/cli/commands/util.d.ts.map +1 -0
  181. package/dist/cli/commands/util.js +30 -0
  182. package/dist/cli/commands/util.js.map +1 -0
  183. package/dist/cli/commands/validate.d.ts +1 -1
  184. package/dist/cli/commands/validate.d.ts.map +1 -1
  185. package/dist/cli/commands/validate.js +264 -83
  186. package/dist/cli/commands/validate.js.map +1 -1
  187. package/dist/cli/commands/workflow.d.ts +16 -0
  188. package/dist/cli/commands/workflow.d.ts.map +1 -0
  189. package/dist/cli/commands/workflow.js +851 -0
  190. package/dist/cli/commands/workflow.js.map +1 -0
  191. package/dist/cli/exit-codes.d.ts +7 -0
  192. package/dist/cli/exit-codes.d.ts.map +1 -1
  193. package/dist/cli/exit-codes.js +26 -18
  194. package/dist/cli/exit-codes.js.map +1 -1
  195. package/dist/cli/help/content.d.ts.map +1 -1
  196. package/dist/cli/help/content.js +86 -71
  197. package/dist/cli/help/content.js.map +1 -1
  198. package/dist/cli/index.d.ts +1 -1
  199. package/dist/cli/index.d.ts.map +1 -1
  200. package/dist/cli/index.js +131 -19
  201. package/dist/cli/index.js.map +1 -1
  202. package/dist/cli/introspection.d.ts +6 -2
  203. package/dist/cli/introspection.d.ts.map +1 -1
  204. package/dist/cli/introspection.js +11 -8
  205. package/dist/cli/introspection.js.map +1 -1
  206. package/dist/cli/output.d.ts +64 -4
  207. package/dist/cli/output.d.ts.map +1 -1
  208. package/dist/cli/output.js +235 -85
  209. package/dist/cli/output.js.map +1 -1
  210. package/dist/cli/parse-utils.d.ts +21 -0
  211. package/dist/cli/parse-utils.d.ts.map +1 -0
  212. package/dist/cli/parse-utils.js +32 -0
  213. package/dist/cli/parse-utils.js.map +1 -0
  214. package/dist/cli/pid-utils.d.ts +72 -0
  215. package/dist/cli/pid-utils.d.ts.map +1 -0
  216. package/dist/cli/pid-utils.js +174 -0
  217. package/dist/cli/pid-utils.js.map +1 -0
  218. package/dist/cli/suggest.d.ts.map +1 -1
  219. package/dist/cli/suggest.js +1 -2
  220. package/dist/cli/suggest.js.map +1 -1
  221. package/dist/cli/validators.d.ts +43 -0
  222. package/dist/cli/validators.d.ts.map +1 -0
  223. package/dist/cli/validators.js +84 -0
  224. package/dist/cli/validators.js.map +1 -0
  225. package/dist/daemon/index.ts +52 -0
  226. package/dist/daemon/middleware/project-context.ts +126 -0
  227. package/dist/daemon/pid.ts +179 -0
  228. package/dist/daemon/project-context.ts +343 -0
  229. package/dist/daemon/routes/inbox.ts +164 -0
  230. package/dist/daemon/routes/items.ts +322 -0
  231. package/dist/daemon/routes/meta.ts +118 -0
  232. package/dist/daemon/routes/projects.ts +162 -0
  233. package/dist/daemon/routes/tasks.ts +327 -0
  234. package/dist/daemon/routes/triage.ts +468 -0
  235. package/dist/daemon/routes/validation.ts +248 -0
  236. package/dist/daemon/server.ts +408 -0
  237. package/dist/daemon/watcher.ts +195 -0
  238. package/dist/daemon/websocket/handler.ts +138 -0
  239. package/dist/daemon/websocket/heartbeat.ts +71 -0
  240. package/dist/daemon/websocket/pubsub.ts +125 -0
  241. package/dist/daemon/websocket/types.ts +66 -0
  242. package/dist/export/html.d.ts +19 -0
  243. package/dist/export/html.d.ts.map +1 -0
  244. package/dist/export/html.js +239 -0
  245. package/dist/export/html.js.map +1 -0
  246. package/dist/export/index.d.ts +10 -0
  247. package/dist/export/index.d.ts.map +1 -0
  248. package/dist/export/index.js +10 -0
  249. package/dist/export/index.js.map +1 -0
  250. package/dist/export/json.d.ts +24 -0
  251. package/dist/export/json.d.ts.map +1 -0
  252. package/dist/export/json.js +198 -0
  253. package/dist/export/json.js.map +1 -0
  254. package/dist/export/triage.d.ts +51 -0
  255. package/dist/export/triage.d.ts.map +1 -0
  256. package/dist/export/triage.js +83 -0
  257. package/dist/export/triage.js.map +1 -0
  258. package/dist/export/types.d.ts +122 -0
  259. package/dist/export/types.d.ts.map +1 -0
  260. package/dist/export/types.js +9 -0
  261. package/dist/export/types.js.map +1 -0
  262. package/dist/index.d.ts +2 -2
  263. package/dist/index.js +2 -2
  264. package/dist/lib/claude-plugin-registry.d.ts +66 -0
  265. package/dist/lib/claude-plugin-registry.d.ts.map +1 -0
  266. package/dist/lib/claude-plugin-registry.js +318 -0
  267. package/dist/lib/claude-plugin-registry.js.map +1 -0
  268. package/dist/merge/arrays.d.ts +87 -0
  269. package/dist/merge/arrays.d.ts.map +1 -0
  270. package/dist/merge/arrays.js +164 -0
  271. package/dist/merge/arrays.js.map +1 -0
  272. package/dist/merge/file-type.d.ts +32 -0
  273. package/dist/merge/file-type.d.ts.map +1 -0
  274. package/dist/merge/file-type.js +70 -0
  275. package/dist/merge/file-type.js.map +1 -0
  276. package/dist/merge/index.d.ts +14 -0
  277. package/dist/merge/index.d.ts.map +1 -0
  278. package/dist/merge/index.js +11 -0
  279. package/dist/merge/index.js.map +1 -0
  280. package/dist/merge/objects.d.ts +46 -0
  281. package/dist/merge/objects.d.ts.map +1 -0
  282. package/dist/merge/objects.js +193 -0
  283. package/dist/merge/objects.js.map +1 -0
  284. package/dist/merge/parse.d.ts +23 -0
  285. package/dist/merge/parse.d.ts.map +1 -0
  286. package/dist/merge/parse.js +78 -0
  287. package/dist/merge/parse.js.map +1 -0
  288. package/dist/merge/resolve.d.ts +66 -0
  289. package/dist/merge/resolve.d.ts.map +1 -0
  290. package/dist/merge/resolve.js +189 -0
  291. package/dist/merge/resolve.js.map +1 -0
  292. package/dist/merge/types.d.ts +82 -0
  293. package/dist/merge/types.d.ts.map +1 -0
  294. package/dist/merge/types.js +8 -0
  295. package/dist/merge/types.js.map +1 -0
  296. package/dist/parser/agent-data-sections.d.ts +53 -0
  297. package/dist/parser/agent-data-sections.d.ts.map +1 -0
  298. package/dist/parser/agent-data-sections.js +118 -0
  299. package/dist/parser/agent-data-sections.js.map +1 -0
  300. package/dist/parser/alignment.d.ts +4 -4
  301. package/dist/parser/alignment.d.ts.map +1 -1
  302. package/dist/parser/alignment.js +27 -22
  303. package/dist/parser/alignment.js.map +1 -1
  304. package/dist/parser/assess.d.ts +5 -5
  305. package/dist/parser/assess.d.ts.map +1 -1
  306. package/dist/parser/assess.js +36 -32
  307. package/dist/parser/assess.js.map +1 -1
  308. package/dist/parser/config.d.ts +351 -0
  309. package/dist/parser/config.d.ts.map +1 -0
  310. package/dist/parser/config.js +326 -0
  311. package/dist/parser/config.js.map +1 -0
  312. package/dist/parser/convention-validation.d.ts +1 -1
  313. package/dist/parser/convention-validation.d.ts.map +1 -1
  314. package/dist/parser/convention-validation.js +21 -16
  315. package/dist/parser/convention-validation.js.map +1 -1
  316. package/dist/parser/coverage-cache.d.ts +49 -0
  317. package/dist/parser/coverage-cache.d.ts.map +1 -0
  318. package/dist/parser/coverage-cache.js +123 -0
  319. package/dist/parser/coverage-cache.js.map +1 -0
  320. package/dist/parser/daemon-status.d.ts +37 -0
  321. package/dist/parser/daemon-status.d.ts.map +1 -0
  322. package/dist/parser/daemon-status.js +67 -0
  323. package/dist/parser/daemon-status.js.map +1 -0
  324. package/dist/parser/doctor.d.ts +107 -0
  325. package/dist/parser/doctor.d.ts.map +1 -0
  326. package/dist/parser/doctor.js +366 -0
  327. package/dist/parser/doctor.js.map +1 -0
  328. package/dist/parser/fix.d.ts +1 -1
  329. package/dist/parser/fix.d.ts.map +1 -1
  330. package/dist/parser/fix.js +31 -27
  331. package/dist/parser/fix.js.map +1 -1
  332. package/dist/parser/index.d.ts +16 -11
  333. package/dist/parser/index.d.ts.map +1 -1
  334. package/dist/parser/index.js +16 -11
  335. package/dist/parser/index.js.map +1 -1
  336. package/dist/parser/items.d.ts +8 -2
  337. package/dist/parser/items.d.ts.map +1 -1
  338. package/dist/parser/items.js +71 -35
  339. package/dist/parser/items.js.map +1 -1
  340. package/dist/parser/meta.d.ts +167 -9
  341. package/dist/parser/meta.d.ts.map +1 -1
  342. package/dist/parser/meta.js +379 -46
  343. package/dist/parser/meta.js.map +1 -1
  344. package/dist/parser/plan-document.d.ts +189 -0
  345. package/dist/parser/plan-document.d.ts.map +1 -0
  346. package/dist/parser/plan-document.js +340 -0
  347. package/dist/parser/plan-document.js.map +1 -0
  348. package/dist/parser/plans.d.ts +59 -0
  349. package/dist/parser/plans.d.ts.map +1 -0
  350. package/dist/parser/plans.js +239 -0
  351. package/dist/parser/plans.js.map +1 -0
  352. package/dist/parser/refs.d.ts +22 -9
  353. package/dist/parser/refs.d.ts.map +1 -1
  354. package/dist/parser/refs.js +102 -50
  355. package/dist/parser/refs.js.map +1 -1
  356. package/dist/parser/setup-status.d.ts +71 -0
  357. package/dist/parser/setup-status.d.ts.map +1 -0
  358. package/dist/parser/setup-status.js +269 -0
  359. package/dist/parser/setup-status.js.map +1 -0
  360. package/dist/parser/shadow.d.ts +150 -19
  361. package/dist/parser/shadow.d.ts.map +1 -1
  362. package/dist/parser/shadow.js +548 -187
  363. package/dist/parser/shadow.js.map +1 -1
  364. package/dist/parser/skill-render.d.ts +317 -0
  365. package/dist/parser/skill-render.d.ts.map +1 -0
  366. package/dist/parser/skill-render.js +943 -0
  367. package/dist/parser/skill-render.js.map +1 -0
  368. package/dist/parser/traits.d.ts +3 -3
  369. package/dist/parser/traits.d.ts.map +1 -1
  370. package/dist/parser/traits.js +2 -2
  371. package/dist/parser/traits.js.map +1 -1
  372. package/dist/parser/validate-skills.d.ts +32 -0
  373. package/dist/parser/validate-skills.d.ts.map +1 -0
  374. package/dist/parser/validate-skills.js +202 -0
  375. package/dist/parser/validate-skills.js.map +1 -0
  376. package/dist/parser/validate.d.ts +45 -3
  377. package/dist/parser/validate.d.ts.map +1 -1
  378. package/dist/parser/validate.js +622 -105
  379. package/dist/parser/validate.js.map +1 -1
  380. package/dist/parser/yaml.d.ts +83 -19
  381. package/dist/parser/yaml.d.ts.map +1 -1
  382. package/dist/parser/yaml.js +478 -173
  383. package/dist/parser/yaml.js.map +1 -1
  384. package/dist/ralph/cli-renderer.d.ts +8 -1
  385. package/dist/ralph/cli-renderer.d.ts.map +1 -1
  386. package/dist/ralph/cli-renderer.js +105 -34
  387. package/dist/ralph/cli-renderer.js.map +1 -1
  388. package/dist/ralph/events.d.ts +10 -10
  389. package/dist/ralph/events.d.ts.map +1 -1
  390. package/dist/ralph/events.js +277 -98
  391. package/dist/ralph/events.js.map +1 -1
  392. package/dist/ralph/index.d.ts +5 -2
  393. package/dist/ralph/index.d.ts.map +1 -1
  394. package/dist/ralph/index.js +9 -3
  395. package/dist/ralph/index.js.map +1 -1
  396. package/dist/ralph/loop-errors.d.ts +83 -0
  397. package/dist/ralph/loop-errors.d.ts.map +1 -0
  398. package/dist/ralph/loop-errors.js +150 -0
  399. package/dist/ralph/loop-errors.js.map +1 -0
  400. package/dist/ralph/subagent.d.ts +83 -0
  401. package/dist/ralph/subagent.d.ts.map +1 -0
  402. package/dist/ralph/subagent.js +174 -0
  403. package/dist/ralph/subagent.js.map +1 -0
  404. package/dist/ralph/wrap-up.d.ts +125 -0
  405. package/dist/ralph/wrap-up.d.ts.map +1 -0
  406. package/dist/ralph/wrap-up.js +270 -0
  407. package/dist/ralph/wrap-up.js.map +1 -0
  408. package/dist/schema/batch.d.ts +95 -0
  409. package/dist/schema/batch.d.ts.map +1 -0
  410. package/dist/schema/batch.js +24 -0
  411. package/dist/schema/batch.js.map +1 -0
  412. package/dist/schema/common.d.ts +2 -2
  413. package/dist/schema/common.d.ts.map +1 -1
  414. package/dist/schema/common.js +34 -31
  415. package/dist/schema/common.js.map +1 -1
  416. package/dist/schema/inbox.d.ts +12 -12
  417. package/dist/schema/inbox.js +4 -4
  418. package/dist/schema/inbox.js.map +1 -1
  419. package/dist/schema/index.d.ts +8 -5
  420. package/dist/schema/index.d.ts.map +1 -1
  421. package/dist/schema/index.js +8 -5
  422. package/dist/schema/index.js.map +1 -1
  423. package/dist/schema/meta.d.ts +1454 -27
  424. package/dist/schema/meta.d.ts.map +1 -1
  425. package/dist/schema/meta.js +198 -21
  426. package/dist/schema/meta.js.map +1 -1
  427. package/dist/schema/plan.d.ts +285 -0
  428. package/dist/schema/plan.d.ts.map +1 -0
  429. package/dist/schema/plan.js +81 -0
  430. package/dist/schema/plan.js.map +1 -0
  431. package/dist/schema/spec.d.ts +72 -33
  432. package/dist/schema/spec.d.ts.map +1 -1
  433. package/dist/schema/spec.js +22 -9
  434. package/dist/schema/spec.js.map +1 -1
  435. package/dist/schema/task.d.ts +172 -161
  436. package/dist/schema/task.d.ts.map +1 -1
  437. package/dist/schema/task.js +21 -12
  438. package/dist/schema/task.js.map +1 -1
  439. package/dist/schema/triage.d.ts +266 -0
  440. package/dist/schema/triage.d.ts.map +1 -0
  441. package/dist/schema/triage.js +134 -0
  442. package/dist/schema/triage.js.map +1 -0
  443. package/dist/sessions/index.d.ts +2 -2
  444. package/dist/sessions/index.d.ts.map +1 -1
  445. package/dist/sessions/index.js +3 -3
  446. package/dist/sessions/index.js.map +1 -1
  447. package/dist/sessions/store.d.ts +233 -1
  448. package/dist/sessions/store.d.ts.map +1 -1
  449. package/dist/sessions/store.js +628 -31
  450. package/dist/sessions/store.js.map +1 -1
  451. package/dist/sessions/types.d.ts +10 -10
  452. package/dist/sessions/types.d.ts.map +1 -1
  453. package/dist/sessions/types.js +10 -9
  454. package/dist/sessions/types.js.map +1 -1
  455. package/dist/strings/errors.d.ts +51 -0
  456. package/dist/strings/errors.d.ts.map +1 -1
  457. package/dist/strings/errors.js +136 -106
  458. package/dist/strings/errors.js.map +1 -1
  459. package/dist/strings/guidance.d.ts.map +1 -1
  460. package/dist/strings/guidance.js +16 -16
  461. package/dist/strings/guidance.js.map +1 -1
  462. package/dist/strings/index.d.ts +4 -4
  463. package/dist/strings/index.d.ts.map +1 -1
  464. package/dist/strings/index.js +4 -4
  465. package/dist/strings/index.js.map +1 -1
  466. package/dist/strings/labels.d.ts +4 -0
  467. package/dist/strings/labels.d.ts.map +1 -1
  468. package/dist/strings/labels.js +45 -41
  469. package/dist/strings/labels.js.map +1 -1
  470. package/dist/strings/validation.d.ts.map +1 -1
  471. package/dist/strings/validation.js +71 -71
  472. package/dist/strings/validation.js.map +1 -1
  473. package/dist/utils/commit.d.ts +1 -1
  474. package/dist/utils/commit.d.ts.map +1 -1
  475. package/dist/utils/commit.js +28 -26
  476. package/dist/utils/commit.js.map +1 -1
  477. package/dist/utils/git.d.ts +1 -1
  478. package/dist/utils/git.d.ts.map +1 -1
  479. package/dist/utils/git.js +40 -38
  480. package/dist/utils/git.js.map +1 -1
  481. package/dist/utils/grep.js +11 -11
  482. package/dist/utils/grep.js.map +1 -1
  483. package/dist/utils/index.d.ts +7 -7
  484. package/dist/utils/index.d.ts.map +1 -1
  485. package/dist/utils/index.js +4 -4
  486. package/dist/utils/index.js.map +1 -1
  487. package/dist/utils/time.d.ts.map +1 -1
  488. package/dist/utils/time.js +10 -10
  489. package/dist/utils/time.js.map +1 -1
  490. package/package.json +28 -5
  491. package/plugin/.claude-plugin/marketplace.json +17 -0
  492. package/plugin/.claude-plugin/plugin.json +5 -0
  493. package/plugin/plugins/kspec/skills/help/SKILL.md +42 -0
  494. package/plugin/plugins/kspec/skills/triage/SKILL.md +206 -0
  495. package/plugin/plugins/kspec/skills/triage/docs/automation.md +120 -0
  496. package/plugin/plugins/kspec/skills/triage/docs/inbox.md +144 -0
  497. package/plugin/plugins/kspec/skills/triage/docs/observations.md +85 -0
  498. package/templates/agents-sections/01-quick-start.md +22 -0
  499. package/templates/agents-sections/02-shadow-branch.md +34 -0
  500. package/templates/agents-sections/03-task-lifecycle.md +48 -0
  501. package/templates/agents-sections/04-pr-workflow.md +17 -0
  502. package/templates/agents-sections/05-commit-convention.md +27 -0
  503. package/templates/agents-sections/06-ralph-loop.md +45 -0
  504. package/templates/hooks/pre-commit +34 -0
  505. package/templates/skills/help/SKILL.md +37 -0
  506. package/templates/skills/manifest.yaml +15 -0
  507. package/templates/skills/triage/SKILL.md +199 -0
  508. package/templates/skills/triage/docs/automation.md +120 -0
  509. package/templates/skills/triage/docs/inbox.md +144 -0
  510. package/templates/skills/triage/docs/observations.md +85 -0
@@ -3,17 +3,18 @@
3
3
  *
4
4
  * Provides context for starting/resuming work sessions.
5
5
  */
6
- import chalk from 'chalk';
7
- import { initContext, loadAllTasks, loadAllItems, loadInboxItems, loadSessionContext, getReadyTasks, ReferenceIndex, } from '../../parser/index.js';
8
- import { output, error, isJsonMode } from '../output.js';
9
- import { sessionHeaders, hints, sessionPrompt, errors } from '../../strings/index.js';
10
- import { parseTimeSpec, formatRelativeTime, isGitRepo, getRecentCommits, getCurrentBranch, getWorkingTreeStatus, formatCommitGuidance, } from '../../utils/index.js';
11
- import { shadowPull } from '../../parser/shadow.js';
12
- import { EXIT_CODES } from '../exit-codes.js';
6
+ import chalk from "chalk";
7
+ import { getReadyTasks, initContext, loadAllItems, loadAllTasks, loadInboxItems, loadSessionContext, ReferenceIndex, } from "../../parser/index.js";
8
+ import { ShadowError, shadowPull, } from "../../parser/shadow.js";
9
+ import { getAllSessionLogSummaries, getSessionLogDetail, resolveSessionId, readEvents, readSessionContext, computeSessionLogStats, computeToolUsageStats, computeTimePeriodStats, searchSessionEvents, } from "../../sessions/store.js";
10
+ import { errors, hints, sessionHeaders, sessionPrompt, } from "../../strings/index.js";
11
+ import { formatCommitGuidance, formatRelativeTime, getCurrentBranch, getRecentCommits, getWorkingTreeStatus, isGitRepo, parseTimeSpec, } from "../../utils/index.js";
12
+ import { EXIT_CODES } from "../exit-codes.js";
13
+ import { error, isJsonMode, isNoteSuperseded, output } from "../output.js";
13
14
  // ─── Data Gathering ──────────────────────────────────────────────────────────
14
15
  function toActiveTaskSummary(task, index) {
15
16
  const lastNote = task.notes.length > 0 ? task.notes[task.notes.length - 1] : null;
16
- const incompleteTodos = task.todos.filter(t => !t.done).length;
17
+ const incompleteTodos = task.todos.filter((t) => !t.done).length;
17
18
  return {
18
19
  ref: index.shortUlid(task._ulid),
19
20
  title: task.title,
@@ -35,14 +36,14 @@ function toReadyTaskSummary(task, index) {
35
36
  tags: task.tags,
36
37
  };
37
38
  }
38
- function toBlockedTaskSummary(task, allTasks, index) {
39
+ function toBlockedTaskSummary(task, _allTasks, index) {
39
40
  // Find unmet dependencies
40
41
  const unmetDeps = [];
41
42
  for (const depRef of task.depends_on) {
42
43
  const result = index.resolve(depRef);
43
44
  if (result.ok) {
44
45
  const depItem = result.item;
45
- if ('status' in depItem && depItem.status !== 'completed') {
46
+ if ("status" in depItem && depItem.status !== "completed") {
46
47
  unmetDeps.push(depRef);
47
48
  }
48
49
  }
@@ -58,7 +59,7 @@ function toCompletedTaskSummary(task, index) {
58
59
  return {
59
60
  ref: index.shortUlid(task._ulid),
60
61
  title: task.title,
61
- completed_at: task.completed_at || '',
62
+ completed_at: task.completed_at || "",
62
63
  closed_reason: task.closed_reason || null,
63
64
  origin: task.origin,
64
65
  };
@@ -66,15 +67,25 @@ function toCompletedTaskSummary(task, index) {
66
67
  function collectRecentNotes(tasks, index, options) {
67
68
  const allNotes = [];
68
69
  for (const task of tasks) {
70
+ // Only include notes from in_progress, pending_review, or completed tasks
71
+ const taskStatus = task.status;
72
+ if (!["in_progress", "pending_review", "needs_work", "completed"].includes(taskStatus)) {
73
+ continue;
74
+ }
69
75
  for (const note of task.notes) {
70
76
  const noteDate = new Date(note.created_at);
71
77
  // Filter by since date if provided
72
78
  if (options.since && noteDate < options.since) {
73
79
  continue;
74
80
  }
81
+ // Filter out superseded notes
82
+ if (isNoteSuperseded(note, task.notes)) {
83
+ continue;
84
+ }
75
85
  allNotes.push({
76
86
  task_ref: index.shortUlid(task._ulid),
77
87
  task_title: task.title,
88
+ task_status: taskStatus,
78
89
  note_ulid: note._ulid.slice(0, 8),
79
90
  created_at: note.created_at,
80
91
  author: note.author || null,
@@ -113,7 +124,7 @@ function collectIncompleteTodos(tasks, index, options) {
113
124
  * Gather session context data
114
125
  */
115
126
  export async function gatherSessionContext(ctx, options) {
116
- const limit = parseInt(options.limit || '10', 10);
127
+ const limit = parseInt(options.limit || "10", 10);
117
128
  const sinceDate = options.since ? parseTimeSpec(options.since) : null;
118
129
  const showGit = options.git !== false; // default true
119
130
  // Load all data
@@ -124,43 +135,63 @@ export async function gatherSessionContext(ctx, options) {
124
135
  // Compute stats
125
136
  const stats = {
126
137
  total_tasks: allTasks.length,
127
- in_progress: allTasks.filter((t) => t.status === 'in_progress').length,
128
- pending_review: allTasks.filter((t) => t.status === 'pending_review').length,
138
+ in_progress: allTasks.filter((t) => t.status === "in_progress").length,
139
+ needs_work: allTasks.filter((t) => t.status === "needs_work").length,
140
+ pending_review: allTasks.filter((t) => t.status === "pending_review")
141
+ .length,
129
142
  ready: getReadyTasks(allTasks).length,
130
- blocked: allTasks.filter((t) => t.status === 'blocked').length,
131
- completed: allTasks.filter((t) => t.status === 'completed').length,
143
+ blocked: allTasks.filter((t) => t.status === "blocked").length,
144
+ completed: allTasks.filter((t) => t.status === "completed").length,
132
145
  inbox_items: inboxItems.length,
133
146
  };
134
- // Get active tasks
147
+ // Get active tasks (in_progress + needs_work, optionally filtered to automation-eligible only)
148
+ // AC: @cli-ralph ac-16
135
149
  const activeTasks = allTasks
136
- .filter((t) => t.status === 'in_progress')
150
+ .filter((t) => t.status === "in_progress" || t.status === "needs_work")
151
+ .filter((t) => !options.eligible || t.automation === "eligible")
137
152
  .sort((a, b) => a.priority - b.priority)
138
153
  .slice(0, options.full ? undefined : limit)
139
154
  .map((t) => toActiveTaskSummary(t, index));
140
155
  // Get pending review tasks
141
156
  const pendingReviewTasks = allTasks
142
- .filter((t) => t.status === 'pending_review')
157
+ .filter((t) => t.status === "pending_review")
143
158
  .sort((a, b) => a.priority - b.priority)
144
159
  .slice(0, options.full ? undefined : limit)
145
160
  .map((t) => toActiveTaskSummary(t, index));
146
- // Get recent notes from active tasks
147
- const recentNotes = collectRecentNotes(allTasks.filter((t) => t.status === 'in_progress'), index, { limit: options.full ? limit * 2 : limit, since: sinceDate });
161
+ // Get recent notes from active, pending_review, and recently completed tasks
162
+ // AC: @cmd-session-start ac-1, ac-2
163
+ // Collect notes per-status first to prevent one status from starving others
164
+ const noteLimitPerStatus = options.full ? limit : Math.ceil(limit / 3);
165
+ const inProgressNotes = collectRecentNotes(allTasks.filter((t) => t.status === "in_progress" || t.status === "needs_work"), index, { limit: noteLimitPerStatus, since: sinceDate });
166
+ const pendingReviewNotes = collectRecentNotes(allTasks.filter((t) => t.status === "pending_review"), index, { limit: noteLimitPerStatus, since: sinceDate });
167
+ const recentlyCompletedForNotes = allTasks
168
+ .filter((t) => t.status === "completed" && t.completed_at)
169
+ .sort((a, b) => {
170
+ const aDate = new Date(a.completed_at || 0);
171
+ const bDate = new Date(b.completed_at || 0);
172
+ return bDate.getTime() - aDate.getTime();
173
+ })
174
+ .slice(0, 5); // Last 3-5 completed tasks per AC-2
175
+ const completedNotes = collectRecentNotes(recentlyCompletedForNotes, index, { limit: noteLimitPerStatus, since: sinceDate });
176
+ // Combine notes from all statuses, preserving representation from each
177
+ const recentNotes = [...inProgressNotes, ...pendingReviewNotes, ...completedNotes]
178
+ .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
148
179
  // Get incomplete todos from active tasks
149
- const activeTodos = collectIncompleteTodos(allTasks.filter((t) => t.status === 'in_progress'), index, { limit: options.full ? limit * 2 : limit });
180
+ const activeTodos = collectIncompleteTodos(allTasks.filter((t) => t.status === "in_progress" || t.status === "needs_work"), index, { limit: options.full ? limit * 2 : limit });
150
181
  // Get ready tasks (optionally filtered to automation-eligible only)
151
182
  const readyTasks = getReadyTasks(allTasks)
152
- .filter((t) => !options.eligible || t.automation === 'eligible')
183
+ .filter((t) => !options.eligible || t.automation === "eligible")
153
184
  .slice(0, options.full ? undefined : limit)
154
185
  .map((t) => toReadyTaskSummary(t, index));
155
186
  // Get blocked tasks
156
187
  const blockedTasks = allTasks
157
- .filter((t) => t.status === 'blocked')
188
+ .filter((t) => t.status === "blocked")
158
189
  .slice(0, options.full ? undefined : limit)
159
190
  .map((t) => toBlockedTaskSummary(t, allTasks, index));
160
191
  // Get recently completed tasks
161
192
  const recentlyCompleted = allTasks
162
193
  .filter((t) => {
163
- if (t.status !== 'completed' || !t.completed_at)
194
+ if (t.status !== "completed" || !t.completed_at)
164
195
  return false;
165
196
  const completedDate = new Date(t.completed_at);
166
197
  if (sinceDate && completedDate < sinceDate)
@@ -225,6 +256,30 @@ export async function gatherSessionContext(ctx, options) {
225
256
  stats,
226
257
  };
227
258
  }
259
+ /**
260
+ * Get iteration stats - tasks completed/started since a given time.
261
+ * AC: @ralph-task-limit ac-detection
262
+ */
263
+ export async function getIterationStats(ctx, since) {
264
+ const allTasks = await loadAllTasks(ctx);
265
+ const completedSince = allTasks.filter((t) => {
266
+ if (t.status !== "completed" || !t.completed_at)
267
+ return false;
268
+ return new Date(t.completed_at) >= since;
269
+ });
270
+ const startedSince = allTasks.filter((t) => {
271
+ if (!t.started_at)
272
+ return false;
273
+ return new Date(t.started_at) >= since;
274
+ });
275
+ // Use first slug or ULID prefix as ref
276
+ const getRef = (t) => t.slugs.length > 0 ? `@${t.slugs[0]}` : `@${t._ulid.slice(0, 8)}`;
277
+ return {
278
+ tasks_completed: completedSince.length,
279
+ tasks_started: startedSince.length,
280
+ completed_refs: completedSince.map(getRef),
281
+ };
282
+ }
228
283
  /**
229
284
  * Perform session checkpoint - check for uncommitted work before ending session.
230
285
  *
@@ -241,11 +296,13 @@ export async function performCheckpoint(ctx, options) {
241
296
  // Load tasks
242
297
  const allTasks = await loadAllTasks(ctx);
243
298
  // Check for in-progress tasks
244
- const inProgressTasks = allTasks.filter((t) => t.status === 'in_progress');
299
+ const inProgressTasks = allTasks.filter((t) => t.status === "in_progress");
245
300
  for (const task of inProgressTasks) {
246
- const ref = task.slugs[0] ? `@${task.slugs[0]}` : `@${task._ulid.slice(0, 8)}`;
301
+ const ref = task.slugs[0]
302
+ ? `@${task.slugs[0]}`
303
+ : `@${task._ulid.slice(0, 8)}`;
247
304
  issues.push({
248
- type: 'in_progress_task',
305
+ type: "in_progress_task",
249
306
  description: `Task ${ref} is still in progress: ${task.title}`,
250
307
  details: {
251
308
  ref,
@@ -257,7 +314,7 @@ export async function performCheckpoint(ctx, options) {
257
314
  const incompleteTodos = task.todos.filter((t) => !t.done);
258
315
  for (const todo of incompleteTodos) {
259
316
  issues.push({
260
- type: 'incomplete_todo',
317
+ type: "incomplete_todo",
261
318
  description: `Incomplete todo on ${ref}: ${todo.text}`,
262
319
  details: {
263
320
  task_ref: ref,
@@ -275,7 +332,7 @@ export async function performCheckpoint(ctx, options) {
275
332
  workingTree.unstaged.length +
276
333
  workingTree.untracked.length;
277
334
  issues.push({
278
- type: 'uncommitted_changes',
335
+ type: "uncommitted_changes",
279
336
  description: `${changeCount} uncommitted changes in working tree`,
280
337
  details: {
281
338
  staged: workingTree.staged.length,
@@ -287,14 +344,17 @@ export async function performCheckpoint(ctx, options) {
287
344
  }
288
345
  // Build instructions based on issues
289
346
  if (issues.length > 0 && !options.force) {
290
- instructions.push('Before ending this session, please:');
291
- const hasInProgress = issues.some((i) => i.type === 'in_progress_task');
292
- const hasUncommitted = issues.some((i) => i.type === 'uncommitted_changes');
293
- const hasIncompleteTodos = issues.some((i) => i.type === 'incomplete_todo');
347
+ instructions.push("If you've been instructed to ignore this message or are working on");
348
+ instructions.push("unrelated things to any in progress tasks then disregard this message,");
349
+ instructions.push("otherwise before ending this session, please:");
350
+ const hasInProgress = issues.some((i) => i.type === "in_progress_task");
351
+ const hasUncommitted = issues.some((i) => i.type === "uncommitted_changes");
352
+ const hasIncompleteTodos = issues.some((i) => i.type === "incomplete_todo");
294
353
  let step = 1;
295
354
  if (hasInProgress) {
296
- instructions.push(`${step++}. Add notes to in-progress tasks documenting current state`);
297
- instructions.push(`${step++}. Either complete the tasks or leave them in_progress with clear notes for next session`);
355
+ instructions.push(`${step++}. Read in-progress task notes to get full context of the current task status`);
356
+ instructions.push(`${step++}. Add notes documenting current state if any context is missing from this session`);
357
+ instructions.push(`${step++}. Complete the task if you've completed the objectives and no AC are left uncovered\notherwise leave it in progress for a future session`);
298
358
  }
299
359
  if (hasIncompleteTodos) {
300
360
  instructions.push(`${step++}. Complete or acknowledge incomplete todos on active tasks`);
@@ -305,16 +365,17 @@ export async function performCheckpoint(ctx, options) {
305
365
  if (inProgressTasks.length > 0) {
306
366
  const task = inProgressTasks[0];
307
367
  const guidance = formatCommitGuidance(task, { wip: true });
308
- instructions.push('');
309
- instructions.push('Suggested WIP commit:');
368
+ instructions.push("");
369
+ instructions.push("Suggested WIP commit:");
310
370
  instructions.push(` ${guidance.message}`);
311
- instructions.push('');
371
+ instructions.push("");
312
372
  for (const trailer of guidance.trailers) {
313
373
  instructions.push(` ${trailer}`);
314
374
  }
315
375
  }
316
376
  }
317
- instructions.push('');
377
+ instructions.push("");
378
+ instructions.push("Use: kspec task @task to get current task state");
318
379
  instructions.push('Use: kspec task note @task "Progress notes..." to document state');
319
380
  instructions.push('Use: kspec task complete @task --reason "Summary" if task is done');
320
381
  }
@@ -329,7 +390,7 @@ export async function performCheckpoint(ctx, options) {
329
390
  message = `[kspec] Session checkpoint: ${issues.length} issue(s) acknowledged - allowing stop`;
330
391
  }
331
392
  else if (ok) {
332
- message = '[kspec] Session checkpoint passed - ready to end session';
393
+ message = "[kspec] Session checkpoint passed - ready to end session";
333
394
  }
334
395
  else {
335
396
  message = `[kspec] Session checkpoint: ${issues.length} issue(s) need attention`;
@@ -348,17 +409,17 @@ function formatCheckpointResult(result) {
348
409
  }
349
410
  else {
350
411
  console.log(chalk.yellow(result.message));
351
- console.log('');
412
+ console.log("");
352
413
  for (const issue of result.issues) {
353
- const icon = issue.type === 'uncommitted_changes'
354
- ? chalk.yellow('')
355
- : issue.type === 'in_progress_task'
356
- ? chalk.blue('')
357
- : chalk.gray('');
414
+ const icon = issue.type === "uncommitted_changes"
415
+ ? chalk.yellow("")
416
+ : issue.type === "in_progress_task"
417
+ ? chalk.blue("")
418
+ : chalk.gray("");
358
419
  console.log(` ${icon} ${issue.description}`);
359
420
  }
360
421
  if (result.instructions.length > 0) {
361
- console.log('');
422
+ console.log("");
362
423
  for (const instruction of result.instructions) {
363
424
  console.log(chalk.gray(instruction));
364
425
  }
@@ -379,26 +440,27 @@ function formatSessionContext(ctx, options) {
379
440
  // Stats summary
380
441
  const pendingReviewNote = ctx.stats.pending_review > 0
381
442
  ? `${ctx.stats.pending_review} awaiting review, `
382
- : '';
383
- const inboxNote = ctx.stats.inbox_items > 0
384
- ? ` | Inbox: ${ctx.stats.inbox_items}`
385
- : '';
443
+ : "";
444
+ const inboxNote = ctx.stats.inbox_items > 0 ? ` | Inbox: ${ctx.stats.inbox_items}` : "";
386
445
  console.log(chalk.gray(`Tasks: ${ctx.stats.in_progress} active, ${pendingReviewNote}${ctx.stats.ready} ready, ` +
387
446
  `${ctx.stats.blocked} blocked, ${ctx.stats.completed}/${ctx.stats.total_tasks} completed${inboxNote}`));
388
447
  // Session context section (focus, threads, questions)
389
- if (ctx.context && (ctx.context.focus || ctx.context.threads.length > 0 || ctx.context.open_questions.length > 0)) {
390
- console.log('\n--- Session Context ---');
448
+ if (ctx.context &&
449
+ (ctx.context.focus ||
450
+ ctx.context.threads.length > 0 ||
451
+ ctx.context.open_questions.length > 0)) {
452
+ console.log("\n--- Session Context ---");
391
453
  if (ctx.context.focus) {
392
- console.log(` ${chalk.cyan('Focus:')} ${ctx.context.focus}`);
454
+ console.log(` ${chalk.cyan("Focus:")} ${ctx.context.focus}`);
393
455
  }
394
456
  if (ctx.context.threads.length > 0) {
395
- console.log(` ${chalk.cyan('Active Threads:')}`);
457
+ console.log(` ${chalk.cyan("Active Threads:")}`);
396
458
  for (const thread of ctx.context.threads) {
397
459
  console.log(` - ${thread}`);
398
460
  }
399
461
  }
400
462
  if (ctx.context.open_questions.length > 0) {
401
- console.log(` ${chalk.cyan('Open Questions:')}`);
463
+ console.log(` ${chalk.cyan("Open Questions:")}`);
402
464
  for (const question of ctx.context.open_questions) {
403
465
  console.log(` - ${question}`);
404
466
  }
@@ -410,11 +472,11 @@ function formatSessionContext(ctx, options) {
410
472
  for (const task of ctx.active_tasks) {
411
473
  const started = task.started_at
412
474
  ? chalk.gray(` (started ${formatRelativeTime(new Date(task.started_at))})`)
413
- : '';
475
+ : "";
414
476
  const priority = task.priority <= 2
415
477
  ? chalk.red(`P${task.priority}`)
416
478
  : chalk.gray(`P${task.priority}`);
417
- console.log(` ${chalk.blue('[in_progress]')} ${priority} ${task.ref} ${task.title}${started}`);
479
+ console.log(` ${chalk.blue("[in_progress]")} ${priority} ${task.ref} ${task.title}${started}`);
418
480
  }
419
481
  }
420
482
  else {
@@ -427,7 +489,7 @@ function formatSessionContext(ctx, options) {
427
489
  const priority = task.priority <= 2
428
490
  ? chalk.red(`P${task.priority}`)
429
491
  : chalk.gray(`P${task.priority}`);
430
- console.log(` ${chalk.yellow('[pending_review]')} ${priority} ${task.ref} ${task.title}`);
492
+ console.log(` ${chalk.yellow("[pending_review]")} ${priority} ${task.ref} ${task.title}`);
431
493
  }
432
494
  }
433
495
  // Recently completed section
@@ -436,46 +498,73 @@ function formatSessionContext(ctx, options) {
436
498
  const observationPromotedTasks = [];
437
499
  for (const task of ctx.recently_completed) {
438
500
  const completedAge = formatRelativeTime(new Date(task.completed_at));
439
- let reason = '';
501
+ let reason = "";
440
502
  if (task.closed_reason) {
441
503
  const maxLen = isBrief ? 60 : 120;
442
504
  const truncated = task.closed_reason.length > maxLen
443
- ? task.closed_reason.slice(0, maxLen).trim() + '...'
505
+ ? `${task.closed_reason.slice(0, maxLen).trim()}...`
444
506
  : task.closed_reason;
445
507
  reason = chalk.gray(` - ${truncated}`);
446
508
  }
447
- console.log(` ${chalk.green('[completed]')} ${task.ref} ${task.title} ${chalk.gray(`(${completedAge})`)}${reason}`);
509
+ console.log(` ${chalk.green("[completed]")} ${task.ref} ${task.title} ${chalk.gray(`(${completedAge})`)}${reason}`);
448
510
  // Track tasks that came from observations
449
- if (task.origin === 'observation_promotion') {
511
+ if (task.origin === "observation_promotion") {
450
512
  observationPromotedTasks.push(task.ref);
451
513
  }
452
514
  }
453
515
  // Show reminder about resolving observations
454
516
  if (observationPromotedTasks.length > 0) {
455
- console.log(chalk.yellow(`\n ℹ Consider resolving linked observations: ${observationPromotedTasks.join(', ')}`));
517
+ console.log(chalk.yellow(`\n ℹ Consider resolving linked observations: ${observationPromotedTasks.join(", ")}`));
456
518
  console.log(chalk.gray(` Run: kspec meta observations --pending-resolution`));
457
519
  }
458
520
  }
459
- // Recent notes section
521
+ // Recent notes section - grouped by task status
522
+ // AC: @cmd-session-start ac-1, ac-2
460
523
  if (ctx.recent_notes.length > 0) {
461
524
  console.log(`\n${sessionHeaders.recentNotes}`);
462
- for (const note of ctx.recent_notes) {
525
+ // Group notes by task status
526
+ const inProgressNotes = ctx.recent_notes.filter((n) => n.task_status === "in_progress");
527
+ const pendingReviewNotes = ctx.recent_notes.filter((n) => n.task_status === "pending_review");
528
+ const completedNotes = ctx.recent_notes.filter((n) => n.task_status === "completed");
529
+ // Helper to format a single note
530
+ const formatNote = (note) => {
463
531
  const age = formatRelativeTime(new Date(note.created_at));
464
- const author = note.author ? chalk.gray(` by ${note.author}`) : '';
465
- console.log(` ${chalk.yellow(age)} on ${note.task_ref}${author}:`);
532
+ const author = note.author ? chalk.gray(` by ${note.author}`) : "";
533
+ console.log(` ${chalk.yellow(age)} on ${note.task_ref}${author}:`);
466
534
  // Truncate content in brief mode
467
535
  let content = note.content.trim();
468
536
  if (isBrief && content.length > 200) {
469
- content = content.slice(0, 200).trim() + '...';
537
+ content = `${content.slice(0, 200).trim()}...`;
470
538
  }
471
539
  // Indent content, limit lines in brief mode
472
- const lines = content.split('\n');
540
+ const lines = content.split("\n");
473
541
  const maxLines = isBrief ? 3 : lines.length;
474
542
  for (const line of lines.slice(0, maxLines)) {
475
- console.log(` ${chalk.white(line)}`);
543
+ console.log(` ${chalk.white(line)}`);
476
544
  }
477
545
  if (isBrief && lines.length > maxLines) {
478
- console.log(chalk.gray(` ... (${lines.length - maxLines} more lines)`));
546
+ console.log(chalk.gray(` ... (${lines.length - maxLines} more lines)`));
547
+ }
548
+ };
549
+ // AC: @cmd-session-start ac-1 - In Progress notes
550
+ if (inProgressNotes.length > 0) {
551
+ console.log(` ${chalk.blue("In Progress:")}`);
552
+ for (const note of inProgressNotes) {
553
+ formatNote(note);
554
+ }
555
+ }
556
+ // AC: @cmd-session-start ac-1 - Pending Review notes (grouped separately)
557
+ if (pendingReviewNotes.length > 0) {
558
+ console.log(` ${chalk.yellow("Pending Review:")}`);
559
+ for (const note of pendingReviewNotes) {
560
+ formatNote(note);
561
+ }
562
+ }
563
+ // AC: @cmd-session-start ac-2 - Recently Completed notes
564
+ if (completedNotes.length > 0) {
565
+ console.log(` ${chalk.green("Recently Completed:")}`);
566
+ for (const note of completedNotes) {
567
+ formatNote(note);
479
568
  }
480
569
  }
481
570
  }
@@ -483,7 +572,7 @@ function formatSessionContext(ctx, options) {
483
572
  if (ctx.active_todos.length > 0) {
484
573
  console.log(`\n${sessionHeaders.incompleteTodos}`);
485
574
  for (const todo of ctx.active_todos) {
486
- console.log(` ${chalk.yellow('[ ]')} ${todo.task_ref}#${todo.id}: ${todo.text}`);
575
+ console.log(` ${chalk.yellow("[ ]")} ${todo.task_ref}#${todo.id}: ${todo.text}`);
487
576
  }
488
577
  }
489
578
  // Ready tasks section
@@ -493,7 +582,7 @@ function formatSessionContext(ctx, options) {
493
582
  const priority = task.priority <= 2
494
583
  ? chalk.red(`P${task.priority}`)
495
584
  : chalk.gray(`P${task.priority}`);
496
- const tags = task.tags.length > 0 ? chalk.cyan(` #${task.tags.join(' #')}`) : '';
585
+ const tags = task.tags.length > 0 ? chalk.cyan(` #${task.tags.join(" #")}`) : "";
497
586
  console.log(` ${priority} ${task.ref} ${task.title}${tags}`);
498
587
  }
499
588
  }
@@ -501,12 +590,12 @@ function formatSessionContext(ctx, options) {
501
590
  if (ctx.blocked_tasks.length > 0) {
502
591
  console.log(`\n${sessionHeaders.blocked}`);
503
592
  for (const task of ctx.blocked_tasks) {
504
- console.log(` ${chalk.red('[blocked]')} ${task.ref} ${task.title}`);
593
+ console.log(` ${chalk.red("[blocked]")} ${task.ref} ${task.title}`);
505
594
  if (task.blocked_by.length > 0) {
506
- console.log(chalk.gray(` Blockers: ${task.blocked_by.join(', ')}`));
595
+ console.log(chalk.gray(` Blockers: ${task.blocked_by.join(", ")}`));
507
596
  }
508
597
  if (task.unmet_deps.length > 0) {
509
- console.log(chalk.gray(` Waiting on: ${task.unmet_deps.join(', ')}`));
598
+ console.log(chalk.gray(` Waiting on: ${task.unmet_deps.join(", ")}`));
510
599
  }
511
600
  }
512
601
  }
@@ -523,12 +612,12 @@ function formatSessionContext(ctx, options) {
523
612
  console.log(`\n${sessionHeaders.inbox}`);
524
613
  for (const item of ctx.inbox_items) {
525
614
  const age = formatRelativeTime(new Date(item.created_at));
526
- const author = item.added_by ? ` by ${item.added_by}` : '';
527
- const tags = item.tags.length > 0 ? chalk.cyan(` [${item.tags.join(', ')}]`) : '';
615
+ const author = item.added_by ? ` by ${item.added_by}` : "";
616
+ const tags = item.tags.length > 0 ? chalk.cyan(` [${item.tags.join(", ")}]`) : "";
528
617
  // Truncate text in brief mode
529
618
  let text = item.text;
530
619
  if (isBrief && text.length > 60) {
531
- text = text.slice(0, 60).trim() + '...';
620
+ text = `${text.slice(0, 60).trim()}...`;
532
621
  }
533
622
  console.log(` ${chalk.magenta(item.ref)} ${chalk.gray(`(${age}${author})`)}${tags}`);
534
623
  console.log(` ${text}`);
@@ -539,22 +628,22 @@ function formatSessionContext(ctx, options) {
539
628
  if (ctx.working_tree && !ctx.working_tree.clean) {
540
629
  console.log(`\n${sessionHeaders.workingTree}`);
541
630
  if (ctx.working_tree.staged.length > 0) {
542
- console.log(chalk.green(' Staged:'));
631
+ console.log(chalk.green(" Staged:"));
543
632
  for (const file of ctx.working_tree.staged) {
544
633
  console.log(` ${chalk.green(file.status[0].toUpperCase())} ${file.path}`);
545
634
  }
546
635
  }
547
636
  if (ctx.working_tree.unstaged.length > 0) {
548
- console.log(chalk.red(' Modified:'));
637
+ console.log(chalk.red(" Modified:"));
549
638
  for (const file of ctx.working_tree.unstaged) {
550
639
  console.log(` ${chalk.red(file.status[0].toUpperCase())} ${file.path}`);
551
640
  }
552
641
  }
553
642
  if (ctx.working_tree.untracked.length > 0) {
554
- console.log(chalk.gray(' Untracked:'));
643
+ console.log(chalk.gray(" Untracked:"));
555
644
  const limit = isBrief ? 5 : ctx.working_tree.untracked.length;
556
645
  for (const path of ctx.working_tree.untracked.slice(0, limit)) {
557
- console.log(` ${chalk.gray('?')} ${path}`);
646
+ console.log(` ${chalk.gray("?")} ${path}`);
558
647
  }
559
648
  if (isBrief && ctx.working_tree.untracked.length > limit) {
560
649
  console.log(chalk.gray(` ... and ${ctx.working_tree.untracked.length - limit} more`));
@@ -568,19 +657,19 @@ function formatSessionContext(ctx, options) {
568
657
  const quickCommands = [];
569
658
  if (ctx.active_tasks.length > 0) {
570
659
  const ref = ctx.active_tasks[0].ref;
571
- quickCommands.push(`kspec task note @${ref} "Progress..." ${chalk.gray('# document work')}`);
572
- quickCommands.push(`kspec task complete @${ref} --reason "..." ${chalk.gray('# finish task')}`);
660
+ quickCommands.push(`kspec task note @${ref} "Progress..." ${chalk.gray("# document work")}`);
661
+ quickCommands.push(`kspec task complete @${ref} --reason "..." ${chalk.gray("# finish task")}`);
573
662
  }
574
663
  else if (ctx.ready_tasks.length > 0) {
575
664
  const ref = ctx.ready_tasks[0].ref;
576
- quickCommands.push(`kspec task start @${ref} ${chalk.gray('# begin work')}`);
665
+ quickCommands.push(`kspec task start @${ref} ${chalk.gray("# begin work")}`);
577
666
  }
578
667
  if (ctx.inbox_items.length > 0) {
579
668
  const ref = ctx.inbox_items[0].ref;
580
- quickCommands.push(`kspec inbox promote @${ref} --title "..." ${chalk.gray('# convert to task')}`);
669
+ quickCommands.push(`kspec inbox promote @${ref} --title "..." ${chalk.gray("# convert to task")}`);
581
670
  }
582
671
  if (ctx.working_tree && !ctx.working_tree.clean) {
583
- quickCommands.push(`git add . && git commit -m "..." ${chalk.gray('# commit changes')}`);
672
+ quickCommands.push(`git add . && git commit -m "..." ${chalk.gray("# commit changes")}`);
584
673
  }
585
674
  if (quickCommands.length > 0) {
586
675
  console.log(`\n${sessionHeaders.quickCommands}`);
@@ -588,24 +677,24 @@ function formatSessionContext(ctx, options) {
588
677
  console.log(` ${hint}`);
589
678
  }
590
679
  }
591
- console.log(''); // Final newline
680
+ console.log(""); // Final newline
592
681
  }
593
682
  // ─── Command Registration ────────────────────────────────────────────────────
594
683
  async function sessionStartAction(options) {
595
684
  try {
596
685
  const ctx = await initContext();
597
- // AC-2: Pull remote changes before showing session context
686
+ // AC: @shadow-sync ac-2 - Pull remote changes before showing session context
598
687
  let syncResult = null;
599
688
  if (ctx.shadow?.enabled) {
600
689
  syncResult = await shadowPull(ctx.shadow.worktreeDir);
601
- // AC-3: Warn about conflicts but continue with local state
690
+ // AC: @shadow-sync ac-3 - Warn about conflicts but continue with local state
602
691
  if (syncResult.hadConflict) {
603
- console.log(chalk.yellow('⚠ Shadow sync conflict detected. Run `kspec shadow resolve` to fix.'));
604
- console.log(chalk.gray(' Continuing with local state...'));
605
- console.log('');
692
+ console.log(chalk.yellow("⚠ Shadow sync conflict detected. Run `kspec shadow resolve` to fix."));
693
+ console.log(chalk.gray(" Continuing with local state..."));
694
+ console.log("");
606
695
  }
607
696
  else if (syncResult.pulled) {
608
- console.log(chalk.gray('ℹ Synced shadow branch from remote'));
697
+ console.log(chalk.gray("ℹ Synced shadow branch from remote"));
609
698
  }
610
699
  }
611
700
  const sessionCtx = await gatherSessionContext(ctx, options);
@@ -625,20 +714,20 @@ async function readStdinIfAvailable() {
625
714
  return null;
626
715
  }
627
716
  return new Promise((resolve) => {
628
- let data = '';
717
+ let data = "";
629
718
  const timeout = setTimeout(() => {
630
719
  process.stdin.removeAllListeners();
631
720
  resolve(data || null);
632
721
  }, 100); // 100ms timeout for stdin
633
- process.stdin.setEncoding('utf8');
634
- process.stdin.on('data', (chunk) => {
722
+ process.stdin.setEncoding("utf8");
723
+ process.stdin.on("data", (chunk) => {
635
724
  data += chunk;
636
725
  });
637
- process.stdin.on('end', () => {
726
+ process.stdin.on("end", () => {
638
727
  clearTimeout(timeout);
639
728
  resolve(data || null);
640
729
  });
641
- process.stdin.on('error', () => {
730
+ process.stdin.on("error", () => {
642
731
  clearTimeout(timeout);
643
732
  resolve(null);
644
733
  });
@@ -686,10 +775,14 @@ async function sessionCheckpointAction(options) {
686
775
  if (isJsonMode()) {
687
776
  if (!result.ok) {
688
777
  // Build reason message with issues and instructions
689
- const issueLines = result.issues.map(i => `- ${i.description}`).join('\n');
690
- const instructionLines = result.instructions.filter(i => i.trim()).join('\n');
778
+ const issueLines = result.issues
779
+ .map((i) => `- ${i.description}`)
780
+ .join("\n");
781
+ const instructionLines = result.instructions
782
+ .filter((i) => i.trim())
783
+ .join("\n");
691
784
  const reason = `${result.message}\n\nIssues:\n${issueLines}\n\n${instructionLines}`;
692
- console.log(JSON.stringify({ decision: 'block', reason }));
785
+ console.log(JSON.stringify({ decision: "block", reason }));
693
786
  }
694
787
  // If ok, exit silently (Claude Code expects no output when allowing stop)
695
788
  }
@@ -701,45 +794,636 @@ async function sessionCheckpointAction(options) {
701
794
  }
702
795
  }
703
796
  catch (err) {
797
+ // Handle RUNNING_FROM_SHADOW gracefully - skip with warning instead of erroring
798
+ // This happens when the stop hook runs while cwd is inside .kspec/ directory
799
+ if (err instanceof ShadowError && err.code === "RUNNING_FROM_SHADOW") {
800
+ if (!isJsonMode()) {
801
+ console.log(chalk.yellow("[kspec] Session checkpoint skipped - running from inside .kspec/ directory"));
802
+ }
803
+ // Allow stop to proceed (exit successfully, no JSON output blocks the stop)
804
+ return;
805
+ }
704
806
  error(errors.failures.runCheckpoint, err);
705
807
  process.exit(EXIT_CODES.ERROR);
706
808
  }
707
809
  }
810
+ const VALID_SORT_FIELDS = [
811
+ "started_at",
812
+ "duration",
813
+ "events",
814
+ "iterations",
815
+ "tasks_completed",
816
+ ];
817
+ /**
818
+ * Format a duration in milliseconds to a human-readable string.
819
+ */
820
+ function formatDuration(ms) {
821
+ if (ms < 0)
822
+ return "—";
823
+ const totalSec = Math.floor(ms / 1000);
824
+ const hours = Math.floor(totalSec / 3600);
825
+ const minutes = Math.floor((totalSec % 3600) / 60);
826
+ if (hours > 0) {
827
+ return `${hours}h ${minutes}m`;
828
+ }
829
+ if (minutes > 0) {
830
+ return `${minutes}m`;
831
+ }
832
+ return `${totalSec}s`;
833
+ }
834
+ /**
835
+ * Sort session summaries by the specified field.
836
+ * Default: started_at descending.
837
+ *
838
+ * AC: @session-log-list ac-5
839
+ */
840
+ function sortSessions(sessions, sortField) {
841
+ return [...sessions].sort((a, b) => {
842
+ switch (sortField) {
843
+ case "started_at":
844
+ return (new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
845
+ case "duration":
846
+ return b.duration_ms - a.duration_ms;
847
+ case "events":
848
+ return b.event_count - a.event_count;
849
+ case "iterations":
850
+ return b.iteration_count - a.iteration_count;
851
+ case "tasks_completed":
852
+ return b.tasks_completed - a.tasks_completed;
853
+ default:
854
+ return (new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
855
+ }
856
+ });
857
+ }
858
+ /**
859
+ * Format the session log list as a table.
860
+ *
861
+ * AC: @session-log-list ac-1
862
+ */
863
+ function formatSessionLogList(sessions) {
864
+ if (sessions.length === 0) {
865
+ // AC: @session-log-list ac-6
866
+ console.log("No sessions found.");
867
+ return;
868
+ }
869
+ // Table header
870
+ console.log(chalk.gray(`${"ID".padEnd(10)} ${"Status".padEnd(11)} ${"Agent".padEnd(20)} ${"Started".padEnd(16)} ${"Duration".padEnd(10)} ${"Events".padEnd(8)} ${"Iters".padEnd(7)} Tasks`));
871
+ console.log(chalk.gray("─".repeat(95)));
872
+ for (const s of sessions) {
873
+ const id = s.id.slice(0, 8);
874
+ const statusColor = s.status === "completed"
875
+ ? chalk.green
876
+ : s.status === "active"
877
+ ? chalk.blue
878
+ : chalk.yellow;
879
+ const status = statusColor(s.status.padEnd(11));
880
+ const agent = s.agent_type.slice(0, 20).padEnd(20);
881
+ const started = formatRelativeTime(new Date(s.started_at)).padEnd(16);
882
+ const duration = formatDuration(s.duration_ms).padEnd(10);
883
+ const events = String(s.event_count).padEnd(8);
884
+ const iters = String(s.iteration_count).padEnd(7);
885
+ const tasks = String(s.tasks_completed);
886
+ console.log(`${chalk.yellow(id)} ${status} ${chalk.gray(agent)} ${chalk.gray(started)} ${duration} ${events} ${iters} ${tasks}`);
887
+ }
888
+ console.log(chalk.gray(`\n${sessions.length} session(s)`));
889
+ }
890
+ /**
891
+ * Session log list action handler.
892
+ */
893
+ async function sessionLogListAction(options) {
894
+ try {
895
+ const ctx = await initContext();
896
+ let sessions = await getAllSessionLogSummaries(ctx.specDir);
897
+ // AC: @session-log-list ac-2 - Filter by status
898
+ if (options.status) {
899
+ const statusFilter = options.status;
900
+ sessions = sessions.filter((s) => s.status === statusFilter);
901
+ }
902
+ // AC: @session-log-list ac-4 - Filter by agent type
903
+ if (options.agent) {
904
+ const agentFilter = options.agent;
905
+ sessions = sessions.filter((s) => s.agent_type === agentFilter);
906
+ }
907
+ // AC: @session-log-list ac-3 - Filter by since date
908
+ if (options.since) {
909
+ const sinceDate = parseTimeSpec(options.since);
910
+ if (sinceDate) {
911
+ sessions = sessions.filter((s) => new Date(s.started_at) >= sinceDate);
912
+ }
913
+ }
914
+ // AC: @session-log-list ac-5 - Sort
915
+ const sortField = options.sort && VALID_SORT_FIELDS.includes(options.sort)
916
+ ? options.sort
917
+ : "started_at";
918
+ sessions = sortSessions(sessions, sortField);
919
+ // AC: @session-log-list ac-7 - Limit output count
920
+ if (options.count) {
921
+ // AC: @trait-filterable-list ac-8
922
+ output({ count: sessions.length }, () => {
923
+ console.log(sessions.length);
924
+ });
925
+ return;
926
+ }
927
+ // Apply --limit (after filtering/sorting, before display)
928
+ if (options.limit) {
929
+ const limit = parseInt(options.limit, 10);
930
+ if (!Number.isNaN(limit) && limit > 0) {
931
+ sessions = sessions.slice(0, limit);
932
+ }
933
+ }
934
+ output(sessions, () => formatSessionLogList(sessions));
935
+ }
936
+ catch (err) {
937
+ error("Failed to list session logs", err);
938
+ process.exit(EXIT_CODES.ERROR);
939
+ }
940
+ }
941
+ /**
942
+ * Format an event timestamp as relative time from session start.
943
+ */
944
+ function formatEventTimestamp(eventTs, sessionStartTs) {
945
+ const relativeMs = eventTs - sessionStartTs;
946
+ const totalSec = Math.floor(relativeMs / 1000);
947
+ const minutes = Math.floor(totalSec / 60);
948
+ const seconds = totalSec % 60;
949
+ if (minutes > 0) {
950
+ return `+${minutes}m${seconds}s`;
951
+ }
952
+ return `+${seconds}s`;
953
+ }
954
+ /**
955
+ * Summarize event data for display.
956
+ * Returns a short string describing the event payload.
957
+ */
958
+ function summarizeEventData(event) {
959
+ const data = event.data;
960
+ if (!data)
961
+ return "";
962
+ // Handle tool_call events
963
+ if (event.type === "session.update") {
964
+ const update = data.update;
965
+ if (update?.sessionUpdate === "tool_call") {
966
+ const toolName = update._meta?.claudeCode?.toolName || "unknown";
967
+ const command = update.rawInput?.command;
968
+ if (command) {
969
+ const truncated = command.length > 60 ? command.slice(0, 57) + "..." : command;
970
+ return `${toolName}: ${truncated}`;
971
+ }
972
+ return toolName;
973
+ }
974
+ }
975
+ // Handle prompt.sent events
976
+ if (event.type === "prompt.sent") {
977
+ const prompt = data.prompt;
978
+ if (prompt) {
979
+ const truncated = prompt.length > 60 ? prompt.slice(0, 57) + "..." : prompt;
980
+ return truncated;
981
+ }
982
+ }
983
+ // Handle session.start/end
984
+ if (event.type === "session.start") {
985
+ return "Session started";
986
+ }
987
+ if (event.type === "session.end") {
988
+ const reason = data.reason;
989
+ return reason ? `Session ended: ${reason}` : "Session ended";
990
+ }
991
+ // Default: show first key
992
+ const keys = Object.keys(data);
993
+ if (keys.length > 0) {
994
+ return `{${keys.slice(0, 3).join(", ")}${keys.length > 3 ? ", ..." : ""}}`;
995
+ }
996
+ return "";
997
+ }
998
+ /**
999
+ * Format the session log show output.
1000
+ *
1001
+ * AC: @session-log-show ac-1
1002
+ */
1003
+ function formatSessionLogShow(detail, events, contextSnapshot, sessionStartTs) {
1004
+ // AC: @session-log-show ac-1 - Session metadata
1005
+ console.log(chalk.bold(`Session ${detail.id.slice(0, 8)}`));
1006
+ console.log(chalk.gray("─".repeat(60)));
1007
+ console.log(` ID: ${detail.id}`);
1008
+ const statusColor = detail.status === "completed"
1009
+ ? chalk.green
1010
+ : detail.status === "active"
1011
+ ? chalk.blue
1012
+ : chalk.yellow;
1013
+ console.log(` Status: ${statusColor(detail.status)}`);
1014
+ console.log(` Agent: ${detail.agent_type}`);
1015
+ if (detail.task_id) {
1016
+ console.log(` Task: ${detail.task_id}`);
1017
+ }
1018
+ console.log(` Started: ${detail.started_at}`);
1019
+ if (detail.ended_at) {
1020
+ console.log(` Ended: ${detail.ended_at}`);
1021
+ }
1022
+ console.log(` Duration: ${formatDuration(detail.duration_ms)}`);
1023
+ console.log(` Events: ${detail.event_count}`);
1024
+ console.log(` Iterations: ${detail.iteration_count}`);
1025
+ // AC: @session-log-show ac-2 - Per-iteration summary
1026
+ if (detail.iterations.length > 0) {
1027
+ console.log("\n" + chalk.bold("Iterations"));
1028
+ console.log(chalk.gray("─".repeat(60)));
1029
+ for (const iter of detail.iterations) {
1030
+ const taskInfo = [];
1031
+ if (iter.tasks_started.length > 0) {
1032
+ taskInfo.push(`started: ${iter.tasks_started.join(", ")}`);
1033
+ }
1034
+ if (iter.tasks_completed.length > 0) {
1035
+ taskInfo.push(`completed: ${iter.tasks_completed.join(", ")}`);
1036
+ }
1037
+ const taskStr = taskInfo.length > 0 ? ` | ${taskInfo.join(" | ")}` : "";
1038
+ console.log(` ${chalk.cyan(`[${iter.iteration}]`)} ${iter.event_count} events${taskStr}`);
1039
+ }
1040
+ }
1041
+ // AC: @session-log-show ac-3 - Event timeline
1042
+ if (events !== null) {
1043
+ console.log("\n" + chalk.bold("Events"));
1044
+ console.log(chalk.gray("─".repeat(60)));
1045
+ if (events.length === 0) {
1046
+ console.log(chalk.gray(" No events to display."));
1047
+ }
1048
+ else {
1049
+ for (const event of events) {
1050
+ const timestamp = formatEventTimestamp(event.ts, sessionStartTs);
1051
+ const summary = summarizeEventData(event);
1052
+ const typeColor = event.type === "session.start" || event.type === "session.end"
1053
+ ? chalk.green
1054
+ : event.type === "session.update"
1055
+ ? chalk.blue
1056
+ : chalk.gray;
1057
+ console.log(` ${chalk.yellow(timestamp.padEnd(10))} ${typeColor(event.type.padEnd(16))} ${chalk.gray(summary)}`);
1058
+ }
1059
+ }
1060
+ }
1061
+ // AC: @session-log-show ac-6 - Context snapshot
1062
+ if (contextSnapshot !== null) {
1063
+ console.log("\n" + chalk.bold("Context Snapshot"));
1064
+ console.log(chalk.gray("─".repeat(60)));
1065
+ console.log(JSON.stringify(contextSnapshot, null, 2));
1066
+ }
1067
+ }
1068
+ /**
1069
+ * Session log show action handler.
1070
+ */
1071
+ async function sessionLogShowAction(sessionRef, options) {
1072
+ try {
1073
+ const ctx = await initContext();
1074
+ // AC: @session-log-show ac-7, ac-8, ac-9 - Resolve session ID
1075
+ const resolution = await resolveSessionId(ctx.specDir, sessionRef);
1076
+ if (!resolution.ok) {
1077
+ if (resolution.error === "not_found") {
1078
+ // AC: @session-log-show ac-9
1079
+ error(`Session not found: ${sessionRef}`);
1080
+ process.exit(EXIT_CODES.NOT_FOUND);
1081
+ }
1082
+ else {
1083
+ // AC: @session-log-show ac-8
1084
+ error(`Ambiguous session ID prefix. Matches:\n ${resolution.matches.join("\n ")}\nPlease provide a more specific prefix.`);
1085
+ process.exit(EXIT_CODES.VALIDATION_FAILED);
1086
+ }
1087
+ }
1088
+ const sessionId = resolution.id;
1089
+ // Get session detail
1090
+ const detail = await getSessionLogDetail(ctx.specDir, sessionId);
1091
+ if (!detail) {
1092
+ error(`Session not found: ${sessionId}`);
1093
+ process.exit(EXIT_CODES.NOT_FOUND);
1094
+ }
1095
+ // AC: @session-log-show ac-3, ac-4, ac-5 - Event timeline
1096
+ let events = null;
1097
+ if (options.events) {
1098
+ let allEvents = await readEvents(ctx.specDir, sessionId);
1099
+ // AC: @session-log-show ac-4 - Filter by type
1100
+ if (options.type) {
1101
+ const typeFilter = options.type;
1102
+ allEvents = allEvents.filter((e) => e.type === typeFilter);
1103
+ }
1104
+ // AC: @session-log-show ac-5 - Limit to last N events
1105
+ if (options.limit) {
1106
+ const limit = parseInt(options.limit, 10);
1107
+ if (!Number.isNaN(limit) && limit > 0) {
1108
+ allEvents = allEvents.slice(-limit);
1109
+ }
1110
+ }
1111
+ events = allEvents;
1112
+ }
1113
+ // AC: @session-log-show ac-6 - Context snapshot
1114
+ let contextSnapshot = null;
1115
+ if (options.context) {
1116
+ const iterNum = parseInt(options.context, 10);
1117
+ if (!Number.isNaN(iterNum) && iterNum > 0) {
1118
+ contextSnapshot = await readSessionContext(ctx.specDir, sessionId, iterNum);
1119
+ if (contextSnapshot === null) {
1120
+ error(`No context snapshot found for iteration ${iterNum}`);
1121
+ process.exit(EXIT_CODES.NOT_FOUND);
1122
+ }
1123
+ }
1124
+ else {
1125
+ error(`Invalid iteration number: ${options.context}`);
1126
+ process.exit(EXIT_CODES.USAGE_ERROR);
1127
+ }
1128
+ }
1129
+ const sessionStartTs = new Date(detail.started_at).getTime();
1130
+ // Build JSON output structure
1131
+ const jsonOutput = {
1132
+ ...detail,
1133
+ ...(events !== null ? { events } : {}),
1134
+ ...(contextSnapshot !== null ? { context: contextSnapshot } : {}),
1135
+ };
1136
+ output(jsonOutput, () => formatSessionLogShow(detail, events, contextSnapshot, sessionStartTs));
1137
+ }
1138
+ catch (err) {
1139
+ error("Failed to show session log", err);
1140
+ process.exit(EXIT_CODES.ERROR);
1141
+ }
1142
+ }
1143
+ /**
1144
+ * Format a duration in milliseconds to human-readable format.
1145
+ * Reuses formatDuration from session log list but handles hours/minutes/seconds.
1146
+ */
1147
+ function formatDurationLong(ms) {
1148
+ if (ms < 0)
1149
+ return "—";
1150
+ const totalSec = Math.floor(ms / 1000);
1151
+ const hours = Math.floor(totalSec / 3600);
1152
+ const minutes = Math.floor((totalSec % 3600) / 60);
1153
+ const seconds = totalSec % 60;
1154
+ if (hours > 0 && minutes > 0) {
1155
+ return `${hours}h ${minutes}m`;
1156
+ }
1157
+ if (hours > 0) {
1158
+ return `${hours}h`;
1159
+ }
1160
+ if (minutes > 0) {
1161
+ return `${minutes}m ${seconds}s`;
1162
+ }
1163
+ return `${seconds}s`;
1164
+ }
1165
+ /**
1166
+ * Format the session log stats output.
1167
+ *
1168
+ * AC: @session-log-stats ac-1, ac-2, ac-3
1169
+ */
1170
+ function formatSessionLogStats(stats, toolUsage, timePeriods, groupBy) {
1171
+ // AC: @session-log-stats ac-1 - Totals
1172
+ console.log(chalk.bold("Session Statistics"));
1173
+ console.log(chalk.gray("─".repeat(50)));
1174
+ console.log(` Total Sessions: ${stats.total_sessions}`);
1175
+ console.log(` Total Events: ${stats.total_events}`);
1176
+ console.log(` Total Iterations: ${stats.total_iterations}`);
1177
+ console.log(` Tasks Completed: ${stats.total_tasks_completed}`);
1178
+ console.log(` Total Duration: ${formatDurationLong(stats.total_duration_ms)}`);
1179
+ // AC: @session-log-stats ac-2 - Averages
1180
+ console.log("\n" + chalk.bold("Averages"));
1181
+ console.log(chalk.gray("─".repeat(50)));
1182
+ console.log(` Avg Duration/Session: ${formatDurationLong(stats.avg_duration_ms)}`);
1183
+ console.log(` Avg Iterations/Session: ${stats.avg_iterations_per_session}`);
1184
+ console.log(` Avg Tasks/Session: ${stats.avg_tasks_per_session}`);
1185
+ // AC: @session-log-stats ac-3 - Status breakdown
1186
+ if (stats.status_breakdown.length > 0) {
1187
+ console.log("\n" + chalk.bold("Status Breakdown"));
1188
+ console.log(chalk.gray("─".repeat(50)));
1189
+ for (const item of stats.status_breakdown) {
1190
+ const statusColor = item.status === "completed"
1191
+ ? chalk.green
1192
+ : item.status === "active"
1193
+ ? chalk.blue
1194
+ : chalk.yellow;
1195
+ console.log(` ${statusColor(item.status.padEnd(12))} ${String(item.count).padEnd(6)} ${item.percentage}%`);
1196
+ }
1197
+ }
1198
+ // AC: @session-log-stats ac-6 - Tool usage
1199
+ if (toolUsage !== null && toolUsage.length > 0) {
1200
+ console.log("\n" + chalk.bold("Top Tool Usage"));
1201
+ console.log(chalk.gray("─".repeat(50)));
1202
+ for (const tool of toolUsage) {
1203
+ console.log(` ${tool.tool_name.padEnd(20)} ${String(tool.count).padEnd(8)} ${tool.percentage}%`);
1204
+ }
1205
+ }
1206
+ // AC: @session-log-stats ac-7 - Time periods
1207
+ if (timePeriods !== null && timePeriods.length > 0) {
1208
+ const label = groupBy === "week" ? "By Week" : "By Day";
1209
+ console.log("\n" + chalk.bold(label));
1210
+ console.log(chalk.gray("─".repeat(50)));
1211
+ console.log(chalk.gray(` ${"Period".padEnd(14)} ${"Sessions".padEnd(10)} ${"Tasks".padEnd(8)} Duration`));
1212
+ for (const period of timePeriods) {
1213
+ console.log(` ${period.period.padEnd(14)} ${String(period.sessions_count).padEnd(10)} ${String(period.tasks_completed).padEnd(8)} ${formatDurationLong(period.total_duration_ms)}`);
1214
+ }
1215
+ }
1216
+ }
1217
+ /**
1218
+ * Session log stats action handler.
1219
+ */
1220
+ async function sessionLogStatsAction(options) {
1221
+ try {
1222
+ const ctx = await initContext();
1223
+ let sessions = await getAllSessionLogSummaries(ctx.specDir);
1224
+ // AC: @session-log-stats ac-4 - Filter by since
1225
+ if (options.since) {
1226
+ const sinceDate = parseTimeSpec(options.since);
1227
+ if (sinceDate) {
1228
+ sessions = sessions.filter((s) => new Date(s.started_at) >= sinceDate);
1229
+ }
1230
+ }
1231
+ // AC: @session-log-stats ac-5 - Filter by agent type
1232
+ if (options.agent) {
1233
+ const agentFilter = options.agent;
1234
+ sessions = sessions.filter((s) => s.agent_type === agentFilter);
1235
+ }
1236
+ // AC: @session-log-stats ac-8 - No sessions match criteria
1237
+ if (sessions.length === 0) {
1238
+ output({ message: "No sessions match criteria" }, () => {
1239
+ console.log("No sessions match criteria.");
1240
+ });
1241
+ return;
1242
+ }
1243
+ // Compute base stats
1244
+ const stats = computeSessionLogStats(sessions);
1245
+ // AC: @session-log-stats ac-6 - Tool usage (optional)
1246
+ let toolUsage = null;
1247
+ if (options.toolUsage) {
1248
+ const sessionIds = sessions.map((s) => s.id);
1249
+ toolUsage = await computeToolUsageStats(ctx.specDir, sessionIds);
1250
+ }
1251
+ // AC: @session-log-stats ac-7 - Time periods (optional)
1252
+ let timePeriods = null;
1253
+ let groupBy = null;
1254
+ if (options.byDay) {
1255
+ groupBy = "day";
1256
+ timePeriods = computeTimePeriodStats(sessions, "day");
1257
+ }
1258
+ else if (options.byWeek) {
1259
+ groupBy = "week";
1260
+ timePeriods = computeTimePeriodStats(sessions, "week");
1261
+ }
1262
+ // Build output structure
1263
+ const jsonOutput = { stats };
1264
+ if (toolUsage !== null) {
1265
+ jsonOutput.tool_usage = toolUsage;
1266
+ }
1267
+ if (timePeriods !== null) {
1268
+ jsonOutput.time_periods = timePeriods;
1269
+ }
1270
+ output(jsonOutput, () => formatSessionLogStats(stats, toolUsage, timePeriods, groupBy));
1271
+ }
1272
+ catch (err) {
1273
+ error("Failed to compute session log stats", err);
1274
+ process.exit(EXIT_CODES.ERROR);
1275
+ }
1276
+ }
1277
+ /**
1278
+ * Format relative timestamp from event timestamp (Unix ms) to session start.
1279
+ */
1280
+ function formatSearchTimestamp(eventTs) {
1281
+ return new Date(eventTs).toISOString();
1282
+ }
1283
+ /**
1284
+ * Format the session log search output.
1285
+ *
1286
+ * AC: @session-log-search ac-1, ac-4
1287
+ */
1288
+ function formatSessionLogSearch(results) {
1289
+ if (results.length === 0) {
1290
+ // AC: @session-log-search ac-6
1291
+ console.log("No matches found.");
1292
+ return;
1293
+ }
1294
+ let totalMatches = 0;
1295
+ for (const session of results) {
1296
+ totalMatches += session.matches.length;
1297
+ }
1298
+ console.log(chalk.bold(`Found ${totalMatches} match(es) in ${results.length} session(s)`));
1299
+ console.log(chalk.gray("─".repeat(60)));
1300
+ for (const session of results) {
1301
+ // Session header
1302
+ console.log(`\n${chalk.cyan(`Session ${session.session_id.slice(0, 8)}`)} ` +
1303
+ `${chalk.gray(`(${session.agent_type}, started ${formatRelativeTime(new Date(session.started_at))})`)}`);
1304
+ // AC: @session-log-search ac-4 - Show matches with session ID, timestamp, type, excerpt
1305
+ for (const match of session.matches) {
1306
+ const ts = formatSearchTimestamp(match.timestamp);
1307
+ const typeColor = match.event_type === "session.start" || match.event_type === "session.end"
1308
+ ? chalk.green
1309
+ : match.event_type === "session.update"
1310
+ ? chalk.blue
1311
+ : chalk.gray;
1312
+ console.log(` ${chalk.yellow(ts)} ${typeColor(match.event_type.padEnd(16))}`);
1313
+ // Content excerpt on next line, indented
1314
+ console.log(` ${chalk.gray(match.content_excerpt)}`);
1315
+ }
1316
+ }
1317
+ }
1318
+ /**
1319
+ * Session log search action handler.
1320
+ *
1321
+ * AC: @session-log-search ac-1 through ac-7
1322
+ */
1323
+ async function sessionLogSearchAction(pattern, options) {
1324
+ try {
1325
+ const ctx = await initContext();
1326
+ // Parse options - validate limit as positive integer
1327
+ let limit = 50;
1328
+ if (options.limit) {
1329
+ const parsed = parseInt(options.limit, 10);
1330
+ if (Number.isNaN(parsed) || parsed <= 0) {
1331
+ error(`Invalid limit: ${options.limit}. Must be a positive integer.`);
1332
+ process.exit(EXIT_CODES.USAGE_ERROR);
1333
+ }
1334
+ limit = parsed;
1335
+ }
1336
+ const sinceDate = options.since ? parseTimeSpec(options.since) : undefined;
1337
+ // AC: @session-log-search ac-1, ac-2, ac-3, ac-5, ac-7
1338
+ const results = await searchSessionEvents(ctx.specDir, pattern, {
1339
+ eventType: options.type,
1340
+ sinceDate: sinceDate || undefined,
1341
+ agentType: options.agent,
1342
+ limit,
1343
+ });
1344
+ // AC: @session-log-search ac-6 - No matches found message
1345
+ // exit code 0 regardless (per @trait-semantic-exit-codes ac-5)
1346
+ output(results, () => formatSessionLogSearch(results));
1347
+ }
1348
+ catch (err) {
1349
+ error("Failed to search session logs", err);
1350
+ process.exit(EXIT_CODES.ERROR);
1351
+ }
1352
+ }
708
1353
  /**
709
1354
  * Register the 'session' command group and aliases
710
1355
  */
711
1356
  export function registerSessionCommands(program) {
712
1357
  const session = program
713
- .command('session')
714
- .description('Session management and context');
1358
+ .command("session")
1359
+ .description("Session management and context");
715
1360
  session
716
- .command('start')
717
- .alias('resume')
718
- .description('Surface relevant context for starting a new working session')
719
- .option('--brief', 'Compact summary (default)')
720
- .option('--full', 'Comprehensive context dump')
721
- .option('--since <time>', 'Filter by recency (ISO8601 or relative: 1h, 2d, 1w)')
722
- .option('--no-git', 'Skip git commit information')
723
- .option('-n, --limit <n>', 'Limit items per section', '10')
1361
+ .command("start")
1362
+ .alias("resume")
1363
+ .description("Surface relevant context for starting a new working session")
1364
+ .option("--brief", "Compact summary (default)")
1365
+ .option("--full", "Comprehensive context dump")
1366
+ .option("--since <time>", "Filter by recency (ISO8601 or relative: 1h, 2d, 1w)")
1367
+ .option("--no-git", "Skip git commit information")
1368
+ .option("-n, --limit <n>", "Limit items per section", "10")
724
1369
  .action(sessionStartAction);
1370
+ // Session log subcommand group
1371
+ const log = session
1372
+ .command("log")
1373
+ .description("Session log analysis commands");
1374
+ log
1375
+ .command("list")
1376
+ .description("List session logs with summary statistics")
1377
+ .option("-s, --status <status>", "Filter by status (active, completed, abandoned)")
1378
+ .option("--agent <type>", "Filter by agent type")
1379
+ .option("--since <time>", "Only show sessions started after this time (ISO8601 or relative: 1h, 2d, 1w)")
1380
+ .option("--sort <field>", "Sort by field (started_at, duration, events, iterations, tasks_completed)", "started_at")
1381
+ .option("--count", "Show only the count of matching sessions")
1382
+ .option("-n, --limit <n>", "Limit number of sessions shown")
1383
+ .action(sessionLogListAction);
1384
+ log
1385
+ .command("show <session-id>")
1386
+ .description("Show detailed view of a single session")
1387
+ .option("-e, --events", "Include chronological event timeline")
1388
+ .option("-t, --type <type>", "Filter events by type (e.g., tool.call)")
1389
+ .option("-n, --limit <n>", "Show only the last N events")
1390
+ .option("-c, --context <n>", "Show context snapshot for iteration N")
1391
+ .action(sessionLogShowAction);
1392
+ log
1393
+ .command("stats")
1394
+ .description("Aggregate analytics across sessions")
1395
+ .option("--since <time>", "Only include sessions started after this time (ISO8601 or relative: 1h, 2d, 1w)")
1396
+ .option("--agent <type>", "Only include sessions with this agent type")
1397
+ .option("--tool-usage", "Display top 10 tool calls by frequency")
1398
+ .option("--by-day", "Group stats by day")
1399
+ .option("--by-week", "Group stats by week")
1400
+ .action(sessionLogStatsAction);
1401
+ log
1402
+ .command("search <pattern>")
1403
+ .description("Search across session events by content")
1404
+ .option("-t, --type <type>", "Only search events of this type (e.g., session.update)")
1405
+ .option("--since <time>", "Only search sessions started after this time (ISO8601 or relative: 1h, 2d, 1w)")
1406
+ .option("--agent <type>", "Only search sessions with this agent type")
1407
+ .option("-n, --limit <n>", "Maximum matches to return (default: 50)")
1408
+ .action(sessionLogSearchAction);
725
1409
  session
726
- .command('checkpoint')
727
- .description('Pre-stop hook: check for uncommitted work before ending session')
728
- .option('--force', 'Allow session end regardless of issues')
1410
+ .command("checkpoint")
1411
+ .description("Pre-stop hook: check for uncommitted work before ending session")
1412
+ .option("--force", "Allow session end regardless of issues")
729
1413
  .action(sessionCheckpointAction);
730
1414
  session
731
- .command('prompt-check')
732
- .description('UserPromptSubmit hook: inject spec-first reminder')
1415
+ .command("prompt-check")
1416
+ .description("UserPromptSubmit hook: inject spec-first reminder")
733
1417
  .action(sessionPromptCheckAction);
734
1418
  // Top-level alias: kspec context
735
1419
  program
736
- .command('context')
737
- .description('Alias for session start - surface session context')
738
- .option('--brief', 'Compact summary (default)')
739
- .option('--full', 'Comprehensive context dump')
740
- .option('--since <time>', 'Filter by recency (ISO8601 or relative: 1h, 2d, 1w)')
741
- .option('--no-git', 'Skip git commit information')
742
- .option('-n, --limit <n>', 'Limit items per section', '10')
1420
+ .command("context")
1421
+ .description("Alias for session start - surface session context")
1422
+ .option("--brief", "Compact summary (default)")
1423
+ .option("--full", "Comprehensive context dump")
1424
+ .option("--since <time>", "Filter by recency (ISO8601 or relative: 1h, 2d, 1w)")
1425
+ .option("--no-git", "Skip git commit information")
1426
+ .option("-n, --limit <n>", "Limit items per section", "10")
743
1427
  .action(sessionStartAction);
744
1428
  }
745
1429
  //# sourceMappingURL=session.js.map