@kynetic-ai/spec 0.1.2 → 0.3.0

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