@kynetic-ai/spec 0.1.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (540) hide show
  1. package/README.md +250 -17
  2. package/dist/acp/client.d.ts +18 -4
  3. package/dist/acp/client.d.ts.map +1 -1
  4. package/dist/acp/client.js +44 -26
  5. package/dist/acp/client.js.map +1 -1
  6. package/dist/acp/framing.d.ts +2 -2
  7. package/dist/acp/framing.d.ts.map +1 -1
  8. package/dist/acp/framing.js +37 -29
  9. package/dist/acp/framing.js.map +1 -1
  10. package/dist/acp/index.d.ts +6 -7
  11. package/dist/acp/index.d.ts.map +1 -1
  12. package/dist/acp/index.js +3 -3
  13. package/dist/acp/index.js.map +1 -1
  14. package/dist/acp/types.d.ts +5 -5
  15. package/dist/acp/types.d.ts.map +1 -1
  16. package/dist/acp/types.js +18 -18
  17. package/dist/acp/types.js.map +1 -1
  18. package/dist/agents/adapters.d.ts.map +1 -1
  19. package/dist/agents/adapters.js +24 -13
  20. package/dist/agents/adapters.js.map +1 -1
  21. package/dist/agents/index.d.ts +2 -2
  22. package/dist/agents/index.js +2 -2
  23. package/dist/agents/spawner.d.ts +4 -4
  24. package/dist/agents/spawner.d.ts.map +1 -1
  25. package/dist/agents/spawner.js +6 -6
  26. package/dist/agents/spawner.js.map +1 -1
  27. package/dist/cli/batch-context.d.ts +43 -0
  28. package/dist/cli/batch-context.d.ts.map +1 -0
  29. package/dist/cli/batch-context.js +93 -0
  30. package/dist/cli/batch-context.js.map +1 -0
  31. package/dist/cli/batch-exec.d.ts +107 -0
  32. package/dist/cli/batch-exec.d.ts.map +1 -0
  33. package/dist/cli/batch-exec.js +706 -0
  34. package/dist/cli/batch-exec.js.map +1 -0
  35. package/dist/cli/batch.d.ts +4 -2
  36. package/dist/cli/batch.d.ts.map +1 -1
  37. package/dist/cli/batch.js +15 -14
  38. package/dist/cli/batch.js.map +1 -1
  39. package/dist/cli/command-annotations.d.ts +23 -0
  40. package/dist/cli/command-annotations.d.ts.map +1 -0
  41. package/dist/cli/command-annotations.js +27 -0
  42. package/dist/cli/command-annotations.js.map +1 -0
  43. package/dist/cli/commands/agents.d.ts +46 -0
  44. package/dist/cli/commands/agents.d.ts.map +1 -0
  45. package/dist/cli/commands/agents.js +377 -0
  46. package/dist/cli/commands/agents.js.map +1 -0
  47. package/dist/cli/commands/batch.d.ts +20 -0
  48. package/dist/cli/commands/batch.d.ts.map +1 -0
  49. package/dist/cli/commands/batch.js +214 -0
  50. package/dist/cli/commands/batch.js.map +1 -0
  51. package/dist/cli/commands/clone-for-testing.d.ts +1 -1
  52. package/dist/cli/commands/clone-for-testing.d.ts.map +1 -1
  53. package/dist/cli/commands/clone-for-testing.js +37 -47
  54. package/dist/cli/commands/clone-for-testing.js.map +1 -1
  55. package/dist/cli/commands/derive.d.ts +1 -1
  56. package/dist/cli/commands/derive.d.ts.map +1 -1
  57. package/dist/cli/commands/derive.js +141 -88
  58. package/dist/cli/commands/derive.js.map +1 -1
  59. package/dist/cli/commands/doctor.d.ts +11 -0
  60. package/dist/cli/commands/doctor.d.ts.map +1 -0
  61. package/dist/cli/commands/doctor.js +152 -0
  62. package/dist/cli/commands/doctor.js.map +1 -0
  63. package/dist/cli/commands/export.d.ts +12 -0
  64. package/dist/cli/commands/export.d.ts.map +1 -0
  65. package/dist/cli/commands/export.js +134 -0
  66. package/dist/cli/commands/export.js.map +1 -0
  67. package/dist/cli/commands/help.d.ts +1 -1
  68. package/dist/cli/commands/help.d.ts.map +1 -1
  69. package/dist/cli/commands/help.js +163 -37
  70. package/dist/cli/commands/help.js.map +1 -1
  71. package/dist/cli/commands/inbox.d.ts +1 -1
  72. package/dist/cli/commands/inbox.d.ts.map +1 -1
  73. package/dist/cli/commands/inbox.js +178 -56
  74. package/dist/cli/commands/inbox.js.map +1 -1
  75. package/dist/cli/commands/index.d.ts +31 -19
  76. package/dist/cli/commands/index.d.ts.map +1 -1
  77. package/dist/cli/commands/index.js +31 -19
  78. package/dist/cli/commands/index.js.map +1 -1
  79. package/dist/cli/commands/init.d.ts +5 -1
  80. package/dist/cli/commands/init.d.ts.map +1 -1
  81. package/dist/cli/commands/init.js +108 -57
  82. package/dist/cli/commands/init.js.map +1 -1
  83. package/dist/cli/commands/item.d.ts +1 -1
  84. package/dist/cli/commands/item.d.ts.map +1 -1
  85. package/dist/cli/commands/item.js +557 -274
  86. package/dist/cli/commands/item.js.map +1 -1
  87. package/dist/cli/commands/link.d.ts +1 -1
  88. package/dist/cli/commands/link.d.ts.map +1 -1
  89. package/dist/cli/commands/link.js +55 -46
  90. package/dist/cli/commands/link.js.map +1 -1
  91. package/dist/cli/commands/log.d.ts +1 -1
  92. package/dist/cli/commands/log.d.ts.map +1 -1
  93. package/dist/cli/commands/log.js +58 -51
  94. package/dist/cli/commands/log.js.map +1 -1
  95. package/dist/cli/commands/merge-driver.d.ts +19 -0
  96. package/dist/cli/commands/merge-driver.d.ts.map +1 -0
  97. package/dist/cli/commands/merge-driver.js +398 -0
  98. package/dist/cli/commands/merge-driver.js.map +1 -0
  99. package/dist/cli/commands/meta.d.ts +1 -1
  100. package/dist/cli/commands/meta.d.ts.map +1 -1
  101. package/dist/cli/commands/meta.js +534 -399
  102. package/dist/cli/commands/meta.js.map +1 -1
  103. package/dist/cli/commands/module.d.ts +1 -1
  104. package/dist/cli/commands/module.d.ts.map +1 -1
  105. package/dist/cli/commands/module.js +30 -25
  106. package/dist/cli/commands/module.js.map +1 -1
  107. package/dist/cli/commands/plan-import.d.ts +11 -0
  108. package/dist/cli/commands/plan-import.d.ts.map +1 -0
  109. package/dist/cli/commands/plan-import.js +547 -0
  110. package/dist/cli/commands/plan-import.js.map +1 -0
  111. package/dist/cli/commands/plan.d.ts +10 -0
  112. package/dist/cli/commands/plan.d.ts.map +1 -0
  113. package/dist/cli/commands/plan.js +421 -0
  114. package/dist/cli/commands/plan.js.map +1 -0
  115. package/dist/cli/commands/ralph.d.ts +1 -1
  116. package/dist/cli/commands/ralph.d.ts.map +1 -1
  117. package/dist/cli/commands/ralph.js +1109 -170
  118. package/dist/cli/commands/ralph.js.map +1 -1
  119. package/dist/cli/commands/refs.d.ts +13 -0
  120. package/dist/cli/commands/refs.d.ts.map +1 -0
  121. package/dist/cli/commands/refs.js +283 -0
  122. package/dist/cli/commands/refs.js.map +1 -0
  123. package/dist/cli/commands/search.d.ts +1 -1
  124. package/dist/cli/commands/search.d.ts.map +1 -1
  125. package/dist/cli/commands/search.js +199 -37
  126. package/dist/cli/commands/search.js.map +1 -1
  127. package/dist/cli/commands/serve.d.ts +10 -0
  128. package/dist/cli/commands/serve.d.ts.map +1 -0
  129. package/dist/cli/commands/serve.js +491 -0
  130. package/dist/cli/commands/serve.js.map +1 -0
  131. package/dist/cli/commands/session.d.ts +25 -6
  132. package/dist/cli/commands/session.d.ts.map +1 -1
  133. package/dist/cli/commands/session.js +810 -127
  134. package/dist/cli/commands/session.js.map +1 -1
  135. package/dist/cli/commands/setup-seeding.d.ts +81 -0
  136. package/dist/cli/commands/setup-seeding.d.ts.map +1 -0
  137. package/dist/cli/commands/setup-seeding.js +292 -0
  138. package/dist/cli/commands/setup-seeding.js.map +1 -0
  139. package/dist/cli/commands/setup.d.ts +77 -3
  140. package/dist/cli/commands/setup.d.ts.map +1 -1
  141. package/dist/cli/commands/setup.js +1267 -274
  142. package/dist/cli/commands/setup.js.map +1 -1
  143. package/dist/cli/commands/shadow.d.ts +1 -1
  144. package/dist/cli/commands/shadow.d.ts.map +1 -1
  145. package/dist/cli/commands/shadow.js +70 -50
  146. package/dist/cli/commands/shadow.js.map +1 -1
  147. package/dist/cli/commands/skill-crud.d.ts +58 -0
  148. package/dist/cli/commands/skill-crud.d.ts.map +1 -0
  149. package/dist/cli/commands/skill-crud.js +753 -0
  150. package/dist/cli/commands/skill-crud.js.map +1 -0
  151. package/dist/cli/commands/skill-diff.d.ts +27 -0
  152. package/dist/cli/commands/skill-diff.d.ts.map +1 -0
  153. package/dist/cli/commands/skill-diff.js +840 -0
  154. package/dist/cli/commands/skill-diff.js.map +1 -0
  155. package/dist/cli/commands/skill-install.d.ts +56 -0
  156. package/dist/cli/commands/skill-install.d.ts.map +1 -0
  157. package/dist/cli/commands/skill-install.js +509 -0
  158. package/dist/cli/commands/skill-install.js.map +1 -0
  159. package/dist/cli/commands/skill.d.ts +20 -0
  160. package/dist/cli/commands/skill.d.ts.map +1 -0
  161. package/dist/cli/commands/skill.js +36 -0
  162. package/dist/cli/commands/skill.js.map +1 -0
  163. package/dist/cli/commands/task.d.ts +1 -1
  164. package/dist/cli/commands/task.d.ts.map +1 -1
  165. package/dist/cli/commands/task.js +584 -350
  166. package/dist/cli/commands/task.js.map +1 -1
  167. package/dist/cli/commands/tasks.d.ts +26 -1
  168. package/dist/cli/commands/tasks.d.ts.map +1 -1
  169. package/dist/cli/commands/tasks.js +225 -122
  170. package/dist/cli/commands/tasks.js.map +1 -1
  171. package/dist/cli/commands/trait.d.ts +1 -1
  172. package/dist/cli/commands/trait.d.ts.map +1 -1
  173. package/dist/cli/commands/trait.js +166 -101
  174. package/dist/cli/commands/trait.js.map +1 -1
  175. package/dist/cli/commands/triage.d.ts +7 -0
  176. package/dist/cli/commands/triage.d.ts.map +1 -0
  177. package/dist/cli/commands/triage.js +483 -0
  178. package/dist/cli/commands/triage.js.map +1 -0
  179. package/dist/cli/commands/util.d.ts +7 -0
  180. package/dist/cli/commands/util.d.ts.map +1 -0
  181. package/dist/cli/commands/util.js +30 -0
  182. package/dist/cli/commands/util.js.map +1 -0
  183. package/dist/cli/commands/validate.d.ts +1 -1
  184. package/dist/cli/commands/validate.d.ts.map +1 -1
  185. package/dist/cli/commands/validate.js +264 -83
  186. package/dist/cli/commands/validate.js.map +1 -1
  187. package/dist/cli/commands/workflow.d.ts +16 -0
  188. package/dist/cli/commands/workflow.d.ts.map +1 -0
  189. package/dist/cli/commands/workflow.js +851 -0
  190. package/dist/cli/commands/workflow.js.map +1 -0
  191. package/dist/cli/exit-codes.d.ts +7 -0
  192. package/dist/cli/exit-codes.d.ts.map +1 -1
  193. package/dist/cli/exit-codes.js +26 -18
  194. package/dist/cli/exit-codes.js.map +1 -1
  195. package/dist/cli/help/content.d.ts.map +1 -1
  196. package/dist/cli/help/content.js +86 -71
  197. package/dist/cli/help/content.js.map +1 -1
  198. package/dist/cli/index.d.ts +1 -1
  199. package/dist/cli/index.d.ts.map +1 -1
  200. package/dist/cli/index.js +131 -19
  201. package/dist/cli/index.js.map +1 -1
  202. package/dist/cli/introspection.d.ts +6 -2
  203. package/dist/cli/introspection.d.ts.map +1 -1
  204. package/dist/cli/introspection.js +11 -8
  205. package/dist/cli/introspection.js.map +1 -1
  206. package/dist/cli/output.d.ts +64 -4
  207. package/dist/cli/output.d.ts.map +1 -1
  208. package/dist/cli/output.js +237 -85
  209. package/dist/cli/output.js.map +1 -1
  210. package/dist/cli/parse-utils.d.ts +21 -0
  211. package/dist/cli/parse-utils.d.ts.map +1 -0
  212. package/dist/cli/parse-utils.js +32 -0
  213. package/dist/cli/parse-utils.js.map +1 -0
  214. package/dist/cli/pid-utils.d.ts +72 -0
  215. package/dist/cli/pid-utils.d.ts.map +1 -0
  216. package/dist/cli/pid-utils.js +174 -0
  217. package/dist/cli/pid-utils.js.map +1 -0
  218. package/dist/cli/suggest.d.ts.map +1 -1
  219. package/dist/cli/suggest.js +1 -2
  220. package/dist/cli/suggest.js.map +1 -1
  221. package/dist/cli/validators.d.ts +43 -0
  222. package/dist/cli/validators.d.ts.map +1 -0
  223. package/dist/cli/validators.js +84 -0
  224. package/dist/cli/validators.js.map +1 -0
  225. package/dist/daemon/index.ts +52 -0
  226. package/dist/daemon/middleware/project-context.ts +126 -0
  227. package/dist/daemon/pid.ts +179 -0
  228. package/dist/daemon/project-context.ts +343 -0
  229. package/dist/daemon/routes/inbox.ts +164 -0
  230. package/dist/daemon/routes/items.ts +322 -0
  231. package/dist/daemon/routes/meta.ts +118 -0
  232. package/dist/daemon/routes/projects.ts +162 -0
  233. package/dist/daemon/routes/tasks.ts +327 -0
  234. package/dist/daemon/routes/triage.ts +402 -0
  235. package/dist/daemon/routes/validation.ts +248 -0
  236. package/dist/daemon/server.ts +408 -0
  237. package/dist/daemon/watcher.ts +195 -0
  238. package/dist/daemon/websocket/handler.ts +138 -0
  239. package/dist/daemon/websocket/heartbeat.ts +71 -0
  240. package/dist/daemon/websocket/pubsub.ts +125 -0
  241. package/dist/daemon/websocket/types.ts +66 -0
  242. package/dist/export/html.d.ts +19 -0
  243. package/dist/export/html.d.ts.map +1 -0
  244. package/dist/export/html.js +239 -0
  245. package/dist/export/html.js.map +1 -0
  246. package/dist/export/index.d.ts +10 -0
  247. package/dist/export/index.d.ts.map +1 -0
  248. package/dist/export/index.js +10 -0
  249. package/dist/export/index.js.map +1 -0
  250. package/dist/export/json.d.ts +24 -0
  251. package/dist/export/json.d.ts.map +1 -0
  252. package/dist/export/json.js +198 -0
  253. package/dist/export/json.js.map +1 -0
  254. package/dist/export/triage.d.ts +51 -0
  255. package/dist/export/triage.d.ts.map +1 -0
  256. package/dist/export/triage.js +83 -0
  257. package/dist/export/triage.js.map +1 -0
  258. package/dist/export/types.d.ts +122 -0
  259. package/dist/export/types.d.ts.map +1 -0
  260. package/dist/export/types.js +9 -0
  261. package/dist/export/types.js.map +1 -0
  262. package/dist/index.d.ts +2 -2
  263. package/dist/index.js +2 -2
  264. package/dist/lib/claude-plugin-registry.d.ts +66 -0
  265. package/dist/lib/claude-plugin-registry.d.ts.map +1 -0
  266. package/dist/lib/claude-plugin-registry.js +318 -0
  267. package/dist/lib/claude-plugin-registry.js.map +1 -0
  268. package/dist/merge/arrays.d.ts +87 -0
  269. package/dist/merge/arrays.d.ts.map +1 -0
  270. package/dist/merge/arrays.js +164 -0
  271. package/dist/merge/arrays.js.map +1 -0
  272. package/dist/merge/file-type.d.ts +32 -0
  273. package/dist/merge/file-type.d.ts.map +1 -0
  274. package/dist/merge/file-type.js +70 -0
  275. package/dist/merge/file-type.js.map +1 -0
  276. package/dist/merge/index.d.ts +14 -0
  277. package/dist/merge/index.d.ts.map +1 -0
  278. package/dist/merge/index.js +11 -0
  279. package/dist/merge/index.js.map +1 -0
  280. package/dist/merge/objects.d.ts +46 -0
  281. package/dist/merge/objects.d.ts.map +1 -0
  282. package/dist/merge/objects.js +193 -0
  283. package/dist/merge/objects.js.map +1 -0
  284. package/dist/merge/parse.d.ts +23 -0
  285. package/dist/merge/parse.d.ts.map +1 -0
  286. package/dist/merge/parse.js +78 -0
  287. package/dist/merge/parse.js.map +1 -0
  288. package/dist/merge/resolve.d.ts +66 -0
  289. package/dist/merge/resolve.d.ts.map +1 -0
  290. package/dist/merge/resolve.js +189 -0
  291. package/dist/merge/resolve.js.map +1 -0
  292. package/dist/merge/types.d.ts +82 -0
  293. package/dist/merge/types.d.ts.map +1 -0
  294. package/dist/merge/types.js +8 -0
  295. package/dist/merge/types.js.map +1 -0
  296. package/dist/parser/agent-data-sections.d.ts +53 -0
  297. package/dist/parser/agent-data-sections.d.ts.map +1 -0
  298. package/dist/parser/agent-data-sections.js +118 -0
  299. package/dist/parser/agent-data-sections.js.map +1 -0
  300. package/dist/parser/alignment.d.ts +4 -4
  301. package/dist/parser/alignment.d.ts.map +1 -1
  302. package/dist/parser/alignment.js +27 -22
  303. package/dist/parser/alignment.js.map +1 -1
  304. package/dist/parser/assess.d.ts +5 -5
  305. package/dist/parser/assess.d.ts.map +1 -1
  306. package/dist/parser/assess.js +36 -32
  307. package/dist/parser/assess.js.map +1 -1
  308. package/dist/parser/config.d.ts +457 -0
  309. package/dist/parser/config.d.ts.map +1 -0
  310. package/dist/parser/config.js +373 -0
  311. package/dist/parser/config.js.map +1 -0
  312. package/dist/parser/convention-validation.d.ts +1 -1
  313. package/dist/parser/convention-validation.d.ts.map +1 -1
  314. package/dist/parser/convention-validation.js +21 -16
  315. package/dist/parser/convention-validation.js.map +1 -1
  316. package/dist/parser/coverage-cache.d.ts +49 -0
  317. package/dist/parser/coverage-cache.d.ts.map +1 -0
  318. package/dist/parser/coverage-cache.js +123 -0
  319. package/dist/parser/coverage-cache.js.map +1 -0
  320. package/dist/parser/daemon-status.d.ts +37 -0
  321. package/dist/parser/daemon-status.d.ts.map +1 -0
  322. package/dist/parser/daemon-status.js +67 -0
  323. package/dist/parser/daemon-status.js.map +1 -0
  324. package/dist/parser/doctor.d.ts +107 -0
  325. package/dist/parser/doctor.d.ts.map +1 -0
  326. package/dist/parser/doctor.js +366 -0
  327. package/dist/parser/doctor.js.map +1 -0
  328. package/dist/parser/fix.d.ts +1 -1
  329. package/dist/parser/fix.d.ts.map +1 -1
  330. package/dist/parser/fix.js +31 -27
  331. package/dist/parser/fix.js.map +1 -1
  332. package/dist/parser/index.d.ts +16 -11
  333. package/dist/parser/index.d.ts.map +1 -1
  334. package/dist/parser/index.js +16 -11
  335. package/dist/parser/index.js.map +1 -1
  336. package/dist/parser/items.d.ts +8 -2
  337. package/dist/parser/items.d.ts.map +1 -1
  338. package/dist/parser/items.js +71 -35
  339. package/dist/parser/items.js.map +1 -1
  340. package/dist/parser/meta.d.ts +167 -9
  341. package/dist/parser/meta.d.ts.map +1 -1
  342. package/dist/parser/meta.js +379 -46
  343. package/dist/parser/meta.js.map +1 -1
  344. package/dist/parser/plan-document.d.ts +197 -0
  345. package/dist/parser/plan-document.d.ts.map +1 -0
  346. package/dist/parser/plan-document.js +341 -0
  347. package/dist/parser/plan-document.js.map +1 -0
  348. package/dist/parser/plans.d.ts +59 -0
  349. package/dist/parser/plans.d.ts.map +1 -0
  350. package/dist/parser/plans.js +239 -0
  351. package/dist/parser/plans.js.map +1 -0
  352. package/dist/parser/refs.d.ts +22 -9
  353. package/dist/parser/refs.d.ts.map +1 -1
  354. package/dist/parser/refs.js +102 -50
  355. package/dist/parser/refs.js.map +1 -1
  356. package/dist/parser/setup-status.d.ts +71 -0
  357. package/dist/parser/setup-status.d.ts.map +1 -0
  358. package/dist/parser/setup-status.js +269 -0
  359. package/dist/parser/setup-status.js.map +1 -0
  360. package/dist/parser/shadow.d.ts +150 -19
  361. package/dist/parser/shadow.d.ts.map +1 -1
  362. package/dist/parser/shadow.js +548 -187
  363. package/dist/parser/shadow.js.map +1 -1
  364. package/dist/parser/skill-render.d.ts +317 -0
  365. package/dist/parser/skill-render.d.ts.map +1 -0
  366. package/dist/parser/skill-render.js +943 -0
  367. package/dist/parser/skill-render.js.map +1 -0
  368. package/dist/parser/traits.d.ts +3 -3
  369. package/dist/parser/traits.d.ts.map +1 -1
  370. package/dist/parser/traits.js +2 -2
  371. package/dist/parser/traits.js.map +1 -1
  372. package/dist/parser/validate-skills.d.ts +32 -0
  373. package/dist/parser/validate-skills.d.ts.map +1 -0
  374. package/dist/parser/validate-skills.js +202 -0
  375. package/dist/parser/validate-skills.js.map +1 -0
  376. package/dist/parser/validate.d.ts +45 -3
  377. package/dist/parser/validate.d.ts.map +1 -1
  378. package/dist/parser/validate.js +622 -105
  379. package/dist/parser/validate.js.map +1 -1
  380. package/dist/parser/yaml.d.ts +83 -19
  381. package/dist/parser/yaml.d.ts.map +1 -1
  382. package/dist/parser/yaml.js +478 -173
  383. package/dist/parser/yaml.js.map +1 -1
  384. package/dist/ralph/cli-renderer.d.ts +8 -1
  385. package/dist/ralph/cli-renderer.d.ts.map +1 -1
  386. package/dist/ralph/cli-renderer.js +105 -34
  387. package/dist/ralph/cli-renderer.js.map +1 -1
  388. package/dist/ralph/events.d.ts +10 -10
  389. package/dist/ralph/events.d.ts.map +1 -1
  390. package/dist/ralph/events.js +301 -98
  391. package/dist/ralph/events.js.map +1 -1
  392. package/dist/ralph/index.d.ts +5 -2
  393. package/dist/ralph/index.d.ts.map +1 -1
  394. package/dist/ralph/index.js +9 -3
  395. package/dist/ralph/index.js.map +1 -1
  396. package/dist/ralph/loop-errors.d.ts +83 -0
  397. package/dist/ralph/loop-errors.d.ts.map +1 -0
  398. package/dist/ralph/loop-errors.js +150 -0
  399. package/dist/ralph/loop-errors.js.map +1 -0
  400. package/dist/ralph/subagent.d.ts +94 -0
  401. package/dist/ralph/subagent.d.ts.map +1 -0
  402. package/dist/ralph/subagent.js +193 -0
  403. package/dist/ralph/subagent.js.map +1 -0
  404. package/dist/ralph/wrap-up.d.ts +125 -0
  405. package/dist/ralph/wrap-up.d.ts.map +1 -0
  406. package/dist/ralph/wrap-up.js +270 -0
  407. package/dist/ralph/wrap-up.js.map +1 -0
  408. package/dist/schema/batch.d.ts +97 -0
  409. package/dist/schema/batch.d.ts.map +1 -0
  410. package/dist/schema/batch.js +24 -0
  411. package/dist/schema/batch.js.map +1 -0
  412. package/dist/schema/common.d.ts +8 -2
  413. package/dist/schema/common.d.ts.map +1 -1
  414. package/dist/schema/common.js +42 -31
  415. package/dist/schema/common.js.map +1 -1
  416. package/dist/schema/inbox.d.ts +12 -12
  417. package/dist/schema/inbox.js +4 -4
  418. package/dist/schema/inbox.js.map +1 -1
  419. package/dist/schema/index.d.ts +8 -5
  420. package/dist/schema/index.d.ts.map +1 -1
  421. package/dist/schema/index.js +8 -5
  422. package/dist/schema/index.js.map +1 -1
  423. package/dist/schema/meta.d.ts +1454 -27
  424. package/dist/schema/meta.d.ts.map +1 -1
  425. package/dist/schema/meta.js +198 -21
  426. package/dist/schema/meta.js.map +1 -1
  427. package/dist/schema/plan.d.ts +285 -0
  428. package/dist/schema/plan.d.ts.map +1 -0
  429. package/dist/schema/plan.js +81 -0
  430. package/dist/schema/plan.js.map +1 -0
  431. package/dist/schema/spec.d.ts +72 -33
  432. package/dist/schema/spec.d.ts.map +1 -1
  433. package/dist/schema/spec.js +22 -9
  434. package/dist/schema/spec.js.map +1 -1
  435. package/dist/schema/task.d.ts +172 -161
  436. package/dist/schema/task.d.ts.map +1 -1
  437. package/dist/schema/task.js +21 -12
  438. package/dist/schema/task.js.map +1 -1
  439. package/dist/schema/triage.d.ts +266 -0
  440. package/dist/schema/triage.d.ts.map +1 -0
  441. package/dist/schema/triage.js +134 -0
  442. package/dist/schema/triage.js.map +1 -0
  443. package/dist/sessions/index.d.ts +2 -2
  444. package/dist/sessions/index.d.ts.map +1 -1
  445. package/dist/sessions/index.js +3 -3
  446. package/dist/sessions/index.js.map +1 -1
  447. package/dist/sessions/store.d.ts +241 -1
  448. package/dist/sessions/store.d.ts.map +1 -1
  449. package/dist/sessions/store.js +810 -31
  450. package/dist/sessions/store.js.map +1 -1
  451. package/dist/sessions/types.d.ts +10 -10
  452. package/dist/sessions/types.d.ts.map +1 -1
  453. package/dist/sessions/types.js +10 -9
  454. package/dist/sessions/types.js.map +1 -1
  455. package/dist/strings/errors.d.ts +55 -0
  456. package/dist/strings/errors.d.ts.map +1 -1
  457. package/dist/strings/errors.js +138 -106
  458. package/dist/strings/errors.js.map +1 -1
  459. package/dist/strings/guidance.d.ts.map +1 -1
  460. package/dist/strings/guidance.js +16 -16
  461. package/dist/strings/guidance.js.map +1 -1
  462. package/dist/strings/index.d.ts +4 -4
  463. package/dist/strings/index.d.ts.map +1 -1
  464. package/dist/strings/index.js +4 -4
  465. package/dist/strings/index.js.map +1 -1
  466. package/dist/strings/labels.d.ts +4 -0
  467. package/dist/strings/labels.d.ts.map +1 -1
  468. package/dist/strings/labels.js +45 -41
  469. package/dist/strings/labels.js.map +1 -1
  470. package/dist/strings/validation.d.ts.map +1 -1
  471. package/dist/strings/validation.js +71 -71
  472. package/dist/strings/validation.js.map +1 -1
  473. package/dist/triage/actions.d.ts +27 -0
  474. package/dist/triage/actions.d.ts.map +1 -0
  475. package/dist/triage/actions.js +95 -0
  476. package/dist/triage/actions.js.map +1 -0
  477. package/dist/triage/constants.d.ts +6 -0
  478. package/dist/triage/constants.d.ts.map +1 -0
  479. package/dist/triage/constants.js +7 -0
  480. package/dist/triage/constants.js.map +1 -0
  481. package/dist/triage/index.d.ts +3 -0
  482. package/dist/triage/index.d.ts.map +1 -0
  483. package/dist/triage/index.js +3 -0
  484. package/dist/triage/index.js.map +1 -0
  485. package/dist/utils/commit.d.ts +1 -1
  486. package/dist/utils/commit.d.ts.map +1 -1
  487. package/dist/utils/commit.js +28 -26
  488. package/dist/utils/commit.js.map +1 -1
  489. package/dist/utils/git.d.ts +1 -1
  490. package/dist/utils/git.d.ts.map +1 -1
  491. package/dist/utils/git.js +40 -38
  492. package/dist/utils/git.js.map +1 -1
  493. package/dist/utils/grep.js +11 -11
  494. package/dist/utils/grep.js.map +1 -1
  495. package/dist/utils/index.d.ts +7 -7
  496. package/dist/utils/index.d.ts.map +1 -1
  497. package/dist/utils/index.js +4 -4
  498. package/dist/utils/index.js.map +1 -1
  499. package/dist/utils/time.d.ts.map +1 -1
  500. package/dist/utils/time.js +10 -10
  501. package/dist/utils/time.js.map +1 -1
  502. package/package.json +28 -5
  503. package/plugin/.claude-plugin/marketplace.json +17 -0
  504. package/plugin/.claude-plugin/plugin.json +5 -0
  505. package/plugin/plugins/kspec/skills/create-workflow/SKILL.md +235 -0
  506. package/plugin/plugins/kspec/skills/help/SKILL.md +42 -0
  507. package/plugin/plugins/kspec/skills/observations/SKILL.md +143 -0
  508. package/plugin/plugins/kspec/skills/plan/SKILL.md +343 -0
  509. package/plugin/plugins/kspec/skills/reflect/SKILL.md +161 -0
  510. package/plugin/plugins/kspec/skills/review/SKILL.md +193 -0
  511. package/plugin/plugins/kspec/skills/task-work/SKILL.md +303 -0
  512. package/plugin/plugins/kspec/skills/triage/SKILL.md +206 -0
  513. package/plugin/plugins/kspec/skills/triage/docs/automation.md +120 -0
  514. package/plugin/plugins/kspec/skills/triage/docs/inbox.md +144 -0
  515. package/plugin/plugins/kspec/skills/triage/docs/observations.md +85 -0
  516. package/plugin/plugins/kspec/skills/triage-automation/SKILL.md +140 -0
  517. package/plugin/plugins/kspec/skills/triage-inbox/SKILL.md +232 -0
  518. package/plugin/plugins/kspec/skills/writing-specs/SKILL.md +340 -0
  519. package/templates/agents-sections/01-quick-start.md +22 -0
  520. package/templates/agents-sections/02-shadow-branch.md +34 -0
  521. package/templates/agents-sections/03-task-lifecycle.md +48 -0
  522. package/templates/agents-sections/04-pr-workflow.md +17 -0
  523. package/templates/agents-sections/05-commit-convention.md +27 -0
  524. package/templates/agents-sections/06-ralph-loop.md +45 -0
  525. package/templates/hooks/pre-commit +34 -0
  526. package/templates/skills/create-workflow/SKILL.md +228 -0
  527. package/templates/skills/help/SKILL.md +37 -0
  528. package/templates/skills/manifest.yaml +60 -0
  529. package/templates/skills/observations/SKILL.md +137 -0
  530. package/templates/skills/plan/SKILL.md +336 -0
  531. package/templates/skills/reflect/SKILL.md +155 -0
  532. package/templates/skills/review/SKILL.md +186 -0
  533. package/templates/skills/task-work/SKILL.md +296 -0
  534. package/templates/skills/triage/SKILL.md +199 -0
  535. package/templates/skills/triage/docs/automation.md +120 -0
  536. package/templates/skills/triage/docs/inbox.md +144 -0
  537. package/templates/skills/triage/docs/observations.md +85 -0
  538. package/templates/skills/triage-automation/SKILL.md +134 -0
  539. package/templates/skills/triage-inbox/SKILL.md +225 -0
  540. package/templates/skills/writing-specs/SKILL.md +333 -0
