@kynetic-ai/spec 0.10.0 → 0.12.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 (487) hide show
  1. package/README.md +55 -455
  2. package/dist/agent-runtime/bootstrap.d.ts +31 -0
  3. package/dist/agent-runtime/bootstrap.d.ts.map +1 -0
  4. package/dist/agent-runtime/bootstrap.js +302 -0
  5. package/dist/agent-runtime/bootstrap.js.map +1 -0
  6. package/dist/agent-runtime/dispatch.d.ts +150 -10
  7. package/dist/agent-runtime/dispatch.d.ts.map +1 -1
  8. package/dist/agent-runtime/dispatch.js +1248 -244
  9. package/dist/agent-runtime/dispatch.js.map +1 -1
  10. package/dist/agent-runtime/invocation.d.ts +28 -1
  11. package/dist/agent-runtime/invocation.d.ts.map +1 -1
  12. package/dist/agent-runtime/invocation.js +172 -60
  13. package/dist/agent-runtime/invocation.js.map +1 -1
  14. package/dist/agent-runtime/prompts.d.ts +9 -0
  15. package/dist/agent-runtime/prompts.d.ts.map +1 -1
  16. package/dist/agent-runtime/prompts.js +42 -7
  17. package/dist/agent-runtime/prompts.js.map +1 -1
  18. package/dist/agent-runtime/session-event-accumulator.d.ts +83 -0
  19. package/dist/agent-runtime/session-event-accumulator.d.ts.map +1 -0
  20. package/dist/agent-runtime/session-event-accumulator.js +203 -0
  21. package/dist/agent-runtime/session-event-accumulator.js.map +1 -0
  22. package/dist/agent-runtime/session-event-types.d.ts +67 -0
  23. package/dist/agent-runtime/session-event-types.d.ts.map +1 -0
  24. package/dist/agent-runtime/session-event-types.js +13 -0
  25. package/dist/agent-runtime/session-event-types.js.map +1 -0
  26. package/dist/agent-runtime/workspace.d.ts +244 -0
  27. package/dist/agent-runtime/workspace.d.ts.map +1 -0
  28. package/dist/agent-runtime/workspace.js +2025 -0
  29. package/dist/agent-runtime/workspace.js.map +1 -0
  30. package/dist/agents/adapters.d.ts.map +1 -1
  31. package/dist/agents/adapters.js +58 -13
  32. package/dist/agents/adapters.js.map +1 -1
  33. package/dist/agents/spawner.d.ts +8 -0
  34. package/dist/agents/spawner.d.ts.map +1 -1
  35. package/dist/agents/spawner.js +25 -3
  36. package/dist/agents/spawner.js.map +1 -1
  37. package/dist/cli/batch-exec.js +1 -1
  38. package/dist/cli/batch-exec.js.map +1 -1
  39. package/dist/cli/command-annotations.d.ts +15 -3
  40. package/dist/cli/command-annotations.d.ts.map +1 -1
  41. package/dist/cli/command-annotations.js +23 -3
  42. package/dist/cli/command-annotations.js.map +1 -1
  43. package/dist/cli/commands/agent.d.ts +2 -0
  44. package/dist/cli/commands/agent.d.ts.map +1 -1
  45. package/dist/cli/commands/agent.js +144 -27
  46. package/dist/cli/commands/agent.js.map +1 -1
  47. package/dist/cli/commands/agents.d.ts.map +1 -1
  48. package/dist/cli/commands/agents.js +5 -5
  49. package/dist/cli/commands/agents.js.map +1 -1
  50. package/dist/cli/commands/derive.d.ts.map +1 -1
  51. package/dist/cli/commands/derive.js +118 -3
  52. package/dist/cli/commands/derive.js.map +1 -1
  53. package/dist/cli/commands/guard.d.ts.map +1 -1
  54. package/dist/cli/commands/guard.js +8 -6
  55. package/dist/cli/commands/guard.js.map +1 -1
  56. package/dist/cli/commands/index.d.ts +1 -0
  57. package/dist/cli/commands/index.d.ts.map +1 -1
  58. package/dist/cli/commands/index.js +1 -0
  59. package/dist/cli/commands/index.js.map +1 -1
  60. package/dist/cli/commands/init.d.ts.map +1 -1
  61. package/dist/cli/commands/init.js +20 -0
  62. package/dist/cli/commands/init.js.map +1 -1
  63. package/dist/cli/commands/item.d.ts.map +1 -1
  64. package/dist/cli/commands/item.js +205 -47
  65. package/dist/cli/commands/item.js.map +1 -1
  66. package/dist/cli/commands/log.d.ts.map +1 -1
  67. package/dist/cli/commands/log.js +24 -10
  68. package/dist/cli/commands/log.js.map +1 -1
  69. package/dist/cli/commands/meta.d.ts.map +1 -1
  70. package/dist/cli/commands/meta.js +10 -1
  71. package/dist/cli/commands/meta.js.map +1 -1
  72. package/dist/cli/commands/plan-import.d.ts +3 -3
  73. package/dist/cli/commands/plan-import.d.ts.map +1 -1
  74. package/dist/cli/commands/plan-import.js +213 -528
  75. package/dist/cli/commands/plan-import.js.map +1 -1
  76. package/dist/cli/commands/plan.d.ts.map +1 -1
  77. package/dist/cli/commands/plan.js +533 -83
  78. package/dist/cli/commands/plan.js.map +1 -1
  79. package/dist/cli/commands/review.d.ts +14 -0
  80. package/dist/cli/commands/review.d.ts.map +1 -0
  81. package/dist/cli/commands/review.js +1142 -0
  82. package/dist/cli/commands/review.js.map +1 -0
  83. package/dist/cli/commands/serve.d.ts +1 -0
  84. package/dist/cli/commands/serve.d.ts.map +1 -1
  85. package/dist/cli/commands/serve.js +33 -10
  86. package/dist/cli/commands/serve.js.map +1 -1
  87. package/dist/cli/commands/session/checkpoint.d.ts +2 -4
  88. package/dist/cli/commands/session/checkpoint.d.ts.map +1 -1
  89. package/dist/cli/commands/session/checkpoint.js +6 -107
  90. package/dist/cli/commands/session/checkpoint.js.map +1 -1
  91. package/dist/cli/commands/session/commands.d.ts.map +1 -1
  92. package/dist/cli/commands/session/commands.js +33 -23
  93. package/dist/cli/commands/session/commands.js.map +1 -1
  94. package/dist/cli/commands/session/compact.js +4 -4
  95. package/dist/cli/commands/session/compact.js.map +1 -1
  96. package/dist/cli/commands/session/create.js +2 -2
  97. package/dist/cli/commands/session/create.js.map +1 -1
  98. package/dist/cli/commands/session/format.d.ts.map +1 -1
  99. package/dist/cli/commands/session/format.js +1 -6
  100. package/dist/cli/commands/session/format.js.map +1 -1
  101. package/dist/cli/commands/session/log.d.ts +32 -7
  102. package/dist/cli/commands/session/log.d.ts.map +1 -1
  103. package/dist/cli/commands/session/log.js +166 -60
  104. package/dist/cli/commands/session/log.js.map +1 -1
  105. package/dist/cli/commands/session/migrate.d.ts +9 -0
  106. package/dist/cli/commands/session/migrate.d.ts.map +1 -0
  107. package/dist/cli/commands/session/migrate.js +46 -0
  108. package/dist/cli/commands/session/migrate.js.map +1 -0
  109. package/dist/cli/commands/session/stale-close.d.ts.map +1 -1
  110. package/dist/cli/commands/session/stale-close.js +5 -8
  111. package/dist/cli/commands/session/stale-close.js.map +1 -1
  112. package/dist/cli/commands/session/types.d.ts +1 -1
  113. package/dist/cli/commands/session/types.d.ts.map +1 -1
  114. package/dist/cli/commands/setup.d.ts +2 -2
  115. package/dist/cli/commands/setup.d.ts.map +1 -1
  116. package/dist/cli/commands/setup.js +287 -257
  117. package/dist/cli/commands/setup.js.map +1 -1
  118. package/dist/cli/commands/shadow.d.ts.map +1 -1
  119. package/dist/cli/commands/shadow.js +147 -31
  120. package/dist/cli/commands/shadow.js.map +1 -1
  121. package/dist/cli/commands/skill-crud.d.ts +7 -0
  122. package/dist/cli/commands/skill-crud.d.ts.map +1 -1
  123. package/dist/cli/commands/skill-crud.js +41 -18
  124. package/dist/cli/commands/skill-crud.js.map +1 -1
  125. package/dist/cli/commands/skill-diff.d.ts.map +1 -1
  126. package/dist/cli/commands/skill-diff.js +29 -3
  127. package/dist/cli/commands/skill-diff.js.map +1 -1
  128. package/dist/cli/commands/skill-install.d.ts.map +1 -1
  129. package/dist/cli/commands/skill-install.js +5 -4
  130. package/dist/cli/commands/skill-install.js.map +1 -1
  131. package/dist/cli/commands/task.d.ts.map +1 -1
  132. package/dist/cli/commands/task.js +359 -49
  133. package/dist/cli/commands/task.js.map +1 -1
  134. package/dist/cli/commands/trait.d.ts.map +1 -1
  135. package/dist/cli/commands/trait.js +5 -27
  136. package/dist/cli/commands/trait.js.map +1 -1
  137. package/dist/cli/commands/validate.d.ts.map +1 -1
  138. package/dist/cli/commands/validate.js +113 -52
  139. package/dist/cli/commands/validate.js.map +1 -1
  140. package/dist/cli/index.d.ts.map +1 -1
  141. package/dist/cli/index.js +69 -2
  142. package/dist/cli/index.js.map +1 -1
  143. package/dist/cli/output.d.ts +26 -0
  144. package/dist/cli/output.d.ts.map +1 -1
  145. package/dist/cli/output.js +108 -1
  146. package/dist/cli/output.js.map +1 -1
  147. package/dist/cli/sync-mode.d.ts +44 -0
  148. package/dist/cli/sync-mode.d.ts.map +1 -0
  149. package/dist/cli/sync-mode.js +64 -0
  150. package/dist/cli/sync-mode.js.map +1 -0
  151. package/dist/daemon/middleware/project-context.ts +25 -7
  152. package/dist/daemon/project-context.ts +18 -0
  153. package/dist/daemon/routes/agent-dispatch.ts +107 -23
  154. package/dist/daemon/routes/aggregation.ts +184 -0
  155. package/dist/daemon/routes/inbox.ts +5 -0
  156. package/dist/daemon/routes/items.ts +167 -0
  157. package/dist/daemon/routes/meta.ts +141 -1
  158. package/dist/daemon/routes/plans.ts +147 -0
  159. package/dist/daemon/routes/projects.ts +28 -6
  160. package/dist/daemon/routes/ref-resolution.ts +119 -0
  161. package/dist/daemon/routes/refs.ts +42 -0
  162. package/dist/daemon/routes/session-related.ts +140 -0
  163. package/dist/daemon/routes/sessions.ts +581 -0
  164. package/dist/daemon/routes/tasks.ts +257 -2
  165. package/dist/daemon/routes/triage.ts +40 -1
  166. package/dist/daemon/routes/validation.ts +1 -1
  167. package/dist/daemon/server.ts +165 -50
  168. package/dist/daemon/session-sync.ts +11 -0
  169. package/dist/daemon/shadow-sync.ts +11 -0
  170. package/dist/daemon/watcher.ts +56 -5
  171. package/dist/daemon/websocket/project-resolution.ts +77 -0
  172. package/dist/export/json.d.ts.map +1 -1
  173. package/dist/export/json.js +104 -1
  174. package/dist/export/json.js.map +1 -1
  175. package/dist/export/types.d.ts +52 -1
  176. package/dist/export/types.d.ts.map +1 -1
  177. package/dist/index.d.ts +1 -0
  178. package/dist/index.d.ts.map +1 -1
  179. package/dist/index.js +1 -0
  180. package/dist/index.js.map +1 -1
  181. package/dist/parser/agent-detection.d.ts +1 -1
  182. package/dist/parser/agent-detection.d.ts.map +1 -1
  183. package/dist/parser/agent-detection.js +10 -0
  184. package/dist/parser/agent-detection.js.map +1 -1
  185. package/dist/parser/alignment.d.ts.map +1 -1
  186. package/dist/parser/alignment.js +4 -2
  187. package/dist/parser/alignment.js.map +1 -1
  188. package/dist/parser/config.d.ts +397 -2
  189. package/dist/parser/config.d.ts.map +1 -1
  190. package/dist/parser/config.js +125 -3
  191. package/dist/parser/config.js.map +1 -1
  192. package/dist/parser/dispatch-workspaces.d.ts +18 -0
  193. package/dist/parser/dispatch-workspaces.d.ts.map +1 -0
  194. package/dist/parser/dispatch-workspaces.js +209 -0
  195. package/dist/parser/dispatch-workspaces.js.map +1 -0
  196. package/dist/parser/doctor.d.ts.map +1 -1
  197. package/dist/parser/doctor.js +27 -8
  198. package/dist/parser/doctor.js.map +1 -1
  199. package/dist/parser/file-lock.d.ts.map +1 -1
  200. package/dist/parser/file-lock.js +9 -2
  201. package/dist/parser/file-lock.js.map +1 -1
  202. package/dist/parser/index.d.ts +6 -0
  203. package/dist/parser/index.d.ts.map +1 -1
  204. package/dist/parser/index.js +6 -0
  205. package/dist/parser/index.js.map +1 -1
  206. package/dist/parser/plans.d.ts.map +1 -1
  207. package/dist/parser/plans.js +1 -0
  208. package/dist/parser/plans.js.map +1 -1
  209. package/dist/parser/refs.d.ts +8 -1
  210. package/dist/parser/refs.d.ts.map +1 -1
  211. package/dist/parser/refs.js +27 -1
  212. package/dist/parser/refs.js.map +1 -1
  213. package/dist/parser/review-operations.d.ts +72 -0
  214. package/dist/parser/review-operations.d.ts.map +1 -0
  215. package/dist/parser/review-operations.js +185 -0
  216. package/dist/parser/review-operations.js.map +1 -0
  217. package/dist/parser/review-task-integration.d.ts +78 -0
  218. package/dist/parser/review-task-integration.d.ts.map +1 -0
  219. package/dist/parser/review-task-integration.js +173 -0
  220. package/dist/parser/review-task-integration.js.map +1 -0
  221. package/dist/parser/review-threads.d.ts +101 -0
  222. package/dist/parser/review-threads.d.ts.map +1 -0
  223. package/dist/parser/review-threads.js +222 -0
  224. package/dist/parser/review-threads.js.map +1 -0
  225. package/dist/parser/review-validation.d.ts +69 -0
  226. package/dist/parser/review-validation.d.ts.map +1 -0
  227. package/dist/parser/review-validation.js +207 -0
  228. package/dist/parser/review-validation.js.map +1 -0
  229. package/dist/parser/reviews.d.ts +58 -0
  230. package/dist/parser/reviews.d.ts.map +1 -0
  231. package/dist/parser/reviews.js +230 -0
  232. package/dist/parser/reviews.js.map +1 -0
  233. package/dist/parser/session-branch.d.ts +91 -0
  234. package/dist/parser/session-branch.d.ts.map +1 -0
  235. package/dist/parser/session-branch.js +565 -0
  236. package/dist/parser/session-branch.js.map +1 -0
  237. package/dist/parser/session-sync-scheduler.d.ts +53 -0
  238. package/dist/parser/session-sync-scheduler.d.ts.map +1 -0
  239. package/dist/parser/session-sync-scheduler.js +100 -0
  240. package/dist/parser/session-sync-scheduler.js.map +1 -0
  241. package/dist/parser/setup-status.d.ts +7 -1
  242. package/dist/parser/setup-status.d.ts.map +1 -1
  243. package/dist/parser/setup-status.js +104 -39
  244. package/dist/parser/setup-status.js.map +1 -1
  245. package/dist/parser/shadow-sync-scheduler.d.ts +71 -0
  246. package/dist/parser/shadow-sync-scheduler.d.ts.map +1 -0
  247. package/dist/parser/shadow-sync-scheduler.js +139 -0
  248. package/dist/parser/shadow-sync-scheduler.js.map +1 -0
  249. package/dist/parser/shadow.d.ts +121 -14
  250. package/dist/parser/shadow.d.ts.map +1 -1
  251. package/dist/parser/shadow.js +752 -27
  252. package/dist/parser/shadow.js.map +1 -1
  253. package/dist/parser/skill-render.d.ts +24 -0
  254. package/dist/parser/skill-render.d.ts.map +1 -1
  255. package/dist/parser/skill-render.js +98 -26
  256. package/dist/parser/skill-render.js.map +1 -1
  257. package/dist/parser/validate.d.ts +43 -3
  258. package/dist/parser/validate.d.ts.map +1 -1
  259. package/dist/parser/validate.js +204 -30
  260. package/dist/parser/validate.js.map +1 -1
  261. package/dist/parser/yaml.d.ts +47 -11
  262. package/dist/parser/yaml.d.ts.map +1 -1
  263. package/dist/parser/yaml.js +329 -149
  264. package/dist/parser/yaml.js.map +1 -1
  265. package/dist/review/checks.d.ts +97 -0
  266. package/dist/review/checks.d.ts.map +1 -0
  267. package/dist/review/checks.js +175 -0
  268. package/dist/review/checks.js.map +1 -0
  269. package/dist/review/index.d.ts +3 -0
  270. package/dist/review/index.d.ts.map +1 -0
  271. package/dist/review/index.js +3 -0
  272. package/dist/review/index.js.map +1 -0
  273. package/dist/review/subject-bindings.d.ts +83 -0
  274. package/dist/review/subject-bindings.d.ts.map +1 -0
  275. package/dist/review/subject-bindings.js +175 -0
  276. package/dist/review/subject-bindings.js.map +1 -0
  277. package/dist/schema/common.d.ts +26 -0
  278. package/dist/schema/common.d.ts.map +1 -1
  279. package/dist/schema/common.js +13 -0
  280. package/dist/schema/common.js.map +1 -1
  281. package/dist/schema/dispatch-workspace.d.ts +2643 -0
  282. package/dist/schema/dispatch-workspace.d.ts.map +1 -0
  283. package/dist/schema/dispatch-workspace.js +187 -0
  284. package/dist/schema/dispatch-workspace.js.map +1 -0
  285. package/dist/schema/inbox.d.ts +8 -8
  286. package/dist/schema/index.d.ts +2 -0
  287. package/dist/schema/index.d.ts.map +1 -1
  288. package/dist/schema/index.js +2 -0
  289. package/dist/schema/index.js.map +1 -1
  290. package/dist/schema/meta.d.ts +663 -116
  291. package/dist/schema/meta.d.ts.map +1 -1
  292. package/dist/schema/meta.js +28 -0
  293. package/dist/schema/meta.js.map +1 -1
  294. package/dist/schema/plan.d.ts +30 -19
  295. package/dist/schema/plan.d.ts.map +1 -1
  296. package/dist/schema/plan.js +3 -1
  297. package/dist/schema/plan.js.map +1 -1
  298. package/dist/schema/review-records.d.ts +2676 -0
  299. package/dist/schema/review-records.d.ts.map +1 -0
  300. package/dist/schema/review-records.js +232 -0
  301. package/dist/schema/review-records.js.map +1 -0
  302. package/dist/schema/spec.d.ts +32 -14
  303. package/dist/schema/spec.d.ts.map +1 -1
  304. package/dist/schema/spec.js +5 -0
  305. package/dist/schema/spec.js.map +1 -1
  306. package/dist/schema/task.d.ts +187 -29
  307. package/dist/schema/task.d.ts.map +1 -1
  308. package/dist/schema/task.js +12 -2
  309. package/dist/schema/task.js.map +1 -1
  310. package/dist/schema/triage.d.ts +22 -22
  311. package/dist/sessions/cache.d.ts +119 -0
  312. package/dist/sessions/cache.d.ts.map +1 -0
  313. package/dist/sessions/cache.js +284 -0
  314. package/dist/sessions/cache.js.map +1 -0
  315. package/dist/sessions/index.d.ts +1 -0
  316. package/dist/sessions/index.d.ts.map +1 -1
  317. package/dist/sessions/index.js +2 -0
  318. package/dist/sessions/index.js.map +1 -1
  319. package/dist/sessions/legacy.d.ts +77 -0
  320. package/dist/sessions/legacy.d.ts.map +1 -0
  321. package/dist/sessions/legacy.js +146 -0
  322. package/dist/sessions/legacy.js.map +1 -0
  323. package/dist/sessions/store.d.ts +115 -71
  324. package/dist/sessions/store.d.ts.map +1 -1
  325. package/dist/sessions/store.js +357 -182
  326. package/dist/sessions/store.js.map +1 -1
  327. package/dist/sessions/types.d.ts +44 -16
  328. package/dist/sessions/types.d.ts.map +1 -1
  329. package/dist/sessions/types.js +11 -2
  330. package/dist/sessions/types.js.map +1 -1
  331. package/dist/strings/errors.d.ts +32 -0
  332. package/dist/strings/errors.d.ts.map +1 -1
  333. package/dist/strings/errors.js +17 -0
  334. package/dist/strings/errors.js.map +1 -1
  335. package/dist/strings/labels.d.ts +1 -0
  336. package/dist/strings/labels.d.ts.map +1 -1
  337. package/dist/strings/labels.js +1 -0
  338. package/dist/strings/labels.js.map +1 -1
  339. package/dist/utils/activity.d.ts +101 -0
  340. package/dist/utils/activity.d.ts.map +1 -0
  341. package/dist/utils/activity.js +408 -0
  342. package/dist/utils/activity.js.map +1 -0
  343. package/dist/utils/git.d.ts +31 -0
  344. package/dist/utils/git.d.ts.map +1 -1
  345. package/dist/utils/git.js +87 -0
  346. package/dist/utils/git.js.map +1 -1
  347. package/dist/utils/index.d.ts +2 -0
  348. package/dist/utils/index.d.ts.map +1 -1
  349. package/dist/utils/index.js +1 -0
  350. package/dist/utils/index.js.map +1 -1
  351. package/dist/web-ui/_app/immutable/assets/0.tmlwn-Ih.css +1 -0
  352. package/dist/web-ui/_app/immutable/assets/9.BwwJybWx.css +1 -0
  353. package/dist/web-ui/_app/immutable/chunks/2KqE8gtn.js +1 -0
  354. package/dist/web-ui/_app/immutable/chunks/70-t_QvE.js +1 -0
  355. package/dist/web-ui/_app/immutable/chunks/AiWQj974.js +1 -0
  356. package/dist/web-ui/_app/immutable/chunks/B25nWFyA.js +5 -0
  357. package/dist/web-ui/_app/immutable/chunks/B2bcA_Q_.js +1 -0
  358. package/dist/web-ui/_app/immutable/chunks/B5e5HYyB.js +1 -0
  359. package/dist/web-ui/_app/immutable/chunks/B7-5z6eA.js +1 -0
  360. package/dist/web-ui/_app/immutable/chunks/B7bGmhK0.js +1 -0
  361. package/dist/web-ui/_app/immutable/chunks/B8tYZKAE.js +1 -0
  362. package/dist/web-ui/_app/immutable/chunks/BFGAyJjD.js +1 -0
  363. package/dist/web-ui/_app/immutable/chunks/BG0850zf.js +1 -0
  364. package/dist/web-ui/_app/immutable/chunks/BG8eSzAd.js +1 -0
  365. package/dist/web-ui/_app/immutable/chunks/BIMxXS8I.js +1 -0
  366. package/dist/web-ui/_app/immutable/chunks/BSzL1fpU.js +1 -0
  367. package/dist/web-ui/_app/immutable/chunks/BYtjHfeq.js +1 -0
  368. package/dist/web-ui/_app/immutable/chunks/{D1ArdqNb.js → Bp5pFYXL.js} +1 -1
  369. package/dist/web-ui/_app/immutable/chunks/BsJFsuAT.js +1 -0
  370. package/dist/web-ui/_app/immutable/chunks/BvpNHcD6.js +1 -0
  371. package/dist/web-ui/_app/immutable/chunks/BypqA25-.js +1 -0
  372. package/dist/web-ui/_app/immutable/chunks/C0w6WDm5.js +1 -0
  373. package/dist/web-ui/_app/immutable/chunks/C5_PAZ0y.js +1 -0
  374. package/dist/web-ui/_app/immutable/chunks/CDRO15Iv.js +1 -0
  375. package/dist/web-ui/_app/immutable/chunks/CF1CoqD5.js +1 -0
  376. package/dist/web-ui/_app/immutable/chunks/CS2sa4_m.js +1 -0
  377. package/dist/web-ui/_app/immutable/chunks/CWUQwB9H.js +1 -0
  378. package/dist/web-ui/_app/immutable/chunks/CY5FDdSU.js +1 -0
  379. package/dist/web-ui/_app/immutable/chunks/C_7MTDoj.js +1 -0
  380. package/dist/web-ui/_app/immutable/chunks/CaAJD3dl.js +1 -0
  381. package/dist/web-ui/_app/immutable/chunks/{i-XnOIX0.js → ChB5iyEL.js} +1 -1
  382. package/dist/web-ui/_app/immutable/chunks/ChQD-6N8.js +1 -0
  383. package/dist/web-ui/_app/immutable/chunks/{BCkp8Hs8.js → CqbsoCwA.js} +1 -1
  384. package/dist/web-ui/_app/immutable/chunks/DCeJW50p.js +1 -0
  385. package/dist/web-ui/_app/immutable/chunks/DJtZNgcs.js +1 -0
  386. package/dist/web-ui/_app/immutable/chunks/DKIeaprD.js +1 -0
  387. package/dist/web-ui/_app/immutable/chunks/DLd2uVIA.js +1 -0
  388. package/dist/web-ui/_app/immutable/chunks/DW_subyT.js +2 -0
  389. package/dist/web-ui/_app/immutable/chunks/DbU6lVn0.js +1 -0
  390. package/dist/web-ui/_app/immutable/chunks/Dc7ZCC5m.js +1 -0
  391. package/dist/web-ui/_app/immutable/chunks/Dd5umPsk.js +2 -0
  392. package/dist/web-ui/_app/immutable/chunks/Dg_zDpDS.js +1 -0
  393. package/dist/web-ui/_app/immutable/chunks/Dgqu8Yuc.js +1 -0
  394. package/dist/web-ui/_app/immutable/chunks/DmxsPZTB.js +1 -0
  395. package/dist/web-ui/_app/immutable/chunks/DphTaFUB.js +1 -0
  396. package/dist/web-ui/_app/immutable/chunks/DqK4iHp0.js +1 -0
  397. package/dist/web-ui/_app/immutable/chunks/DqT6OH_u.js +2 -0
  398. package/dist/web-ui/_app/immutable/chunks/Ds9I9wQb.js +1 -0
  399. package/dist/web-ui/_app/immutable/chunks/Du5ng3u4.js +1 -0
  400. package/dist/web-ui/_app/immutable/chunks/DxJw79Wi.js +1 -0
  401. package/dist/web-ui/_app/immutable/chunks/GFTX8GgV.js +1 -0
  402. package/dist/web-ui/_app/immutable/chunks/HNjs76Zz.js +1 -0
  403. package/dist/web-ui/_app/immutable/chunks/HVMjDi4_.js +1 -0
  404. package/dist/web-ui/_app/immutable/chunks/P0A_fJvS.js +1 -0
  405. package/dist/web-ui/_app/immutable/chunks/T3vGWjIL.js +1 -0
  406. package/dist/web-ui/_app/immutable/chunks/VTmrX9Qu.js +1 -0
  407. package/dist/web-ui/_app/immutable/chunks/Xvwhx_F1.js +1 -0
  408. package/dist/web-ui/_app/immutable/chunks/Yyz1XMQA.js +1 -0
  409. package/dist/web-ui/_app/immutable/chunks/dh5HeqUr.js +1 -0
  410. package/dist/web-ui/_app/immutable/chunks/fZMteyca.js +62 -0
  411. package/dist/web-ui/_app/immutable/chunks/{D28BF5MJ.js → gPrj-hqC.js} +1 -1
  412. package/dist/web-ui/_app/immutable/chunks/htcWMiYN.js +1 -0
  413. package/dist/web-ui/_app/immutable/chunks/oTsvd9y4.js +1 -0
  414. package/dist/web-ui/_app/immutable/chunks/qJfLUwU4.js +1 -0
  415. package/dist/web-ui/_app/immutable/chunks/xCtiO_JE.js +1 -0
  416. package/dist/web-ui/_app/immutable/chunks/y4GeEH6k.js +1 -0
  417. package/dist/web-ui/_app/immutable/entry/app.C4h_eOn6.js +2 -0
  418. package/dist/web-ui/_app/immutable/entry/start.CQFTf9ep.js +1 -0
  419. package/dist/web-ui/_app/immutable/nodes/0.Dh1xO970.js +1 -0
  420. package/dist/web-ui/_app/immutable/nodes/1.l75D3Opx.js +1 -0
  421. package/dist/web-ui/_app/immutable/nodes/10.DBidBPc-.js +1 -0
  422. package/dist/web-ui/_app/immutable/nodes/11.Ab0gUKWe.js +1 -0
  423. package/dist/web-ui/_app/immutable/nodes/12.CMsnoxfs.js +1 -0
  424. package/dist/web-ui/_app/immutable/nodes/13.D8YKuknB.js +1 -0
  425. package/dist/web-ui/_app/immutable/nodes/14.DZ0aan7y.js +1 -0
  426. package/dist/web-ui/_app/immutable/nodes/15.CUIKreDL.js +2 -0
  427. package/dist/web-ui/_app/immutable/nodes/16.BWc8--BO.js +1 -0
  428. package/dist/web-ui/_app/immutable/nodes/2.CDUonbuh.js +1 -0
  429. package/dist/web-ui/_app/immutable/nodes/3.Ctg3M00i.js +1 -0
  430. package/dist/web-ui/_app/immutable/nodes/4.Ci-JDwbA.js +2 -0
  431. package/dist/web-ui/_app/immutable/nodes/5.CTyEDAq0.js +1 -0
  432. package/dist/web-ui/_app/immutable/nodes/6.BTZZqsAb.js +1 -0
  433. package/dist/web-ui/_app/immutable/nodes/7.BI52g_Jo.js +137 -0
  434. package/dist/web-ui/_app/immutable/nodes/8.3hZPaB9x.js +1 -0
  435. package/dist/web-ui/_app/immutable/nodes/9.DS49kvwl.js +29 -0
  436. package/dist/web-ui/_app/version.json +1 -1
  437. package/dist/web-ui/favicon-192.png +0 -0
  438. package/dist/web-ui/favicon-32.png +0 -0
  439. package/dist/web-ui/favicon.ico +0 -0
  440. package/dist/web-ui/index.html +14 -11
  441. package/package.json +14 -7
  442. package/plugin/.claude-plugin/marketplace.json +1 -1
  443. package/plugin/.claude-plugin/plugin.json +1 -1
  444. package/plugin/plugins/kspec/skills/merge/SKILL.md +127 -0
  445. package/plugin/plugins/kspec/skills/plan/SKILL.md +55 -26
  446. package/plugin/plugins/kspec/skills/review/SKILL.md +350 -133
  447. package/plugin/plugins/kspec/skills/task-work/SKILL.md +96 -106
  448. package/templates/agents-sections/04-pr-workflow.md +15 -12
  449. package/templates/agents-sections/06-ralph-loop.md +15 -10
  450. package/templates/skills/manifest.yaml +25 -7
  451. package/templates/skills/merge/SKILL.md +120 -0
  452. package/templates/skills/plan/SKILL.md +55 -26
  453. package/templates/skills/review/SKILL.md +346 -130
  454. package/templates/skills/task-work/SKILL.md +93 -103
  455. package/dist/web-ui/_app/immutable/assets/0.BxCxvrZR.css +0 -1
  456. package/dist/web-ui/_app/immutable/chunks/B-CZR0q8.js +0 -1
  457. package/dist/web-ui/_app/immutable/chunks/B1IR5Su5.js +0 -1
  458. package/dist/web-ui/_app/immutable/chunks/B_Cvvtc4.js +0 -1
  459. package/dist/web-ui/_app/immutable/chunks/BtFaGGII.js +0 -1
  460. package/dist/web-ui/_app/immutable/chunks/Bu8JVsCH.js +0 -1
  461. package/dist/web-ui/_app/immutable/chunks/C87u-CNA.js +0 -1
  462. package/dist/web-ui/_app/immutable/chunks/CrFkBTYp.js +0 -1
  463. package/dist/web-ui/_app/immutable/chunks/D6RtLpzL.js +0 -1
  464. package/dist/web-ui/_app/immutable/chunks/D7FHSgx2.js +0 -1
  465. package/dist/web-ui/_app/immutable/chunks/DBXrsxZQ.js +0 -2
  466. package/dist/web-ui/_app/immutable/chunks/Da_hHMuA.js +0 -1
  467. package/dist/web-ui/_app/immutable/chunks/Do6LchSF.js +0 -1
  468. package/dist/web-ui/_app/immutable/chunks/DoNPtcAw.js +0 -1
  469. package/dist/web-ui/_app/immutable/chunks/DtUbXRZz.js +0 -1
  470. package/dist/web-ui/_app/immutable/chunks/DyFPRlLl.js +0 -1
  471. package/dist/web-ui/_app/immutable/chunks/DzAP8lRM.js +0 -1
  472. package/dist/web-ui/_app/immutable/chunks/DzVXElzN.js +0 -2
  473. package/dist/web-ui/_app/immutable/chunks/aoPBFken.js +0 -1
  474. package/dist/web-ui/_app/immutable/chunks/laxtrUO3.js +0 -1
  475. package/dist/web-ui/_app/immutable/chunks/q1nIWgqB.js +0 -1
  476. package/dist/web-ui/_app/immutable/chunks/sTLbk5Nm.js +0 -1
  477. package/dist/web-ui/_app/immutable/chunks/vwKgQu5P.js +0 -5
  478. package/dist/web-ui/_app/immutable/entry/app.BCwMcqnT.js +0 -2
  479. package/dist/web-ui/_app/immutable/entry/start.wKCQH-tt.js +0 -1
  480. package/dist/web-ui/_app/immutable/nodes/0.CjGVMG74.js +0 -1
  481. package/dist/web-ui/_app/immutable/nodes/1.B6_AIPan.js +0 -1
  482. package/dist/web-ui/_app/immutable/nodes/2.q4oCS7Ws.js +0 -1
  483. package/dist/web-ui/_app/immutable/nodes/3.rTKZf9o2.js +0 -1
  484. package/dist/web-ui/_app/immutable/nodes/4.DVIDRu1d.js +0 -1
  485. package/dist/web-ui/_app/immutable/nodes/5.8PtPXIOd.js +0 -1
  486. package/dist/web-ui/_app/immutable/nodes/6.ZZrTemy_.js +0 -1
  487. package/dist/web-ui/_app/immutable/nodes/7.IP-gxCxi.js +0 -1
