@kynetic-ai/spec 0.1.2 → 0.4.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 (540) 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 +107 -0
  32. package/dist/cli/batch-exec.d.ts.map +1 -0
  33. package/dist/cli/batch-exec.js +706 -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 +141 -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 +58 -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 +534 -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 +547 -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 +1109 -170
  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 +810 -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 +1267 -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 +56 -0
  156. package/dist/cli/commands/skill-install.d.ts.map +1 -0
  157. package/dist/cli/commands/skill-install.js +509 -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 +584 -350
  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 +225 -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 +483 -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 +237 -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 +402 -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 +457 -0
  309. package/dist/parser/config.d.ts.map +1 -0
  310. package/dist/parser/config.js +373 -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 +197 -0
  345. package/dist/parser/plan-document.d.ts.map +1 -0
  346. package/dist/parser/plan-document.js +341 -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 +301 -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 +94 -0
  401. package/dist/ralph/subagent.d.ts.map +1 -0
  402. package/dist/ralph/subagent.js +193 -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 +97 -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 +8 -2
  413. package/dist/schema/common.d.ts.map +1 -1
  414. package/dist/schema/common.js +42 -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 +241 -1
  448. package/dist/sessions/store.d.ts.map +1 -1
  449. package/dist/sessions/store.js +810 -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 +55 -0
  456. package/dist/strings/errors.d.ts.map +1 -1
  457. package/dist/strings/errors.js +138 -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/triage/actions.d.ts +27 -0
  474. package/dist/triage/actions.d.ts.map +1 -0
  475. package/dist/triage/actions.js +95 -0
  476. package/dist/triage/actions.js.map +1 -0
  477. package/dist/triage/constants.d.ts +6 -0
  478. package/dist/triage/constants.d.ts.map +1 -0
  479. package/dist/triage/constants.js +7 -0
  480. package/dist/triage/constants.js.map +1 -0
  481. package/dist/triage/index.d.ts +3 -0
  482. package/dist/triage/index.d.ts.map +1 -0
  483. package/dist/triage/index.js +3 -0
  484. package/dist/triage/index.js.map +1 -0
  485. package/dist/utils/commit.d.ts +1 -1
  486. package/dist/utils/commit.d.ts.map +1 -1
  487. package/dist/utils/commit.js +28 -26
  488. package/dist/utils/commit.js.map +1 -1
  489. package/dist/utils/git.d.ts +1 -1
  490. package/dist/utils/git.d.ts.map +1 -1
  491. package/dist/utils/git.js +40 -38
  492. package/dist/utils/git.js.map +1 -1
  493. package/dist/utils/grep.js +11 -11
  494. package/dist/utils/grep.js.map +1 -1
  495. package/dist/utils/index.d.ts +7 -7
  496. package/dist/utils/index.d.ts.map +1 -1
  497. package/dist/utils/index.js +4 -4
  498. package/dist/utils/index.js.map +1 -1
  499. package/dist/utils/time.d.ts.map +1 -1
  500. package/dist/utils/time.js +10 -10
  501. package/dist/utils/time.js.map +1 -1
  502. package/package.json +28 -5
  503. package/plugin/.claude-plugin/marketplace.json +17 -0
  504. package/plugin/.claude-plugin/plugin.json +5 -0
  505. package/plugin/plugins/kspec/skills/create-workflow/SKILL.md +235 -0
  506. package/plugin/plugins/kspec/skills/help/SKILL.md +42 -0
  507. package/plugin/plugins/kspec/skills/observations/SKILL.md +143 -0
  508. package/plugin/plugins/kspec/skills/plan/SKILL.md +343 -0
  509. package/plugin/plugins/kspec/skills/reflect/SKILL.md +161 -0
  510. package/plugin/plugins/kspec/skills/review/SKILL.md +193 -0
  511. package/plugin/plugins/kspec/skills/task-work/SKILL.md +303 -0
  512. package/plugin/plugins/kspec/skills/triage/SKILL.md +206 -0
  513. package/plugin/plugins/kspec/skills/triage/docs/automation.md +120 -0
  514. package/plugin/plugins/kspec/skills/triage/docs/inbox.md +144 -0
  515. package/plugin/plugins/kspec/skills/triage/docs/observations.md +85 -0
  516. package/plugin/plugins/kspec/skills/triage-automation/SKILL.md +140 -0
  517. package/plugin/plugins/kspec/skills/triage-inbox/SKILL.md +232 -0
  518. package/plugin/plugins/kspec/skills/writing-specs/SKILL.md +340 -0
  519. package/templates/agents-sections/01-quick-start.md +22 -0
  520. package/templates/agents-sections/02-shadow-branch.md +34 -0
  521. package/templates/agents-sections/03-task-lifecycle.md +48 -0
  522. package/templates/agents-sections/04-pr-workflow.md +17 -0
  523. package/templates/agents-sections/05-commit-convention.md +27 -0
  524. package/templates/agents-sections/06-ralph-loop.md +45 -0
  525. package/templates/hooks/pre-commit +34 -0
  526. package/templates/skills/create-workflow/SKILL.md +228 -0
  527. package/templates/skills/help/SKILL.md +37 -0
  528. package/templates/skills/manifest.yaml +60 -0
  529. package/templates/skills/observations/SKILL.md +137 -0
  530. package/templates/skills/plan/SKILL.md +336 -0
  531. package/templates/skills/reflect/SKILL.md +155 -0
  532. package/templates/skills/review/SKILL.md +186 -0
  533. package/templates/skills/task-work/SKILL.md +296 -0
  534. package/templates/skills/triage/SKILL.md +199 -0
  535. package/templates/skills/triage/docs/automation.md +120 -0
  536. package/templates/skills/triage/docs/inbox.md +144 -0
  537. package/templates/skills/triage/docs/observations.md +85 -0
  538. package/templates/skills/triage-automation/SKILL.md +134 -0
  539. package/templates/skills/triage-inbox/SKILL.md +225 -0
  540. package/templates/skills/writing-specs/SKILL.md +333 -0