@@ -3,13 +3,14 @@
3
3
  *
4
4
  * Provides schema validation, reference validation, and orphan detection.
5
5
  */
6
- import * as fs from 'node:fs/promises';
7
- import * as path from 'node:path';
8
- import { TaskSchema, TasksFileSchema, ManifestSchema, SpecItemSchema, MetaManifestSchema, AgentSchema, WorkflowSchema, ConventionSchema, ObservationSchema, UlidSchema, } from '../schema/index.js';
9
- import { readYamlFile, findTaskFiles, loadSpecFile, expandIncludePattern, extractItemsFromRaw, } from './yaml.js';
10
- import { ReferenceIndex, validateRefs } from './refs.js';
11
- import { findMetaManifest, loadMetaContext } from './meta.js';
12
- import { TraitIndex } from './traits.js';
6
+ import * as fs from "node:fs/promises";
7
+ import * as path from "node:path";
8
+ import { AgentSchema, ConventionSchema, isSkill, ManifestSchema, MetaManifestSchema, ObservationSchema, SkillSchema, SpecItemSchema, TaskSchema, TasksFileSchema, UlidSchema, WorkflowSchema, } from "../schema/index.js";
9
+ import { findMetaManifest, getSkillContentPath, loadMetaContext } from "./meta.js";
10
+ import { loadPlans } from "./plans.js";
11
+ import { ReferenceIndex, validateRefs, } from "./refs.js";
12
+ import { TraitIndex } from "./traits.js";
13
+ import { expandIncludePattern, extractItemsFromRaw, findTaskFiles, loadSpecFile, readYamlFile, } from "./yaml.js";
13
14
  // ============================================================
