@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,126 @@
1
+ /**
2
+ * Project Context Middleware for Multi-Directory Daemon
3
+ *
4
+ * Extracts X-Kspec-Dir header and attaches ProjectContext to request state.
5
+ * Implements path validation and automatic project registration.
6
+ *
7
+ * AC: @multi-directory-daemon ac-1, ac-2, ac-3, ac-4, ac-5, ac-6, ac-7, ac-8, ac-8b, ac-8c, ac-20b
8
+ */
9
+
10
+ import type { Elysia } from 'elysia';
11
+ import { ProjectContextManager, type ProjectContext } from '../project-context';
12
+ import type { PubSubManager } from '../websocket/pubsub';
13
+
14
+ export interface ProjectContextMiddlewareOptions {
15
+ /**
16
+ * Optional startup project path (daemon's cwd at boot if it has .kspec/)
17
+ */
18
+ startupProject?: string;
19
+ /**
20
+ * PubSubManager for broadcasting file changes
21
+ */
22
+ pubsub?: PubSubManager;
23
+ }
24
+
25
+ /**
26
+ * Creates project context middleware plugin for Elysia.
27
+ *
28
+ * Extracts X-Kspec-Dir header, validates path, registers/retrieves project,
29
+ * and attaches ProjectContext to request state.
30
+ *
31
+ * Returns both the manager (for external access) and the middleware function.
32
+ */
33
+ export function projectContextMiddleware(options: ProjectContextMiddlewareOptions = {}) {
34
+ const manager = new ProjectContextManager(options.startupProject, options.pubsub);
35
+
36
+ // Register startup project if provided
37
+ // Note: Watcher will be started later after full initialization
38
+ if (options.startupProject) {
39
+ try {
40
+ manager.registerProject(options.startupProject, true);
41
+ } catch (error) {
42
+ console.warn(`[daemon] Failed to register startup project: ${error}`);
43
+ }
44
+ }
45
+
46
+ const middleware = (app: Elysia) =>
47
+ app
48
+ // Store manager in app state for WebSocket access
49
+ .state('projectManager', manager)
50
+ .derive(async ({ request, set }) => {
51
+ try {
52
+ // AC: @multi-directory-daemon ac-1 - Extract X-Kspec-Dir header
53
+ const projectPath = request.headers.get('X-Kspec-Dir') || undefined;
54
+
55
+ let projectContext: ProjectContext;
56
+
57
+ if (projectPath) {
58
+ // AC: @multi-directory-daemon ac-1, ac-4, ac-5, ac-6, ac-7, ac-8, ac-8b, ac-8c
59
+ // Try to get existing or register new project
60
+ try {
61
+ projectContext = manager.getProject(projectPath);
62
+ } catch (err) {
63
+ // Not registered - try to register (ac-4: auto-register)
64
+ projectContext = manager.registerProject(projectPath);
65
+ // Start watcher asynchronously (don't block request)
66
+ void manager.startWatcher(projectPath).catch((watcherError) => {
67
+ console.error(`[daemon] Failed to start watcher for ${projectPath}:`, watcherError);
68
+ });
69
+ }
70
+ } else {
71
+ // AC: @multi-directory-daemon ac-2, ac-3, ac-20b
72
+ // No header - use default project
73
+ projectContext = manager.getProject();
74
+ }
75
+
76
+ return { projectContext };
77
+ } catch (err: unknown) {
78
+ const message = err instanceof Error ? err.message : String(err);
79
+
80
+ // AC: @multi-directory-daemon ac-3, ac-20b
81
+ if (
82
+ message.includes('No default project configured') ||
83
+ message.includes('Default project no longer valid')
84
+ ) {
85
+ set.status = 400;
86
+ return { error: message };
87
+ }
88
+
89
+ // AC: @multi-directory-daemon ac-5
90
+ if (message.includes('Invalid kspec project')) {
91
+ set.status = 400;
92
+ return { error: message };
93
+ }
94
+
95
+ // AC: @multi-directory-daemon ac-6
96
+ if (message.includes('Path must be absolute')) {
97
+ set.status = 400;
98
+ return { error: 'Path must be absolute' };
99
+ }
100
+
101
+ // AC: @multi-directory-daemon ac-7
102
+ if (message.includes('Path must not contain parent traversal')) {
103
+ set.status = 400;
104
+ return { error: 'Path must not contain parent traversal' };
105
+ }
106
+
107
+ // AC: @multi-directory-daemon ac-8b - permission denied
108
+ if (message.includes('Permission denied')) {
109
+ set.status = 403;
110
+ return { error: message };
111
+ }
112
+
113
+ // AC: @multi-directory-daemon ac-19 - OS resource limits
114
+ if (message.includes('Unable to watch project - resource limit reached')) {
115
+ set.status = 503;
116
+ return { error: message };
117
+ }
118
+
119
+ // Other errors
120
+ set.status = 500;
121
+ return { error: 'Internal server error' };
122
+ }
123
+ });
124
+
125
+ return { manager, middleware };
126
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * PID File Management
3
+ *
4
+ * Manages daemon process PID and port files for lifecycle control.
5
+ * Uses global config directory (~/.config/kspec/) instead of per-project .kspec/
6
+ * AC: @multi-directory-daemon ac-9, ac-9b, ac-9c, ac-10, ac-11, ac-13
7
+ */
8
+
9
+ import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync, openSync, closeSync, constants } from 'fs';
10
+ import { join } from 'path';
11
+ import { homedir } from 'os';
12
+
13
+ export class PidFileManager {
14
+ private configDir: string;
15
+ private pidFilePath: string;
16
+ private portFilePath: string;
17
+
18
+ constructor(configDir: string = join(homedir(), '.config', 'kspec')) {
19
+ this.configDir = configDir;
20
+ this.pidFilePath = join(configDir, 'daemon.pid');
21
+ this.portFilePath = join(configDir, 'daemon.port');
22
+ }
23
+
24
+ /**
25
+ * AC: @multi-directory-daemon ac-9b
26
+ * Creates config directory with mode 0755 if it doesn't exist
27
+ */
28
+ private ensureConfigDir(): void {
29
+ if (!existsSync(this.configDir)) {
30
+ mkdirSync(this.configDir, { recursive: true, mode: 0o755 });
31
+ }
32
+ }
33
+
34
+ /**
35
+ * AC: @multi-directory-daemon ac-9, ac-10b
36
+ * Writes current process PID to ~/.config/kspec/daemon.pid
37
+ * Creates parent directory if it doesn't exist.
38
+ * Uses exclusive file creation flag to prevent concurrent daemon starts.
39
+ */
40
+ writePid(): void {
41
+ this.ensureConfigDir();
42
+
43
+ // AC: @multi-directory-daemon ac-10b
44
+ // Use O_CREAT | O_EXCL flags for atomic file creation
45
+ // This prevents race conditions between concurrent daemon starts
46
+ try {
47
+ const fd = openSync(this.pidFilePath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o644);
48
+ try {
49
+ writeFileSync(fd, process.pid.toString(), 'utf-8');
50
+ } finally {
51
+ closeSync(fd);
52
+ }
53
+ } catch (err: unknown) {
54
+ if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'EEXIST') {
55
+ // File already exists - check if daemon is actually running
56
+ if (this.isDaemonRunning()) {
57
+ throw new Error('Daemon already running');
58
+ }
59
+ // Stale PID file - remove it and retry
60
+ this.remove();
61
+ const fd = openSync(this.pidFilePath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o644);
62
+ try {
63
+ writeFileSync(fd, process.pid.toString(), 'utf-8');
64
+ } finally {
65
+ closeSync(fd);
66
+ }
67
+ } else {
68
+ throw err;
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * AC: @multi-directory-daemon ac-9
75
+ * Writes daemon port to ~/.config/kspec/daemon.port
76
+ * Creates parent directory if it doesn't exist.
77
+ */
78
+ writePort(port: number): void {
79
+ this.ensureConfigDir();
80
+ writeFileSync(this.portFilePath, port.toString(), 'utf-8');
81
+ }
82
+
83
+ /**
84
+ * Reads PID from ~/.config/kspec/daemon.pid
85
+ * Returns null if file doesn't exist or is invalid
86
+ */
87
+ readPid(): number | null {
88
+ if (!existsSync(this.pidFilePath)) {
89
+ return null;
90
+ }
91
+
92
+ try {
93
+ const content = readFileSync(this.pidFilePath, 'utf-8').trim();
94
+ const pid = parseInt(content, 10);
95
+ return isNaN(pid) ? null : pid;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * AC: @multi-directory-daemon ac-9c, ac-13
103
+ * Reads port from ~/.config/kspec/daemon.port
104
+ * Throws error if file doesn't exist or contains invalid port
105
+ */
106
+ readPort(): number {
107
+ if (!existsSync(this.portFilePath)) {
108
+ throw new Error('Invalid daemon port file');
109
+ }
110
+
111
+ try {
112
+ const content = readFileSync(this.portFilePath, 'utf-8').trim();
113
+ const port = parseInt(content, 10);
114
+
115
+ // AC: @multi-directory-daemon ac-9c - validate port content
116
+ if (isNaN(port) || port < 1 || port > 65535) {
117
+ throw new Error('Invalid daemon port file');
118
+ }
119
+
120
+ return port;
121
+ } catch (err) {
122
+ throw new Error('Invalid daemon port file');
123
+ }
124
+ }
125
+
126
+ /**
127
+ * AC: @multi-directory-daemon ac-11
128
+ * Removes both PID and port files during graceful shutdown
129
+ */
130
+ remove(): void {
131
+ if (existsSync(this.pidFilePath)) {
132
+ unlinkSync(this.pidFilePath);
133
+ }
134
+ if (existsSync(this.portFilePath)) {
135
+ unlinkSync(this.portFilePath);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Checks if a process with given PID is running
141
+ */
142
+ isProcessRunning(pid: number): boolean {
143
+ try {
144
+ // Sending signal 0 checks if process exists without actually sending a signal
145
+ process.kill(pid, 0);
146
+ return true;
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * AC: @multi-directory-daemon ac-10
154
+ * Checks if daemon is currently running based on PID file
155
+ */
156
+ isDaemonRunning(): boolean {
157
+ const pid = this.readPid();
158
+ if (pid === null) {
159
+ return false;
160
+ }
161
+ return this.isProcessRunning(pid);
162
+ }
163
+
164
+ /**
165
+ * Backwards compatibility: read() method maps to readPid()
166
+ * @deprecated Use readPid() instead
167
+ */
168
+ read(): number | null {
169
+ return this.readPid();
170
+ }
171
+
172
+ /**
173
+ * Backwards compatibility: write() method maps to writePid()
174
+ * @deprecated Use writePid() instead
175
+ */
176
+ write(): void {
177
+ this.writePid();
178
+ }
179
+ }
@@ -0,0 +1,343 @@
1
+ /**
2
+ * ProjectContextManager - Multi-project daemon support
3
+ *
4
+ * Manages project registration, caching, path validation, and context management
5
+ * for multi-directory daemon architecture.
6
+ *
7
+ * AC: @multi-directory-daemon ac-1 through ac-20b
8
+ */
9
+
10
+ import { existsSync } from 'fs';
11
+ import { isAbsolute, join, normalize, relative } from 'path';
12
+ import { KspecWatcher } from './watcher';
13
+ import type { PubSubManager } from './websocket/pubsub';
14
+
15
+ export interface ProjectContext {
16
+ path: string;
17
+ registeredAt: Date;
18
+ watcherActive: boolean;
19
+ }
20
+
21
+ /**
22
+ * Manages multiple kspec project contexts for the daemon server.
23
+ *
24
+ * Key responsibilities:
25
+ * - Project registration and caching
26
+ * - Path validation and normalization
27
+ * - Default project handling
28
+ * - Project lifecycle management
29
+ * - Per-project file watcher management
30
+ */
31
+ export class ProjectContextManager {
32
+ private projects: Map<string, ProjectContext> = new Map();
33
+ private watchers: Map<string, KspecWatcher> = new Map();
34
+ private defaultProjectPath: string | null = null;
35
+ private pubsub: PubSubManager | null = null;
36
+
37
+ constructor(defaultProjectPath?: string, pubsub?: PubSubManager) {
38
+ if (defaultProjectPath) {
39
+ this.defaultProjectPath = defaultProjectPath;
40
+ }
41
+ if (pubsub) {
42
+ this.pubsub = pubsub;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Set the PubSubManager for broadcasting file changes.
48
+ * Must be called before starting watchers.
49
+ *
50
+ * @param pubsub - PubSubManager instance
51
+ */
52
+ setPubSub(pubsub: PubSubManager): void {
53
+ this.pubsub = pubsub;
54
+ }
55
+
56
+ /**
57
+ * Start a file watcher for a project.
58
+ *
59
+ * AC: @multi-directory-daemon ac-17, ac-19
60
+ *
61
+ * @param projectPath - Absolute path to project root
62
+ * @throws Error if watcher creation fails (e.g., OS resource limits)
63
+ */
64
+ async startWatcher(projectPath: string): Promise<void> {
65
+ const normalizedPath = this.normalizePath(projectPath);
66
+
67
+ // AC: @multi-directory-daemon ac-16 - Don't create duplicate watchers
68
+ if (this.watchers.has(normalizedPath)) {
69
+ return; // Watcher already running
70
+ }
71
+
72
+ const kspecDir = join(normalizedPath, '.kspec');
73
+
74
+ try {
75
+ // AC: @multi-directory-daemon ac-17, ac-18 - Create watcher with project-scoped broadcasts
76
+ const watcher = new KspecWatcher({
77
+ kspecDir,
78
+ onFileChange: (file, content) => {
79
+ // AC: @multi-directory-daemon ac-17 - File changes trigger events scoped to project
80
+ if (this.pubsub) {
81
+ const relativePath = relative(kspecDir, file);
82
+ this.pubsub.broadcast('files:updates', 'file_changed', {
83
+ ref: relativePath,
84
+ action: 'modified'
85
+ }, normalizedPath);
86
+ }
87
+ },
88
+ onError: (error, file) => {
89
+ // Broadcast error event scoped to project
90
+ if (this.pubsub) {
91
+ const relativePath = file ? relative(kspecDir, file) : undefined;
92
+ this.pubsub.broadcast('files:errors', 'file_error', {
93
+ ref: relativePath,
94
+ error: error.message
95
+ }, normalizedPath);
96
+ }
97
+ }
98
+ });
99
+
100
+ await watcher.start();
101
+ this.watchers.set(normalizedPath, watcher);
102
+
103
+ // Update context
104
+ const context = this.projects.get(normalizedPath);
105
+ if (context) {
106
+ context.watcherActive = true;
107
+ }
108
+ } catch (error: unknown) {
109
+ // AC: @multi-directory-daemon ac-19 - Handle OS limits (EMFILE/ENFILE)
110
+ const code = error instanceof Error && 'code' in error ? (error as NodeJS.ErrnoException).code : undefined;
111
+ if (code === 'EMFILE' || code === 'ENFILE') {
112
+ throw new Error('Unable to watch project - resource limit reached');
113
+ }
114
+ throw error;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Stop a file watcher for a project.
120
+ *
121
+ * AC: @multi-directory-daemon ac-20, ac-11b
122
+ *
123
+ * @param projectPath - Absolute path to project root
124
+ */
125
+ async stopWatcher(projectPath: string): Promise<void> {
126
+ const normalizedPath = this.normalizePath(projectPath);
127
+ const watcher = this.watchers.get(normalizedPath);
128
+
129
+ if (watcher) {
130
+ await watcher.stop();
131
+ this.watchers.delete(normalizedPath);
132
+
133
+ // Update context
134
+ const context = this.projects.get(normalizedPath);
135
+ if (context) {
136
+ context.watcherActive = false;
137
+ }
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Stop all file watchers.
143
+ *
144
+ * AC: @multi-directory-daemon ac-11b - Shutdown stops all watchers
145
+ */
146
+ async stopAllWatchers(): Promise<void> {
147
+ const stopPromises = Array.from(this.watchers.keys()).map(path =>
148
+ this.stopWatcher(path)
149
+ );
150
+ await Promise.all(stopPromises);
151
+ }
152
+
153
+ /**
154
+ * Register a project for multi-directory daemon support.
155
+ *
156
+ * AC: @multi-directory-daemon ac-4, ac-5, ac-6, ac-7, ac-8, ac-8c
157
+ *
158
+ * Note: This method is synchronous. Start watchers separately via startWatcher().
159
+ *
160
+ * @param projectPath - Absolute path to project root directory
161
+ * @param isDefault - Whether this project should be the default
162
+ * @returns Registered project context
163
+ * @throws Error if path validation fails or .kspec/ not found
164
+ */
165
+ registerProject(projectPath: string, isDefault = false): ProjectContext {
166
+ // AC: @multi-directory-daemon ac-6 - reject relative paths
167
+ if (!this.isAbsolutePath(projectPath)) {
168
+ throw new Error('Path must be absolute');
169
+ }
170
+
171
+ // AC: @multi-directory-daemon ac-7 - reject parent traversal
172
+ if (projectPath.includes('..')) {
173
+ throw new Error('Path must not contain parent traversal');
174
+ }
175
+
176
+ // AC: @multi-directory-daemon ac-8 - normalize path (but don't resolve symlinks)
177
+ const normalizedPath = this.normalizePath(projectPath);
178
+
179
+ // AC: @multi-directory-daemon ac-5 - validate .kspec/ exists
180
+ const kspecDir = join(normalizedPath, '.kspec');
181
+ if (!existsSync(kspecDir)) {
182
+ throw new Error(`Invalid kspec project - .kspec/ not found at ${normalizedPath}`);
183
+ }
184
+
185
+ // AC: @multi-directory-daemon ac-16 - check if already registered (avoid duplicates)
186
+ if (this.projects.has(normalizedPath)) {
187
+ const existing = this.projects.get(normalizedPath)!;
188
+ if (isDefault) {
189
+ this.defaultProjectPath = normalizedPath;
190
+ }
191
+ return existing;
192
+ }
193
+
194
+ // AC: @multi-directory-daemon ac-4 - auto-register and cache
195
+ const context: ProjectContext = {
196
+ path: normalizedPath,
197
+ registeredAt: new Date(),
198
+ watcherActive: false, // Set to true when watcher is started
199
+ };
200
+
201
+ this.projects.set(normalizedPath, context);
202
+
203
+ if (isDefault) {
204
+ this.defaultProjectPath = normalizedPath;
205
+ }
206
+
207
+ return context;
208
+ }
209
+
210
+ /**
211
+ * Get a project by path, or use default project if no path provided.
212
+ *
213
+ * AC: @multi-directory-daemon ac-1, ac-2, ac-3, ac-20b
214
+ *
215
+ * @param projectPath - Optional absolute path to project
216
+ * @returns Project context
217
+ * @throws Error if project not registered, no default, or default invalid
218
+ */
219
+ getProject(projectPath?: string): ProjectContext {
220
+ // AC: @multi-directory-daemon ac-1 - use provided path
221
+ if (projectPath) {
222
+ const normalizedPath = this.normalizePath(projectPath);
223
+ const context = this.projects.get(normalizedPath);
224
+ if (!context) {
225
+ throw new Error(`Project not registered: ${normalizedPath}`);
226
+ }
227
+ return context;
228
+ }
229
+
230
+ // AC: @multi-directory-daemon ac-2, ac-3 - use default or error
231
+ if (!this.defaultProjectPath) {
232
+ throw new Error('No default project configured. Specify X-Kspec-Dir header.');
233
+ }
234
+
235
+ // AC: @multi-directory-daemon ac-20b - check if default project still valid
236
+ const kspecDir = join(this.defaultProjectPath, '.kspec');
237
+ if (!existsSync(kspecDir)) {
238
+ throw new Error('Default project no longer valid. Specify X-Kspec-Dir header.');
239
+ }
240
+
241
+ const context = this.projects.get(this.defaultProjectPath);
242
+ if (!context) {
243
+ throw new Error('Default project not registered');
244
+ }
245
+
246
+ return context;
247
+ }
248
+
249
+ /**
250
+ * Set the default project explicitly.
251
+ *
252
+ * AC: @multi-directory-daemon ac-2
253
+ *
254
+ * @param projectPath - Absolute path to project
255
+ * @throws Error if project not registered
256
+ */
257
+ setDefaultProject(projectPath: string): void {
258
+ const normalizedPath = this.normalizePath(projectPath);
259
+ if (!this.projects.has(normalizedPath)) {
260
+ throw new Error('Project must be registered before setting as default');
261
+ }
262
+ this.defaultProjectPath = normalizedPath;
263
+ }
264
+
265
+ /**
266
+ * Check if a project is registered.
267
+ *
268
+ * @param projectPath - Absolute path to project
269
+ * @returns True if project is registered
270
+ */
271
+ hasProject(projectPath: string): boolean {
272
+ const normalizedPath = this.normalizePath(projectPath);
273
+ return this.projects.has(normalizedPath);
274
+ }
275
+
276
+ /**
277
+ * Unregister a project and stop its watcher.
278
+ *
279
+ * AC: @multi-directory-daemon ac-20
280
+ *
281
+ * @param projectPath - Absolute path to project
282
+ */
283
+ unregisterProject(projectPath: string): void {
284
+ const normalizedPath = this.normalizePath(projectPath);
285
+
286
+ // AC: @multi-directory-daemon ac-20 - Stop watcher when unregistering (async, fire-and-forget)
287
+ void this.stopWatcher(normalizedPath);
288
+
289
+ this.projects.delete(normalizedPath);
290
+
291
+ if (this.defaultProjectPath === normalizedPath) {
292
+ this.defaultProjectPath = null;
293
+ }
294
+ }
295
+
296
+ /**
297
+ * List all registered projects.
298
+ *
299
+ * AC: @multi-directory-daemon ac-14, ac-15
300
+ *
301
+ * @returns Array of registered project contexts
302
+ */
303
+ listProjects(): ProjectContext[] {
304
+ return Array.from(this.projects.values());
305
+ }
306
+
307
+ /**
308
+ * Normalize path without resolving symlinks.
309
+ *
310
+ * AC: @multi-directory-daemon ac-8, ac-8c
311
+ *
312
+ * Normalizes the path by:
313
+ * - Resolving "." segments
314
+ * - Removing trailing slashes
315
+ * - Normalizing multiple slashes
316
+ * - NOT resolving symlinks (symlinked paths treated as separate projects)
317
+ *
318
+ * @param projectPath - Path to normalize
319
+ * @returns Normalized path
320
+ */
321
+ private normalizePath(projectPath: string): string {
322
+ // Remove trailing slashes and resolve "." segments
323
+ // But do NOT resolve symlinks (no realpath/fs.realpathSync)
324
+ let normalized = normalize(projectPath);
325
+
326
+ // Remove trailing slash (normalize doesn't always do this)
327
+ if (normalized !== '/' && normalized.endsWith('/')) {
328
+ normalized = normalized.slice(0, -1);
329
+ }
330
+
331
+ return normalized;
332
+ }
333
+
334
+ /**
335
+ * Check if path is absolute.
336
+ *
337
+ * @param projectPath - Path to check
338
+ * @returns True if path is absolute
339
+ */
340
+ private isAbsolutePath(projectPath: string): boolean {
341
+ return isAbsolute(projectPath);
342
+ }
343
+ }