@@ -16,6 +16,7 @@ import { PubSubManager } from './websocket/pubsub';
16
16
  import { HeartbeatManager } from './websocket/heartbeat';
17
17
  import { WebSocketHandler } from './websocket/handler';
18
18
  import { handleWebSocketClose } from './websocket/lifecycle';
19
+ import { resolveWebSocketProject } from './websocket/project-resolution';
19
20
  import type { ConnectionData, ConnectedEvent } from './websocket/types';
20
21
  import { PidFileManager } from './pid';
21
22
  import { projectContextMiddleware } from './middleware/project-context';
@@ -27,6 +28,12 @@ import { createValidationRoutes } from './routes/validation';
27
28
  import { createProjectsRoutes } from './routes/projects';
28
29
  import { createTriageRoutes } from './routes/triage';
29
30
  import { createAgentDispatchRoutes, getDispatchEngine, stopAllEngines } from './routes/agent-dispatch';
31
+ import { createSessionRoutes } from './routes/sessions';
32
+ import { createPlansRoutes } from './routes/plans';
33
+ import { createAggregationRoutes } from './routes/aggregation';
34
+ import { createRefsRoutes } from './routes/refs';
35
+ import { ShadowSyncScheduler } from './shadow-sync';
36
+ import { SessionSyncScheduler } from './session-sync';
30
37
  import { join } from 'path';
