@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
@@ -7,54 +7,64 @@
7
7
  * AC-agent-1: kspec meta agents outputs table
8
8
  * AC-agent-2: kspec meta agents --json outputs JSON
9
9
  */
10
- import chalk from 'chalk';
11
- import Table from 'cli-table3';
12
- import { ulid } from 'ulid';
13
- import { initContext, loadMetaContext, getMetaStats, createObservation, saveObservation, saveMetaItem, deleteMetaItem, createTask, saveTask, loadAllTasks, loadAllItems, ReferenceIndex, loadSessionContext, saveSessionContext, loadInboxItems, findInboxItemByRef, deleteInboxItem, } from '../../parser/index.js';
14
- import { output, error, success, isJsonMode } from '../output.js';
15
- import { errors } from '../../strings/errors.js';
16
- import { commitIfShadow } from '../../parser/shadow.js';
17
- import { EXIT_CODES } from '../exit-codes.js';
10
+ import chalk from "chalk";
11
+ import Table from "cli-table3";
12
+ import { ulid } from "ulid";
13
+ import { markMutating } from "../command-annotations.js";
14
+ import { createObservation, createTask, deleteInboxItem, deleteMetaItem, findInboxItemByRef, getMetaStats, initContext, loadAllItems, loadAllTasks, loadInboxItems, loadMetaContext, loadSessionContext, ReferenceIndex, resolveMetaRef, saveMetaItem, saveObservation, saveSessionContext, saveTask, } from "../../parser/index.js";
15
+ import { commitIfShadow } from "../../parser/shadow.js";
16
+ import { normalizeRefInput } from "../../schema/index.js";
17
+ import { z } from "zod";
18
+ import { WorkflowStepSchema, } from "../../schema/index.js";
19
+ import { errors } from "../../strings/errors.js";
20
+ import { executeBatchOperation, formatBatchOutput } from "../batch.js";
21
+ import { EXIT_CODES } from "../exit-codes.js";
22
+ import { error, isJsonMode, output, success } from "../output.js";
23
+ import { parseTagsArray } from "../parse-utils.js";
24
+ import { parseIntOption } from "../validators.js";
18
25
  /**
19
- * Resolve a meta reference to its ULID
20
- * Handles semantic IDs (agent.id, workflow.id, convention.domain) and ULID prefixes
26
+ * Resolve a meta reference to its ULID and type.
27
+ * Wrapper around resolveMetaRef for backward compatibility with existing call sites.
28
+ * AC: @skill-meta-integration ac-4 - skills included in resolution
21
29
  */
22
30
  function resolveMetaRefToUlid(ref, metaCtx) {
23
- const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
24
- // Check agents
25
- const agent = (metaCtx.agents || []).find((a) => a.id === normalizedRef || a._ulid.startsWith(normalizedRef));
26
- if (agent)
27
- return { ulid: agent._ulid, type: 'agent' };
28
- // Check workflows
29
- const workflow = (metaCtx.workflows || []).find((w) => w.id === normalizedRef || w._ulid.startsWith(normalizedRef));
30
- if (workflow)
31
- return { ulid: workflow._ulid, type: 'workflow' };
32
- // Check conventions
33
- const convention = (metaCtx.conventions || []).find((c) => c.domain === normalizedRef || c._ulid.startsWith(normalizedRef));
34
- if (convention)
35
- return { ulid: convention._ulid, type: 'convention' };
36
- // Check observations
37
- const observation = (metaCtx.observations || []).find((o) => o._ulid.startsWith(normalizedRef));
38
- if (observation)
39
- return { ulid: observation._ulid, type: 'observation' };
40
- return null;
31
+ const result = resolveMetaRef(metaCtx, ref);
32
+ if (!result)
33
+ return null;
34
+ return { ulid: result.ulid, type: result.type };
35
+ }
36
+ /**
37
+ * Batch-compatible resolver for observations.
38
+ * Returns null instead of calling process.exit() to allow partial failure handling.
39
+ * AC: @trait-multi-ref-batch ac-2, ac-8 - Partial failure handling and ref resolution
40
+ */
41
+ function resolveObservationRefForBatch(ref, observations) {
42
+ const normalizedRef = ref.startsWith("@") ? ref.substring(1) : ref;
43
+ const observation = observations.find((o) => o._ulid.startsWith(normalizedRef));
44
+ if (!observation) {
45
+ return { item: null, error: `Observation not found: ${ref}` };
46
+ }
47
+ return { item: observation };
41
48
  }
42
49
  /**
43
50
  * Format meta show output
51
+ * AC: @skill-meta-integration ac-3 - skill count included in summary
44
52
  */
45
53
  function formatMetaShow(meta) {
46
54
  const stats = getMetaStats(meta);
47
55
  if (!meta.manifest) {
48
- console.log(chalk.yellow('No meta manifest found (kynetic.meta.yaml)'));
49
- console.log(chalk.gray('Create one to define agents, workflows, conventions, and observations'));
56
+ console.log(chalk.yellow("No meta manifest found (kynetic.meta.yaml)"));
57
+ console.log(chalk.gray("Create one to define agents, workflows, conventions, observations, and skills"));
50
58
  return;
51
59
  }
52
- console.log(chalk.bold('Meta-Spec Summary'));
53
- console.log(chalk.gray(''.repeat(40)));
60
+ console.log(chalk.bold("Meta-Spec Summary"));
61
+ console.log(chalk.gray("".repeat(40)));
54
62
  console.log(`Agents: ${stats.agents}`);
55
63
  console.log(`Workflows: ${stats.workflows}`);
56
64
  console.log(`Conventions: ${stats.conventions}`);
57
65
  console.log(`Observations: ${stats.observations} (${stats.unresolvedObservations} unresolved)`);
66
+ // AC: @skill-meta-integration ac-3 - include skill count
67
+ console.log(`Skills: ${stats.skills}`);
58
68
  }
59
69
  /**
60
70
  * Format agents table output
@@ -62,36 +72,37 @@ function formatMetaShow(meta) {
62
72
  */
63
73
  function formatAgents(agents) {
64
74
  if (agents.length === 0) {
65
- console.log(chalk.yellow('No agents defined'));
75
+ console.log(chalk.yellow("No agents defined"));
66
76
  return;
67
77
  }
68
78
  const table = new Table({
69
- head: [chalk.bold('ID'), chalk.bold('Name'), chalk.bold('Capabilities')],
79
+ head: [chalk.bold("ID"), chalk.bold("Name"), chalk.bold("Capabilities")],
70
80
  style: {
71
81
  head: [],
72
82
  border: [],
73
83
  },
74
84
  });
75
85
  for (const agent of agents) {
76
- table.push([
77
- agent.id,
78
- agent.name,
79
- agent.capabilities.join(', '),
80
- ]);
86
+ table.push([agent.id, agent.name, agent.capabilities.join(", ")]);
81
87
  }
82
88
  console.log(table.toString());
83
89
  }
84
90
  /**
85
91
  * Format workflows table output
86
- * AC-workflow-1: outputs table with columns: ID, Trigger, Steps (count)
92
+ * AC-workflow-1: outputs table with columns: ID, Trigger, Steps (count), Mode
87
93
  */