14
15
  // SCHEMA VALIDATION
15
16
  // ============================================================
@@ -25,7 +26,7 @@ async function validateManifestFile(filePath) {
25
26
  for (const issue of result.error.issues) {
26
27
  errors.push({
27
28
  file: filePath,
28
- path: issue.path.join('.'),
29
+ path: issue.path.join("."),
29
30
  message: issue.message,
30
31
  details: issue,
31
32
  });
@@ -52,7 +53,7 @@ async function validateTasksFile(filePath) {
52
53
  if (Array.isArray(raw)) {
53
54
  taskList = raw;
54
55
  }
55
- else if (raw && typeof raw === 'object' && 'tasks' in raw) {
56
+ else if (raw && typeof raw === "object" && "tasks" in raw) {
56
57
  // Try full TasksFile schema first
57
58
  const fileResult = TasksFileSchema.safeParse(raw);
58
59
  if (!fileResult.success) {
@@ -67,7 +68,7 @@ async function validateTasksFile(filePath) {
67
68
  else {
68
69
  errors.push({
69
70
  file: filePath,
70
- message: 'Invalid tasks file format: expected array or { tasks: [...] }',
71
+ message: "Invalid tasks file format: expected array or { tasks: [...] }",
71
72
  });
72
73
  return errors;
73
74
  }
@@ -79,7 +80,7 @@ async function validateTasksFile(filePath) {
79
80
  for (const issue of result.error.issues) {
80
81
  errors.push({
81
82
  file: filePath,
82
- path: `tasks[${i}].${issue.path.join('.')}`,
83
+ path: `tasks[${i}].${issue.path.join(".")}`,
83
84
  message: issue.message,
84
85
  details: issue,
85
86
  });
@@ -103,7 +104,7 @@ async function validateSpecFile(filePath) {
103
104
  try {
104
105
  const raw = await readYamlFile(filePath);
105
106
  // Recursively validate spec items
106
- validateSpecItemRecursive(raw, filePath, '', errors);
107
+ validateSpecItemRecursive(raw, filePath, "", errors);
107
108
  }
108
109
  catch (err) {
109
110
  errors.push({
@@ -127,7 +128,7 @@ async function validateMetaManifestFile(filePath) {
127
128
  for (const issue of manifestResult.error.issues) {
128
129
  errors.push({
129
130
  file: filePath,
130
- path: issue.path.join('.'),
131
+ path: issue.path.join("."),
131
132
  message: issue.message,
132
133
  details: issue,
133
134
  });
@@ -135,7 +136,10 @@ async function validateMetaManifestFile(filePath) {
135
136
  return errors;
136
137
  }
137
138
  // Validate each agent with strict ULID validation
138
- if (raw && typeof raw === 'object' && 'agents' in raw && Array.isArray(raw.agents)) {
139
+ if (raw &&
140
+ typeof raw === "object" &&
141
+ "agents" in raw &&
142
+ Array.isArray(raw.agents)) {
139
143
  const agents = raw.agents;
140
144
  for (let i = 0; i < agents.length; i++) {
141
145
  const agent = agents[i];
@@ -144,27 +148,30 @@ async function validateMetaManifestFile(filePath) {
144
148
  for (const issue of agentResult.error.issues) {
145
149
  errors.push({
146
150
  file: filePath,
147
- path: `agents[${i}].${issue.path.join('.')}`,
151
+ path: `agents[${i}].${issue.path.join(".")}`,
148
152
  message: issue.message,
149
153
  details: issue,
150
154
  });
151
155
  }
152
156
  }
153
157
  // Strict ULID validation
154
- if (agent && typeof agent === 'object' && '_ulid' in agent) {
158
+ if (agent && typeof agent === "object" && "_ulid" in agent) {
155
159
  const ulidResult = UlidSchema.safeParse(agent._ulid);
156
160
  if (!ulidResult.success) {
157
161
  errors.push({
158
162
  file: filePath,
159
163
  path: `agents[${i}]._ulid`,
160
- message: 'Invalid ULID format (expected 26 characters)',
164
+ message: "Invalid ULID format (expected 26 characters)",
161
165
  });
162
166
  }
163
167
  }
164
168
  }
165
169
  }
166
170
  // Validate each workflow with strict ULID validation
167
- if (raw && typeof raw === 'object' && 'workflows' in raw && Array.isArray(raw.workflows)) {
171
+ if (raw &&
172
+ typeof raw === "object" &&
173
+ "workflows" in raw &&
174
+ Array.isArray(raw.workflows)) {
168
175
  const workflows = raw.workflows;
169
176
  for (let i = 0; i < workflows.length; i++) {
170
177
  const workflow = workflows[i];
@@ -173,28 +180,32 @@ async function validateMetaManifestFile(filePath) {
173
180
  for (const issue of workflowResult.error.issues) {
174
181
  errors.push({
175
182
  file: filePath,
176
- path: `workflows[${i}].${issue.path.join('.')}`,
183
+ path: `workflows[${i}].${issue.path.join(".")}`,
177
184
  message: issue.message,
178
185
  details: issue,
179
186
  });
180
187
  }
181
188
  }
182
189
  // Strict ULID validation
183
- if (workflow && typeof workflow === 'object' && '_ulid' in workflow) {
190
+ if (workflow && typeof workflow === "object" && "_ulid" in workflow) {
184
191
  const ulidResult = UlidSchema.safeParse(workflow._ulid);
185
192
  if (!ulidResult.success) {
186
193
  errors.push({
187
194
  file: filePath,
188
195
  path: `workflows[${i}]._ulid`,
189
- message: 'Invalid ULID format (expected 26 characters)',
196
+ message: "Invalid ULID format (expected 26 characters)",
190
197
  });
191
198
  }
192
199
  }
193
200
  }
194
201
  }
195
202
  // Validate each convention with strict ULID validation
196
- if (raw && typeof raw === 'object' && 'conventions' in raw && Array.isArray(raw.conventions)) {
197
- const conventions = raw.conventions;
203
+ if (raw &&
204
+ typeof raw === "object" &&
205
+ "conventions" in raw &&
206
+ Array.isArray(raw.conventions)) {
207
+ const conventions = raw
208
+ .conventions;
198
209
  for (let i = 0; i < conventions.length; i++) {
199
210
  const convention = conventions[i];
200
211
  const conventionResult = ConventionSchema.safeParse(convention);
@@ -202,28 +213,34 @@ async function validateMetaManifestFile(filePath) {
202
213
  for (const issue of conventionResult.error.issues) {
203
214
  errors.push({
204
215
  file: filePath,
205
- path: `conventions[${i}].${issue.path.join('.')}`,
216
+ path: `conventions[${i}].${issue.path.join(".")}`,
206
217
  message: issue.message,
207
218
  details: issue,
208
219
  });
209
220
  }
210
221
  }
211
222
  // Strict ULID validation
212
- if (convention && typeof convention === 'object' && '_ulid' in convention) {
223
+ if (convention &&
224
+ typeof convention === "object" &&
225
+ "_ulid" in convention) {
213
226
  const ulidResult = UlidSchema.safeParse(convention._ulid);
214
227
  if (!ulidResult.success) {
215
228
  errors.push({
216
229
  file: filePath,
217
230
  path: `conventions[${i}]._ulid`,
218
- message: 'Invalid ULID format (expected 26 characters)',
231
+ message: "Invalid ULID format (expected 26 characters)",
219
232
  });
220
233
  }
221
234
  }
222
235
  }
223
236
  }
224
237
  // Validate each observation with strict ULID validation
225
- if (raw && typeof raw === 'object' && 'observations' in raw && Array.isArray(raw.observations)) {
226
- const observations = raw.observations;
238
+ if (raw &&
239
+ typeof raw === "object" &&
240
+ "observations" in raw &&
241
+ Array.isArray(raw.observations)) {
242
+ const observations = raw
243
+ .observations;
227
244
  for (let i = 0; i < observations.length; i++) {
228
245
  const observation = observations[i];
229
246
  const observationResult = ObservationSchema.safeParse(observation);
@@ -231,20 +248,54 @@ async function validateMetaManifestFile(filePath) {
231
248
  for (const issue of observationResult.error.issues) {
232
249
  errors.push({
233
250
  file: filePath,
234
- path: `observations[${i}].${issue.path.join('.')}`,
251
+ path: `observations[${i}].${issue.path.join(".")}`,
235
252
  message: issue.message,
236
253
  details: issue,
237
254
  });
238
255
  }
239
256
  }
240
257
  // Strict ULID validation
241
- if (observation && typeof observation === 'object' && '_ulid' in observation) {
258
+ if (observation &&
259
+ typeof observation === "object" &&
260
+ "_ulid" in observation) {
242
261
  const ulidResult = UlidSchema.safeParse(observation._ulid);
243
262
  if (!ulidResult.success) {
244
263
  errors.push({
245
264
  file: filePath,
246
265
  path: `observations[${i}]._ulid`,
247
- message: 'Invalid ULID format (expected 26 characters)',
266
+ message: "Invalid ULID format (expected 26 characters)",
267
+ });
268
+ }
269
+ }
270
+ }
271
+ }
272
+ // AC: @skill-validation ac-4 - validate each skill with strict ULID validation
273
+ if (raw &&
274
+ typeof raw === "object" &&
275
+ "skills" in raw &&
276
+ Array.isArray(raw.skills)) {
277
+ const skills = raw.skills;
278
+ for (let i = 0; i < skills.length; i++) {
279
+ const skill = skills[i];
280
+ const skillResult = SkillSchema.safeParse(skill);
281
+ if (!skillResult.success) {
282
+ for (const issue of skillResult.error.issues) {
283
+ errors.push({
284
+ file: filePath,
285
+ path: `skills[${i}].${issue.path.join(".")}`,
286
+ message: issue.message,
287
+ details: issue,
288
+ });
289
+ }
290
+ }
291
+ // Strict ULID validation
292
+ if (skill && typeof skill === "object" && "_ulid" in skill) {
293
+ const ulidResult = UlidSchema.safeParse(skill._ulid);
294
+ if (!ulidResult.success) {
295
+ errors.push({
296
+ file: filePath,
297
+ path: `skills[${i}]._ulid`,
298
+ message: "Invalid ULID format (expected 26 characters)",
248
299
  });
249
300
  }
250
301
  }
@@ -263,16 +314,18 @@ async function validateMetaManifestFile(filePath) {
263
314
  * Recursively validate spec items in a structure
264
315
  */
265
316
  function validateSpecItemRecursive(raw, file, pathPrefix, errors) {
266
- if (!raw || typeof raw !== 'object')
317
+ if (!raw || typeof raw !== "object")
267
318
  return;
268
319
  // Check if this is a spec item (has _ulid)
269
- if ('_ulid' in raw) {
320
+ if ("_ulid" in raw) {
270
321
  const result = SpecItemSchema.safeParse(raw);
271
322
  if (!result.success) {
272
323
  for (const issue of result.error.issues) {
273
324
  errors.push({
274
325
  file,
275
- path: pathPrefix ? `${pathPrefix}.${issue.path.join('.')}` : issue.path.join('.'),
326
+ path: pathPrefix
327
+ ? `${pathPrefix}.${issue.path.join(".")}`
328
+ : issue.path.join("."),
276
329
  message: issue.message,
277
330
  details: issue,
278
331
  });
@@ -280,13 +333,22 @@ function validateSpecItemRecursive(raw, file, pathPrefix, errors) {
280
333
  }
281
334
  }
282
335
  // Recurse into nested structures
283
- const nestedFields = ['modules', 'features', 'requirements', 'constraints', 'decisions', 'items'];
336
+ const nestedFields = [
337
+ "modules",
338
+ "features",
339
+ "requirements",
340
+ "constraints",
341
+ "decisions",
342
+ "items",
343
+ ];
284
344
  const obj = raw;
285
345
  for (const field of nestedFields) {
286
346
  if (field in obj && Array.isArray(obj[field])) {
287
347
  const arr = obj[field];
288
348
  for (let i = 0; i < arr.length; i++) {
289
- const newPath = pathPrefix ? `${pathPrefix}.${field}[${i}]` : `${field}[${i}]`;
349
+ const newPath = pathPrefix
350
+ ? `${pathPrefix}.${field}[${i}]`
351
+ : `${field}[${i}]`;
290
352
  validateSpecItemRecursive(arr[i], file, newPath, errors);
291
353
  }
292
354
  }
@@ -305,20 +367,20 @@ function findOrphans(tasks, items, index) {
305
367
  const allItems = [...tasks, ...items];
306
368
  // Fields that contain references
307
369
  const refFields = [
308
- 'depends_on',
309
- 'blocked_by',
310
- 'implements',
311
- 'relates_to',
312
- 'tests',
313
- 'supersedes',
314
- 'spec_ref',
315
- 'context',
370
+ "depends_on",
371
+ "blocked_by",
372
+ "implements",
373
+ "relates_to",
374
+ "tests",
375
+ "supersedes",
376
+ "spec_ref",
377
+ "context",
316
378
  ];
317
379
  for (const item of allItems) {
318
380
  const obj = item;
319
381
  for (const field of refFields) {
320
382
  const value = obj[field];
321
- if (typeof value === 'string' && value.startsWith('@')) {
383
+ if (typeof value === "string" && value.startsWith("@")) {
322
384
  const resolved = index.resolve(value);
323
385
  if (resolved.ok) {
324
386
  referenced.add(resolved.ulid);
@@ -326,7 +388,7 @@ function findOrphans(tasks, items, index) {
326
388
  }
327
389
  else if (Array.isArray(value)) {
328
390
  for (const v of value) {
329
- if (typeof v === 'string' && v.startsWith('@')) {
391
+ if (typeof v === "string" && v.startsWith("@")) {
330
392
  const resolved = index.resolve(v);
331
393
  if (resolved.ok) {
332
394
  referenced.add(resolved.ulid);
@@ -338,12 +400,12 @@ function findOrphans(tasks, items, index) {
338
400
  }
339
401
  // Find items not in the referenced set
340
402
  // Skip entry point types: modules are spec entry points, tasks are work items
341
- const entryPointTypes = ['module', 'task', 'epic', 'bug', 'spike', 'infra'];
403
+ const entryPointTypes = ["module", "task", "epic", "bug", "spike", "infra"];
342
404
  for (const item of items) {
343
405
  // Only check spec items, not tasks
344
406
  if (!referenced.has(item._ulid)) {
345
407
  // Skip entry point types
346
- if (entryPointTypes.includes(item.type || ''))
408
+ if (entryPointTypes.includes(item.type || ""))
347
409
  continue;
348
410
  // Skip nested items - they're implicitly referenced by their parent
349
411
  // _path indicates nesting (e.g., "features[0].requirements[2]")
@@ -352,7 +414,7 @@ function findOrphans(tasks, items, index) {
352
414
  orphans.push({
353
415
  ulid: item._ulid,
354
416
  title: item.title,
355
- type: item.type || 'unknown',
417
+ type: item.type || "unknown",
356
418
  file: item._sourceFile,
357
419
  });
358
420
  }
@@ -368,12 +430,14 @@ function findOrphans(tasks, items, index) {
368
430
  */
369
431
  function detectTraitCycles(items, index) {
370
432
  const errors = [];
371
- const traits = items.filter(item => item.type === 'trait');
433
+ const traits = items.filter((item) => item.type === "trait");
372
434
  // Build adjacency list: trait ULID → trait ULIDs it references
373
435
  const graph = new Map();
374
436
  const traitInfo = new Map();
375
437
  for (const trait of traits) {
376
- const ref = trait.slugs?.[0] ? `@${trait.slugs[0]}` : `@${trait._ulid.slice(0, 8)}`;
438
+ const ref = trait.slugs?.[0]
439
+ ? `@${trait.slugs[0]}`
440
+ : `@${trait._ulid.slice(0, 8)}`;
377
441
  traitInfo.set(trait._ulid, { ref, title: trait.title });
378
442
  const dependencies = [];
379
443
  if (trait.traits && trait.traits.length > 0) {
@@ -419,7 +483,7 @@ function detectTraitCycles(items, index) {
419
483
  if (cycle) {
420
484
  const info = traitInfo.get(cycle[0]);
421
485
  if (info) {
422
- const cycleRefs = cycle.map(ulid => {
486
+ const cycleRefs = cycle.map((ulid) => {
423
487
  const cycleInfo = traitInfo.get(ulid);
424
488
  return cycleInfo ? cycleInfo.ref : `@${ulid.slice(0, 8)}`;
425
489
  });
@@ -427,7 +491,7 @@ function detectTraitCycles(items, index) {
427
491
  traitRef: info.ref,
428
492
  traitTitle: info.title,
429
493
  cycle: cycleRefs,
430
- message: `Circular trait reference: ${cycleRefs.join('')} → ${cycleRefs[0]}`,
494
+ message: `Circular trait reference: ${cycleRefs.join("")} → ${cycleRefs[0]}`,
431
495
  });
432
496
  }
433
497
  // Mark all traits in cycle as visited to avoid duplicate errors
@@ -443,21 +507,45 @@ function detectTraitCycles(items, index) {
443
507
  // COMPLETENESS VALIDATION
444
508
  // ============================================================
445
509
  /**
446
- * Scan test files for AC annotations to build coverage index
510
+ * Recursively find all test files in a directory
511
+ */
512
+ async function findTestFilesRecursive(dir) {
513
+ const testFiles = [];
514
+ try {
515
+ const entries = await fs.readdir(dir, { withFileTypes: true });
516
+ for (const entry of entries) {
517
+ const fullPath = path.join(dir, entry.name);
518
+ if (entry.isDirectory()) {
519
+ // Recurse into subdirectories
520
+ const subFiles = await findTestFilesRecursive(fullPath);
521
+ testFiles.push(...subFiles);
522
+ }
523
+ else if (entry.isFile() &&
524
+ (entry.name.endsWith(".test.ts") || entry.name.endsWith(".test.js"))) {
525
+ testFiles.push(fullPath);
526
+ }
527
+ }
528
+ }
529
+ catch {
530
+ // Directory doesn't exist or can't be read - return empty
531
+ }
532
+ return testFiles;
533
+ }
534
+ /**
535
+ * Scan test files for AC annotations to build coverage index.
536
+ * Recursively scans all subdirectories under tests/.
447
537
  * Returns a Set of covered ACs in format "@spec-ref ac-N"
448
538
  */
449
- async function scanTestCoverage(rootDir) {
539
+ export async function scanTestCoverage(rootDir) {
450
540
  const coveredACs = new Set();
451
- const testsDir = path.join(rootDir, 'tests');
541
+ const testsDir = path.join(rootDir, "tests");
452
542
  try {
453
543
  // Check if tests directory exists
454
544
  await fs.access(testsDir);
455
- // Read all test files
456
- const files = await fs.readdir(testsDir);
457
- const testFiles = files.filter(f => f.endsWith('.test.ts') || f.endsWith('.test.js'));
458
- for (const file of testFiles) {
459
- const filePath = path.join(testsDir, file);
460
- const content = await fs.readFile(filePath, 'utf-8');
545
+ // Recursively find all test files
546
+ const testFiles = await findTestFilesRecursive(testsDir);
547
+ for (const filePath of testFiles) {
548
+ const content = await fs.readFile(filePath, "utf-8");
461
549
  // Match AC annotations: // AC: @spec-ref ac-N
462
550
  // Also handle multiple ACs on one line: // AC: @spec-ref ac-1, ac-2
463
551
  const acPattern = /\/\/\s*AC:\s*(@[\w-]+)(?:\s+(ac-\d+(?:\s*,\s*ac-\d+)*))?/g;
@@ -467,7 +555,7 @@ async function scanTestCoverage(rootDir) {
467
555
  const acList = match[2]; // "ac-1, ac-2" or just "ac-1" or undefined
468
556
  if (acList) {
469
557
  // Split by comma and trim
470
- const acs = acList.split(',').map(ac => ac.trim());
558
+ const acs = acList.split(",").map((ac) => ac.trim());
471
559
  for (const ac of acs) {
472
560
  coveredACs.add(`${specRef} ${ac}`);
473
561
  }
@@ -480,68 +568,97 @@ async function scanTestCoverage(rootDir) {
480
568
  }
481
569
  }
482
570
  }
483
- catch (err) {
571
+ catch (_err) {
484
572
  // Tests directory doesn't exist or can't be read - that's ok
485
573
  }
486
574
  return coveredACs;
487
575
  }
576
+ /**
577
+ * Compute coverage status for acceptance criteria of a spec item.
578
+ * Shared utility used by both daemon API and JSON export.
579
+ *
580
+ * @param item - The spec item containing acceptance_criteria
581
+ * @param coveredACs - Set of covered AC references from scanTestCoverage()
582
+ * @returns Array of ACs with covered field populated
583
+ */
584
+ export function computeACCoverage(item, coveredACs) {
585
+ if (!item.acceptance_criteria || item.acceptance_criteria.length === 0) {
586
+ return [];
587
+ }
588
+ return item.acceptance_criteria.map((ac, index) => {
589
+ const acId = `ac-${index + 1}`;
590
+ const possibleRefs = [];
591
+ // Try with primary slug
592
+ if (item.slugs && item.slugs.length > 0) {
593
+ possibleRefs.push(`@${item.slugs[0]} ${acId}`);
594
+ possibleRefs.push(`@${item.slugs[0]}`);
595
+ }
596
+ // Try with ULID (short form)
597
+ possibleRefs.push(`@${item._ulid.slice(0, 8)} ${acId}`);
598
+ possibleRefs.push(`@${item._ulid.slice(0, 8)}`);
599
+ const covered = possibleRefs.some((ref) => coveredACs.has(ref));
600
+ return { ...ac, covered };
601
+ });
602
+ }
488
603
  /**
489
604
  * Check spec items for completeness
490
605
  * AC: @spec-completeness ac-1, ac-2, ac-3
491
606
  * AC: @trait-validation ac-1, ac-2, ac-3
492
607
  */
493
- async function checkCompleteness(items, index, rootDir, traitIndex) {
608
+ async function checkCompleteness(items, _index, rootDir, traitIndex) {
494
609
  const warnings = [];
495
610
  // Scan test files for AC coverage
496
611
  const coveredACs = await scanTestCoverage(rootDir);
497
612
  for (const item of items) {
498
- const itemRef = item.slugs?.[0] ? `@${item.slugs[0]}` : `@${item._ulid.slice(0, 8)}`;
499
- const isTrait = item.type === 'trait';
613
+ const itemRef = item.slugs?.[0]
614
+ ? `@${item.slugs[0]}`
615
+ : `@${item._ulid.slice(0, 8)}`;
616
+ const isTrait = item.type === "trait";
500
617
  // AC: @spec-completeness ac-1
501
618
  // AC: @trait-type ac-2 - Traits should have acceptance criteria for completeness
502
619
  // Check for missing acceptance criteria
503
620
  if (!item.acceptance_criteria || item.acceptance_criteria.length === 0) {
504
621
  warnings.push({
505
- type: 'missing_acceptance_criteria',
622
+ type: "missing_acceptance_criteria",
506
623
  itemRef,
507
624
  itemTitle: item.title,
508
- message: `${isTrait ? 'Trait' : 'Item'} ${itemRef} has no acceptance criteria`,
625
+ message: `${isTrait ? "Trait" : "Item"} ${itemRef} has no acceptance criteria`,
509
626
  });
510
627
  }
511
628
  // AC: @spec-completeness ac-2
512
629
  // AC: @trait-type ac-3 - Traits should have description for completeness
513
630
  // Check for missing description
514
- if (!item.description || item.description.trim() === '') {
631
+ if (!item.description || item.description.trim() === "") {
515
632
  warnings.push({
516
- type: 'missing_description',
633
+ type: "missing_description",
517
634
  itemRef,
518
635
  itemTitle: item.title,
519
- message: `${isTrait ? 'Trait' : 'Item'} ${itemRef} has no description`,
636
+ message: `${isTrait ? "Trait" : "Item"} ${itemRef} has no description`,
520
637
  });
521
638
  }
522
639
  // AC: @spec-completeness ac-3
523
640
  // Check for status inconsistency between parent and children
524
- if (item.status?.implementation === 'implemented') {
641
+ if (item.status?.implementation === "implemented") {
525
642
  // Check if this item has children with not_started status
526
643
  const childFields = [
527
- 'modules',
528
- 'features',
529
- 'requirements',
530
- 'constraints',
531
- 'epics',
532
- 'themes',
533
- 'capabilities',
644
+ "modules",
645
+ "features",
646
+ "requirements",
647
+ "constraints",
648
+ "epics",
649
+ "themes",
650
+ "capabilities",
534
651
  ];
535
652
  for (const field of childFields) {
536
653
  const children = item[field];
537
654
  if (Array.isArray(children)) {
538
655
  for (const child of children) {
539
- if (child.status?.implementation === 'not_started') {
656
+ if (child.status?.implementation === "not_started") {
540
657
  const childRef = child.slugs?.[0]
541
658
  ? `@${child.slugs[0]}`
542
- : `@${child._ulid?.slice(0, 8) || 'unknown'}`;
659
+ : `@${child._ulid?.slice(0, 8) || "unknown"}`;
543
660
  warnings.push({
544
- type: 'status_inconsistency',
661
+ type: "status_inconsistency",
545
662
  itemRef,
546
663
  itemTitle: item.title,
547
664
  message: `Parent ${itemRef} is implemented but child ${childRef} is not_started`,
@@ -568,7 +685,7 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
568
685
  possibleRefs.push(`@${item._ulid.slice(0, 8)} ${ac.id}`);
569
686
  possibleRefs.push(`@${item._ulid.slice(0, 8)}`);
570
687
  // Check if any of these references are covered
571
- const isCovered = possibleRefs.some(ref => coveredACs.has(ref));
688
+ const isCovered = possibleRefs.some((ref) => coveredACs.has(ref));
572
689
  if (!isCovered) {
573
690
  uncoveredACs.push(ac.id);
574
691
  }
@@ -576,11 +693,11 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
576
693
  // Only warn if there are uncovered ACs
577
694
  if (uncoveredACs.length > 0) {
578
695
  warnings.push({
579
- type: 'missing_test_coverage',
696
+ type: "missing_test_coverage",
580
697
  itemRef,
581
698
  itemTitle: item.title,
582
699
  message: `Item ${itemRef} has ${uncoveredACs.length} AC(s) without test coverage`,
583
- details: `Uncovered: ${uncoveredACs.join(', ')}`,
700
+ details: `Uncovered: ${uncoveredACs.join(", ")}`,
584
701
  });
585
702
  }
586
703
  }
@@ -599,7 +716,7 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
599
716
  possibleRefs.push(`@${trait.ulid.slice(0, 8)} ${ac.id}`);
600
717
  possibleRefs.push(`@${trait.ulid.slice(0, 8)}`);
601
718
  // Check if any of these references are covered
602
- const isCovered = possibleRefs.some(ref => coveredACs.has(ref));
719
+ const isCovered = possibleRefs.some((ref) => coveredACs.has(ref));
603
720
  if (!isCovered) {
604
721
  uncoveredTraitACs.push({ traitSlug: trait.slug, acId: ac.id });
605
722
  }
@@ -608,9 +725,9 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
608
725
  if (uncoveredTraitACs.length > 0) {
609
726
  const details = uncoveredTraitACs
610
727
  .map(({ traitSlug, acId }) => `@${traitSlug} ${acId}`)
611
- .join(', ');
728
+ .join(", ");
612
729
  warnings.push({
613
- type: 'missing_test_coverage',
730
+ type: "missing_test_coverage",
614
731
  itemRef,
615
732
  itemTitle: item.title,
616
733
  message: `Item ${itemRef} has ${uncoveredTraitACs.length} inherited trait AC(s) without test coverage`,
@@ -618,10 +735,270 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
618
735
  });
619
736
  }
620
737
  }
738
+ // AC: @trait-retrospective ac-2, ac-3
739
+ // Check retrospective specs have required verification metadata
740
+ const isRetrospective = item.traits?.includes("@trait-retrospective");
741
+ if (isRetrospective) {
742
+ const isImplementedOrVerified = item.status?.implementation === "implemented" ||
743
+ item.status?.implementation === "verified";
744
+ // AC: @trait-retrospective ac-2
745
+ // Retrospective specs with implemented/verified status must have verification metadata
746
+ if (isImplementedOrVerified) {
747
+ if (!item.verified_at || !item.verified_by) {
748
+ warnings.push({
749
+ type: "status_inconsistency",
750
+ itemRef,
751
+ itemTitle: item.title,
752
+ message: `Retrospective spec ${itemRef} is ${item.status?.implementation} but missing verified_at/verified_by`,
753
+ details: "Specs using @trait-retrospective must have verified_at and verified_by fields when marked as implemented or verified",
754
+ });
755
+ }
756
+ }
757
+ // Inverse validation: If verification metadata exists, status should be implemented/verified
758
+ if ((item.verified_at || item.verified_by) && !isImplementedOrVerified) {
759
+ warnings.push({
760
+ type: "status_inconsistency",
761
+ itemRef,
762
+ itemTitle: item.title,
763
+ message: `Retrospective spec ${itemRef} has verification metadata but status is not implemented/verified`,
764
+ details: "Retrospective specs with verified_at/verified_by should have implementation status set to 'implemented' or 'verified'",
765
+ });
766
+ }
767
+ }
621
768
  }
622
769
  return warnings;
623
770
  }
624
771
  // ============================================================
772
+ // AC SCHEMA DRIFT DETECTION
773
+ // ============================================================
774
+ /**
775
+ * Known schema fields for SpecItem
776
+ * These are the actual fields defined in SpecItemSchema
777
+ */
778
+ const SPEC_ITEM_FIELDS = new Set([
779
+ "_ulid",
780
+ "slugs",
781
+ "title",
782
+ "type",
783
+ "status",
784
+ "maturity",
785
+ "implementation",
786
+ "priority",
787
+ "tags",
788
+ "description",
789
+ "acceptance_criteria",
790
+ "depends_on",
791
+ "implements",
792
+ "relates_to",
793
+ "tests",
794
+ "traits",
795
+ "supersedes",
796
+ "traceability",
797
+ "created",
798
+ "created_by",
799
+ "deprecated_in",
800
+ "superseded_by",
801
+ "verified_at",
802
+ "verified_by",
803
+ "notes",
804
+ ]);
805
+ /**
806
+ * Known schema fields for Task
807
+ * These are the actual fields defined in TaskSchema
808
+ */
809
+ const TASK_FIELDS = new Set([
810
+ "_ulid",
811
+ "slugs",
812
+ "title",
813
+ "type",
814
+ "description",
815
+ "spec_ref",
816
+ "derivation",
817
+ "meta_ref",
818
+ "plan_ref",
819
+ "origin",
820
+ "status",
821
+ "blocked_by",
822
+ "closed_reason",
823
+ "depends_on",
824
+ "context",
825
+ "priority",
826
+ "complexity",
827
+ "tags",
828
+ "assignee",
829
+ "vcs_refs",
830
+ "created_at",
831
+ "started_at",
832
+ "completed_at",
833
+ "notes",
834
+ "todos",
835
+ "automation",
836
+ ]);
837
+ /**
838
+ * Known fields that are referenced but are parse-time or conceptual only
839
+ * These are common pseudo-fields that ACs reference but don't exist in schema
840
+ */
841
+ const CONCEPTUAL_FIELDS = new Set([
842
+ "children", // Hierarchy is parse-time only
843
+ "parent", // Parent references are derived, not stored
844
+ "modules", // These are container structures in YAML, not item fields
845
+ "features",
846
+ "requirements",
847
+ "constraints",
848
+ "decisions",
849
+ "epics",
850
+ "themes",
851
+ "capabilities",
852
+ ]);
853
+ /**
854
+ * Extract field reference patterns from AC text
855
+ * Looks for patterns like:
856
+ * - item.field
857
+ * - spec.field
858
+ * - task.field
859
+ * - status.field
860
+ * - spec_ref.field
861
+ * - the field
862
+ * - their field
863
+ * - item's field
864
+ */
865
+ /**
866
+ * File extensions to exclude from field matching
867
+ * These are common file extensions that would otherwise match our patterns
868
+ */
869
+ const FILE_EXTENSIONS = new Set([
870
+ "yaml",
871
+ "yml",
872
+ "json",
873
+ "js",
874
+ "ts",
875
+ "md",
876
+ "txt",
877
+ "html",
878
+ "css",
879
+ "log",
880
+ ]);
881
+ function extractFieldReferences(text) {
882
+ const references = [];
883
+ // Pattern: context.field (e.g., item.status, spec_ref.children)
884
+ // This matches entity.property patterns in prose
885
+ const dotPattern = /\b(item|spec|task|status|spec_ref|task_ref|meta_ref|plan_ref)\.(\w+)/gi;
886
+ let match;
887
+ while ((match = dotPattern.exec(text)) !== null) {
888
+ const field = match[2].toLowerCase();
889
+ // Skip file extensions (e.g., spec.yaml, task.json)
890
+ if (FILE_EXTENSIONS.has(field)) {
891
+ continue;
892
+ }
893
+ references.push({ context: match[1].toLowerCase(), field });
894
+ }
895
+ // Pattern: "the <field>" when discussing items/tasks (common in ACs)
896
+ // Only match known schema-like words to avoid false positives
897
+ const thePattern = /\bthe\s+(status|children|parent|spec_ref|depends_on|traits|tags|notes|todos|acceptance_criteria)\b/gi;
898
+ while ((match = thePattern.exec(text)) !== null) {
899
+ references.push({ context: "item", field: match[1].toLowerCase() });
900
+ }
901
+ return references;
902
+ }
903
+ /**
904
+ * Check if a field exists in the appropriate schema
905
+ */
906
+ function isValidSchemaField(context, field) {
907
+ // If it's a known conceptual field, it's technically invalid but we can give a specific message
908
+ if (CONCEPTUAL_FIELDS.has(field)) {
909
+ return { valid: false, reason: `'${field}' is a parse-time/conceptual field, not stored in schema` };
910
+ }
911
+ // Check based on context
912
+ switch (context) {
913
+ case "spec":
914
+ case "item":
915
+ if (SPEC_ITEM_FIELDS.has(field)) {
916
+ return { valid: true };
917
+ }
918
+ break;
919
+ case "task":
920
+ if (TASK_FIELDS.has(field)) {
921
+ return { valid: true };
922
+ }
923
+ break;
924
+ case "status":
925
+ // status.maturity, status.implementation are valid for specs
926
+ if (field === "maturity" || field === "implementation") {
927
+ return { valid: true };
928
+ }
929
+ break;
930
+ case "spec_ref":
931
+ case "task_ref":
932
+ case "meta_ref":
933
+ case "plan_ref":
934
+ // These are string references, not objects with fields
935
+ return { valid: false, reason: `'${context}' is a reference string, not an object with fields` };
936
+ }
937
+ // Check if field exists in either schema (might be ambiguous context)
938
+ if (SPEC_ITEM_FIELDS.has(field) || TASK_FIELDS.has(field)) {
939
+ return { valid: true };
940
+ }
941
+ return { valid: false, reason: `'${field}' is not a known schema field` };
942
+ }
943
+ /**
944
+ * Check acceptance criteria for field references that don't exist in schema
945
+ * Catches drift between spec prose and implementation reality.
946
+ */
947
+ export function checkACSchemaReferences(items) {
948
+ const warnings = [];
949
+ for (const item of items) {
950
+ if (!item.acceptance_criteria || item.acceptance_criteria.length === 0) {
951
+ continue;
952
+ }
953
+ const itemRef = item.slugs?.[0]
954
+ ? `@${item.slugs[0]}`
955
+ : `@${item._ulid.slice(0, 8)}`;
956
+ for (const ac of item.acceptance_criteria) {
957
+ // Check all three parts of the AC for field references
958
+ const textToCheck = `${ac.given} ${ac.when} ${ac.then}`;
959
+ const fieldRefs = extractFieldReferences(textToCheck);
960
+ for (const { context, field } of fieldRefs) {
961
+ const result = isValidSchemaField(context, field);
962
+ if (!result.valid) {
963
+ warnings.push({
964
+ type: "ac_schema_field_mismatch",
965
+ itemRef,
966
+ itemTitle: item.title,
967
+ message: `AC ${ac.id} references '${context}.${field}' which doesn't exist in schema`,
968
+ details: result.reason,
969
+ });
970
+ }
971
+ }
972
+ }
973
+ }
974
+ return warnings;
975
+ }
976
+ // ============================================================
977
+ // SKILL CONTENT VALIDATION
978
+ // ============================================================
979
+ /**
980
+ * Validate that skill meta entries have corresponding SKILL.md files.
981
+ * AC: @skill-content-model ac-3 - missing content file reports validation error
982
+ */
983
+ async function validateSkillContentFiles(ctx, skills) {
984
+ const errors = [];
985
+ for (const skill of skills) {
986
+ const contentPath = getSkillContentPath(ctx, skill.id);
987
+ try {
988
+ await fs.access(contentPath);
989
+ }
990
+ catch {
991
+ // File doesn't exist - report validation error
992
+ errors.push({
993
+ file: skill._sourceFile || "kynetic.meta.yaml",
994
+ path: `skills.${skill.id}`,
995
+ message: `Skill '${skill.id}' is missing content file at ${contentPath}`,
996
+ });
997
+ }
998
+ }
999
+ return errors;
1000
+ }
1001
+ // ============================================================
625
1002
  // AUTOMATION VALIDATION
626
1003
  // ============================================================
627
1004
  /**
@@ -631,12 +1008,14 @@ async function checkCompleteness(items, index, rootDir, traitIndex) {
631
1008
  function checkAutomationEligibility(tasks, index) {
632
1009
  const warnings = [];
633
1010
  for (const task of tasks) {
634
- const taskRef = task.slugs?.[0] ? `@${task.slugs[0]}` : `@${task._ulid.slice(0, 8)}`;
1011
+ const taskRef = task.slugs?.[0]
1012
+ ? `@${task.slugs[0]}`
1013
+ : `@${task._ulid.slice(0, 8)}`;
635
1014
  // AC: @task-automation-eligibility ac-21
636
1015
  // Warn if eligible but no spec_ref
637
- if (task.automation === 'eligible' && !task.spec_ref) {
1016
+ if (task.automation === "eligible" && !task.spec_ref) {
638
1017
  warnings.push({
639
- type: 'automation_eligible_no_spec',
1018
+ type: "automation_eligible_no_spec",
640
1019
  itemRef: taskRef,
641
1020
  itemTitle: task.title,
642
1021
  message: `Task ${taskRef} is automation: eligible but has no spec_ref - eligible tasks should have linked specs`,
@@ -644,11 +1023,11 @@ function checkAutomationEligibility(tasks, index) {
644
1023
  }
645
1024
  // AC: @task-automation-eligibility ac-23
646
1025
  // Warn if eligible but spec_ref doesn't resolve
647
- if (task.automation === 'eligible' && task.spec_ref) {
1026
+ if (task.automation === "eligible" && task.spec_ref) {
648
1027
  const specResult = index.resolve(task.spec_ref);
649
1028
  if (!specResult.ok) {
650
1029
  warnings.push({
651
- type: 'automation_eligible_no_spec',
1030
+ type: "automation_eligible_no_spec",
652
1031
  itemRef: taskRef,
653
1032
  itemTitle: task.title,
654
1033
  message: `Task ${taskRef} is automation: eligible but spec_ref ${task.spec_ref} cannot be resolved`,
@@ -659,6 +1038,81 @@ function checkAutomationEligibility(tasks, index) {
659
1038
  return warnings;
660
1039
  }
661
1040
  // ============================================================
1041
+ // SKILL VALIDATION
1042
+ // ============================================================
1043
+ /**
1044
+ * Validate skill depends_on references
1045
+ * AC: @skill-validation ac-2 - broken depends_on ref reports warning
1046
+ */
1047
+ function validateSkillDependsOn(skills, index) {
1048
+ const warnings = [];
1049
+ for (const skill of skills) {
1050
+ if (!skill.depends_on || skill.depends_on.length === 0) {
1051
+ continue;
1052
+ }
1053
+ for (const depRef of skill.depends_on) {
1054
+ const result = index.resolve(depRef);
1055
+ if (!result.ok) {
1056
+ warnings.push({
1057
+ ref: depRef,
1058
+ sourceFile: skill._sourceFile,
1059
+ sourceUlid: skill._ulid,
1060
+ field: "depends_on",
1061
+ warning: "deprecated_target", // Reuse warning type for broken refs
1062
+ message: `Skill '${skill.id}' depends_on reference '${depRef}' cannot be resolved`,
1063
+ });
1064
+ }
1065
+ else {
1066
+ // Check that the resolved item is actually a skill (uses _type discriminant)
1067
+ if (!isSkill(result.item)) {
1068
+ warnings.push({
1069
+ ref: depRef,
1070
+ sourceFile: skill._sourceFile,
1071
+ sourceUlid: skill._ulid,
1072
+ field: "depends_on",
1073
+ warning: "deprecated_target",
1074
+ message: `Skill '${skill.id}' depends_on reference '${depRef}' points to non-skill item`,
1075
+ });
1076
+ }
1077
+ }
1078
+ }
1079
+ }
1080
+ return warnings;
1081
+ }
1082
+ /**
1083
+ * Find orphaned skill directories (directories with no corresponding meta entry)
1084
+ * AC: @skill-validation ac-3 - orphaned directory reports warning
1085
+ */
1086
+ async function findOrphanedSkillDirectories(ctx, skills) {
1087
+ const warnings = [];
1088
+ const skillsDir = path.join(ctx.specDir, "skills");
1089
+ try {
1090
+ // Check if skills directory exists
1091
+ await fs.access(skillsDir);
1092
+ // Read all directories in skills/
1093
+ const entries = await fs.readdir(skillsDir, { withFileTypes: true });
1094
+ const directories = entries.filter(entry => entry.isDirectory());
1095
+ // Build set of skill IDs from meta
1096
+ const skillIds = new Set(skills.map(s => s.id));
1097
+ // Find orphaned directories
1098
+ for (const dir of directories) {
1099
+ if (!skillIds.has(dir.name)) {
1100
+ warnings.push({
1101
+ ref: `@${dir.name}`,
1102
+ sourceFile: path.join(skillsDir, dir.name),
1103
+ field: "directory",
1104
+ warning: "deprecated_target", // Reuse warning type
1105
+ message: `Orphaned skill directory '${dir.name}' has no corresponding meta entry`,
1106
+ });
1107
+ }
1108
+ }
1109
+ }
1110
+ catch {
1111
+ // Skills directory doesn't exist - that's fine
1112
+ }
1113
+ return warnings;
1114
+ }
1115
+ // ============================================================
662
1116
  // MAIN VALIDATION
663
1117
  // ============================================================
664
1118
  /**
@@ -699,9 +1153,10 @@ export async function validate(ctx, options = {}) {
699
1153
  result.stats.itemsChecked += manifestItems.length;
700
1154
  }
701
1155
  // Find and validate task files
1156
+ // Exclude test fixtures which have their own self-contained references
702
1157
  const taskFiles = await findTaskFiles(ctx.rootDir);
703
- const specTaskFiles = await findTaskFiles(path.join(ctx.rootDir, 'spec'));
704
- const allTaskFiles = [...new Set([...taskFiles, ...specTaskFiles])];
1158
+ const specTaskFiles = await findTaskFiles(path.join(ctx.rootDir, "spec"));
1159
+ const allTaskFiles = [...new Set([...taskFiles, ...specTaskFiles])].filter((f) => !f.includes("/fixtures/"));
705
1160
  for (const taskFile of allTaskFiles) {
706
1161
  if (runSchema) {
707
1162
  const taskErrors = await validateTasksFile(taskFile);
@@ -715,7 +1170,7 @@ export async function validate(ctx, options = {}) {
715
1170
  if (Array.isArray(raw)) {
716
1171
  taskList = raw;
717
1172
  }
718
- else if (raw && typeof raw === 'object' && 'tasks' in raw) {
1173
+ else if (raw && typeof raw === "object" && "tasks" in raw) {
719
1174
  taskList = raw.tasks || [];
720
1175
  }
721
1176
  for (const t of taskList) {
@@ -762,13 +1217,46 @@ export async function validate(ctx, options = {}) {
762
1217
  ...metaCtx.workflows,
763
1218
  ...metaCtx.conventions,
764
1219
  ...metaCtx.observations,
1220
+ ...metaCtx.skills, // Include skills for reference indexing
765
1221
  ];
1222
+ // Load plans for reference validation
1223
+ // AC: @plan-validation ac-10
1224
+ const allPlans = await loadPlans(ctx);
766
1225
  // Reference validation
767
- if (runRefs && (allTasks.length > 0 || allItems.length > 0 || allMetaItems.length > 0)) {
768
- const index = new ReferenceIndex(allTasks, allItems, allMetaItems);
1226
+ if (runRefs &&
1227
+ (allTasks.length > 0 ||
1228
+ allItems.length > 0 ||
1229
+ allMetaItems.length > 0 ||
1230
+ allPlans.length > 0)) {
1231
+ const index = new ReferenceIndex(allTasks, allItems, allMetaItems, allPlans);
769
1232
  const refResult = validateRefs(index, allTasks, allItems);
770
- result.refErrors = refResult.errors;
771
- result.refWarnings = refResult.warnings;
1233
+ // AC: @config-validation ac-2 ac-3 — strict_refs controls error vs warning
1234
+ // When strictRefs is false, demote "not_found" ref errors to warnings
1235
+ if (options.strictRefs === false) {
1236
+ // Move not_found errors to warnings
1237
+ const notFoundErrors = refResult.errors.filter((e) => e.error === "not_found");
1238
+ const otherErrors = refResult.errors.filter((e) => e.error !== "not_found");
1239
+ result.refErrors = otherErrors;
1240
+ result.refWarnings = [
1241
+ ...refResult.warnings,
1242
+ ...notFoundErrors.map((e) => ({
1243
+ ref: e.ref,
1244
+ sourceFile: e.sourceFile,
1245
+ sourceUlid: e.sourceUlid,
1246
+ field: e.field,
1247
+ warning: "deprecated_target", // Reuse existing warning type
1248
+ message: e.message,
1249
+ })),
1250
+ ];
1251
+ }
1252
+ else {
1253
+ // Default/strict behavior: not_found refs are errors
1254
+ result.refErrors = refResult.errors;
1255
+ result.refWarnings = refResult.warnings;
1256
+ }
1257
+ // AC: @skill-validation ac-2 - validate skill depends_on references
1258
+ const skillDependsOnWarnings = validateSkillDependsOn(metaCtx.skills, index);
1259
+ result.refWarnings.push(...skillDependsOnWarnings);
772
1260
  // AC: @trait-edge-cases ac-2
773
1261
  // Detect circular trait references
774
1262
  result.traitCycleErrors = detectTraitCycles(allItems, index);
@@ -782,11 +1270,28 @@ export async function validate(ctx, options = {}) {
782
1270
  if (runCompleteness) {
783
1271
  // Build trait index for trait AC coverage validation
784
1272
  const traitIndex = new TraitIndex(allItems, index);
785
- result.completenessWarnings = await checkCompleteness(allItems, index, ctx.rootDir, traitIndex);
1273
+ const completenessWarnings = await checkCompleteness(allItems, index, ctx.rootDir, traitIndex);
786
1274
  // AC: @task-automation-eligibility ac-21, ac-23
787
1275
  // Check automation eligibility warnings for tasks
788
1276
  const automationWarnings = checkAutomationEligibility(allTasks, index);
789
- result.completenessWarnings.push(...automationWarnings);
1277
+ completenessWarnings.push(...automationWarnings);
1278
+ // AC: @config-validation ac-1 — require_acceptance promotes missing AC to errors
1279
+ if (options.requireAcceptance) {
1280
+ const missingAC = completenessWarnings.filter((w) => w.type === "missing_acceptance_criteria");
1281
+ const otherWarnings = completenessWarnings.filter((w) => w.type !== "missing_acceptance_criteria");
1282
+ // Promote missing AC warnings to schema errors
1283
+ for (const w of missingAC) {
1284
+ result.schemaErrors.push({
1285
+ file: "completeness",
1286
+ path: w.itemRef,
1287
+ message: w.message,
1288
+ });
1289
+ }
1290
+ result.completenessWarnings = otherWarnings;
1291
+ }
1292
+ else {
1293
+ result.completenessWarnings = completenessWarnings;
1294
+ }
790
1295
  }
791
1296
  }
792
1297
  // Meta manifest validation (AC-meta-manifest-2, AC-meta-manifest-3)
@@ -798,20 +1303,32 @@ export async function validate(ctx, options = {}) {
798
1303
  workflows: metaCtx.workflows.length,
799
1304
  conventions: metaCtx.conventions.length,
800
1305
  observations: metaCtx.observations.length,
1306
+ skills: metaCtx.skills.length,
801
1307
  };
802
1308
  // Validate meta manifest schema with strict ULID validation
803
1309
  if (runSchema) {
804
1310
  const metaErrors = await validateMetaManifestFile(metaManifestPath);
805
1311
  // Prefix all meta errors with "meta:"
806
1312
  for (const err of metaErrors) {
807
- err.path = err.path ? `meta:${err.path}` : 'meta:';
1313
+ err.path = err.path ? `meta:${err.path}` : "meta:";
808
1314
  }
809
1315
  result.schemaErrors.push(...metaErrors);
810
1316
  result.stats.filesChecked++;
1317
+ // AC: @skill-content-model ac-3 - validate skill content files exist
1318
+ if (metaCtx.skills.length > 0) {
1319
+ const skillContentErrors = await validateSkillContentFiles(ctx, metaCtx.skills);
1320
+ result.schemaErrors.push(...skillContentErrors);
1321
+ }
1322
+ // AC: @skill-validation ac-3 - detect orphaned skill directories
1323
+ const orphanedSkillWarnings = await findOrphanedSkillDirectories(ctx, metaCtx.skills);
1324
+ result.refWarnings.push(...orphanedSkillWarnings);
811
1325
  }
812
1326
  }
813
1327
  // Set valid flag
814
- result.valid = result.schemaErrors.length === 0 && result.refErrors.length === 0 && result.traitCycleErrors.length === 0;
1328
+ result.valid =
1329
+ result.schemaErrors.length === 0 &&
1330
+ result.refErrors.length === 0 &&
1331
+ result.traitCycleErrors.length === 0;
815
1332
  return result;
816
1333
  }
817
1334
  //# sourceMappingURL=validate.js.map