@@ -0,0 +1,408 @@
1
+ /**
2
+ * Kspec Daemon Server
3
+ *
4
+ * Elysia.js HTTP server with WebSocket support for real-time kspec state updates.
5
+ * Implements localhost-only security, file watching, and graceful shutdown.
6
+ */
7
+
8
+ import { Elysia } from 'elysia';
9
+ import { cors } from '@elysiajs/cors';
10
+ import { staticPlugin } from '@elysiajs/static';
11
+ import { ulid } from 'ulidx';
12
+ import { existsSync } from 'fs';
13
+ import { PubSubManager } from './websocket/pubsub';
14
+ import { HeartbeatManager } from './websocket/heartbeat';
15
+ import { WebSocketHandler } from './websocket/handler';
16
+ import type { ConnectionData, ConnectedEvent } from './websocket/types';
17
+ import { PidFileManager } from './pid';
18
+ import { projectContextMiddleware } from './middleware/project-context';
19
+ import { createTasksRoutes } from './routes/tasks';
20
+ import { createItemsRoutes } from './routes/items';
21
+ import { createInboxRoutes } from './routes/inbox';
22
+ import { createMetaRoutes } from './routes/meta';
23
+ import { createValidationRoutes } from './routes/validation';
24
+ import { createProjectsRoutes } from './routes/projects';
25
+ import { createTriageRoutes } from './routes/triage';
26
+ import { join } from 'path';
27
+
28
+ export interface ServerOptions {
29
+ port: number;
30
+ isDaemon: boolean;
31
+ kspecDir?: string; // Path to .kspec directory (default: .kspec in cwd)
32
+ webUiDir?: string; // Path to web UI build directory (default: auto-detect)
33
+ }
34
+
35
+ /**
36
+ * Resolves the path to the web UI build directory.
37
+ * Tries multiple locations in order:
38
+ * 1. Explicit webUiDir option
39
+ * 2. WEB_UI_DIR environment variable
40
+ * 3. packages/web-ui/build in current working directory (monorepo dev)
41
+ * 4. web-ui/build in current working directory
42
+ */
43
+ function resolveWebUiPath(webUiDir?: string): string | null {
44
+ // 1. Explicit option
45
+ if (webUiDir && existsSync(webUiDir)) {
46
+ return webUiDir;
47
+ }
48
+
49
+ // 2. Environment variable
50
+ const envPath = process.env.WEB_UI_DIR;
51
+ if (envPath && existsSync(envPath)) {
52
+ return envPath;
53
+ }
54
+
55
+ // 3. Monorepo development: packages/web-ui/build from cwd
56
+ // The daemon is spawned with cwd set to project root
57
+ const monorepoPath = join(process.cwd(), 'packages', 'web-ui', 'build');
58
+ if (existsSync(monorepoPath)) {
59
+ return monorepoPath;
60
+ }
61
+
62
+ // 4. Alternate location: web-ui/build in cwd
63
+ const altPath = join(process.cwd(), 'web-ui', 'build');
64
+ if (existsSync(altPath)) {
65
+ return altPath;
66
+ }
67
+
68
+ return null;
69
+ }
70
+
71
+ // WebSocket pub/sub and heartbeat managers
72
+ let pubsubManager: PubSubManager;
73
+ let heartbeatManager: HeartbeatManager;
74
+ let wsHandler: WebSocketHandler;
75
+ let projectManager: import('./project-context').ProjectContextManager | undefined;
76
+
77
+ /**
78
+ * Middleware to enforce localhost-only connections.
79
+ * AC-3: Reject non-localhost connections with 403 Forbidden
80
+ */
81
+ function localhostOnly() {
82
+ return (context: { request: Request }) => {
83
+ const host = context.request.headers.get('host');
84
+ if (!host) {
85
+ return new Response(JSON.stringify({
86
+ error: 'Forbidden',
87
+ message: 'This server only accepts connections from localhost'
88
+ }), {
89
+ status: 403,
90
+ headers: { 'Content-Type': 'application/json' }
91
+ });
92
+ }
93
+
94
+ // Extract hostname, handling IPv6 brackets
95
+ let hostname: string;
96
+ if (host.startsWith('[')) {
97
+ // IPv6 with brackets: [::1]:3456 -> ::1
98
+ const closeBracket = host.indexOf(']');
99
+ hostname = closeBracket > 0 ? host.substring(1, closeBracket) : host;
100
+ } else {
101
+ // IPv4 or hostname: localhost:3456 -> localhost
102
+ hostname = host.split(':')[0];
103
+ }
104
+
105
+ // Allow localhost, 127.0.0.1, and ::1
106
+ const isLocalhost =
107
+ hostname === 'localhost' ||
108
+ hostname === '127.0.0.1' ||
109
+ hostname === '::1';
110
+
111
+ if (!isLocalhost) {
112
+ return new Response(JSON.stringify({
113
+ error: 'Forbidden',
114
+ message: 'This server only accepts connections from localhost'
115
+ }), {
116
+ status: 403,
117
+ headers: { 'Content-Type': 'application/json' }
118
+ });
119
+ }
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Creates and configures the Elysia server instance.
125
+ *
126
+ * AC Coverage:
127
+ * - ac-1: Server starts on configurable port (default 3456)
128
+ * - ac-2: Binds to localhost only (127.0.0.1 and ::1)
129
+ * - ac-3: Rejects non-localhost connections with 403
130
+ * - ac-15: Uses plugin pattern for middleware
131
+ */
132
+ export async function createServer(options: ServerOptions) {
133
+ const { port, isDaemon, kspecDir = join(process.cwd(), '.kspec'), webUiDir } = options;
134
+
135
+ // Determine startup project path (project root, not .kspec/)
136
+ // AC: @multi-directory-daemon ac-2 - daemon uses startup directory as default project
137
+ const startupProjectPath = kspecDir.endsWith('.kspec')
138
+ ? kspecDir.slice(0, -('.kspec'.length + 1)) // Remove '/.kspec'
139
+ : kspecDir;
140
+
141
+ // Import ProjectContextManager (needed for WebSocket binding)
142
+ const { ProjectContextManager } = await import('./project-context');
143
+
144
+ // AC: @daemon-server ac-17 - Resolve web UI path for static file serving
145
+ const resolvedWebUiPath = resolveWebUiPath(webUiDir);
146
+ if (resolvedWebUiPath) {
147
+ console.log(`[daemon] Web UI assets found at: ${resolvedWebUiPath}`);
148
+ } else {
149
+ console.log('[daemon] Web UI assets not found - UI will not be served');
150
+ console.log('[daemon] Build the web UI with: cd packages/web-ui && npm run build');
151
+ }
152
+
153
+ // Initialize PID file manager (uses global ~/.config/kspec/)
154
+ const pidManager = new PidFileManager();
155
+
156
+ // AC: @multi-directory-daemon ac-9 - Write PID and port files in daemon mode
157
+ if (isDaemon) {
158
+ pidManager.writePid();
159
+ pidManager.writePort(port);
160
+ console.log(`[daemon] PID file written: ${process.pid}`);
161
+ console.log(`[daemon] Port file written: ${port}`);
162
+ }
163
+
164
+ // Initialize WebSocket managers
165
+ pubsubManager = new PubSubManager();
166
+ heartbeatManager = new HeartbeatManager();
167
+ wsHandler = new WebSocketHandler(pubsubManager);
168
+
169
+ // WeakMap to store project path during WebSocket upgrade
170
+ const wsProjectPaths = new Map<string, string>();
171
+
172
+ const app = new Elysia()
173
+ // AC-15: Plugin pattern for middleware
174
+ // AC: @api-contract ac-1 - Allow CORS from dev server on localhost:5173
175
+ .use(cors({
176
+ origin: ['http://localhost:5173', 'http://127.0.0.1:5173'], // Dev server origins
177
+ credentials: true,
178
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
179
+ }))
180
+
181
+ // AC-3: Enforce localhost-only connections
182
+ .onRequest(localhostOnly());
183
+
184
+ // AC: @multi-directory-daemon ac-1, ac-2, ac-3 - Project context middleware
185
+ const { manager: projectContextManager, middleware: projectMiddleware } = projectContextMiddleware({
186
+ startupProject: startupProjectPath,
187
+ pubsub: pubsubManager
188
+ });
189
+
190
+ // Store manager globally for shutdown
191
+ projectManager = projectContextManager;
192
+
193
+ app.use(projectMiddleware)
194
+
195
+ // AC-11: Health check endpoint
196
+ .get('/api/health', () => ({
197
+ status: 'ok',
198
+ uptime: process.uptime(),
199
+ connections: pubsubManager.getConnectionCount(),
200
+ version: '0.1.0'
201
+ }))
202
+
203
+ // AC: @api-contract ac-2 through ac-7 - Task API endpoints
204
+ // AC: @multi-directory-daemon ac-24 - Routes use projectContext from middleware
205
+ .use(createTasksRoutes({ pubsub: pubsubManager }))
206
+
207
+ // AC: @api-contract ac-8 through ac-11 - Spec Item API endpoints
208
+ .use(createItemsRoutes())
209
+
210
+ // AC: @api-contract ac-12 through ac-14 - Inbox API endpoints
211
+ .use(createInboxRoutes({ pubsub: pubsubManager }))
212
+
213
+ // AC: @api-contract ac-15 through ac-18 - Meta API endpoints
214
+ .use(createMetaRoutes())
215
+
216
+ // AC: @triage-daemon-api ac-1 through ac-9 - Triage API endpoints
217
+ .use(createTriageRoutes({ pubsub: pubsubManager }))
218
+
219
+ // AC: @api-contract ac-19 through ac-21 - Validation and search endpoints
220
+ .use(createValidationRoutes())
221
+
222
+ // AC: @multi-directory-daemon ac-28, ac-29, ac-30 - Projects management endpoints
223
+ .use(createProjectsRoutes({ projectManager: projectContextManager }))
224
+
225
+ // AC-4: WebSocket endpoint for real-time updates
226
+ .ws<ConnectionData>('/ws', {
227
+ beforeHandle({ request, store }) {
228
+ // AC: @multi-directory-daemon ac-21, ac-22, ac-23, ac-34 - Extract and validate project binding
229
+ // AC: @multi-directory-daemon ac-34 - Browser WebSocket API doesn't support custom headers,
230
+ // so we also accept project path as query parameter
231
+ const url = new URL(request.url, `http://${request.headers.get('host')}`);
232
+ const projectPath = request.headers.get('X-Kspec-Dir')
233
+ || url.searchParams.get('project')
234
+ || undefined;
235
+ const requestId = ulid(); // Temporary ID to correlate upgrade with open
236
+
237
+ try {
238
+ const manager = (store as Record<string, unknown>).projectManager as import('./project-context').ProjectContextManager | undefined;
239
+ if (!manager) {
240
+ // Fallback: project manager not initialized yet
241
+ wsProjectPaths.set(requestId, startupProjectPath);
242
+ return { wsRequestId: requestId };
243
+ }
244
+
245
+ let projectContext;
246
+ if (projectPath) {
247
+ // Explicit project specified
248
+ try {
249
+ projectContext = manager.getProject(projectPath);
250
+ } catch {
251
+ // AC: @multi-directory-daemon ac-4 - auto-register
252
+ projectContext = manager.registerProject(projectPath);
253
+ }
254
+ } else {
255
+ // AC: @multi-directory-daemon ac-22, ac-23 - Use default or reject
256
+ try {
257
+ projectContext = manager.getProject();
258
+ } catch (err: unknown) {
259
+ // AC: @multi-directory-daemon ac-23 - Reject when no default
260
+ if (err instanceof Error && err.message.includes('No default project configured')) {
261
+ throw new Error('No project specified');
262
+ }
263
+ throw err;
264
+ }
265
+ }
266
+
267
+ // Store resolved path for open() handler
268
+ wsProjectPaths.set(requestId, projectContext.path);
269
+ return { wsRequestId: requestId };
270
+ } catch (err: unknown) {
271
+ console.error(`[daemon] WebSocket connection rejected: ${err instanceof Error ? err.message : String(err)}`);
272
+ throw err;
273
+ }
274
+ },
275
+ open(ws) {
276
+ // AC: @api-contract ac-25, @trait-websocket-protocol ac-1
277
+ const sessionId = ulid();
278
+
279
+ // AC: @multi-directory-daemon ac-21 - Get bound project path
280
+ // Fallback to startup project if not found (shouldn't happen)
281
+ const requestId = (ws.data as ConnectionData & { wsRequestId?: string }).wsRequestId;
282
+ const projectPath = requestId ? wsProjectPaths.get(requestId) || startupProjectPath : startupProjectPath;
283
+
284
+ // Clean up temporary mapping
285
+ if (requestId) {
286
+ wsProjectPaths.delete(requestId);
287
+ }
288
+
289
+ ws.data = {
290
+ sessionId,
291
+ topics: new Set<string>(),
292
+ seq: 0,
293
+ lastPing: undefined,
294
+ lastPong: Date.now(),
295
+ projectPath // AC: @multi-directory-daemon ac-21 - immutable binding
296
+ };
297
+
298
+ pubsubManager.addConnection(sessionId, ws);
299
+ console.log(`[daemon] WebSocket client connected: ${sessionId} bound to ${projectPath} (${pubsubManager.getConnectionCount()} total)`);
300
+
301
+ // Send connected event with session_id
302
+ const connectedEvent: ConnectedEvent = {
303
+ event: 'connected',
304
+ data: {
305
+ session_id: sessionId
306
+ }
307
+ };
308
+ ws.send(JSON.stringify(connectedEvent));
309
+ },
310
+ message(ws, message) {
311
+ // AC: @api-contract ac-26, ac-27
312
+ wsHandler.handleMessage(ws, message);
313
+ },
314
+ pong(ws) {
315
+ // AC: @trait-websocket-protocol ac-5
316
+ heartbeatManager.recordPong(ws);
317
+ },
318
+ close(ws, code, reason) {
319
+ pubsubManager.removeConnection(ws.data.sessionId);
320
+ console.log(`[daemon] WebSocket client disconnected: ${ws.data.sessionId} (code: ${code}, reason: ${reason})`);
321
+ }
322
+ });
323
+
324
+ // AC: @daemon-server ac-17 - Serve web UI static assets
325
+ // Added after API routes so API routes take precedence
326
+ if (resolvedWebUiPath) {
327
+ const indexHtmlPath = join(resolvedWebUiPath, 'index.html');
328
+
329
+ // Serve static files from web UI build directory
330
+ app.use(await staticPlugin({
331
+ assets: resolvedWebUiPath,
332
+ prefix: '/',
333
+ noCache: process.env.NODE_ENV === 'development', // Disable cache in dev
334
+ }));
335
+
336
+ // SPA fallback routes for client-side routing
337
+ // These catch paths like /tasks, /items, /inbox that don't have static files
338
+ const spaRoutes = ['/tasks', '/tasks/*', '/items', '/items/*', '/inbox', '/observations', '/triage'];
339
+ for (const route of spaRoutes) {
340
+ app.get(route, () => Bun.file(indexHtmlPath));
341
+ }
342
+
343
+ console.log('[daemon] Web UI static file serving enabled');
344
+ }
345
+
346
+ // AC-1, AC-2: Start server on localhost only
347
+ // Using 'localhost' hostname allows Bun/OS to bind to both 127.0.0.1 and ::1
348
+ app.listen({
349
+ port,
350
+ hostname: 'localhost', // Resolves to both IPv4 and IPv6 loopback
351
+ });
352
+
353
+ console.log(`[daemon] Server listening on http://localhost:${port} (IPv4: 127.0.0.1, IPv6: ::1)`);
354
+ console.log(`[daemon] WebSocket available at ws://localhost:${port}/ws`);
355
+
356
+ // AC: @multi-directory-daemon ac-17 - Start file watcher for startup project
357
+ if (startupProjectPath) {
358
+ try {
359
+ await projectContextManager.startWatcher(startupProjectPath);
360
+ console.log(`[daemon] File watcher started for startup project: ${startupProjectPath}`);
361
+ } catch (error) {
362
+ console.error('[daemon] Failed to start file watcher for startup project:', error);
363
+ }
364
+ }
365
+
366
+ // AC: @daemon-server ac-13, ac-14 - Start heartbeat monitoring
367
+ heartbeatManager.start(pubsubManager.getAllConnections());
368
+
369
+ // AC-12: Graceful shutdown on SIGTERM/SIGINT
370
+ const shutdown = async (signal: string) => {
371
+ console.log(`[daemon] Received ${signal}, shutting down gracefully...`);
372
+
373
+ try {
374
+ // Stop heartbeat monitoring
375
+ heartbeatManager.stop();
376
+
377
+ // AC: @multi-directory-daemon ac-11b - Stop all file watchers
378
+ await projectContextManager.stopAllWatchers();
379
+ console.log('[daemon] All file watchers stopped');
380
+
381
+ // Close all WebSocket connections with code 1000 (clean close)
382
+ // AC: @trait-websocket-protocol ac-7
383
+ for (const [sessionId, ws] of pubsubManager.getAllConnections()) {
384
+ ws.close(1000, 'Server shutting down');
385
+ }
386
+
387
+ // Stop the server
388
+ await app.server?.stop();
389
+
390
+ // AC: @daemon-server ac-10 - Remove PID file on shutdown
391
+ if (isDaemon) {
392
+ pidManager.remove();
393
+ console.log('[daemon] PID file removed');
394
+ }
395
+
396
+ console.log('[daemon] Server stopped successfully');
397
+ process.exit(0);
398
+ } catch (error) {
399
+ console.error('[daemon] Error during shutdown:', error);
400
+ process.exit(1);
401
+ }
402
+ };
403
+
404
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
405
+ process.on('SIGINT', () => shutdown('SIGINT'));
406
+
407
+ return app;
408
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * File watcher for .kspec directory
3
+ *
4
+ * AC Coverage:
5
+ * - ac-4: Watch .kspec/*.yaml files and broadcast changes
6
+ * - ac-5: Debounce rapid changes (500ms)
7
+ * - ac-6: Handle YAML parse errors gracefully
8
+ * - ac-7: Recovery with exponential backoff for directory access errors
9
+ * - ac-8: Fallback to Chokidar if Bun fs.watch fails
10
+ */
11
+
12
+ import { watch, type FSWatcher } from 'fs';
13
+ import { readFile } from 'fs/promises';
14
+ import { parse as parseYaml } from 'yaml';
15
+ import chokidar, { type FSWatcher as ChokidarWatcher } from 'chokidar';
16
+ import { join } from 'path';
17
+
18
+ export interface WatcherOptions {
19
+ kspecDir: string;
20
+ onFileChange: (file: string, content: string) => void;
21
+ onError: (error: Error, file?: string) => void;
22
+ }
23
+
24
+ export interface WatcherEvent {
25
+ type: 'change' | 'error';
26
+ file: string;
27
+ content?: string;
28
+ error?: string;
29
+ }
30
+
31
+ /**
32
+ * File watcher with debouncing and error handling
33
+ */
34
+ export class KspecWatcher {
35
+ private watcher: FSWatcher | ChokidarWatcher | null = null;
36
+ private debounceTimers = new Map<string, NodeJS.Timeout>();
37
+ private debounceMs = 500;
38
+ private usingChokidar = false;
39
+ private retryCount = 0;
40
+ private maxRetries = 5;
41
+ private baseBackoffMs = 1000;
42
+
43
+ constructor(private options: WatcherOptions) {}
44
+
45
+ /**
46
+ * AC-4, AC-8: Start watching .kspec directory (with Chokidar fallback)
47
+ */
48
+ async start(): Promise<void> {
49
+ try {
50
+ // Try Bun's native fs.watch first
51
+ await this.startBunWatcher();
52
+ } catch (error) {
53
+ console.warn('[watcher] Bun fs.watch failed, falling back to Chokidar', error);
54
+ // AC-8: Fallback to Chokidar
55
+ this.usingChokidar = true;
56
+ await this.startChokidarWatcher();
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Start Bun's native file watcher
62
+ */
63
+ private async startBunWatcher(): Promise<void> {
64
+ this.watcher = watch(
65
+ this.options.kspecDir,
66
+ { recursive: true },
67
+ (eventType, filename) => {
68
+ if (!filename || !filename.endsWith('.yaml')) return;
69
+
70
+ const fullPath = join(this.options.kspecDir, filename);
71
+ this.handleFileChange(fullPath);
72
+ }
73
+ );
74
+
75
+ console.log('[watcher] Watching .kspec directory with Bun fs.watch');
76
+ }
77
+
78
+ /**
79
+ * AC-8: Start Chokidar watcher as fallback
80
+ */
81
+ private async startChokidarWatcher(): Promise<void> {
82
+ this.watcher = chokidar.watch(join(this.options.kspecDir, '**/*.yaml'), {
83
+ ignoreInitial: true,
84
+ awaitWriteFinish: {
85
+ stabilityThreshold: 100,
86
+ pollInterval: 50
87
+ }
88
+ });
89
+
90
+ (this.watcher as ChokidarWatcher).on('change', (path: string) => {
91
+ this.handleFileChange(path);
92
+ });
93
+
94
+ (this.watcher as ChokidarWatcher).on('error', (err: unknown) => {
95
+ // AC-7: Recovery with exponential backoff
96
+ this.handleWatcherError(err instanceof Error ? err : new Error(String(err)));
97
+ });
98
+
99
+ console.log('[watcher] Watching .kspec directory with Chokidar');
100
+ }
101
+
102
+ /**
103
+ * AC-5: Debounce file changes (500ms)
104
+ */
105
+ private handleFileChange(filePath: string): void {
106
+ // Clear existing timer for this file
107
+ const existingTimer = this.debounceTimers.get(filePath);
108
+ if (existingTimer) {
109
+ clearTimeout(existingTimer);
110
+ }
111
+
112
+ // Set new debounced timer
113
+ const timer = setTimeout(async () => {
114
+ this.debounceTimers.delete(filePath);
115
+ await this.processFileChange(filePath);
116
+ }, this.debounceMs);
117
+
118
+ this.debounceTimers.set(filePath, timer);
119
+ }
120
+
121
+ /**
122
+ * AC-4, AC-6: Process file change and broadcast to clients
123
+ */
124
+ private async processFileChange(filePath: string): Promise<void> {
125
+ try {
126
+ const content = await readFile(filePath, 'utf-8');
127
+
128
+ // AC-6: Validate YAML before broadcasting
129
+ try {
130
+ parseYaml(content);
131
+ this.options.onFileChange(filePath, content);
132
+ this.retryCount = 0; // Reset retry count on success
133
+ } catch (parseError) {
134
+ // AC-6: Log parse error and broadcast error event
135
+ const error = new Error(`YAML parse error in ${filePath}: ${parseError}`);
136
+ console.error('[watcher]', error.message);
137
+ this.options.onError(error, filePath);
138
+ }
139
+ } catch (error) {
140
+ // AC-7: Handle file read errors (directory inaccessible, etc.)
141
+ console.error('[watcher] Error reading file:', error);
142
+ this.handleWatcherError(error as Error);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * AC-7: Handle watcher errors with exponential backoff
148
+ */
149
+ private async handleWatcherError(error: Error): Promise<void> {
150
+ this.options.onError(error);
151
+
152
+ if (this.retryCount >= this.maxRetries) {
153
+ console.error('[watcher] Max retries reached, giving up');
154
+ return;
155
+ }
156
+
157
+ this.retryCount++;
158
+ const backoffMs = this.baseBackoffMs * Math.pow(2, this.retryCount - 1);
159
+
160
+ console.log(`[watcher] Attempting recovery in ${backoffMs}ms (attempt ${this.retryCount}/${this.maxRetries})`);
161
+
162
+ setTimeout(async () => {
163
+ try {
164
+ await this.stop();
165
+ await this.start();
166
+ console.log('[watcher] Recovery successful');
167
+ } catch (retryError) {
168
+ console.error('[watcher] Recovery failed:', retryError);
169
+ // Will retry again if under max retries
170
+ this.handleWatcherError(retryError as Error);
171
+ }
172
+ }, backoffMs);
173
+ }
174
+
175
+ /**
176
+ * Stop watching
177
+ */
178
+ async stop(): Promise<void> {
179
+ // Clear all debounce timers
180
+ for (const timer of this.debounceTimers.values()) {
181
+ clearTimeout(timer);
182
+ }
183
+ this.debounceTimers.clear();
184
+
185
+ // Close watcher
186
+ if (this.watcher) {
187
+ if (this.usingChokidar) {
188
+ await (this.watcher as ChokidarWatcher).close();
189
+ } else {
190
+ (this.watcher as FSWatcher).close();
191
+ }
192
+ this.watcher = null;
193
+ }
194
+ }
195
+ }