31
38
 
32
39
  export interface ServerOptions {
@@ -41,10 +48,9 @@ export interface ServerOptions {
41
48
  * Tries multiple locations in order:
42
49
  * 1. Explicit webUiDir option
43
50
  * 2. WEB_UI_DIR environment variable
44
- * 3. packages/web-ui/build in current working directory (monorepo dev)
45
- * 4. web-ui/build in current working directory
46
- * 5. Bundled dist/web-ui/ relative to this module (npm package installs)
51
+ * 3. Bundled dist/web-ui/ relative to this module (npm package installs)
47
52
  *
53
+ * AC: @daemon-web-ui-bundle ac-4, ac-5
48
54
  * Exported for testing only.
49
55
  */
50
56
  export function resolveWebUiPath(webUiDir?: string): string | null {
@@ -59,20 +65,7 @@ export function resolveWebUiPath(webUiDir?: string): string | null {
59
65
  return envPath;
60
66
  }
61
67
 
62
- // 3. Monorepo development: packages/web-ui/build from cwd
63
- // The daemon is spawned with cwd set to project root
64
- const monorepoPath = join(process.cwd(), 'packages', 'web-ui', 'build');
65
- if (existsSync(monorepoPath)) {
66
- return monorepoPath;
67
- }
68
-
69
- // 4. Alternate location: web-ui/build in cwd
70
- const altPath = join(process.cwd(), 'web-ui', 'build');
71
- if (existsSync(altPath)) {
72
- return altPath;
73
- }
74
-
75
- // 5. Bundled assets: dist/web-ui/ relative to daemon module location
68
+ // 3. Bundled assets: dist/web-ui/ relative to daemon module location
76
69
  // Covers npm package installs where no local web UI build exists.
77
70
  // import.meta.url resolves to dist/daemon/server.js → sibling is dist/web-ui/
78
71
  const selfDir = dirname(fileURLToPath(import.meta.url));
@@ -89,6 +82,66 @@ let pubsubManager: PubSubManager;
89
82
  let heartbeatManager: HeartbeatManager;
90
83
  let wsHandler: WebSocketHandler;
91
84
  let projectManager: import('./project-context').ProjectContextManager | undefined;
85
+ let shadowSyncScheduler: ShadowSyncScheduler | undefined;
86
+ const sessionSyncSchedulers: Map<string, SessionSyncScheduler> = new Map();
87
+
88
+ /**
89
+ * Start a session sync scheduler for a project if it has session branch configured.
90
+ * Safe to call multiple times — skips if scheduler already exists for that project.
91
+ */
92
+ async function startSessionSyncForProject(
93
+ projectPath: string,
94
+ pubsub: PubSubManager,
95
+ ): Promise<void> {
96
+ if (sessionSyncSchedulers.has(projectPath)) {
97
+ return;
98
+ }
99
+
100
+ const { loadProjectConfig } = await import('../parser/config.js');
101
+ const { config } = await loadProjectConfig(projectPath);
102
+ const specDir = join(projectPath, config.shadow.directory);
103
+
104
+ // AC: @multi-directory-daemon ac-31, @manifest-discovery ac-6
105
+ // Use discovery API instead of hardcoding kynetic.yaml
106
+ const { findManifestInDir, readYamlFile } = await import('../parser/yaml.js');
107
+ const manifestPath = await findManifestInDir(specDir);
108
+ if (!manifestPath) {
109
+ // No manifest found — gracefully skip session sync for this project
110
+ return;
111
+ }
112
+ const manifest = await readYamlFile<{ sessions?: { storage?: string; branch?: string } }>(manifestPath);
113
+
114
+ if (manifest?.sessions?.storage === 'branch') {
115
+ const syncInterval = config.shadow.sync_interval;
116
+
117
+ if (syncInterval > 0) {
118
+ const { resolveSessionBranchConfig } = await import('../parser/session-branch.js');
119
+ const sessionConfig = resolveSessionBranchConfig(projectPath, manifest);
120
+
121
+ if (sessionConfig) {
122
+ const scheduler = new SessionSyncScheduler({
123
+ worktreeDir: sessionConfig.worktreeDir,
124
+ intervalSeconds: syncInterval,
125
+ branchName: sessionConfig.branchName,
126
+ pubsub,
127
+ });
128
+ scheduler.start();
129
+ sessionSyncSchedulers.set(projectPath, scheduler);
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Stop session sync scheduler for a specific project.
137
+ */
138
+ function stopSessionSyncForProject(projectPath: string): void {
139
+ const scheduler = sessionSyncSchedulers.get(projectPath);
140
+ if (scheduler) {
141
+ scheduler.stop();
142
+ sessionSyncSchedulers.delete(projectPath);
143
+ }
144
+ }
92
145
 
93
146
  /**
94
147
  * Middleware to enforce localhost-only connections.
@@ -199,10 +252,16 @@ export async function createServer(options: ServerOptions) {
199
252
  // AC-3: Enforce localhost-only connections
200
253
  .onRequest(localhostOnly());
201
254
 
255
+ // Shared callback for all registration paths (middleware, projects API, WebSocket)
256
+ const onProjectRegistered = async (projectPath: string) => {
257
+ await startSessionSyncForProject(projectPath, pubsubManager);
258
+ };
259
+
202
260
  // AC: @multi-directory-daemon ac-1, ac-2, ac-3 - Project context middleware
203
261
  const { manager: projectContextManager, middleware: projectMiddleware } = projectContextMiddleware({
204
262
  startupProject: startupProjectPath,
205
- pubsub: pubsubManager
263
+ pubsub: pubsubManager,
264
+ onProjectRegistered,
206
265
  });
207
266
 
208
267
  // Store manager globally for shutdown
@@ -238,7 +297,25 @@ export async function createServer(options: ServerOptions) {
238
297
  .use(createValidationRoutes())
239
298
 
240
299
  // AC: @multi-directory-daemon ac-28, ac-29, ac-30 - Projects management endpoints
241
- .use(createProjectsRoutes({ projectManager: projectContextManager }))
300
+ .use(createProjectsRoutes({
301
+ projectManager: projectContextManager,
302
+ onProjectRegistered,
303
+ onProjectUnregistered: (projectPath) => {
304
+ stopSessionSyncForProject(projectPath);
305
+ },
306
+ }))
307
+
308
+ // AC: @ui-session-stream ac-1, ac-4 - Session data endpoints
309
+ .use(createSessionRoutes())
310
+
311
+ // AC: @ui-plans-view ac-1 - Plans data endpoints
312
+ .use(createPlansRoutes())
313
+
314
+ // AC: @ui-api-aggregation ac-1, ac-2, ac-3 - Aggregation endpoints
315
+ .use(createAggregationRoutes())
316
+
317
+ // AC: @ui-api-ref-resolution ac-4, ac-5 - Lightweight ref index endpoint
318
+ .use(createRefsRoutes())
242
319
 
243
320
  // AC: @agent-dispatch-engine ac-4 - Agent dispatch API endpoints
244
321
  // AC: @daemon-agent-dispatch ac-3, ac-4 - Pass pubsub for WebSocket broadcast on invocation events
@@ -247,14 +324,6 @@ export async function createServer(options: ServerOptions) {
247
324
  // AC-4: WebSocket endpoint for real-time updates
248
325
  .ws<ConnectionData>('/ws', {
249
326
  beforeHandle({ request, store }) {
250
- // AC: @multi-directory-daemon ac-21, ac-22, ac-23, ac-34 - Extract and validate project binding
251
- // AC: @multi-directory-daemon ac-34 - Browser WebSocket API doesn't support custom headers,
252
- // so we also accept project path as query parameter
253
- const url = new URL(request.url, `http://${request.headers.get('host')}`);
254
- const projectPath = request.headers.get('X-Kspec-Dir')
255
- || url.searchParams.get('project')
256
- || undefined;
257
-
258
327
  // IMPORTANT: Do NOT return a value from ws beforeHandle.
259
328
  // In Elysia 1.4 with derive middleware, returning a value short-circuits
260
329
  // the WebSocket upgrade and sends the value as an HTTP 200 response.
@@ -267,30 +336,15 @@ export async function createServer(options: ServerOptions) {
267
336
  return;
268
337
  }
269
338
 
270
- let projectContext;
271
- if (projectPath) {
272
- // Explicit project specified
273
- try {
274
- projectContext = manager.getProject(projectPath);
275
- } catch {
276
- // AC: @multi-directory-daemon ac-4 - auto-register
277
- projectContext = manager.registerProject(projectPath);
278
- }
279
- } else {
280
- // AC: @multi-directory-daemon ac-22, ac-23 - Use default or reject
281
- try {
282
- projectContext = manager.getProject();
283
- } catch (err: unknown) {
284
- // AC: @multi-directory-daemon ac-23 - Reject when no default
285
- if (err instanceof Error && err.message.includes('No default project configured')) {
286
- throw new Error('No project specified');
287
- }
288
- throw err;
289
- }
290
- }
339
+ const { resolvedPath } = resolveWebSocketProject({
340
+ request,
341
+ manager,
342
+ fallbackPath: startupProjectPath,
343
+ onProjectRegistered,
344
+ });
291
345
 
292
346
  // Store resolved path for open() handler via WeakMap
293
- wsProjectPaths.set(request, projectContext.path);
347
+ wsProjectPaths.set(request, resolvedPath);
294
348
  } catch (err: unknown) {
295
349
  console.error(`[daemon] WebSocket connection rejected: ${err instanceof Error ? err.message : String(err)}`);
296
350
  throw err;
@@ -355,7 +409,21 @@ export async function createServer(options: ServerOptions) {
355
409
 
356
410
  // SPA fallback routes for client-side routing
357
411
  // These catch paths like /tasks, /items, /inbox that don't have static files
358
- const spaRoutes = ['/tasks', '/tasks/*', '/items', '/items/*', '/inbox', '/observations', '/triage'];
412
+ const spaRoutes = [
413
+ '/',
414
+ '/tasks', '/tasks/*',
415
+ '/items', '/items/*',
416
+ '/inbox',
417
+ '/observations',
418
+ '/triage',
419
+ '/validate',
420
+ '/sessions', '/sessions/*',
421
+ '/agents',
422
+ '/specs',
423
+ '/workflows',
424
+ '/plans',
425
+ '/settings',
426
+ ];
359
427
  for (const route of spaRoutes) {
360
428
  app.get(route, () => Bun.file(indexHtmlPath));
361
429
  }
@@ -395,6 +463,44 @@ export async function createServer(options: ServerOptions) {
395
463
  }
396
464
  }
397
465
 
466
+ // AC: @config-shadow ac-12 - Start periodic shadow sync if remote tracking configured
467
+ if (startupProjectPath) {
468
+ try {
469
+ const { loadProjectConfig } = await import('../parser/config.js');
470
+ const { config } = await loadProjectConfig(startupProjectPath);
471
+ const syncInterval = config.shadow.sync_interval;
472
+ const worktreeDir = join(startupProjectPath, config.shadow.directory);
473
+
474
+ if (syncInterval > 0) {
475
+ shadowSyncScheduler = new ShadowSyncScheduler({
476
+ worktreeDir,
477
+ intervalSeconds: syncInterval,
478
+ shadowOptions: {
479
+ branchName: config.shadow.branch,
480
+ directory: config.shadow.directory,
481
+ remote: config.shadow.remote?.value,
482
+ remoteType: config.shadow.remote?.type,
483
+ },
484
+ pubsub: pubsubManager,
485
+ });
486
+ shadowSyncScheduler.start();
487
+ }
488
+ } catch (error) {
489
+ console.error('[daemon] Failed to initialize shadow sync scheduler:', error);
490
+ }
491
+ }
492
+
493
+ // AC: @session-branch-worktree ac-sync - Start periodic session branch sync if configured
494
+ // Session sync runs independently from kspec-meta sync — failures in one do not affect the other
495
+ if (startupProjectPath) {
496
+ try {
497
+ await startSessionSyncForProject(startupProjectPath, pubsubManager);
498
+ } catch (error) {
499
+ // Session sync init failure does not block daemon startup
500
+ console.error('[daemon] Failed to initialize session sync scheduler:', error);
501
+ }
502
+ }
503
+
398
504
  // AC: @daemon-server ac-13, ac-14 - Start heartbeat monitoring
399
505
  heartbeatManager.start(pubsubManager.getAllConnections());
400
506
 
@@ -406,6 +512,15 @@ export async function createServer(options: ServerOptions) {
406
512
  // Stop heartbeat monitoring
407
513
  heartbeatManager.stop();
408
514
 
515
+ // AC: @config-shadow ac-12 - Stop shadow sync scheduler
516
+ shadowSyncScheduler?.stop();
517
+
518
+ // AC: @session-branch-worktree ac-sync - Stop all session sync schedulers
519
+ for (const scheduler of sessionSyncSchedulers.values()) {
520
+ scheduler.stop();
521
+ }
522
+ sessionSyncSchedulers.clear();
523
+
409
524
  // AC: @agent-dispatch-engine ac-11 - Stop all dispatch engines before shutting down
410
525
  await stopAllEngines();
411
526
 
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Re-export SessionSyncScheduler from its canonical location in src/parser/.
3
+ * The daemon server.ts imports from this file; the actual implementation
4
+ * lives in src/parser/session-sync-scheduler.ts so that both daemon runtime
5
+ * (via dist/parser/) and vitest tests can import the same production code.
6
+ *
7
+ * AC: @session-branch-worktree ac-sync
8
+ */
9
+
10
+ export { SessionSyncScheduler } from '../parser/session-sync-scheduler.js';
11
+ export type { SessionSyncSchedulerOptions, SessionSyncPubSub } from '../parser/session-sync-scheduler.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Re-export ShadowSyncScheduler from its canonical location in src/parser/.
3
+ * The daemon server.ts imports from this file; the actual implementation
4
+ * lives in src/parser/shadow-sync-scheduler.ts so that both daemon runtime
5
+ * (via dist/parser/) and vitest tests can import the same production code.
6
+ *
7
+ * AC: @config-shadow ac-12
8
+ */
9
+
10
+ export { ShadowSyncScheduler } from '../parser/shadow-sync-scheduler.js';
11
+ export type { ShadowSyncSchedulerOptions, ShadowSyncPubSub } from '../parser/shadow-sync-scheduler.js';
@@ -9,11 +9,11 @@
9
9
  * - ac-8: Fallback to Chokidar if Bun fs.watch fails
10
10
  */
11
11
 
12
- import { watch, type FSWatcher } from 'fs';
13
- import { readFile } from 'fs/promises';
12
+ import { existsSync, watch, type FSWatcher } from 'fs';
13
+ import { readFile, lstat } from 'fs/promises';
14
14
  import { parse as parseYaml } from 'yaml';
15
15
  import chokidar, { type FSWatcher as ChokidarWatcher } from 'chokidar';
16
- import { join } from 'path';
16
+ import { join, relative } from 'path';
17
17
 
18
18
  export interface WatcherOptions {
19
19
  kspecDir: string;
@@ -39,6 +39,8 @@ export class KspecWatcher {
39
39
  private retryCount = 0;
40
40
  private maxRetries = 5;
41
41
  private baseBackoffMs = 1000;
42
+ private stopped = false;
43
+ private recoveryTimer: NodeJS.Timeout | null = null;
42
44
 
43
45
  constructor(private options: WatcherOptions) {}
44
46
 
@@ -46,6 +48,7 @@ export class KspecWatcher {
46
48
  * AC-4, AC-8: Start watching .kspec directory (with Chokidar fallback)
47
49
  */
48
50
  async start(): Promise<void> {
51
+ this.stopped = false;
49
52
  try {
50
53
  // Try Bun's native fs.watch first
51
54
  await this.startBunWatcher();
@@ -68,9 +71,14 @@ export class KspecWatcher {
68
71
  if (!filename || !filename.endsWith('.yaml')) return;
69
72
 
70
73
  const fullPath = join(this.options.kspecDir, filename);
74
+ // Guard against nested .kspec symlink loops (e.g. .kspec/.kspec -> .kspec)
75
+ if (this.isNestedKspecPath(fullPath)) return;
71
76
  this.handleFileChange(fullPath);
72
77
  }
73
78
  );
79
+ (this.watcher as FSWatcher).on('error', (error) => {
80
+ void this.handleWatcherError(error);
81
+ });
74
82
 
75
83
  console.log('[watcher] Watching .kspec directory with Bun fs.watch');
76
84
  }
@@ -81,6 +89,8 @@ export class KspecWatcher {
81
89
  private async startChokidarWatcher(): Promise<void> {
82
90
  this.watcher = chokidar.watch(join(this.options.kspecDir, '**/*.yaml'), {
83
91
  ignoreInitial: true,
92
+ followSymlinks: false,
93
+ ignored: (filePath: string) => this.isNestedKspecPath(filePath),
84
94
  awaitWriteFinish: {
85
95
  stabilityThreshold: 100,
86
96
  pollInterval: 50
@@ -123,6 +133,12 @@ export class KspecWatcher {
123
133
  */
124
134
  private async processFileChange(filePath: string): Promise<void> {
125
135
  try {
136
+ if (this.isNestedKspecPath(filePath)) return;
137
+
138
+ // Skip symlinks to prevent ELOOP errors
139
+ const stat = await lstat(filePath);
140
+ if (stat.isSymbolicLink()) return;
141
+
126
142
  const content = await readFile(filePath, 'utf-8');
127
143
 
128
144
  // AC-6: Validate YAML before broadcasting
@@ -143,13 +159,35 @@ export class KspecWatcher {
143
159
  }
144
160
  }
145
161
 
162
+ /**
163
+ * Ignore only recursive ".kspec" entries inside the watched root, not the
164
+ * root .kspec directory itself.
165
+ */
166
+ private isNestedKspecPath(filePath: string): boolean {
167
+ const relPath = relative(this.options.kspecDir, filePath);
168
+ if (!relPath || relPath === '' || relPath.startsWith('..')) return false;
169
+
170
+ const [firstSegment] = relPath.split(/[\\/]+/);
171
+ return firstSegment === '.kspec';
172
+ }
173
+
146
174
  /**
147
175
  * AC-7: Handle watcher errors with exponential backoff
148
176
  */
149
177
  private async handleWatcherError(error: Error): Promise<void> {
178
+ if (this.stopped) {
179
+ return;
180
+ }
181
+
150
182
  this.options.onError(error);
151
183
 
184
+ const nodeError = error as NodeJS.ErrnoException;
152
185
  if (this.retryCount >= this.maxRetries) {
186
+ if (nodeError.code === 'ENOENT' && !existsSync(this.options.kspecDir)) {
187
+ console.warn('[watcher] Watched .kspec directory no longer exists after recovery attempts; stopping watcher');
188
+ await this.stop();
189
+ return;
190
+ }
153
191
  console.error('[watcher] Max retries reached, giving up');
154
192
  return;
155
193
  }
@@ -159,29 +197,42 @@ export class KspecWatcher {
159
197
 
160
198
  console.log(`[watcher] Attempting recovery in ${backoffMs}ms (attempt ${this.retryCount}/${this.maxRetries})`);
161
199
 
162
- setTimeout(async () => {
200
+ this.recoveryTimer = setTimeout(async () => {
201
+ this.recoveryTimer = null;
163
202
  try {
203
+ if (this.stopped) return;
164
204
  await this.stop();
205
+ this.stopped = false;
165
206
  await this.start();
166
207
  console.log('[watcher] Recovery successful');
167
208
  } catch (retryError) {
168
209
  console.error('[watcher] Recovery failed:', retryError);
169
210
  // Will retry again if under max retries
170
- this.handleWatcherError(retryError as Error);
211
+ await this.handleWatcherError(retryError as Error);
171
212
  }
172
213
  }, backoffMs);
214
+ if (typeof this.recoveryTimer === 'object' && 'unref' in this.recoveryTimer) {
215
+ this.recoveryTimer.unref();
216
+ }
173
217
  }
174
218
 
175
219
  /**
176
220
  * Stop watching
177
221
  */
178
222
  async stop(): Promise<void> {
223
+ this.stopped = true;
224
+
179
225
  // Clear all debounce timers
180
226
  for (const timer of this.debounceTimers.values()) {
181
227
  clearTimeout(timer);
182
228
  }
183
229
  this.debounceTimers.clear();
184
230
 
231
+ if (this.recoveryTimer) {
232
+ clearTimeout(this.recoveryTimer);
233
+ this.recoveryTimer = null;
234
+ }
235
+
185
236
  // Close watcher
186
237
  if (this.watcher) {
187
238
  if (this.usingChokidar) {
@@ -0,0 +1,77 @@
1
+ /**
2
+ * WebSocket Project Resolution
3
+ *
4
+ * Extracts and resolves the project path for a WebSocket connection request.
5
+ * Used by the WebSocket beforeHandle hook in server.ts.
6
+ *
7
+ * Task: @01KKBD6KH5F5MVC5BXV2NQG474
8
+ */
9
+
10
+ import type { ProjectContextManager } from '../project-context';
11
+
12
+ export interface ResolveWebSocketProjectOptions {
13
+ request: Request;
14
+ manager: ProjectContextManager;
15
+ fallbackPath: string;
16
+ onProjectRegistered?: (projectPath: string) => Promise<void>;
17
+ }
18
+
19
+ export interface ResolveWebSocketProjectResult {
20
+ resolvedPath: string;
21
+ wasRegistered: boolean;
22
+ }
23
+
24
+ /**
25
+ * Resolves the project path for a WebSocket connection request.
26
+ *
27
+ * Extracts the project path from X-Kspec-Dir header or ?project= query param,
28
+ * registers it if new, and fires onProjectRegistered for newly-registered projects
29
+ * so that session sync starts.
30
+ *
31
+ * AC: @multi-directory-daemon ac-21, ac-22, ac-23, ac-34
32
+ */
33
+ export function resolveWebSocketProject(
34
+ options: ResolveWebSocketProjectOptions,
35
+ ): ResolveWebSocketProjectResult {
36
+ const { request, manager, fallbackPath, onProjectRegistered } = options;
37
+
38
+ // AC: @multi-directory-daemon ac-34 - Browser WebSocket API doesn't support custom headers,
39
+ // so we also accept project path as query parameter
40
+ const url = new URL(request.url, `http://${request.headers.get('host')}`);
41
+ const projectPath = request.headers.get('X-Kspec-Dir')
42
+ || url.searchParams.get('project')
43
+ || undefined;
44
+
45
+ if (!manager) {
46
+ return { resolvedPath: fallbackPath, wasRegistered: false };
47
+ }
48
+
49
+ let projectContext;
50
+ let wasRegistered = false;
51
+ if (projectPath) {
52
+ // Explicit project specified
53
+ // AC: @multi-directory-daemon ac-4 - auto-register
54
+ const result = manager.getOrRegisterProject(projectPath);
55
+ projectContext = result.context;
56
+ wasRegistered = result.wasRegistered;
57
+ if (result.wasRegistered && onProjectRegistered) {
58
+ // Start session sync for newly auto-registered project (don't block upgrade)
59
+ void onProjectRegistered(projectContext.path).catch((syncError) => {
60
+ console.error(`[daemon] Failed to start session sync for WebSocket-registered ${projectContext.path}:`, syncError);
61
+ });
62
+ }
63
+ } else {
64
+ // AC: @multi-directory-daemon ac-22, ac-23 - Use default or reject
65
+ try {
66
+ projectContext = manager.getProject();
67
+ } catch (err: unknown) {
68
+ // AC: @multi-directory-daemon ac-23 - Reject when no default
69
+ if (err instanceof Error && err.message.includes('No default project configured')) {
70
+ throw new Error('No project specified');
71
+ }
72
+ throw err;
73
+ }
74
+ }
75
+
76
+ return { resolvedPath: projectContext.path, wasRegistered };
77
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/export/json.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAqBH,OAAO,KAAK,EAIV,WAAW,EAEX,aAAa,EACd,MAAM,YAAY,CAAC;AA+IpB;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,iBAAiB,UAAQ,GACxB,OAAO,CAAC,aAAa,CAAC,CAoDxB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW,CAazE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD"}
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/export/json.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAwBH,OAAO,KAAK,EAIV,WAAW,EAEX,aAAa,EACd,MAAM,YAAY,CAAC;AA4PpB;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,iBAAiB,UAAQ,GACxB,OAAO,CAAC,aAAa,CAAC,CA2DxB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW,CAezE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD"}