88
94
  function formatWorkflows(workflows) {
89
95
  if (workflows.length === 0) {
90
- console.log(chalk.yellow('No workflows defined'));
96
+ console.log(chalk.yellow("No workflows defined"));
91
97
  return;
92
98
  }
93
99
  const table = new Table({
94
- head: [chalk.bold('ID'), chalk.bold('Trigger'), chalk.bold('Steps')],
100
+ head: [
101
+ chalk.bold("ID"),
102
+ chalk.bold("Trigger"),
103
+ chalk.bold("Steps"),
104
+ chalk.bold("Mode"),
105
+ ],
95
106
  style: {
96
107
  head: [],
97
108
  border: [],
@@ -102,6 +113,7 @@ function formatWorkflows(workflows) {
102
113
  workflow.id,
103
114
  workflow.trigger,
104
115
  workflow.steps.length.toString(),
116
+ workflow.mode || "interactive",
105
117
  ]);
106
118
  }
107
119
  console.log(table.toString());
@@ -109,23 +121,32 @@ function formatWorkflows(workflows) {
109
121
  /**
110
122
  * Format workflows verbose output
111
123
  * AC-workflow-2: outputs each workflow with full step list
124
+ * AC: @loop-mode-workflows ac-3 (shows based_on field)
112
125
  */
113
126
  function formatWorkflowsVerbose(workflows) {
114
127
  if (workflows.length === 0) {
115
- console.log(chalk.yellow('No workflows defined'));
128
+ console.log(chalk.yellow("No workflows defined"));
116
129
  return;
117
130
  }
118
131
  for (const workflow of workflows) {
119
132
  console.log(chalk.bold(`${workflow.id} - ${workflow.trigger}`));
133
+ // Show mode if it's loop (skip for interactive as it's default)
134
+ if (workflow.mode === "loop") {
135
+ console.log(chalk.cyan(`Mode: ${workflow.mode}`));
136
+ }
137
+ // AC: @loop-mode-workflows ac-3 - Show based_on reference
138
+ if (workflow.based_on) {
139
+ console.log(chalk.cyan(`Based on: ${workflow.based_on}`));
140
+ }
120
141
  if (workflow.description) {
121
142
  console.log(chalk.gray(workflow.description));
122
143
  }
123
- console.log(chalk.gray(''.repeat(60)));
144
+ console.log(chalk.gray("".repeat(60)));
124
145
  for (const step of workflow.steps) {
125
146
  const prefix = {
126
- check: chalk.yellow('[check]'),
127
- action: chalk.blue('[action]'),
128
- decision: chalk.magenta('[decision]'),
147
+ check: chalk.yellow("[check]"),
148
+ action: chalk.blue("[action]"),
149
+ decision: chalk.magenta("[decision]"),
129
150
  }[step.type];
130
151
  console.log(`${prefix} ${step.content}`);
131
152
  if (step.on_fail) {
@@ -137,7 +158,7 @@ function formatWorkflowsVerbose(workflows) {
137
158
  }
138
159
  }
139
160
  }
140
- console.log('');
161
+ console.log("");
141
162
  }
142
163
  }
143
164
  /**
@@ -146,11 +167,11 @@ function formatWorkflowsVerbose(workflows) {
146
167
  */
147
168
  function formatConventions(conventions) {
148
169
  if (conventions.length === 0) {
149
- console.log(chalk.yellow('No conventions defined'));
170
+ console.log(chalk.yellow("No conventions defined"));
150
171
  return;
151
172
  }
152
173
  const table = new Table({
153
- head: [chalk.bold('Domain'), chalk.bold('Rules'), chalk.bold('Validation')],
174
+ head: [chalk.bold("Domain"), chalk.bold("Rules"), chalk.bold("Validation")],
154
175
  style: {
155
176
  head: [],
156
177
  border: [],
@@ -160,7 +181,7 @@ function formatConventions(conventions) {
160
181
  table.push([
161
182
  convention.domain,
162
183
  convention.rules.length.toString(),
163
- convention.validation ? 'yes' : 'no',
184
+ convention.validation ? "yes" : "no",
164
185
  ]);
165
186
  }
166
187
  console.log(table.toString());
@@ -171,20 +192,20 @@ function formatConventions(conventions) {
171
192
  */
172
193
  function formatConventionDetail(convention) {
173
194
  console.log(chalk.bold(`${convention.domain} Convention`));
174
- console.log(chalk.gray(''.repeat(60)));
175
- console.log(chalk.bold('\nRules:'));
195
+ console.log(chalk.gray("".repeat(60)));
196
+ console.log(chalk.bold("\nRules:"));
176
197
  for (const rule of convention.rules) {
177
198
  console.log(` • ${rule}`);
178
199
  }
179
200
  if (convention.examples && convention.examples.length > 0) {
180
- console.log(chalk.bold('\nExamples:'));
201
+ console.log(chalk.bold("\nExamples:"));
181
202
  for (const example of convention.examples) {
182
203
  console.log(chalk.green(` ✓ ${example.good}`));
183
204
  console.log(chalk.red(` ✗ ${example.bad}`));
184
205
  }
185
206
  }
186
207
  if (convention.validation) {
187
- console.log(chalk.bold('\nValidation:'));
208
+ console.log(chalk.bold("\nValidation:"));
188
209
  console.log(` Type: ${convention.validation.type}`);
189
210
  if (convention.validation.pattern) {
190
211
  console.log(` Pattern: ${convention.validation.pattern}`);
@@ -193,39 +214,69 @@ function formatConventionDetail(convention) {
193
214
  console.log(` Message: ${convention.validation.message}`);
194
215
  }
195
216
  }
196
- console.log('');
217
+ console.log("");
197
218
  }
198
219
  /**
199
220
  * Format observations table output
200
221
  * AC-obs-2: outputs table with columns: ID, Type, Workflow, Created, Content (truncated)
222
+ * AC: @obs-list-display ac-1 - When --all flag used, show Resolved column with ✓/✗
201
223
  */
202
224
  function formatObservations(observations, showResolved) {
203
- const filtered = showResolved ? observations : observations.filter(o => !o.resolved);
225
+ const filtered = showResolved
226
+ ? observations
227
+ : observations.filter((o) => !o.resolved);
204
228
  if (filtered.length === 0) {
205
- console.log(chalk.yellow(showResolved ? 'No observations found' : 'No unresolved observations'));
229
+ console.log(chalk.yellow(showResolved ? "No observations found" : "No unresolved observations"));
206
230
  return;
207
231
  }
232
+ // AC: @obs-list-display ac-1 - Include Resolved column when showing all observations
233
+ const headers = showResolved
234
+ ? [
235
+ chalk.bold("ID"),
236
+ chalk.bold("Type"),
237
+ chalk.bold("Resolved"),
238
+ chalk.bold("Workflow"),
239
+ chalk.bold("Created"),
240
+ chalk.bold("Content"),
241
+ ]
242
+ : [
243
+ chalk.bold("ID"),
244
+ chalk.bold("Type"),
245
+ chalk.bold("Workflow"),
246
+ chalk.bold("Created"),
247
+ chalk.bold("Content"),
248
+ ];
249
+ const colWidths = showResolved
250
+ ? [10, 10, 10, 20, 12, 40]
251
+ : [10, 10, 20, 12, 50];
208
252
  const table = new Table({
209
- head: [
210
- chalk.bold('ID'),
211
- chalk.bold('Type'),
212
- chalk.bold('Workflow'),
213
- chalk.bold('Created'),
214
- chalk.bold('Content'),
215
- ],
253
+ head: headers,
216
254
  style: {
217
255
  head: [],
218
256
  border: [],
219
257
  },
220
- colWidths: [10, 10, 20, 12, 50],
258
+ colWidths,
221
259
  wordWrap: true,
222
260
  });
223
261
  for (const obs of filtered) {
224
262
  const id = obs._ulid.substring(0, 8);
225
- const workflow = obs.workflow_ref || '-';
226
- const created = new Date(obs.created_at).toISOString().split('T')[0];
227
- const content = obs.content.length > 47 ? obs.content.substring(0, 47) + '...' : obs.content;
228
- table.push([id, obs.type, workflow, created, content]);
263
+ const workflow = obs.workflow_ref || "-";
264
+ const created = new Date(obs.created_at).toISOString().split("T")[0];
265
+ // Adjust content truncation based on available column width
266
+ const maxContentLen = showResolved ? 37 : 47;
267
+ const content = obs.content.length > maxContentLen
268
+ ? `${obs.content.substring(0, maxContentLen)}...`
269
+ : obs.content;
270
+ // AC: @obs-list-display ac-1 - Show ✓ for resolved, ✗ for unresolved
271
+ if (showResolved) {
272
+ const resolvedIndicator = obs.resolved
273
+ ? chalk.green("✓")
274
+ : chalk.red("✗");
275
+ table.push([id, obs.type, resolvedIndicator, workflow, created, content]);
276
+ }
277
+ else {
278
+ table.push([id, obs.type, workflow, created, content]);
279
+ }
229
280
  }
230
281
  console.log(table.toString());
231
282
  }
@@ -234,12 +285,12 @@ function formatObservations(observations, showResolved) {
234
285
  */
235
286
  export function registerMetaCommands(program) {
236
287
  const meta = program
237
- .command('meta')
238
- .description('Meta-spec commands (agents, workflows, conventions, observations)');
288
+ .command("meta")
289
+ .description("Meta-spec commands (agents, workflows, conventions, observations)");
239
290
  // AC-meta-manifest-1: kspec meta show outputs summary with counts
240
291
  meta
241
- .command('show')
242
- .description('Display meta-spec summary')
292
+ .command("show")
293
+ .description("Display meta-spec summary")
243
294
  .action(async () => {
244
295
  try {
245
296
  const ctx = await initContext();
@@ -261,8 +312,8 @@ export function registerMetaCommands(program) {
261
312
  });
262
313
  // AC-agent-1, AC-agent-2: kspec meta agents
263
314
  meta
264
- .command('agents')
265
- .description('List agents defined in meta-spec')
315
+ .command("agents")
316
+ .description("List agents defined in meta-spec")
266
317
  .action(async () => {
267
318
  try {
268
319
  const ctx = await initContext();
@@ -291,10 +342,12 @@ export function registerMetaCommands(program) {
291
342
  }
292
343
  });
293
344
  // AC-workflow-1, AC-workflow-2, AC-workflow-4: kspec meta workflows
345
+ // AC: @loop-mode-workflows ac-1 (--tag loop filtering)
294
346
  meta
295
- .command('workflows')
296
- .description('List workflows defined in meta-spec')
297
- .option('--verbose', 'Show full workflow details with all steps')
347
+ .command("workflows")
348
+ .description("List workflows defined in meta-spec")
349
+ .option("--verbose", "Show full workflow details with all steps")
350
+ .option("--tag <tag>", "Filter workflows by tag (e.g., --tag loop)")
298
351
  .action(async (options) => {
299
352
  try {
300
353
  const ctx = await initContext();
@@ -303,13 +356,29 @@ export function registerMetaCommands(program) {
303
356
  process.exit(EXIT_CODES.ERROR);
304
357
  }
305
358
  const metaCtx = await loadMetaContext(ctx);
306
- const workflows = metaCtx.workflows || [];
359
+ let workflows = metaCtx.workflows || [];
360
+ // AC: @loop-mode-workflows ac-1 - Filter by tag
361
+ if (options.tag) {
362
+ workflows = workflows.filter((w) => {
363
+ // Match by explicit tags array or by mode field (for "loop" tag)
364
+ const tags = w.tags || [];
365
+ if (tags.includes(options.tag))
366
+ return true;
367
+ // Special case: --tag loop also matches mode: loop
368
+ if (options.tag === "loop" && w.mode === "loop")
369
+ return true;
370
+ return false;
371
+ });
372
+ }
307
373
  // AC-workflow-4: JSON output includes full workflow details
308
374
  output(workflows.map((workflow) => ({
309
375
  id: workflow.id,
310
376
  trigger: workflow.trigger,
311
377
  description: workflow.description,
312
378
  steps: workflow.steps,
379
+ mode: workflow.mode || "interactive",
380
+ based_on: workflow.based_on,
381
+ tags: workflow.tags || [],
313
382
  })),
314
383
  // AC-workflow-1 (table) or AC-workflow-2 (verbose)
315
384
  () => {
@@ -328,9 +397,9 @@ export function registerMetaCommands(program) {
328
397
  });
329
398
  // AC-conv-1, AC-conv-2, AC-conv-5: kspec meta conventions
330
399
  meta
331
- .command('conventions')
332
- .description('List conventions defined in meta-spec')
333
- .option('--domain <domain>', 'Filter by specific domain')
400
+ .command("conventions")
401
+ .description("List conventions defined in meta-spec")
402
+ .option("--domain <domain>", "Filter by specific domain")
334
403
  .action(async (options) => {
335
404
  try {
336
405
  const ctx = await initContext();
@@ -367,9 +436,10 @@ export function registerMetaCommands(program) {
367
436
  }
368
437
  });
369
438
  // meta-get-cmd: kspec meta get <ref>
439
+ // AC: @skill-meta-integration ac-1 - skills can be retrieved by id
370
440
  meta
371
- .command('get <ref>')
372
- .description('Get a meta item by reference (agent, workflow, convention, or observation)')
441
+ .command("get <ref>")
442
+ .description("Get a meta item by reference (agent, workflow, convention, observation, or skill)")
373
443
  .action(async (ref) => {
374
444
  try {
375
445
  const ctx = await initContext();
@@ -378,54 +448,17 @@ export function registerMetaCommands(program) {
378
448
  process.exit(EXIT_CODES.ERROR);
379
449
  }
380
450
  const metaCtx = await loadMetaContext(ctx);
381
- // Normalize reference
382
- const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
383
- // Search in all meta item types
384
- const agents = metaCtx.agents || [];
385
- const workflows = metaCtx.workflows || [];
386
- const conventions = metaCtx.conventions || [];
387
- const observations = metaCtx.observations || [];
388
- // Try to find by ID or ULID prefix
389
- let found = null;
390
- let itemType = '';
391
- // Check agents (by id or ULID)
392
- const agent = agents.find((a) => a.id === normalizedRef || a._ulid.startsWith(normalizedRef));
393
- if (agent) {
394
- found = agent;
395
- itemType = 'agent';
396
- }
397
- // Check workflows (by id or ULID)
398
- if (!found) {
399
- const workflow = workflows.find((w) => w.id === normalizedRef || w._ulid.startsWith(normalizedRef));
400
- if (workflow) {
401
- found = workflow;
402
- itemType = 'workflow';
403
- }
404
- }
405
- // Check conventions (by domain or ULID)
406
- if (!found) {
407
- const convention = conventions.find((c) => c.domain === normalizedRef || c._ulid.startsWith(normalizedRef));
408
- if (convention) {
409
- found = convention;
410
- itemType = 'convention';
411
- }
412
- }
413
- // Check observations (by ULID)
414
- if (!found) {
415
- const observation = observations.find((o) => o._ulid.startsWith(normalizedRef));
416
- if (observation) {
417
- found = observation;
418
- itemType = 'observation';
419
- }
420
- }
421
- if (!found) {
451
+ // AC: @skill-meta-integration ac-1 - Use unified resolver
452
+ const resolved = resolveMetaRef(metaCtx, ref);
453
+ if (!resolved) {
422
454
  error(errors.reference.metaNotFound(ref));
423
455
  process.exit(EXIT_CODES.ERROR);
424
456
  }
457
+ const { item: found, type: itemType } = resolved;
425
458
  // Output the item
426
459
  output(found, () => {
427
460
  console.log(chalk.bold(`${itemType.charAt(0).toUpperCase() + itemType.slice(1)}: ${ref}`));
428
- console.log(chalk.gray(''.repeat(60)));
461
+ console.log(chalk.gray("".repeat(60)));
429
462
  console.log(JSON.stringify(found, null, 2));
430
463
  });
431
464
  }
@@ -435,10 +468,11 @@ export function registerMetaCommands(program) {
435
468
  }
436
469
  });
437
470
  // meta-list-cmd: kspec meta list
471
+ // AC: @skill-meta-integration ac-2 - skills can be filtered with --type skill
438
472
  meta
439
- .command('list')
440
- .description('List all meta items')
441
- .option('--type <type>', 'Filter by type (agent, workflow, convention, observation)')
473
+ .command("list")
474
+ .description("List all meta items")
475
+ .option("--type <type>", "Filter by type (agent, workflow, convention, observation, skill)")
442
476
  .action(async (options) => {
443
477
  try {
444
478
  const ctx = await initContext();
@@ -449,58 +483,69 @@ export function registerMetaCommands(program) {
449
483
  const metaCtx = await loadMetaContext(ctx);
450
484
  const items = [];
451
485
  // Add agents
452
- if (!options.type || options.type === 'agent') {
486
+ if (!options.type || options.type === "agent") {
453
487
  for (const agent of metaCtx.agents || []) {
454
488
  items.push({
455
489
  id: agent.id,
456
- type: 'agent',
490
+ type: "agent",
457
491
  context: agent.name,
458
492
  ulid: agent._ulid,
459
493
  });
460
494
  }
461
495
  }
462
496
  // Add workflows
463
- if (!options.type || options.type === 'workflow') {
497
+ if (!options.type || options.type === "workflow") {
464
498
  for (const workflow of metaCtx.workflows || []) {
465
499
  items.push({
466
500
  id: workflow.id,
467
- type: 'workflow',
501
+ type: "workflow",
468
502
  context: workflow.trigger,
469
503
  ulid: workflow._ulid,
470
504
  });
471
505
  }
472
506
  }
473
507
  // Add conventions
474
- if (!options.type || options.type === 'convention') {
508
+ if (!options.type || options.type === "convention") {
475
509
  for (const convention of metaCtx.conventions || []) {
476
510
  items.push({
477
511
  id: convention.domain,
478
- type: 'convention',
512
+ type: "convention",
479
513
  context: `${convention.rules.length} rules`,
480
514
  ulid: convention._ulid,
481
515
  });
482
516
  }
483
517
  }
484
518
  // Add observations
485
- if (!options.type || options.type === 'observation') {
519
+ if (!options.type || options.type === "observation") {
486
520
  for (const observation of metaCtx.observations || []) {
487
521
  const ulidPrefix = observation._ulid.substring(0, 8);
488
522
  items.push({
489
523
  id: ulidPrefix,
490
- type: 'observation',
491
- context: `${observation.type} ${observation.resolved ? '(resolved)' : ''}`,
524
+ type: "observation",
525
+ context: `${observation.type} ${observation.resolved ? "(resolved)" : ""}`,
492
526
  ulid: observation._ulid,
493
527
  });
494
528
  }
495
529
  }
530
+ // AC: @skill-meta-integration ac-2 - Add skills
531
+ if (!options.type || options.type === "skill") {
532
+ for (const skill of metaCtx.skills || []) {
533
+ items.push({
534
+ id: skill.id,
535
+ type: "skill",
536
+ context: skill.name || skill.description || skill.origin,
537
+ ulid: skill._ulid,
538
+ });
539
+ }
540
+ }
496
541
  // Output
497
542
  output(items, () => {
498
543
  if (items.length === 0) {
499
- console.log(chalk.yellow('No meta items found'));
544
+ console.log(chalk.yellow("No meta items found"));
500
545
  return;
501
546
  }
502
547
  const table = new Table({
503
- head: [chalk.bold('ID'), chalk.bold('Type'), chalk.bold('Context')],
548
+ head: [chalk.bold("ID"), chalk.bold("Type"), chalk.bold("Context")],
504
549
  style: {
505
550
  head: [],
506
551
  border: [],
@@ -519,13 +564,12 @@ export function registerMetaCommands(program) {
519
564
  });
520
565
  // AC-obs-1: kspec meta observe <type> <content>
521
566
  // AC: @meta-observe-cmd from-inbox-conversion
522
- meta
523
- .command('observe [type] [content]')
524
- .description('Create an observation (friction, success, question, idea)')
525
- .option('--workflow <ref>', 'Reference to workflow this observation relates to')
526
- .option('--author <author>', 'Author of the observation')
527
- .option('--from-inbox <ref>', 'Convert inbox item to observation')
528
- .option('--type <type>', 'Override type when using --from-inbox (defaults to idea)')
567
+ markMutating(meta.command("observe [type] [content]"))
568
+ .description("Create an observation (friction, success, question, idea)")
569
+ .option("--workflow <ref>", "Reference to workflow this observation relates to")
570
+ .option("--author <author>", "Author of the observation")
571
+ .option("--from-inbox <ref>", "Convert inbox item to observation")
572
+ .option("--type <type>", "Override type when using --from-inbox (defaults to idea)")
529
573
  .action(async (type, content, options) => {
530
574
  try {
531
575
  const ctx = await initContext();
@@ -546,51 +590,65 @@ export function registerMetaCommands(program) {
546
590
  // Use inbox item content
547
591
  const observationContent = item.text;
548
592
  // Type defaults to 'idea' but can be overridden with --type flag
549
- const observationType = (options.type || 'idea');
593
+ const observationType = (options.type || "idea");
550
594
  // Validate observation type
551
- const validTypes = ['friction', 'success', 'question', 'idea'];
595
+ const validTypes = [
596
+ "friction",
597
+ "success",
598
+ "question",
599
+ "idea",
600
+ ];
552
601
  if (!validTypes.includes(observationType)) {
553
602
  error(errors.validation.invalidObservationType(observationType));
554
- console.log(`Valid types: ${validTypes.join(', ')}`);
603
+ console.log(`Valid types: ${validTypes.join(", ")}`);
555
604
  process.exit(EXIT_CODES.ERROR);
556
605
  }
557
606
  // Create observation
558
607
  const observation = createObservation(observationType, observationContent, {
559
608
  workflow_ref: options.workflow,
560
609
  author: options.author,
610
+ configAuthor: ctx.config?.identity?.author,
561
611
  });
562
612
  // Save observation
563
613
  await saveObservation(ctx, observation);
564
614
  // Delete inbox item
565
615
  const deleted = await deleteInboxItem(ctx, item._ulid);
566
616
  if (!deleted) {
567
- error('Failed to delete inbox item after creating observation');
617
+ error("Failed to delete inbox item after creating observation");
568
618
  process.exit(EXIT_CODES.ERROR);
569
619
  }
570
- await commitIfShadow(ctx.shadow, 'meta-observe-from-inbox', observation._ulid.substring(0, 8), `Convert inbox item to ${observationType} observation`);
620
+ await commitIfShadow(ctx.shadow, "meta-observe-from-inbox", observation._ulid.substring(0, 8), `Convert inbox item to ${observationType} observation`);
571
621
  // Return observation ref
572
622
  output(observation, () => success(`Created observation: ${observation._ulid.substring(0, 8)}`));
573
623
  return;
574
624
  }
575
625
  // Standard observe flow (without --from-inbox)
576
626
  if (!type || !content) {
577
- error('Type and content are required when not using --from-inbox');
627
+ error("Type and content are required when not using --from-inbox");
578
628
  process.exit(EXIT_CODES.ERROR);
579
629
  }
580
630
  // Validate observation type
581
- const validTypes = ['friction', 'success', 'question', 'idea'];
631
+ const validTypes = [
632
+ "friction",
633
+ "success",
634
+ "question",
635
+ "idea",
636
+ ];
582
637
  if (!validTypes.includes(type)) {
583
638
  error(errors.validation.invalidObservationType(type));
584
- console.log(`Valid types: ${validTypes.join(', ')}`);
639
+ console.log(`Valid types: ${validTypes.join(", ")}`);
585
640
  process.exit(EXIT_CODES.ERROR);
586
641
  }
587
642
  // Create observation
588
643
  const observation = createObservation(type, content, {
589
644
  workflow_ref: options.workflow,
590
645
  author: options.author,
646
+ configAuthor: ctx.config?.identity?.author,
591
647
  });
592
648
  // Save to manifest
593
649
  await saveObservation(ctx, observation);
650
+ // AC: @trait-shadow-commit ac-1
651
+ await commitIfShadow(ctx.shadow, "meta-observe", observation._ulid.substring(0, 8), type);
594
652
  // AC-obs-1: outputs "OK Created observation: <ULID-prefix>"
595
653
  // In JSON mode, return the created observation object
596
654
  output(observation, () => success(`Created observation: ${observation._ulid.substring(0, 8)}`));
@@ -601,14 +659,16 @@ export function registerMetaCommands(program) {
601
659
  }
602
660
  });
603
661
  // AC-obs-2, AC-obs-5: kspec meta observations
662
+ // AC: @observation-content-search ac-search-flag, ac-regex-support, ac-combined-filters
604
663
  meta
605
- .command('observations')
606
- .description('List observations (shows unresolved by default)')
607
- .option('--type <type>', 'Filter by observation type (friction/success/question/idea)')
608
- .option('--workflow <ref>', 'Filter by workflow reference')
609
- .option('--all', 'Include resolved observations')
610
- .option('--promoted', 'Show only observations promoted to tasks')
611
- .option('--pending-resolution', 'Show observations with completed tasks awaiting resolution')
664
+ .command("observations")
665
+ .description("List observations (shows unresolved by default)")
666
+ .option("--type <type>", "Filter by observation type (friction/success/question/idea)")
667
+ .option("--workflow <ref>", "Filter by workflow reference")
668
+ .option("--all", "Include resolved observations")
669
+ .option("--promoted", "Show only observations promoted to tasks")
670
+ .option("--pending-resolution", "Show observations with completed tasks awaiting resolution")
671
+ .option("--search <pattern>", "Search observations by regex pattern (matches content)")
612
672
  .action(async (options) => {
613
673
  try {
614
674
  const ctx = await initContext();
@@ -641,7 +701,18 @@ export function registerMetaCommands(program) {
641
701
  return false;
642
702
  const item = taskResult.item;
643
703
  // Type guard: check if item is a task (has status and depends_on properties)
644
- return 'status' in item && 'depends_on' in item && item.status === 'completed';
704
+ return ("status" in item &&
705
+ "depends_on" in item &&
706
+ item.status === "completed");
707
+ });
708
+ }
709
+ // AC: @observation-content-search ac-search-flag, ac-regex-support, ac-combined-filters, ac-search-all-fields
710
+ // Apply --search filter using regex pattern (consistent with kspec search behavior)
711
+ if (options.search) {
712
+ const { grepItem } = await import("../../utils/grep.js");
713
+ observations = observations.filter((obs) => {
714
+ const match = grepItem(obs, options.search);
715
+ return match !== null;
645
716
  });
646
717
  }
647
718
  // AC-obs-5: JSON output includes full observation objects
@@ -667,12 +738,11 @@ export function registerMetaCommands(program) {
667
738
  }
668
739
  });
669
740
  // AC-obs-3, AC-obs-6, AC-obs-8: kspec meta promote
670
- meta
671
- .command('promote <ref>')
672
- .description('Promote observation to a task')
673
- .requiredOption('--title <title>', 'Task title')
674
- .option('--priority <priority>', 'Task priority (1-5)', '2')
675
- .option('--force', 'Force promotion even if observation is resolved')
741
+ markMutating(meta.command("promote <ref>"))
742
+ .description("Promote observation to a task")
743
+ .requiredOption("--title <title>", "Task title")
744
+ .option("--priority <priority>", "Task priority (1-5)", "2")
745
+ .option("--force", "Force promotion even if observation is resolved")
676
746
  .action(async (ref, options) => {
677
747
  try {
678
748
  const ctx = await initContext();
@@ -681,14 +751,17 @@ export function registerMetaCommands(program) {
681
751
  process.exit(EXIT_CODES.ERROR);
682
752
  }
683
753
  const metaCtx = await loadMetaContext(ctx);
684
- const observations = metaCtx.observations || [];
685
- // Find observation
686
- const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
687
- const observation = observations.find((o) => o._ulid.startsWith(normalizedRef));
688
- if (!observation) {
754
+ // Use unified resolver - promotes only observations
755
+ const resolved = resolveMetaRef(metaCtx, ref);
756
+ if (!resolved) {
689
757
  error(errors.reference.observationNotFound(ref));
690
758
  process.exit(EXIT_CODES.ERROR);
691
759
  }
760
+ if (resolved.type !== "observation") {
761
+ error(`Cannot promote ${resolved.type}. Only observations can be promoted to tasks.`);
762
+ process.exit(EXIT_CODES.ERROR);
763
+ }
764
+ const observation = resolved.item;
692
765
  // AC-obs-6: Check if already promoted
693
766
  if (observation.promoted_to) {
694
767
  error(errors.conflict.observationAlreadyPromoted(observation.promoted_to));
@@ -699,21 +772,33 @@ export function registerMetaCommands(program) {
699
772
  error(errors.operation.cannotPromoteResolved);
700
773
  process.exit(EXIT_CODES.ERROR);
701
774
  }
775
+ // Validate priority
776
+ const priorityResult = parseIntOption(options.priority, {
777
+ min: 1,
778
+ max: 5,
779
+ name: "Priority",
780
+ });
781
+ if (!priorityResult.ok) {
782
+ error(priorityResult.error);
783
+ process.exit(EXIT_CODES.VALIDATION_FAILED);
784
+ }
702
785
  // AC-obs-3: Create task with title, description from observation, meta_ref, and origin
703
786
  const task = createTask({
704
787
  title: options.title,
705
788
  description: observation.content,
706
- priority: Number.parseInt(options.priority, 10),
789
+ priority: priorityResult.value,
707
790
  meta_ref: observation.workflow_ref,
708
- origin: 'observation_promotion',
791
+ origin: "observation_promotion",
709
792
  });
710
793
  // Save task
711
794
  await saveTask(ctx, task);
712
- await commitIfShadow(ctx.shadow, 'task-add', task.slugs[0] || task._ulid.slice(0, 8), task.title);
795
+ await commitIfShadow(ctx.shadow, "task-add", task.slugs[0] || task._ulid.slice(0, 8), task.title);
713
796
  const taskRef = `@${task._ulid.substring(0, 8)}`;
714
797
  // Update observation with promoted_to field
715
798
  observation.promoted_to = taskRef;
716
799
  await saveObservation(ctx, observation);
800
+ // AC: @trait-shadow-commit ac-1
801
+ await commitIfShadow(ctx.shadow, "observation-promote", observation._ulid.substring(0, 8));
717
802
  // AC-obs-3: outputs "OK Created task: <ULID-prefix>"
718
803
  // In JSON mode, return the created task object
719
804
  output(task, () => success(`Created task: ${taskRef.substring(0, 9)}`));
@@ -724,10 +809,16 @@ export function registerMetaCommands(program) {
724
809
  }
725
810
  });
726
811
  // AC-obs-4, AC-obs-7, AC-obs-9: kspec meta resolve
727
- meta
728
- .command('resolve <ref> [resolution]')
729
- .description('Resolve an observation')
730
- .action(async (ref, resolution) => {
812
+ // AC: @trait-multi-ref-batch - Batch support with --refs flag
813
+ markMutating(meta.command("resolve [ref] [resolution]"))
814
+ .description("Resolve an observation (or multiple with --refs)")
815
+ .option("--refs <refs...>", "Resolve multiple observations by ref")
816
+ .option("--resolution <text>", "Resolution text (required for batch mode unless observations have promoted tasks)")
817
+ .addHelpText("after", `
818
+ Examples:
819
+ $ kspec meta resolve @obs-ref "Fixed in PR #123"
820
+ $ kspec meta resolve --refs @obs1 @obs2 --resolution "Resolved in batch"`)
821
+ .action(async (ref, resolutionArg, options) => {
731
822
  try {
732
823
  const ctx = await initContext();
733
824
  if (!ctx.manifestPath) {
@@ -736,69 +827,100 @@ export function registerMetaCommands(program) {
736
827
  }
737
828
  const metaCtx = await loadMetaContext(ctx);
738
829
  const observations = metaCtx.observations || [];
739
- // Find observation
740
- const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
741
- const observation = observations.find((o) => o._ulid.startsWith(normalizedRef));
742
- if (!observation) {
743
- error(errors.reference.observationNotFound(ref));
744
- process.exit(EXIT_CODES.ERROR);
745
- }
746
- // AC-obs-7: Check if already resolved
747
- if (observation.resolved) {
748
- const resolvedDate = new Date(observation.resolved_at).toISOString().split('T')[0];
749
- const resolutionText = observation.resolution || '';
750
- const truncated = resolutionText.length > 50
751
- ? resolutionText.substring(0, 50) + '...'
752
- : resolutionText;
753
- error(errors.conflict.observationAlreadyResolved(resolvedDate, truncated));
754
- process.exit(EXIT_CODES.CONFLICT);
755
- }
756
- // AC-obs-9: Auto-populate resolution from task completion if promoted
757
- let finalResolution = resolution;
758
- if (!finalResolution && observation.promoted_to) {
759
- // Fetch task to get completion reason
760
- const tasks = await loadAllTasks(ctx);
761
- const items = await loadAllItems(ctx);
762
- const index = new ReferenceIndex(tasks, items);
763
- const taskResult = index.resolve(observation.promoted_to);
764
- if (taskResult.ok) {
765
- const item = taskResult.item;
766
- // Type guard: ensure this is a task (has status and depends_on properties)
767
- if ('status' in item && 'depends_on' in item) {
768
- const task = item;
769
- if (task.status === 'completed' && task.closed_reason) {
770
- finalResolution = `Resolved via task ${observation.promoted_to}: ${task.closed_reason}`;
771
- }
772
- else if (task.status === 'completed') {
773
- finalResolution = `Resolved via task ${observation.promoted_to}`;
830
+ // Load tasks/items for auto-resolution from promoted tasks
831
+ const tasks = await loadAllTasks(ctx);
832
+ const items = await loadAllItems(ctx);
833
+ const index = new ReferenceIndex(tasks, items);
834
+ // Resolution can come from positional arg or --resolution flag
835
+ const resolution = resolutionArg || options.resolution;
836
+ // AC: @trait-multi-ref-batch ac-8 - Deduplicate refs
837
+ const refsFlag = options.refs
838
+ ? [...new Set(options.refs)]
839
+ : undefined;
840
+ // AC: @trait-multi-ref-batch ac-1, ac-2, ac-3, ac-4, ac-5
841
+ const result = await executeBatchOperation({
842
+ positionalRef: ref,
843
+ refsFlag,
844
+ context: { ctx, observations, tasks, items, index, resolution },
845
+ items: observations,
846
+ index: index,
847
+ resolveRef: (refStr, obsList) => {
848
+ return resolveObservationRefForBatch(refStr, obsList);
849
+ },
850
+ executeOperation: async (observation, { ctx, tasks, items, index, resolution }) => {
851
+ // AC-obs-7: Check if already resolved
852
+ if (observation.resolved) {
853
+ const resolvedDate = new Date(observation.resolved_at)
854
+ .toISOString()
855
+ .split("T")[0];
856
+ const resolutionText = observation.resolution || "";
857
+ const truncated = resolutionText.length > 50
858
+ ? `${resolutionText.substring(0, 50)}...`
859
+ : resolutionText;
860
+ return {
861
+ success: false,
862
+ error: `Already resolved on ${resolvedDate}: ${truncated}`,
863
+ };
864
+ }
865
+ // AC-obs-9: Auto-populate resolution from task completion if promoted
866
+ let finalResolution = resolution;
867
+ if (!finalResolution && observation.promoted_to) {
868
+ const taskResult = index.resolve(observation.promoted_to);
869
+ if (taskResult.ok) {
870
+ const item = taskResult.item;
871
+ // Type guard: ensure this is a task
872
+ if ("status" in item && "depends_on" in item) {
873
+ const task = item;
874
+ if (task.status === "completed" && task.closed_reason) {
875
+ finalResolution = `Resolved via task ${observation.promoted_to}: ${task.closed_reason}`;
876
+ }
877
+ else if (task.status === "completed") {
878
+ finalResolution = `Resolved via task ${observation.promoted_to}`;
879
+ }
880
+ else {
881
+ return {
882
+ success: false,
883
+ error: `Task ${observation.promoted_to} is not completed yet`,
884
+ };
885
+ }
886
+ }
887
+ else {
888
+ return {
889
+ success: false,
890
+ error: `Reference ${observation.promoted_to} is not a task`,
891
+ };
892
+ }
774
893
  }
775
894
  else {
776
- error(`Task ${observation.promoted_to} is not completed yet`);
777
- process.exit(EXIT_CODES.ERROR);
895
+ return {
896
+ success: false,
897
+ error: `Task ${observation.promoted_to} not found`,
898
+ };
778
899
  }
779
900
  }
780
- else {
781
- error(`Reference ${observation.promoted_to} is not a task`);
782
- process.exit(EXIT_CODES.ERROR);
901
+ if (!finalResolution) {
902
+ return {
903
+ success: false,
904
+ error: "Resolution text required",
905
+ };
783
906
  }
784
- }
785
- else {
786
- error(`Task ${observation.promoted_to} not found`);
787
- process.exit(EXIT_CODES.ERROR);
788
- }
789
- }
790
- if (!finalResolution) {
791
- error(errors.validation.resolutionRequired);
792
- process.exit(EXIT_CODES.ERROR);
793
- }
794
- // AC-obs-4: Update observation
795
- observation.resolved = true;
796
- observation.resolution = finalResolution;
797
- observation.resolved_at = new Date().toISOString();
798
- observation.resolved_by = observation.author; // Use same author
799
- await saveObservation(ctx, observation);
800
- // AC-obs-4: outputs "OK Resolved: <ULID-prefix>"
801
- success(`Resolved: ${observation._ulid.substring(0, 8)}`);
907
+ // AC-obs-4: Update observation
908
+ observation.resolved = true;
909
+ observation.resolution = finalResolution;
910
+ observation.resolved_at = new Date().toISOString();
911
+ observation.resolved_by = observation.author;
912
+ await saveObservation(ctx, observation);
913
+ // AC: @trait-shadow-commit ac-1
914
+ await commitIfShadow(ctx.shadow, "observation-resolve", observation._ulid.substring(0, 8));
915
+ return {
916
+ success: true,
917
+ message: `Resolved: ${observation._ulid.substring(0, 8)}`,
918
+ };
919
+ },
920
+ getUlid: (obs) => obs._ulid,
921
+ });
922
+ // AC: @trait-multi-ref-batch ac-5, ac-7 - Output formatting
923
+ formatBatchOutput(result, "Resolve");
802
924
  }
803
925
  catch (err) {
804
926
  error(errors.failures.resolveObservation, err);
@@ -806,23 +928,31 @@ export function registerMetaCommands(program) {
806
928
  }
807
929
  });
808
930
  // Meta add command - create new meta items
809
- meta
810
- .command('add <type>')
811
- .description('Create a new meta item (agent, workflow, or convention)')
812
- .option('--id <id>', 'Semantic ID (required for agents and workflows)')
813
- .option('--domain <domain>', 'Domain (required for conventions)')
814
- .option('--name <name>', 'Name (for agents)')
815
- .option('--trigger <trigger>', 'Trigger (for workflows)')
816
- .option('--description <desc>', 'Description')
817
- .option('--capability <cap...>', 'Capabilities (for agents)')
818
- .option('--tool <tool...>', 'Tools (for agents)')
819
- .option('--convention <conv...>', 'Convention references (for agents)')
820
- .option('--rule <rule...>', 'Rules (for conventions)')
931
+ markMutating(meta.command("add <type>"))
932
+ .description("Create a new meta item (agent, workflow, or convention)")
933
+ .option("--id <id>", "Semantic ID (required for agents and workflows)")
934
+ .option("--domain <domain>", "Domain (required for conventions)")
935
+ .option("--name <name>", "Name (for agents)")
936
+ .option("--trigger <trigger>", "Trigger (for workflows)")
937
+ .option("--description <desc>", "Description")
938
+ .option("--capability <cap...>", "Capabilities (for agents)")
939
+ .option("--tool <tool...>", "Tools (for agents)")
940
+ .option("--convention <conv...>", "Convention references (for agents)")
941
+ .option("--rule <rule...>", "Rules (for conventions)")
942
+ .option("--steps <json>", "Workflow steps as JSON array (for workflows)")
943
+ .option("--mode <mode>", "Workflow mode: interactive (default) or loop (for workflows)")
944
+ .option("--based-on <ref>", "Base workflow reference (for loop workflows)")
945
+ .option("--tag <tag...>", "Tags for the workflow (for workflows)")
946
+ .addHelpText("after", `
947
+ Examples:
948
+ $ kspec meta add agent --id my-agent --name "My Agent" --capability search code
949
+ $ kspec meta add convention --domain testing --rule "Always test" --rule "Use mocks"
950
+ $ kspec meta add workflow --id my-flow --trigger manual --tag automation ci`)
821
951
  .action(async (type, options) => {
822
952
  try {
823
953
  const ctx = await initContext();
824
954
  // Validate type
825
- const validTypes = ['agent', 'workflow', 'convention'];
955
+ const validTypes = ["agent", "workflow", "convention"];
826
956
  if (!validTypes.includes(type)) {
827
957
  error(errors.validation.invalidType(type, validTypes));
828
958
  process.exit(EXIT_CODES.ERROR);
@@ -831,7 +961,7 @@ export function registerMetaCommands(program) {
831
961
  const itemUlid = ulid();
832
962
  // Create the item based on type
833
963
  let item;
834
- if (type === 'agent') {
964
+ if (type === "agent") {
835
965
  // Validate required fields
836
966
  if (!options.id) {
837
967
  error(errors.validation.agentRequiresId);
@@ -845,13 +975,13 @@ export function registerMetaCommands(program) {
845
975
  _ulid: itemUlid,
846
976
  id: options.id,
847
977
  name: options.name,
848
- description: options.description || '',
978
+ description: options.description || "",
849
979
  capabilities: options.capability || [],
850
980
  tools: options.tool || [],
851
981
  conventions: options.convention || [],
852
982
  };
853
983
  }
854
- else if (type === 'workflow') {
984
+ else if (type === "workflow") {
855
985
  // Validate required fields
856
986
  if (!options.id) {
857
987
  error(errors.validation.workflowRequiresId);
@@ -861,12 +991,50 @@ export function registerMetaCommands(program) {
861
991
  error(errors.validation.workflowRequiresTrigger);
862
992
  process.exit(EXIT_CODES.ERROR);
863
993
  }
994
+ // Parse and validate --steps if provided
995
+ let steps = [];
996
+ if (options.steps) {
997
+ // AC: @meta-add-cmd ac-2 - Parse JSON
998
+ let parsedSteps;
999
+ try {
1000
+ parsedSteps = JSON.parse(options.steps);
1001
+ }
1002
+ catch (err) {
1003
+ const message = err instanceof Error ? err.message : String(err);
1004
+ error(errors.validation.invalidStepsJson(message));
1005
+ process.exit(EXIT_CODES.ERROR);
1006
+ }
1007
+ // AC: @meta-add-cmd ac-3 - Verify it's an array
1008
+ if (!Array.isArray(parsedSteps)) {
1009
+ error(errors.validation.stepsNotArray);
1010
+ process.exit(EXIT_CODES.ERROR);
1011
+ }
1012
+ // AC: @meta-add-cmd ac-4 - Validate with schema
1013
+ const result = z.array(WorkflowStepSchema).safeParse(parsedSteps);
1014
+ if (!result.success) {
1015
+ const issues = result.error.issues
1016
+ .map((i) => `${i.path.join(".")}: ${i.message}`)
1017
+ .join("; ");
1018
+ error(errors.validation.invalidStepsSchema(issues));
1019
+ process.exit(EXIT_CODES.ERROR);
1020
+ }
1021
+ steps = result.data;
1022
+ }
1023
+ // Validate mode if provided
1024
+ const validModes = ["interactive", "loop"];
1025
+ if (options.mode && !validModes.includes(options.mode)) {
1026
+ error(`Invalid mode: ${options.mode}. Valid modes: ${validModes.join(", ")}`);
1027
+ process.exit(EXIT_CODES.ERROR);
1028
+ }
864
1029
  item = {
865
1030
  _ulid: itemUlid,
866
1031
  id: options.id,
867
1032
  trigger: options.trigger,
868
- description: options.description || '',
869
- steps: [],
1033
+ description: options.description || "",
1034
+ steps,
1035
+ ...(options.mode && { mode: options.mode }),
1036
+ ...(options.basedOn && { based_on: options.basedOn }),
1037
+ ...(options.tag && options.tag.length > 0 && { tags: parseTagsArray(options.tag) }),
870
1038
  };
871
1039
  }
872
1040
  else {
@@ -884,12 +1052,14 @@ export function registerMetaCommands(program) {
884
1052
  }
885
1053
  // Save the item
886
1054
  await saveMetaItem(ctx, item, type);
1055
+ // AC: @trait-shadow-commit ac-1
1056
+ await commitIfShadow(ctx.shadow, `meta-add-${type}`, itemUlid.substring(0, 8), "id" in item ? item.id : "domain" in item ? item.domain : undefined);
887
1057
  if (isJsonMode()) {
888
1058
  // In JSON mode, output the item data directly
889
1059
  console.log(JSON.stringify(item, null, 2));
890
1060
  }
891
1061
  else {
892
- const idOrDomain = 'id' in item ? item.id : 'domain' in item ? item.domain : itemUlid;
1062
+ const idOrDomain = "id" in item ? item.id : "domain" in item ? item.domain : itemUlid;
893
1063
  success(`Created ${type}: ${idOrDomain} (@${itemUlid.substring(0, 8)})`);
894
1064
  }
895
1065
  }
@@ -899,55 +1069,35 @@ export function registerMetaCommands(program) {
899
1069
  }
900
1070
  });
901
1071
  // Meta set command - update existing meta items
902
- meta
903
- .command('set <ref>')
904
- .description('Update an existing meta item')
905
- .option('--name <name>', 'Update name (for agents)')
906
- .option('--description <desc>', 'Update description')
907
- .option('--trigger <trigger>', 'Update trigger (for workflows)')
908
- .option('--add-capability <cap>', 'Add capability (for agents)')
909
- .option('--add-tool <tool>', 'Add tool (for agents)')
910
- .option('--add-convention <conv>', 'Add convention reference (for agents)')
911
- .option('--add-rule <rule>', 'Add rule (for conventions)')
1072
+ markMutating(meta.command("set <ref>"))
1073
+ .description("Update an existing meta item")
1074
+ .option("--name <name>", "Update name (for agents)")
1075
+ .option("--description <desc>", "Update description")
1076
+ .option("--trigger <trigger>", "Update trigger (for workflows)")
1077
+ .option("--add-capability <cap>", "Add capability (for agents)")
1078
+ .option("--add-tool <tool>", "Add tool (for agents)")
1079
+ .option("--add-convention <conv>", "Add convention reference (for agents)")
1080
+ .option("--add-rule <rule>", "Add rule (for conventions)")
912
1081
  .action(async (ref, options) => {
913
1082
  try {
914
1083
  const ctx = await initContext();
915
1084
  const metaCtx = await loadMetaContext(ctx);
916
- // Find the item using unified lookup
917
- const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
918
- let found = null;
919
- let itemType = null;
920
- // Search in agents
921
- const agents = metaCtx.manifest?.agents || [];
922
- const agent = agents.find((a) => a.id === normalizedRef || a._ulid.startsWith(normalizedRef));
923
- if (agent) {
924
- found = agent;
925
- itemType = 'agent';
926
- }
927
- // Search in workflows
928
- if (!found) {
929
- const workflows = metaCtx.manifest?.workflows || [];
930
- const workflow = workflows.find((w) => w.id === normalizedRef || w._ulid.startsWith(normalizedRef));
931
- if (workflow) {
932
- found = workflow;
933
- itemType = 'workflow';
934
- }
935
- }
936
- // Search in conventions
937
- if (!found) {
938
- const conventions = metaCtx.manifest?.conventions || [];
939
- const convention = conventions.find((c) => c.domain === normalizedRef || c._ulid.startsWith(normalizedRef));
940
- if (convention) {
941
- found = convention;
942
- itemType = 'convention';
943
- }
944
- }
945
- if (!found || !itemType) {
1085
+ // Use unified resolver
1086
+ const resolved = resolveMetaRef(metaCtx, ref);
1087
+ if (!resolved) {
946
1088
  error(errors.reference.metaNotFound(ref));
947
1089
  process.exit(EXIT_CODES.ERROR);
948
1090
  }
1091
+ // meta set only supports agent, workflow, convention
1092
+ // Skills have their own `kspec skill set` command
1093
+ const { item, type: itemType } = resolved;
1094
+ if (itemType !== "agent" && itemType !== "workflow" && itemType !== "convention") {
1095
+ error(`Cannot use 'meta set' with ${itemType}. Use 'kspec ${itemType === "skill" ? "skill" : "meta"} ${itemType === "observation" ? "resolve" : "set"} ${ref}' instead.`);
1096
+ process.exit(EXIT_CODES.ERROR);
1097
+ }
1098
+ const found = item;
949
1099
  // Update fields based on type
950
- if (itemType === 'agent') {
1100
+ if (itemType === "agent") {
951
1101
  const item = found;
952
1102
  if (options.name)
953
1103
  item.name = options.name;
@@ -969,7 +1119,7 @@ export function registerMetaCommands(program) {
969
1119
  }
970
1120
  }
971
1121
  }
972
- else if (itemType === 'workflow') {
1122
+ else if (itemType === "workflow") {
973
1123
  const item = found;
974
1124
  if (options.trigger)
975
1125
  item.trigger = options.trigger;
@@ -987,14 +1137,16 @@ export function registerMetaCommands(program) {
987
1137
  }
988
1138
  // Save the updated item
989
1139
  await saveMetaItem(ctx, found, itemType);
1140
+ // AC: @trait-shadow-commit ac-1
1141
+ await commitIfShadow(ctx.shadow, `meta-set-${itemType}`, found._ulid.substring(0, 8));
990
1142
  if (isJsonMode()) {
991
1143
  // In JSON mode, output the item data directly
992
1144
  console.log(JSON.stringify(found, null, 2));
993
1145
  }
994
1146
  else {
995
- const idOrDomain = itemType === 'agent'
1147
+ const idOrDomain = itemType === "agent"
996
1148
  ? found.id
997
- : itemType === 'workflow'
1149
+ : itemType === "workflow"
998
1150
  ? found.id
999
1151
  : found.domain;
1000
1152
  success(`Updated ${itemType}: ${idOrDomain}`);
@@ -1006,60 +1158,40 @@ export function registerMetaCommands(program) {
1006
1158
  }
1007
1159
  });
1008
1160
  // Meta delete command - delete meta items
1009
- meta
1010
- .command('delete <ref>')
1011
- .description('Delete a meta item')
1012
- .option('--confirm', 'Skip confirmation prompt')
1161
+ markMutating(meta.command("delete <ref>"))
1162
+ .description("Delete a meta item")
1163
+ .option("--confirm", "Skip confirmation prompt")
1013
1164
  .action(async (ref, options) => {
1014
1165
  try {
1015
1166
  const ctx = await initContext();
1016
1167
  const metaCtx = await loadMetaContext(ctx);
1017
- // Find the item to determine type
1018
- const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
1019
- let itemType = null;
1020
- let itemUlid = null;
1021
- let itemLabel = null;
1022
- // Search in agents
1023
- const agents = metaCtx.manifest?.agents || [];
1024
- const agent = agents.find((a) => a.id === normalizedRef || a._ulid.startsWith(normalizedRef));
1025
- if (agent) {
1026
- itemType = 'agent';
1027
- itemUlid = agent._ulid;
1028
- itemLabel = `agent ${agent.id}`;
1029
- }
1030
- // Search in workflows
1031
- if (!itemType) {
1032
- const workflows = metaCtx.manifest?.workflows || [];
1033
- const workflow = workflows.find((w) => w.id === normalizedRef || w._ulid.startsWith(normalizedRef));
1034
- if (workflow) {
1035
- itemType = 'workflow';
1036
- itemUlid = workflow._ulid;
1037
- itemLabel = `workflow ${workflow.id}`;
1038
- }
1168
+ // Use unified resolver
1169
+ const resolved = resolveMetaRef(metaCtx, ref);
1170
+ if (!resolved) {
1171
+ error(errors.reference.metaNotFound(ref));
1172
+ process.exit(EXIT_CODES.ERROR);
1039
1173
  }
1040
- // Search in conventions
1041
- if (!itemType) {
1042
- const conventions = metaCtx.manifest?.conventions || [];
1043
- const convention = conventions.find((c) => c.domain === normalizedRef || c._ulid.startsWith(normalizedRef));
1044
- if (convention) {
1045
- itemType = 'convention';
1046
- itemUlid = convention._ulid;
1047
- itemLabel = `convention ${convention.domain}`;
1048
- }
1174
+ // meta delete does not support skills - they use `kspec skill delete`
1175
+ if (resolved.type === "skill") {
1176
+ error(`Cannot use 'meta delete' with skills. Use 'kspec skill delete ${ref}' instead.`);
1177
+ process.exit(EXIT_CODES.ERROR);
1049
1178
  }
1050
- // Search in observations
1051
- if (!itemType) {
1052
- const observations = metaCtx.observations || [];
1053
- const observation = observations.find((o) => o._ulid.startsWith(normalizedRef));
1054
- if (observation) {
1055
- itemType = 'observation';
1056
- itemUlid = observation._ulid;
1057
- itemLabel = `observation ${observation._ulid.substring(0, 8)}`;
1058
- }
1179
+ const itemType = resolved.type;
1180
+ const itemUlid = resolved.ulid;
1181
+ // Build human-readable label for the item
1182
+ let itemLabel;
1183
+ const item = resolved.item;
1184
+ if (itemType === "agent" && "id" in item) {
1185
+ itemLabel = `agent ${item.id}`;
1059
1186
  }
1060
- if (!itemType || !itemUlid || !itemLabel) {
1061
- error(errors.reference.metaNotFound(ref));
1062
- process.exit(EXIT_CODES.ERROR);
1187
+ else if (itemType === "workflow" && "id" in item) {
1188
+ itemLabel = `workflow ${item.id}`;
1189
+ }
1190
+ else if (itemType === "convention" && "domain" in item) {
1191
+ itemLabel = `convention ${item.domain}`;
1192
+ }
1193
+ else {
1194
+ itemLabel = `observation ${itemUlid.substring(0, 8)}`;
1063
1195
  }
1064
1196
  // Check for dangling references (unless --confirm is used to override)
1065
1197
  if (!options.confirm) {
@@ -1076,12 +1208,12 @@ export function registerMetaCommands(program) {
1076
1208
  if (referencingTasks.length > 0) {
1077
1209
  const taskRefs = referencingTasks
1078
1210
  .map((t) => `@${t.slugs?.[0] || t._ulid.substring(0, 8)}`)
1079
- .join(', ');
1211
+ .join(", ");
1080
1212
  error(errors.operation.cannotDeleteReferencedByTasks(itemLabel, referencingTasks.length, taskRefs));
1081
1213
  process.exit(EXIT_CODES.ERROR);
1082
1214
  }
1083
1215
  // Check observations with workflow_ref (only for workflows)
1084
- if (itemType === 'workflow') {
1216
+ if (itemType === "workflow") {
1085
1217
  const observations = metaCtx.observations || [];
1086
1218
  const referencingObservations = observations.filter((o) => {
1087
1219
  if (!o.workflow_ref)
@@ -1094,7 +1226,7 @@ export function registerMetaCommands(program) {
1094
1226
  if (referencingObservations.length > 0) {
1095
1227
  const obsRefs = referencingObservations
1096
1228
  .map((o) => `@${o._ulid.substring(0, 8)}`)
1097
- .join(', ');
1229
+ .join(", ");
1098
1230
  error(errors.operation.cannotDeleteReferencedByObservations(itemLabel, referencingObservations.length, obsRefs));
1099
1231
  process.exit(EXIT_CODES.ERROR);
1100
1232
  }
@@ -1109,6 +1241,8 @@ export function registerMetaCommands(program) {
1109
1241
  error(errors.operation.deleteItemFailed(itemLabel));
1110
1242
  process.exit(EXIT_CODES.ERROR);
1111
1243
  }
1244
+ // AC: @trait-shadow-commit ac-1
1245
+ await commitIfShadow(ctx.shadow, `meta-delete-${itemType}`, itemUlid.substring(0, 8));
1112
1246
  success(`Deleted ${itemLabel}`);
1113
1247
  }
1114
1248
  catch (err) {
@@ -1117,10 +1251,9 @@ export function registerMetaCommands(program) {
1117
1251
  }
1118
1252
  });
1119
1253
  // meta-focus-cmd: kspec meta focus [ref]
1120
- meta
1121
- .command('focus [ref]')
1122
- .description('Get or set session focus')
1123
- .option('--clear', 'Clear current focus')
1254
+ markMutating(meta.command("focus [ref]"))
1255
+ .description("Get or set session focus")
1256
+ .option("--clear", "Clear current focus")
1124
1257
  .action(async (ref, options) => {
1125
1258
  try {
1126
1259
  const ctx = await initContext();
@@ -1133,7 +1266,7 @@ export function registerMetaCommands(program) {
1133
1266
  if (options.clear) {
1134
1267
  sessionCtx.focus = null;
1135
1268
  await saveSessionContext(ctx, sessionCtx);
1136
- output({ focus: null }, () => success('Cleared session focus'));
1269
+ output({ focus: null }, () => success("Cleared session focus"));
1137
1270
  return;
1138
1271
  }
1139
1272
  // Show current focus
@@ -1143,13 +1276,13 @@ export function registerMetaCommands(program) {
1143
1276
  console.log(`Current focus: ${sessionCtx.focus}`);
1144
1277
  }
1145
1278
  else {
1146
- console.log(chalk.yellow('No focus set'));
1279
+ console.log(chalk.yellow("No focus set"));
1147
1280
  }
1148
1281
  });
1149
1282
  return;
1150
1283
  }
1151
1284
  // Set focus to ref
1152
- sessionCtx.focus = ref.startsWith('@') ? ref : `@${ref}`;
1285
+ sessionCtx.focus = normalizeRefInput(ref);
1153
1286
  await saveSessionContext(ctx, sessionCtx);
1154
1287
  output({ focus: sessionCtx.focus }, () => success(`Set focus to: ${sessionCtx.focus}`));
1155
1288
  }
@@ -1159,9 +1292,8 @@ export function registerMetaCommands(program) {
1159
1292
  }
1160
1293
  });
1161
1294
  // meta-thread-cmd: kspec meta thread <action> [text]
1162
- meta
1163
- .command('thread <action> [text]')
1164
- .description('Manage active threads')
1295
+ markMutating(meta.command("thread <action> [text]"))
1296
+ .description("Manage active threads")
1165
1297
  .action(async (action, text) => {
1166
1298
  try {
1167
1299
  const ctx = await initContext();
@@ -1171,13 +1303,13 @@ export function registerMetaCommands(program) {
1171
1303
  }
1172
1304
  const sessionCtx = await loadSessionContext(ctx);
1173
1305
  // List threads
1174
- if (action === 'list') {
1306
+ if (action === "list") {
1175
1307
  output({ threads: sessionCtx.threads }, () => {
1176
1308
  if (sessionCtx.threads.length === 0) {
1177
- console.log(chalk.yellow('No active threads'));
1309
+ console.log(chalk.yellow("No active threads"));
1178
1310
  }
1179
1311
  else {
1180
- console.log('Active threads:');
1312
+ console.log("Active threads:");
1181
1313
  sessionCtx.threads.forEach((thread, idx) => {
1182
1314
  console.log(` ${idx + 1}. ${thread}`);
1183
1315
  });
@@ -1186,16 +1318,16 @@ export function registerMetaCommands(program) {
1186
1318
  return;
1187
1319
  }
1188
1320
  // Clear all threads
1189
- if (action === 'clear') {
1321
+ if (action === "clear") {
1190
1322
  sessionCtx.threads = [];
1191
1323
  await saveSessionContext(ctx, sessionCtx);
1192
- output({ threads: [] }, () => success('Cleared all threads'));
1324
+ output({ threads: [] }, () => success("Cleared all threads"));
1193
1325
  return;
1194
1326
  }
1195
1327
  // Add thread
1196
- if (action === 'add') {
1328
+ if (action === "add") {
1197
1329
  if (!text) {
1198
- error('Thread text is required for add action');
1330
+ error("Thread text is required for add action");
1199
1331
  process.exit(EXIT_CODES.ERROR);
1200
1332
  }
1201
1333
  sessionCtx.threads.push(text);
@@ -1204,13 +1336,15 @@ export function registerMetaCommands(program) {
1204
1336
  return;
1205
1337
  }
1206
1338
  // Remove thread by index (1-based)
1207
- if (action === 'remove') {
1339
+ if (action === "remove") {
1208
1340
  if (!text) {
1209
- error('Index is required for remove action');
1341
+ error("Index is required for remove action");
1210
1342
  process.exit(EXIT_CODES.ERROR);
1211
1343
  }
1212
1344
  const index = parseInt(text, 10);
1213
- if (isNaN(index) || index < 1 || index > sessionCtx.threads.length) {
1345
+ if (Number.isNaN(index) ||
1346
+ index < 1 ||
1347
+ index > sessionCtx.threads.length) {
1214
1348
  error(`Invalid index: ${text}. Must be between 1 and ${sessionCtx.threads.length}`);
1215
1349
  process.exit(EXIT_CODES.ERROR);
1216
1350
  }
@@ -1229,9 +1363,8 @@ export function registerMetaCommands(program) {
1229
1363
  }
1230
1364
  });
1231
1365
  // meta-question-cmd: kspec meta question <action> [text]
1232
- meta
1233
- .command('question <action> [text]')
1234
- .description('Manage open questions')
1366
+ markMutating(meta.command("question <action> [text]"))
1367
+ .description("Manage open questions")
1235
1368
  .action(async (action, text) => {
1236
1369
  try {
1237
1370
  const ctx = await initContext();
@@ -1241,13 +1374,13 @@ export function registerMetaCommands(program) {
1241
1374
  }
1242
1375
  const sessionCtx = await loadSessionContext(ctx);
1243
1376
  // List questions
1244
- if (action === 'list') {
1377
+ if (action === "list") {
1245
1378
  output({ questions: sessionCtx.open_questions }, () => {
1246
1379
  if (sessionCtx.open_questions.length === 0) {
1247
- console.log(chalk.yellow('No open questions'));
1380
+ console.log(chalk.yellow("No open questions"));
1248
1381
  }
1249
1382
  else {
1250
- console.log('Open questions:');
1383
+ console.log("Open questions:");
1251
1384
  sessionCtx.open_questions.forEach((question, idx) => {
1252
1385
  console.log(` ${idx + 1}. ${question}`);
1253
1386
  });
@@ -1256,16 +1389,16 @@ export function registerMetaCommands(program) {
1256
1389
  return;
1257
1390
  }
1258
1391
  // Clear all questions
1259
- if (action === 'clear') {
1392
+ if (action === "clear") {
1260
1393
  sessionCtx.open_questions = [];
1261
1394
  await saveSessionContext(ctx, sessionCtx);
1262
- output({ questions: [] }, () => success('Cleared all questions'));
1395
+ output({ questions: [] }, () => success("Cleared all questions"));
1263
1396
  return;
1264
1397
  }
1265
1398
  // Add question
1266
- if (action === 'add') {
1399
+ if (action === "add") {
1267
1400
  if (!text) {
1268
- error('Question text is required for add action');
1401
+ error("Question text is required for add action");
1269
1402
  process.exit(EXIT_CODES.ERROR);
1270
1403
  }
1271
1404
  sessionCtx.open_questions.push(text);
@@ -1274,13 +1407,15 @@ export function registerMetaCommands(program) {
1274
1407
  return;
1275
1408
  }
1276
1409
  // Remove question by index (1-based)
1277
- if (action === 'remove') {
1410
+ if (action === "remove") {
1278
1411
  if (!text) {
1279
- error('Index is required for remove action');
1412
+ error("Index is required for remove action");
1280
1413
  process.exit(EXIT_CODES.ERROR);
1281
1414
  }
1282
1415
  const index = parseInt(text, 10);
1283
- if (isNaN(index) || index < 1 || index > sessionCtx.open_questions.length) {
1416
+ if (Number.isNaN(index) ||
1417
+ index < 1 ||
1418
+ index > sessionCtx.open_questions.length) {
1284
1419
  error(`Invalid index: ${text}. Must be between 1 and ${sessionCtx.open_questions.length}`);
1285
1420
  process.exit(EXIT_CODES.ERROR);
1286
1421
  }
@@ -1300,9 +1435,9 @@ export function registerMetaCommands(program) {
1300
1435
  });
1301
1436
  // meta-context-cmd: kspec meta context
1302
1437
  meta
1303
- .command('context')
1304
- .description('Show full session context')
1305
- .option('--clear', 'Clear all session context')
1438
+ .command("context")
1439
+ .description("Show full session context")
1440
+ .option("--clear", "Clear all session context")
1306
1441
  .action(async (options) => {
1307
1442
  try {
1308
1443
  const ctx = await initContext();
@@ -1322,7 +1457,7 @@ export function registerMetaCommands(program) {
1322
1457
  threads: [],
1323
1458
  open_questions: [],
1324
1459
  updated_at: sessionCtx.updated_at,
1325
- }, () => success('Cleared all session context'));
1460
+ }, () => success("Cleared all session context"));
1326
1461
  return;
1327
1462
  }
1328
1463
  // Show full session context
@@ -1332,38 +1467,38 @@ export function registerMetaCommands(program) {
1332
1467
  open_questions: sessionCtx.open_questions,
1333
1468
  updated_at: sessionCtx.updated_at,
1334
1469
  }, () => {
1335
- console.log(chalk.bold('Session Context'));
1336
- console.log(chalk.gray(''.repeat(60)));
1470
+ console.log(chalk.bold("Session Context"));
1471
+ console.log(chalk.gray("".repeat(60)));
1337
1472
  // Focus
1338
- console.log(chalk.bold('\nFocus:'));
1473
+ console.log(chalk.bold("\nFocus:"));
1339
1474
  if (sessionCtx.focus) {
1340
1475
  console.log(` ${sessionCtx.focus}`);
1341
1476
  }
1342
1477
  else {
1343
- console.log(chalk.gray(' (none)'));
1478
+ console.log(chalk.gray(" (none)"));
1344
1479
  }
1345
1480
  // Active threads
1346
- console.log(chalk.bold('\nActive Threads:'));
1481
+ console.log(chalk.bold("\nActive Threads:"));
1347
1482
  if (sessionCtx.threads.length > 0) {
1348
1483
  sessionCtx.threads.forEach((thread, idx) => {
1349
1484
  console.log(` ${idx + 1}. ${thread}`);
1350
1485
  });
1351
1486
  }
1352
1487
  else {
1353
- console.log(chalk.gray(' (none)'));
1488
+ console.log(chalk.gray(" (none)"));
1354
1489
  }
1355
1490
  // Open questions
1356
- console.log(chalk.bold('\nOpen Questions:'));
1491
+ console.log(chalk.bold("\nOpen Questions:"));
1357
1492
  if (sessionCtx.open_questions.length > 0) {
1358
1493
  sessionCtx.open_questions.forEach((question, idx) => {
1359
1494
  console.log(` ${idx + 1}. ${question}`);
1360
1495
  });
1361
1496
  }
1362
1497
  else {
1363
- console.log(chalk.gray(' (none)'));
1498
+ console.log(chalk.gray(" (none)"));
1364
1499
  }
1365
1500
  // Last updated
1366
- console.log(chalk.bold('\nLast Updated:'));
1501
+ console.log(chalk.bold("\nLast Updated:"));
1367
1502
  const updatedDate = new Date(sessionCtx.updated_at);
1368
1503
  console.log(` ${updatedDate.toISOString()}`);
1369
1504
  console.log(chalk.gray(` (${updatedDate.toLocaleString()})`));