@kynetic-ai/spec 0.1.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (540) hide show
  1. package/README.md +250 -17
  2. package/dist/acp/client.d.ts +18 -4
  3. package/dist/acp/client.d.ts.map +1 -1
  4. package/dist/acp/client.js +44 -26
  5. package/dist/acp/client.js.map +1 -1
  6. package/dist/acp/framing.d.ts +2 -2
  7. package/dist/acp/framing.d.ts.map +1 -1
  8. package/dist/acp/framing.js +37 -29
  9. package/dist/acp/framing.js.map +1 -1
  10. package/dist/acp/index.d.ts +6 -7
  11. package/dist/acp/index.d.ts.map +1 -1
  12. package/dist/acp/index.js +3 -3
  13. package/dist/acp/index.js.map +1 -1
  14. package/dist/acp/types.d.ts +5 -5
  15. package/dist/acp/types.d.ts.map +1 -1
  16. package/dist/acp/types.js +18 -18
  17. package/dist/acp/types.js.map +1 -1
  18. package/dist/agents/adapters.d.ts.map +1 -1
  19. package/dist/agents/adapters.js +24 -13
  20. package/dist/agents/adapters.js.map +1 -1
  21. package/dist/agents/index.d.ts +2 -2
  22. package/dist/agents/index.js +2 -2
  23. package/dist/agents/spawner.d.ts +4 -4
  24. package/dist/agents/spawner.d.ts.map +1 -1
  25. package/dist/agents/spawner.js +6 -6
  26. package/dist/agents/spawner.js.map +1 -1
  27. package/dist/cli/batch-context.d.ts +43 -0
  28. package/dist/cli/batch-context.d.ts.map +1 -0
  29. package/dist/cli/batch-context.js +93 -0
  30. package/dist/cli/batch-context.js.map +1 -0
  31. package/dist/cli/batch-exec.d.ts +107 -0
  32. package/dist/cli/batch-exec.d.ts.map +1 -0
  33. package/dist/cli/batch-exec.js +706 -0
  34. package/dist/cli/batch-exec.js.map +1 -0
  35. package/dist/cli/batch.d.ts +4 -2
  36. package/dist/cli/batch.d.ts.map +1 -1
  37. package/dist/cli/batch.js +15 -14
  38. package/dist/cli/batch.js.map +1 -1
  39. package/dist/cli/command-annotations.d.ts +23 -0
  40. package/dist/cli/command-annotations.d.ts.map +1 -0
  41. package/dist/cli/command-annotations.js +27 -0
  42. package/dist/cli/command-annotations.js.map +1 -0
  43. package/dist/cli/commands/agents.d.ts +46 -0
  44. package/dist/cli/commands/agents.d.ts.map +1 -0
  45. package/dist/cli/commands/agents.js +377 -0
  46. package/dist/cli/commands/agents.js.map +1 -0
  47. package/dist/cli/commands/batch.d.ts +20 -0
  48. package/dist/cli/commands/batch.d.ts.map +1 -0
  49. package/dist/cli/commands/batch.js +214 -0
  50. package/dist/cli/commands/batch.js.map +1 -0
  51. package/dist/cli/commands/clone-for-testing.d.ts +1 -1
  52. package/dist/cli/commands/clone-for-testing.d.ts.map +1 -1
  53. package/dist/cli/commands/clone-for-testing.js +37 -47
  54. package/dist/cli/commands/clone-for-testing.js.map +1 -1
  55. package/dist/cli/commands/derive.d.ts +1 -1
  56. package/dist/cli/commands/derive.d.ts.map +1 -1
  57. package/dist/cli/commands/derive.js +141 -88
  58. package/dist/cli/commands/derive.js.map +1 -1
  59. package/dist/cli/commands/doctor.d.ts +11 -0
  60. package/dist/cli/commands/doctor.d.ts.map +1 -0
  61. package/dist/cli/commands/doctor.js +152 -0
  62. package/dist/cli/commands/doctor.js.map +1 -0
  63. package/dist/cli/commands/export.d.ts +12 -0
  64. package/dist/cli/commands/export.d.ts.map +1 -0
  65. package/dist/cli/commands/export.js +134 -0
  66. package/dist/cli/commands/export.js.map +1 -0
  67. package/dist/cli/commands/help.d.ts +1 -1
  68. package/dist/cli/commands/help.d.ts.map +1 -1
  69. package/dist/cli/commands/help.js +163 -37
  70. package/dist/cli/commands/help.js.map +1 -1
  71. package/dist/cli/commands/inbox.d.ts +1 -1
  72. package/dist/cli/commands/inbox.d.ts.map +1 -1
  73. package/dist/cli/commands/inbox.js +178 -56
  74. package/dist/cli/commands/inbox.js.map +1 -1
  75. package/dist/cli/commands/index.d.ts +31 -19
  76. package/dist/cli/commands/index.d.ts.map +1 -1
  77. package/dist/cli/commands/index.js +31 -19
  78. package/dist/cli/commands/index.js.map +1 -1
  79. package/dist/cli/commands/init.d.ts +5 -1
  80. package/dist/cli/commands/init.d.ts.map +1 -1
  81. package/dist/cli/commands/init.js +108 -57
  82. package/dist/cli/commands/init.js.map +1 -1
  83. package/dist/cli/commands/item.d.ts +1 -1
  84. package/dist/cli/commands/item.d.ts.map +1 -1
  85. package/dist/cli/commands/item.js +557 -274
  86. package/dist/cli/commands/item.js.map +1 -1
  87. package/dist/cli/commands/link.d.ts +1 -1
  88. package/dist/cli/commands/link.d.ts.map +1 -1
  89. package/dist/cli/commands/link.js +55 -46
  90. package/dist/cli/commands/link.js.map +1 -1
  91. package/dist/cli/commands/log.d.ts +1 -1
  92. package/dist/cli/commands/log.d.ts.map +1 -1
  93. package/dist/cli/commands/log.js +58 -51
  94. package/dist/cli/commands/log.js.map +1 -1
  95. package/dist/cli/commands/merge-driver.d.ts +19 -0
  96. package/dist/cli/commands/merge-driver.d.ts.map +1 -0
  97. package/dist/cli/commands/merge-driver.js +398 -0
  98. package/dist/cli/commands/merge-driver.js.map +1 -0
  99. package/dist/cli/commands/meta.d.ts +1 -1
  100. package/dist/cli/commands/meta.d.ts.map +1 -1
  101. package/dist/cli/commands/meta.js +534 -399
  102. package/dist/cli/commands/meta.js.map +1 -1
  103. package/dist/cli/commands/module.d.ts +1 -1
  104. package/dist/cli/commands/module.d.ts.map +1 -1
  105. package/dist/cli/commands/module.js +30 -25
  106. package/dist/cli/commands/module.js.map +1 -1
  107. package/dist/cli/commands/plan-import.d.ts +11 -0
  108. package/dist/cli/commands/plan-import.d.ts.map +1 -0
  109. package/dist/cli/commands/plan-import.js +547 -0
  110. package/dist/cli/commands/plan-import.js.map +1 -0
  111. package/dist/cli/commands/plan.d.ts +10 -0
  112. package/dist/cli/commands/plan.d.ts.map +1 -0
  113. package/dist/cli/commands/plan.js +421 -0
  114. package/dist/cli/commands/plan.js.map +1 -0
  115. package/dist/cli/commands/ralph.d.ts +1 -1
  116. package/dist/cli/commands/ralph.d.ts.map +1 -1
  117. package/dist/cli/commands/ralph.js +1109 -170
  118. package/dist/cli/commands/ralph.js.map +1 -1
  119. package/dist/cli/commands/refs.d.ts +13 -0
  120. package/dist/cli/commands/refs.d.ts.map +1 -0
  121. package/dist/cli/commands/refs.js +283 -0
  122. package/dist/cli/commands/refs.js.map +1 -0
  123. package/dist/cli/commands/search.d.ts +1 -1
  124. package/dist/cli/commands/search.d.ts.map +1 -1
  125. package/dist/cli/commands/search.js +199 -37
  126. package/dist/cli/commands/search.js.map +1 -1
  127. package/dist/cli/commands/serve.d.ts +10 -0
  128. package/dist/cli/commands/serve.d.ts.map +1 -0
  129. package/dist/cli/commands/serve.js +491 -0
  130. package/dist/cli/commands/serve.js.map +1 -0
  131. package/dist/cli/commands/session.d.ts +25 -6
  132. package/dist/cli/commands/session.d.ts.map +1 -1
  133. package/dist/cli/commands/session.js +810 -127
  134. package/dist/cli/commands/session.js.map +1 -1
  135. package/dist/cli/commands/setup-seeding.d.ts +81 -0
  136. package/dist/cli/commands/setup-seeding.d.ts.map +1 -0
  137. package/dist/cli/commands/setup-seeding.js +292 -0
  138. package/dist/cli/commands/setup-seeding.js.map +1 -0
  139. package/dist/cli/commands/setup.d.ts +77 -3
  140. package/dist/cli/commands/setup.d.ts.map +1 -1
  141. package/dist/cli/commands/setup.js +1267 -274
  142. package/dist/cli/commands/setup.js.map +1 -1
  143. package/dist/cli/commands/shadow.d.ts +1 -1
  144. package/dist/cli/commands/shadow.d.ts.map +1 -1
  145. package/dist/cli/commands/shadow.js +70 -50
  146. package/dist/cli/commands/shadow.js.map +1 -1
  147. package/dist/cli/commands/skill-crud.d.ts +58 -0
  148. package/dist/cli/commands/skill-crud.d.ts.map +1 -0
  149. package/dist/cli/commands/skill-crud.js +753 -0
  150. package/dist/cli/commands/skill-crud.js.map +1 -0
  151. package/dist/cli/commands/skill-diff.d.ts +27 -0
  152. package/dist/cli/commands/skill-diff.d.ts.map +1 -0
  153. package/dist/cli/commands/skill-diff.js +840 -0
  154. package/dist/cli/commands/skill-diff.js.map +1 -0
  155. package/dist/cli/commands/skill-install.d.ts +56 -0
  156. package/dist/cli/commands/skill-install.d.ts.map +1 -0
  157. package/dist/cli/commands/skill-install.js +509 -0
  158. package/dist/cli/commands/skill-install.js.map +1 -0
  159. package/dist/cli/commands/skill.d.ts +20 -0
  160. package/dist/cli/commands/skill.d.ts.map +1 -0
  161. package/dist/cli/commands/skill.js +36 -0
  162. package/dist/cli/commands/skill.js.map +1 -0
  163. package/dist/cli/commands/task.d.ts +1 -1
  164. package/dist/cli/commands/task.d.ts.map +1 -1
  165. package/dist/cli/commands/task.js +584 -350
  166. package/dist/cli/commands/task.js.map +1 -1
  167. package/dist/cli/commands/tasks.d.ts +26 -1
  168. package/dist/cli/commands/tasks.d.ts.map +1 -1
  169. package/dist/cli/commands/tasks.js +225 -122
  170. package/dist/cli/commands/tasks.js.map +1 -1
  171. package/dist/cli/commands/trait.d.ts +1 -1
  172. package/dist/cli/commands/trait.d.ts.map +1 -1
  173. package/dist/cli/commands/trait.js +166 -101
  174. package/dist/cli/commands/trait.js.map +1 -1
  175. package/dist/cli/commands/triage.d.ts +7 -0
  176. package/dist/cli/commands/triage.d.ts.map +1 -0
  177. package/dist/cli/commands/triage.js +483 -0
  178. package/dist/cli/commands/triage.js.map +1 -0
  179. package/dist/cli/commands/util.d.ts +7 -0
  180. package/dist/cli/commands/util.d.ts.map +1 -0
  181. package/dist/cli/commands/util.js +30 -0
  182. package/dist/cli/commands/util.js.map +1 -0
  183. package/dist/cli/commands/validate.d.ts +1 -1
  184. package/dist/cli/commands/validate.d.ts.map +1 -1
  185. package/dist/cli/commands/validate.js +264 -83
  186. package/dist/cli/commands/validate.js.map +1 -1
  187. package/dist/cli/commands/workflow.d.ts +16 -0
  188. package/dist/cli/commands/workflow.d.ts.map +1 -0
  189. package/dist/cli/commands/workflow.js +851 -0
  190. package/dist/cli/commands/workflow.js.map +1 -0
  191. package/dist/cli/exit-codes.d.ts +7 -0
  192. package/dist/cli/exit-codes.d.ts.map +1 -1
  193. package/dist/cli/exit-codes.js +26 -18
  194. package/dist/cli/exit-codes.js.map +1 -1
  195. package/dist/cli/help/content.d.ts.map +1 -1
  196. package/dist/cli/help/content.js +86 -71
  197. package/dist/cli/help/content.js.map +1 -1
  198. package/dist/cli/index.d.ts +1 -1
  199. package/dist/cli/index.d.ts.map +1 -1
  200. package/dist/cli/index.js +131 -19
  201. package/dist/cli/index.js.map +1 -1
  202. package/dist/cli/introspection.d.ts +6 -2
  203. package/dist/cli/introspection.d.ts.map +1 -1
  204. package/dist/cli/introspection.js +11 -8
  205. package/dist/cli/introspection.js.map +1 -1
  206. package/dist/cli/output.d.ts +64 -4
  207. package/dist/cli/output.d.ts.map +1 -1
  208. package/dist/cli/output.js +237 -85
  209. package/dist/cli/output.js.map +1 -1
  210. package/dist/cli/parse-utils.d.ts +21 -0
  211. package/dist/cli/parse-utils.d.ts.map +1 -0
  212. package/dist/cli/parse-utils.js +32 -0
  213. package/dist/cli/parse-utils.js.map +1 -0
  214. package/dist/cli/pid-utils.d.ts +72 -0
  215. package/dist/cli/pid-utils.d.ts.map +1 -0
  216. package/dist/cli/pid-utils.js +174 -0
  217. package/dist/cli/pid-utils.js.map +1 -0
  218. package/dist/cli/suggest.d.ts.map +1 -1
  219. package/dist/cli/suggest.js +1 -2
  220. package/dist/cli/suggest.js.map +1 -1
  221. package/dist/cli/validators.d.ts +43 -0
  222. package/dist/cli/validators.d.ts.map +1 -0
  223. package/dist/cli/validators.js +84 -0
  224. package/dist/cli/validators.js.map +1 -0
  225. package/dist/daemon/index.ts +52 -0
  226. package/dist/daemon/middleware/project-context.ts +126 -0
  227. package/dist/daemon/pid.ts +179 -0
  228. package/dist/daemon/project-context.ts +343 -0
  229. package/dist/daemon/routes/inbox.ts +164 -0
  230. package/dist/daemon/routes/items.ts +322 -0
  231. package/dist/daemon/routes/meta.ts +118 -0
  232. package/dist/daemon/routes/projects.ts +162 -0
  233. package/dist/daemon/routes/tasks.ts +327 -0
  234. package/dist/daemon/routes/triage.ts +402 -0
  235. package/dist/daemon/routes/validation.ts +248 -0
  236. package/dist/daemon/server.ts +408 -0
  237. package/dist/daemon/watcher.ts +195 -0
  238. package/dist/daemon/websocket/handler.ts +138 -0
  239. package/dist/daemon/websocket/heartbeat.ts +71 -0
  240. package/dist/daemon/websocket/pubsub.ts +125 -0
  241. package/dist/daemon/websocket/types.ts +66 -0
  242. package/dist/export/html.d.ts +19 -0
  243. package/dist/export/html.d.ts.map +1 -0
  244. package/dist/export/html.js +239 -0
  245. package/dist/export/html.js.map +1 -0
  246. package/dist/export/index.d.ts +10 -0
  247. package/dist/export/index.d.ts.map +1 -0
  248. package/dist/export/index.js +10 -0
  249. package/dist/export/index.js.map +1 -0
  250. package/dist/export/json.d.ts +24 -0
  251. package/dist/export/json.d.ts.map +1 -0
  252. package/dist/export/json.js +198 -0
  253. package/dist/export/json.js.map +1 -0
  254. package/dist/export/triage.d.ts +51 -0
  255. package/dist/export/triage.d.ts.map +1 -0
  256. package/dist/export/triage.js +83 -0
  257. package/dist/export/triage.js.map +1 -0
  258. package/dist/export/types.d.ts +122 -0
  259. package/dist/export/types.d.ts.map +1 -0
  260. package/dist/export/types.js +9 -0
  261. package/dist/export/types.js.map +1 -0
  262. package/dist/index.d.ts +2 -2
  263. package/dist/index.js +2 -2
  264. package/dist/lib/claude-plugin-registry.d.ts +66 -0
  265. package/dist/lib/claude-plugin-registry.d.ts.map +1 -0
  266. package/dist/lib/claude-plugin-registry.js +318 -0
  267. package/dist/lib/claude-plugin-registry.js.map +1 -0
  268. package/dist/merge/arrays.d.ts +87 -0
  269. package/dist/merge/arrays.d.ts.map +1 -0
  270. package/dist/merge/arrays.js +164 -0
  271. package/dist/merge/arrays.js.map +1 -0
  272. package/dist/merge/file-type.d.ts +32 -0
  273. package/dist/merge/file-type.d.ts.map +1 -0
  274. package/dist/merge/file-type.js +70 -0
  275. package/dist/merge/file-type.js.map +1 -0
  276. package/dist/merge/index.d.ts +14 -0
  277. package/dist/merge/index.d.ts.map +1 -0
  278. package/dist/merge/index.js +11 -0
  279. package/dist/merge/index.js.map +1 -0
  280. package/dist/merge/objects.d.ts +46 -0
  281. package/dist/merge/objects.d.ts.map +1 -0
  282. package/dist/merge/objects.js +193 -0
  283. package/dist/merge/objects.js.map +1 -0
  284. package/dist/merge/parse.d.ts +23 -0
  285. package/dist/merge/parse.d.ts.map +1 -0
  286. package/dist/merge/parse.js +78 -0
  287. package/dist/merge/parse.js.map +1 -0
  288. package/dist/merge/resolve.d.ts +66 -0
  289. package/dist/merge/resolve.d.ts.map +1 -0
  290. package/dist/merge/resolve.js +189 -0
  291. package/dist/merge/resolve.js.map +1 -0
  292. package/dist/merge/types.d.ts +82 -0
  293. package/dist/merge/types.d.ts.map +1 -0
  294. package/dist/merge/types.js +8 -0
  295. package/dist/merge/types.js.map +1 -0
  296. package/dist/parser/agent-data-sections.d.ts +53 -0
  297. package/dist/parser/agent-data-sections.d.ts.map +1 -0
  298. package/dist/parser/agent-data-sections.js +118 -0
  299. package/dist/parser/agent-data-sections.js.map +1 -0
  300. package/dist/parser/alignment.d.ts +4 -4
  301. package/dist/parser/alignment.d.ts.map +1 -1
  302. package/dist/parser/alignment.js +27 -22
  303. package/dist/parser/alignment.js.map +1 -1
  304. package/dist/parser/assess.d.ts +5 -5
  305. package/dist/parser/assess.d.ts.map +1 -1
  306. package/dist/parser/assess.js +36 -32
  307. package/dist/parser/assess.js.map +1 -1
  308. package/dist/parser/config.d.ts +457 -0
  309. package/dist/parser/config.d.ts.map +1 -0
  310. package/dist/parser/config.js +373 -0
  311. package/dist/parser/config.js.map +1 -0
  312. package/dist/parser/convention-validation.d.ts +1 -1
  313. package/dist/parser/convention-validation.d.ts.map +1 -1
  314. package/dist/parser/convention-validation.js +21 -16
  315. package/dist/parser/convention-validation.js.map +1 -1
  316. package/dist/parser/coverage-cache.d.ts +49 -0
  317. package/dist/parser/coverage-cache.d.ts.map +1 -0
  318. package/dist/parser/coverage-cache.js +123 -0
  319. package/dist/parser/coverage-cache.js.map +1 -0
  320. package/dist/parser/daemon-status.d.ts +37 -0
  321. package/dist/parser/daemon-status.d.ts.map +1 -0
  322. package/dist/parser/daemon-status.js +67 -0
  323. package/dist/parser/daemon-status.js.map +1 -0
  324. package/dist/parser/doctor.d.ts +107 -0
  325. package/dist/parser/doctor.d.ts.map +1 -0
  326. package/dist/parser/doctor.js +366 -0
  327. package/dist/parser/doctor.js.map +1 -0
  328. package/dist/parser/fix.d.ts +1 -1
  329. package/dist/parser/fix.d.ts.map +1 -1
  330. package/dist/parser/fix.js +31 -27
  331. package/dist/parser/fix.js.map +1 -1
  332. package/dist/parser/index.d.ts +16 -11
  333. package/dist/parser/index.d.ts.map +1 -1
  334. package/dist/parser/index.js +16 -11
  335. package/dist/parser/index.js.map +1 -1
  336. package/dist/parser/items.d.ts +8 -2
  337. package/dist/parser/items.d.ts.map +1 -1
  338. package/dist/parser/items.js +71 -35
  339. package/dist/parser/items.js.map +1 -1
  340. package/dist/parser/meta.d.ts +167 -9
  341. package/dist/parser/meta.d.ts.map +1 -1
  342. package/dist/parser/meta.js +379 -46
  343. package/dist/parser/meta.js.map +1 -1
  344. package/dist/parser/plan-document.d.ts +197 -0
  345. package/dist/parser/plan-document.d.ts.map +1 -0
  346. package/dist/parser/plan-document.js +341 -0
  347. package/dist/parser/plan-document.js.map +1 -0
  348. package/dist/parser/plans.d.ts +59 -0
  349. package/dist/parser/plans.d.ts.map +1 -0
  350. package/dist/parser/plans.js +239 -0
  351. package/dist/parser/plans.js.map +1 -0
  352. package/dist/parser/refs.d.ts +22 -9
  353. package/dist/parser/refs.d.ts.map +1 -1
  354. package/dist/parser/refs.js +102 -50
  355. package/dist/parser/refs.js.map +1 -1
  356. package/dist/parser/setup-status.d.ts +71 -0
  357. package/dist/parser/setup-status.d.ts.map +1 -0
  358. package/dist/parser/setup-status.js +269 -0
  359. package/dist/parser/setup-status.js.map +1 -0
  360. package/dist/parser/shadow.d.ts +150 -19
  361. package/dist/parser/shadow.d.ts.map +1 -1
  362. package/dist/parser/shadow.js +548 -187
  363. package/dist/parser/shadow.js.map +1 -1
  364. package/dist/parser/skill-render.d.ts +317 -0
  365. package/dist/parser/skill-render.d.ts.map +1 -0
  366. package/dist/parser/skill-render.js +943 -0
  367. package/dist/parser/skill-render.js.map +1 -0
  368. package/dist/parser/traits.d.ts +3 -3
  369. package/dist/parser/traits.d.ts.map +1 -1
  370. package/dist/parser/traits.js +2 -2
  371. package/dist/parser/traits.js.map +1 -1
  372. package/dist/parser/validate-skills.d.ts +32 -0
  373. package/dist/parser/validate-skills.d.ts.map +1 -0
  374. package/dist/parser/validate-skills.js +202 -0
  375. package/dist/parser/validate-skills.js.map +1 -0
  376. package/dist/parser/validate.d.ts +45 -3
  377. package/dist/parser/validate.d.ts.map +1 -1
  378. package/dist/parser/validate.js +622 -105
  379. package/dist/parser/validate.js.map +1 -1
  380. package/dist/parser/yaml.d.ts +83 -19
  381. package/dist/parser/yaml.d.ts.map +1 -1
  382. package/dist/parser/yaml.js +478 -173
  383. package/dist/parser/yaml.js.map +1 -1
  384. package/dist/ralph/cli-renderer.d.ts +8 -1
  385. package/dist/ralph/cli-renderer.d.ts.map +1 -1
  386. package/dist/ralph/cli-renderer.js +105 -34
  387. package/dist/ralph/cli-renderer.js.map +1 -1
  388. package/dist/ralph/events.d.ts +10 -10
  389. package/dist/ralph/events.d.ts.map +1 -1
  390. package/dist/ralph/events.js +301 -98
  391. package/dist/ralph/events.js.map +1 -1
  392. package/dist/ralph/index.d.ts +5 -2
  393. package/dist/ralph/index.d.ts.map +1 -1
  394. package/dist/ralph/index.js +9 -3
  395. package/dist/ralph/index.js.map +1 -1
  396. package/dist/ralph/loop-errors.d.ts +83 -0
  397. package/dist/ralph/loop-errors.d.ts.map +1 -0
  398. package/dist/ralph/loop-errors.js +150 -0
  399. package/dist/ralph/loop-errors.js.map +1 -0
  400. package/dist/ralph/subagent.d.ts +94 -0
  401. package/dist/ralph/subagent.d.ts.map +1 -0
  402. package/dist/ralph/subagent.js +193 -0
  403. package/dist/ralph/subagent.js.map +1 -0
  404. package/dist/ralph/wrap-up.d.ts +125 -0
  405. package/dist/ralph/wrap-up.d.ts.map +1 -0
  406. package/dist/ralph/wrap-up.js +270 -0
  407. package/dist/ralph/wrap-up.js.map +1 -0
  408. package/dist/schema/batch.d.ts +97 -0
  409. package/dist/schema/batch.d.ts.map +1 -0
  410. package/dist/schema/batch.js +24 -0
  411. package/dist/schema/batch.js.map +1 -0
  412. package/dist/schema/common.d.ts +8 -2
  413. package/dist/schema/common.d.ts.map +1 -1
  414. package/dist/schema/common.js +42 -31
  415. package/dist/schema/common.js.map +1 -1
  416. package/dist/schema/inbox.d.ts +12 -12
  417. package/dist/schema/inbox.js +4 -4
  418. package/dist/schema/inbox.js.map +1 -1
  419. package/dist/schema/index.d.ts +8 -5
  420. package/dist/schema/index.d.ts.map +1 -1
  421. package/dist/schema/index.js +8 -5
  422. package/dist/schema/index.js.map +1 -1
  423. package/dist/schema/meta.d.ts +1454 -27
  424. package/dist/schema/meta.d.ts.map +1 -1
  425. package/dist/schema/meta.js +198 -21
  426. package/dist/schema/meta.js.map +1 -1
  427. package/dist/schema/plan.d.ts +285 -0
  428. package/dist/schema/plan.d.ts.map +1 -0
  429. package/dist/schema/plan.js +81 -0
  430. package/dist/schema/plan.js.map +1 -0
  431. package/dist/schema/spec.d.ts +72 -33
  432. package/dist/schema/spec.d.ts.map +1 -1
  433. package/dist/schema/spec.js +22 -9
  434. package/dist/schema/spec.js.map +1 -1
  435. package/dist/schema/task.d.ts +172 -161
  436. package/dist/schema/task.d.ts.map +1 -1
  437. package/dist/schema/task.js +21 -12
  438. package/dist/schema/task.js.map +1 -1
  439. package/dist/schema/triage.d.ts +266 -0
  440. package/dist/schema/triage.d.ts.map +1 -0
  441. package/dist/schema/triage.js +134 -0
  442. package/dist/schema/triage.js.map +1 -0
  443. package/dist/sessions/index.d.ts +2 -2
  444. package/dist/sessions/index.d.ts.map +1 -1
  445. package/dist/sessions/index.js +3 -3
  446. package/dist/sessions/index.js.map +1 -1
  447. package/dist/sessions/store.d.ts +241 -1
  448. package/dist/sessions/store.d.ts.map +1 -1
  449. package/dist/sessions/store.js +810 -31
  450. package/dist/sessions/store.js.map +1 -1
  451. package/dist/sessions/types.d.ts +10 -10
  452. package/dist/sessions/types.d.ts.map +1 -1
  453. package/dist/sessions/types.js +10 -9
  454. package/dist/sessions/types.js.map +1 -1
  455. package/dist/strings/errors.d.ts +55 -0
  456. package/dist/strings/errors.d.ts.map +1 -1
  457. package/dist/strings/errors.js +138 -106
  458. package/dist/strings/errors.js.map +1 -1
  459. package/dist/strings/guidance.d.ts.map +1 -1
  460. package/dist/strings/guidance.js +16 -16
  461. package/dist/strings/guidance.js.map +1 -1
  462. package/dist/strings/index.d.ts +4 -4
  463. package/dist/strings/index.d.ts.map +1 -1
  464. package/dist/strings/index.js +4 -4
  465. package/dist/strings/index.js.map +1 -1
  466. package/dist/strings/labels.d.ts +4 -0
  467. package/dist/strings/labels.d.ts.map +1 -1
  468. package/dist/strings/labels.js +45 -41
  469. package/dist/strings/labels.js.map +1 -1
  470. package/dist/strings/validation.d.ts.map +1 -1
  471. package/dist/strings/validation.js +71 -71
  472. package/dist/strings/validation.js.map +1 -1
  473. package/dist/triage/actions.d.ts +27 -0
  474. package/dist/triage/actions.d.ts.map +1 -0
  475. package/dist/triage/actions.js +95 -0
  476. package/dist/triage/actions.js.map +1 -0
  477. package/dist/triage/constants.d.ts +6 -0
  478. package/dist/triage/constants.d.ts.map +1 -0
  479. package/dist/triage/constants.js +7 -0
  480. package/dist/triage/constants.js.map +1 -0
  481. package/dist/triage/index.d.ts +3 -0
  482. package/dist/triage/index.d.ts.map +1 -0
  483. package/dist/triage/index.js +3 -0
  484. package/dist/triage/index.js.map +1 -0
  485. package/dist/utils/commit.d.ts +1 -1
  486. package/dist/utils/commit.d.ts.map +1 -1
  487. package/dist/utils/commit.js +28 -26
  488. package/dist/utils/commit.js.map +1 -1
  489. package/dist/utils/git.d.ts +1 -1
  490. package/dist/utils/git.d.ts.map +1 -1
  491. package/dist/utils/git.js +40 -38
  492. package/dist/utils/git.js.map +1 -1
  493. package/dist/utils/grep.js +11 -11
  494. package/dist/utils/grep.js.map +1 -1
  495. package/dist/utils/index.d.ts +7 -7
  496. package/dist/utils/index.d.ts.map +1 -1
  497. package/dist/utils/index.js +4 -4
  498. package/dist/utils/index.js.map +1 -1
  499. package/dist/utils/time.d.ts.map +1 -1
  500. package/dist/utils/time.js +10 -10
  501. package/dist/utils/time.js.map +1 -1
  502. package/package.json +28 -5
  503. package/plugin/.claude-plugin/marketplace.json +17 -0
  504. package/plugin/.claude-plugin/plugin.json +5 -0
  505. package/plugin/plugins/kspec/skills/create-workflow/SKILL.md +235 -0
  506. package/plugin/plugins/kspec/skills/help/SKILL.md +42 -0
  507. package/plugin/plugins/kspec/skills/observations/SKILL.md +143 -0
  508. package/plugin/plugins/kspec/skills/plan/SKILL.md +343 -0
  509. package/plugin/plugins/kspec/skills/reflect/SKILL.md +161 -0
  510. package/plugin/plugins/kspec/skills/review/SKILL.md +193 -0
  511. package/plugin/plugins/kspec/skills/task-work/SKILL.md +303 -0
  512. package/plugin/plugins/kspec/skills/triage/SKILL.md +206 -0
  513. package/plugin/plugins/kspec/skills/triage/docs/automation.md +120 -0
  514. package/plugin/plugins/kspec/skills/triage/docs/inbox.md +144 -0
  515. package/plugin/plugins/kspec/skills/triage/docs/observations.md +85 -0
  516. package/plugin/plugins/kspec/skills/triage-automation/SKILL.md +140 -0
  517. package/plugin/plugins/kspec/skills/triage-inbox/SKILL.md +232 -0
  518. package/plugin/plugins/kspec/skills/writing-specs/SKILL.md +340 -0
  519. package/templates/agents-sections/01-quick-start.md +22 -0
  520. package/templates/agents-sections/02-shadow-branch.md +34 -0
  521. package/templates/agents-sections/03-task-lifecycle.md +48 -0
  522. package/templates/agents-sections/04-pr-workflow.md +17 -0
  523. package/templates/agents-sections/05-commit-convention.md +27 -0
  524. package/templates/agents-sections/06-ralph-loop.md +45 -0
  525. package/templates/hooks/pre-commit +34 -0
  526. package/templates/skills/create-workflow/SKILL.md +228 -0
  527. package/templates/skills/help/SKILL.md +37 -0
  528. package/templates/skills/manifest.yaml +60 -0
  529. package/templates/skills/observations/SKILL.md +137 -0
  530. package/templates/skills/plan/SKILL.md +336 -0
  531. package/templates/skills/reflect/SKILL.md +155 -0
  532. package/templates/skills/review/SKILL.md +186 -0
  533. package/templates/skills/task-work/SKILL.md +296 -0
  534. package/templates/skills/triage/SKILL.md +199 -0
  535. package/templates/skills/triage/docs/automation.md +120 -0
  536. package/templates/skills/triage/docs/inbox.md +144 -0
  537. package/templates/skills/triage/docs/observations.md +85 -0
  538. package/templates/skills/triage-automation/SKILL.md +134 -0
  539. package/templates/skills/triage-inbox/SKILL.md +225 -0
  540. package/templates/skills/writing-specs/SKILL.md +333 -0
@@ -0,0 +1,943 @@
1
+ /**
2
+ * Platform Skill Renderers
3
+ *
4
+ * Defines the contract for platform-specific skill renderers and provides
5
+ * the Claude Code implementation. Each renderer writes platform-specific output
6
+ * (SKILL.md with frontmatter, sidecar config files, supporting directories)
7
+ * to a configurable output directory.
8
+ *
9
+ * AC: @claude-code-renderer ac-1 - renderClaudeCodeSkill creates .claude/skills/<id>/SKILL.md with YAML frontmatter
10
+ * AC: @claude-code-renderer ac-2 - rendered output has YAML frontmatter delimiters with name and description fields
11
+ * AC: @claude-code-renderer ac-3 - skill body content appears verbatim below frontmatter
12
+ * AC: @claude-code-renderer ac-4 - rendered files appear as unstaged changes on main branch
13
+ *
14
+ * AC: @skill-drift-detection ac-1 - Skill shows as in-sync when not manually edited
15
+ * AC: @skill-drift-detection ac-2 - Skill shows as drifted when manually edited
16
+ * AC: @skill-drift-detection ac-5 - Render hash stored in .kspec/skills/<id>/.render-hash
17
+ *
18
+ * AC: @consolidate-skill-render ac-3 - hash/drift functions exported from this module
19
+ *
20
+ * AC: @platform-renderer-trait ac-1 - platform-specific output files written to configured output directory
21
+ * AC: @platform-renderer-trait ac-2 - PlatformRenderResult returned with id, platform, action, paths
22
+ * AC: @platform-renderer-trait ac-3 - supporting directories (references/, scripts/, assets/) copied
23
+ * AC: @platform-renderer-trait ac-4 - dryRun mode: no files written, result reflects what would happen
24
+ * AC: @platform-renderer-trait ac-5 - custom outputDir goes to custom path instead of platform default
25
+ * AC: @platform-renderer-trait ac-6 - per-platform render hash written to .render-hash-<platform>
26
+ *
27
+ * AC: @codex-renderer ac-1 - .agents/skills/<id>/SKILL.md with only name and description in frontmatter
28
+ * AC: @codex-renderer ac-2 - .agents/skills/<id>/agents/openai.yaml sidecar with platform_config.codex fields
29
+ * AC: @codex-renderer ac-3 - no sidecar created when no platform_config.codex
30
+ * AC: @codex-renderer ac-4 - rendered file contains <!-- kspec-managed --> marker
31
+ * AC: @codex-renderer ac-5 - supporting directories (references/, scripts/, assets/) copied
32
+ * AC: @codex-renderer ac-6 - hash written to .render-hash-codex
33
+ */
34
+ import * as crypto from "node:crypto";
35
+ import * as fs from "node:fs/promises";
36
+ import * as path from "node:path";
37
+ import yaml from "yaml";
38
+ import { loadSkillContent } from "./meta.js";
39
+ /**
40
+ * Marker comment that identifies skill directories managed by kspec
41
+ */
42
+ export const KSPEC_MANAGED_MARKER = "<!-- kspec-managed -->";
43
+ /**
44
+ * Generate YAML frontmatter for a skill.
45
+ * AC: @claude-code-renderer ac-2 - YAML frontmatter with name and description fields
46
+ * AC: @claude-code-renderer-extended ac-1 - portable fields (license, allowed-tools)
47
+ * AC: @claude-code-renderer-extended ac-2 - user-invocable from platform_config
48
+ * AC: @claude-code-renderer-extended ac-3 - context and agent from platform_config
49
+ * AC: @claude-code-renderer-extended ac-4 - disable-model-invocation from platform_config
50
+ * AC: @claude-code-renderer-extended ac-5 - only portable fields when no platform_config
51
+ * AC: @claude-code-renderer-extended ac-8 - snake_case to kebab-case conversion
52
+ */
53
+ export function generateFrontmatter(skill) {
54
+ const frontmatter = {
55
+ name: skill.id,
56
+ description: skill.description || skill.name,
57
+ };
58
+ // AC: @claude-code-renderer-extended ac-1 - Add portable fields
59
+ if (skill.license) {
60
+ frontmatter.license = skill.license;
61
+ }
62
+ if (skill.allowed_tools && skill.allowed_tools.length > 0) {
63
+ frontmatter["allowed-tools"] = skill.allowed_tools;
64
+ }
65
+ if (skill.compatibility) {
66
+ frontmatter.compatibility = skill.compatibility;
67
+ }
68
+ // AC: @claude-code-renderer-extended ac-2, ac-3, ac-4 - Add Claude Code platform fields
69
+ const claudeCodeConfig = skill.platform_config?.claude_code;
70
+ if (claudeCodeConfig) {
71
+ // AC: @claude-code-renderer-extended ac-2 - user_invocable
72
+ if (claudeCodeConfig.user_invocable !== undefined) {
73
+ frontmatter["user-invocable"] = claudeCodeConfig.user_invocable;
74
+ }
75
+ // AC: @claude-code-renderer-extended ac-4 - disable_model_invocation
76
+ if (claudeCodeConfig.disable_model_invocation !== undefined) {
77
+ frontmatter["disable-model-invocation"] = claudeCodeConfig.disable_model_invocation;
78
+ }
79
+ // AC: @claude-code-renderer-extended ac-3 - context and agent
80
+ if (claudeCodeConfig.context) {
81
+ frontmatter.context = claudeCodeConfig.context;
82
+ }
83
+ if (claudeCodeConfig.agent) {
84
+ frontmatter.agent = claudeCodeConfig.agent;
85
+ }
86
+ // Other Claude Code fields
87
+ if (claudeCodeConfig.model) {
88
+ frontmatter.model = claudeCodeConfig.model;
89
+ }
90
+ if (claudeCodeConfig.argument_hint) {
91
+ frontmatter["argument-hint"] = claudeCodeConfig.argument_hint;
92
+ }
93
+ }
94
+ return `---\n${yaml.stringify(frontmatter).trim()}\n---`;
95
+ }
96
+ /**
97
+ * Plugin system constants for Claude Code core skill rendering.
98
+ * Core skills render into the plugin directory so Claude Code discovers
99
+ * them as /kspec:<id> commands.
100
+ * AC: @skill-rendering ac-7
101
+ */
102
+ export const PLUGIN_DIR = ".claude/plugins/kspec";
103
+ export const PLUGIN_SKILLS_DIR = ".claude/plugins/kspec/skills";
104
+ /**
105
+ * Get the rendered skill subdirectory segment for a given platform.
106
+ * Core skills on codex are prefixed with kspec- for namespace clarity.
107
+ * Core skills on claude-code just use the skill id (plugin path provides namespace).
108
+ * All other combinations use the skill id directly.
109
+ * AC: @skill-rendering ac-7
110
+ */
111
+ export function getSkillSubdir(skillId, origin, platform) {
112
+ if (origin === "core" && platform === "codex") {
113
+ return `kspec-${skillId}`;
114
+ }
115
+ // Core+claude-code: just ID (plugin path provides namespace)
116
+ // Project/local: just ID (flat under .claude/skills/)
117
+ return skillId;
118
+ }
119
+ /**
120
+ * Convenience wrapper for LoadedSkill objects (Claude Code platform).
121
+ */
122
+ export function getClaudeCodeSkillSubdir(skill) {
123
+ return getSkillSubdir(skill.id, skill.origin, "claude-code");
124
+ }
125
+ /**
126
+ * Get the target directory for rendered skills on main branch.
127
+ * Core skills on claude-code are plugin-provided (returns null).
128
+ * Project/local skills go to .claude/skills/.
129
+ * AC: @skill-rendering ac-7
130
+ */
131
+ function getRenderedSkillPath(projectRoot, skillId, origin) {
132
+ if (origin === "core") {
133
+ // Core skills are plugin-provided, not locally rendered
134
+ return null;
135
+ }
136
+ return path.join(projectRoot, ".claude", "skills", skillId);
137
+ }
138
+ /**
139
+ * Check if two contents are equal (for idempotency check)
140
+ */
141
+ export function contentsEqual(a, b) {
142
+ return a.trim() === b.trim();
143
+ }
144
+ /**
145
+ * Recursively copy a directory
146
+ */
147
+ export async function copyDirectory(src, dest) {
148
+ const entries = await fs.readdir(src, { withFileTypes: true });
149
+ for (const entry of entries) {
150
+ const srcPath = path.join(src, entry.name);
151
+ const destPath = path.join(dest, entry.name);
152
+ if (entry.isDirectory()) {
153
+ await fs.mkdir(destPath, { recursive: true });
154
+ await copyDirectory(srcPath, destPath);
155
+ }
156
+ else {
157
+ await fs.copyFile(srcPath, destPath);
158
+ }
159
+ }
160
+ }
161
+ /**
162
+ * Compute SHA256 hash of content
163
+ * AC: @skill-drift-detection ac-5 - Hash computation for render tracking
164
+ */
165
+ export function computeContentHash(content) {
166
+ return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
167
+ }
168
+ /**
169
+ * Get the path to the render hash file for a skill
170
+ * AC: @skill-drift-detection ac-5 - Hash stored in .kspec/skills/<id>/.render-hash
171
+ * AC: @consolidate-skill-render ac-3 - exported from skill-render.ts
172
+ */
173
+ export function getRenderHashPath(specDir, skillId) {
174
+ return path.join(specDir, "skills", skillId, ".render-hash");
175
+ }
176
+ /**
177
+ * Read the stored render hash for a skill
178
+ * AC: @skill-drift-detection ac-5 - Read hash from .kspec/skills/<id>/.render-hash
179
+ * AC: @consolidate-skill-render ac-3 - exported from skill-render.ts
180
+ */
181
+ export async function readRenderHash(specDir, skillId) {
182
+ try {
183
+ const hashPath = getRenderHashPath(specDir, skillId);
184
+ const content = await fs.readFile(hashPath, "utf-8");
185
+ return content.trim();
186
+ }
187
+ catch {
188
+ return null;
189
+ }
190
+ }
191
+ /**
192
+ * Write the render hash for a skill
193
+ * AC: @skill-drift-detection ac-5 - Store hash in .kspec/skills/<id>/.render-hash
194
+ * AC: @consolidate-skill-render ac-3 - exported from skill-render.ts
195
+ */
196
+ export async function writeRenderHash(specDir, skillId, hash) {
197
+ const hashPath = getRenderHashPath(specDir, skillId);
198
+ await fs.mkdir(path.dirname(hashPath), { recursive: true });
199
+ await fs.writeFile(hashPath, hash + "\n", "utf-8");
200
+ }
201
+ /**
202
+ * Check if a rendered skill has drifted from its last render
203
+ * AC: @skill-drift-detection ac-1, ac-2 - Drift detection via hash comparison
204
+ * AC: @consolidate-skill-render ac-3 - exported from skill-render.ts
205
+ *
206
+ * Returns:
207
+ * - "not-rendered": Rendered file doesn't exist
208
+ * - "in-sync": Rendered file matches stored hash
209
+ * - "drifted": Rendered file differs from stored hash (manually edited)
210
+ * - "no-hash": Rendered file exists but no stored hash (first render or hash deleted)
211
+ */
212
+ export async function checkSkillDrift(specDir, projectRoot, skillId, origin) {
213
+ const basePath = getRenderedSkillPath(projectRoot, skillId, origin);
214
+ if (!basePath) {
215
+ return "plugin-provided"; // Core skills are provided by npm package plugin
216
+ }
217
+ const renderedPath = path.join(basePath, "SKILL.md");
218
+ // Check if rendered file exists
219
+ let renderedContent;
220
+ try {
221
+ renderedContent = await fs.readFile(renderedPath, "utf-8");
222
+ }
223
+ catch {
224
+ return "not-rendered";
225
+ }
226
+ // Get stored hash
227
+ const storedHash = await readRenderHash(specDir, skillId);
228
+ if (!storedHash) {
229
+ return "no-hash";
230
+ }
231
+ // Compare hashes
232
+ const currentHash = computeContentHash(renderedContent);
233
+ return currentHash === storedHash ? "in-sync" : "drifted";
234
+ }
235
+ /**
236
+ * Recursively check if two directories have the same contents
237
+ */
238
+ export async function directoriesEqual(src, dest) {
239
+ try {
240
+ const srcEntries = await fs.readdir(src, { withFileTypes: true });
241
+ const destEntries = await fs.readdir(dest, { withFileTypes: true });
242
+ // Different number of entries = not equal
243
+ if (srcEntries.length !== destEntries.length) {
244
+ return false;
245
+ }
246
+ // Create a map of dest entries for quick lookup
247
+ const destMap = new Map(destEntries.map((e) => [e.name, e]));
248
+ for (const srcEntry of srcEntries) {
249
+ const destEntry = destMap.get(srcEntry.name);
250
+ if (!destEntry) {
251
+ return false;
252
+ }
253
+ const srcPath = path.join(src, srcEntry.name);
254
+ const destPath = path.join(dest, srcEntry.name);
255
+ if (srcEntry.isDirectory() && destEntry.isDirectory()) {
256
+ if (!(await directoriesEqual(srcPath, destPath))) {
257
+ return false;
258
+ }
259
+ }
260
+ else if (srcEntry.isFile() && destEntry.isFile()) {
261
+ const srcContent = await fs.readFile(srcPath, "utf-8");
262
+ const destContent = await fs.readFile(destPath, "utf-8");
263
+ if (!contentsEqual(srcContent, destContent)) {
264
+ return false;
265
+ }
266
+ }
267
+ else {
268
+ // Type mismatch
269
+ return false;
270
+ }
271
+ }
272
+ return true;
273
+ }
274
+ catch {
275
+ return false;
276
+ }
277
+ }
278
+ // ============================================================================
279
+ // Supporting Directories (AC: @platform-renderer-trait ac-3, @claude-code-renderer-extended ac-7)
280
+ // ============================================================================
281
+ /** Known supporting directory names */
282
+ const SUPPORTING_DIRS = ["references", "scripts", "assets", "docs"];
283
+ /**
284
+ * Copy supporting directories from source skill to rendered output
285
+ * AC: @platform-renderer-trait ac-3 - Supporting directories copied to platform output
286
+ * AC: @claude-code-renderer-extended ac-7 - references/, scripts/, assets/ copied
287
+ */
288
+ async function copySupportingDirectories(sourceSkillDir, targetSkillDir, dryRun) {
289
+ const results = {};
290
+ for (const dirName of SUPPORTING_DIRS) {
291
+ const sourceDir = path.join(sourceSkillDir, dirName);
292
+ const targetDir = path.join(targetSkillDir, dirName);
293
+ try {
294
+ const stats = await fs.stat(sourceDir);
295
+ if (!stats.isDirectory()) {
296
+ results[dirName] = "skipped";
297
+ continue;
298
+ }
299
+ // Check if target exists
300
+ let targetExists = false;
301
+ try {
302
+ await fs.stat(targetDir);
303
+ targetExists = true;
304
+ }
305
+ catch {
306
+ // Target doesn't exist
307
+ }
308
+ if (!targetExists) {
309
+ results[dirName] = "created";
310
+ }
311
+ else {
312
+ // Compare directories
313
+ const equal = await directoriesEqual(sourceDir, targetDir);
314
+ results[dirName] = equal ? "unchanged" : "updated";
315
+ }
316
+ // Apply changes
317
+ if (!dryRun && results[dirName] !== "unchanged") {
318
+ if (targetExists) {
319
+ await fs.rm(targetDir, { recursive: true, force: true });
320
+ }
321
+ await fs.mkdir(targetDir, { recursive: true });
322
+ await copyDirectory(sourceDir, targetDir);
323
+ }
324
+ }
325
+ catch {
326
+ // Source directory doesn't exist
327
+ results[dirName] = "skipped";
328
+ }
329
+ }
330
+ return results;
331
+ }
332
+ /**
333
+ * Render a skill to Claude Code format.
334
+ *
335
+ * Reads skill content from .kspec/skills/<id>/SKILL.md and writes to
336
+ * .claude/skills/<id>/SKILL.md with YAML frontmatter containing name and description.
337
+ *
338
+ * AC: @claude-code-renderer ac-1 - Creates .claude/skills/<id>/SKILL.md with YAML frontmatter
339
+ * AC: @claude-code-renderer ac-2 - YAML frontmatter has name and description fields
340
+ * AC: @claude-code-renderer ac-3 - Skill body content appears verbatim below frontmatter
341
+ * AC: @claude-code-renderer ac-4 - Files are written to disk as unstaged changes (no git commit)
342
+ *
343
+ * @param ctx - Kspec context with specDir pointing to .kspec/
344
+ * @param projectRoot - Root directory of the project (where .claude/ will be created)
345
+ * @param skill - The skill to render
346
+ * @param options - Render options (dryRun, etc.)
347
+ * @returns Result indicating what action was taken
348
+ */
349
+ export async function renderClaudeCodeSkill(ctx, projectRoot, skill, options = {}) {
350
+ const dryRun = options.dryRun ?? false;
351
+ const storeHash = options.storeHash ?? false;
352
+ const targetDir = getRenderedSkillPath(projectRoot, skill.id, skill.origin);
353
+ // Core skills are plugin-provided; skip local render
354
+ if (!targetDir) {
355
+ return {
356
+ id: skill.id,
357
+ action: "skipped",
358
+ path: "",
359
+ };
360
+ }
361
+ const targetSkillMd = path.join(targetDir, "SKILL.md");
362
+ // AC: @claude-code-renderer ac-3 - Load source content verbatim
363
+ const sourceContent = await loadSkillContent(ctx, skill);
364
+ if (!sourceContent) {
365
+ // No source content, but skill exists in meta - create placeholder
366
+ const frontmatter = generateFrontmatter(skill);
367
+ const renderedContent = `${frontmatter}\n${KSPEC_MANAGED_MARKER}\n\n# ${skill.name}\n\n${skill.description || ""}\n`;
368
+ // AC: @claude-code-renderer ac-4 - Write to disk (unstaged)
369
+ if (!dryRun) {
370
+ await fs.mkdir(targetDir, { recursive: true });
371
+ await fs.writeFile(targetSkillMd, renderedContent, "utf-8");
372
+ // AC: @skill-drift-detection ac-5 - Store hash of rendered output
373
+ if (storeHash) {
374
+ const hash = computeContentHash(renderedContent);
375
+ await writeRenderHash(ctx.specDir, skill.id, hash);
376
+ }
377
+ }
378
+ return {
379
+ id: skill.id,
380
+ action: "created",
381
+ path: targetSkillMd,
382
+ };
383
+ }
384
+ // AC: @claude-code-renderer ac-2 - Generate frontmatter with name and description
385
+ const frontmatter = generateFrontmatter(skill);
386
+ // Check if source already has frontmatter - if so, strip it
387
+ const frontmatterMatch = sourceContent.match(/^---\n[\s\S]*?\n---\n?/);
388
+ const contentWithoutFrontmatter = frontmatterMatch
389
+ ? sourceContent.slice(frontmatterMatch[0].length)
390
+ : sourceContent;
391
+ // AC: @claude-code-renderer ac-1, ac-3 - Build rendered content with frontmatter + verbatim body
392
+ const renderedContent = `${frontmatter}\n${KSPEC_MANAGED_MARKER}\n${contentWithoutFrontmatter}`;
393
+ // Check if target exists and compare for idempotency
394
+ let targetExists = false;
395
+ let targetContent = "";
396
+ try {
397
+ targetContent = await fs.readFile(targetSkillMd, "utf-8");
398
+ targetExists = true;
399
+ }
400
+ catch {
401
+ // Target doesn't exist
402
+ }
403
+ // Determine action based on comparison
404
+ let action;
405
+ if (!targetExists) {
406
+ action = "created";
407
+ }
408
+ else if (contentsEqual(renderedContent, targetContent)) {
409
+ action = "unchanged";
410
+ }
411
+ else {
412
+ action = "updated";
413
+ }
414
+ // AC: @claude-code-renderer ac-4 - Apply changes to disk (no git commit)
415
+ if (!dryRun && action !== "unchanged") {
416
+ await fs.mkdir(targetDir, { recursive: true });
417
+ await fs.writeFile(targetSkillMd, renderedContent, "utf-8");
418
+ // AC: @skill-drift-detection ac-5 - Store hash of rendered output
419
+ if (storeHash) {
420
+ const hash = computeContentHash(renderedContent);
421
+ await writeRenderHash(ctx.specDir, skill.id, hash);
422
+ }
423
+ }
424
+ // AC: @claude-code-renderer-extended ac-7 - Copy supporting directories (references/, scripts/, assets/, docs/)
425
+ const sourceSkillDir = path.join(ctx.specDir, "skills", skill.id);
426
+ const supportingDirsAction = await copySupportingDirectories(sourceSkillDir, targetDir, dryRun);
427
+ // For backward compatibility, also expose docsAction
428
+ const docsAction = supportingDirsAction.docs || "skipped";
429
+ return {
430
+ id: skill.id,
431
+ action,
432
+ path: targetSkillMd,
433
+ docsAction,
434
+ };
435
+ }
436
+ /**
437
+ * Check if a skill directory is managed by kspec.
438
+ * Looks for the KSPEC_MANAGED_MARKER in the SKILL.md file.
439
+ */
440
+ export async function isKspecManagedSkill(skillMdPath) {
441
+ try {
442
+ const content = await fs.readFile(skillMdPath, "utf-8");
443
+ return content.includes(KSPEC_MANAGED_MARKER);
444
+ }
445
+ catch {
446
+ return false;
447
+ }
448
+ }
449
+ // ============================================================================
450
+ // Per-Platform Hash Functions (AC: @platform-renderer-trait ac-6)
451
+ // ============================================================================
452
+ /**
453
+ * Get the path to the per-platform render hash file for a skill
454
+ * AC: @platform-renderer-trait ac-6 - Per-platform hash stored in .render-hash-<platform>
455
+ */
456
+ export function getPlatformRenderHashPath(specDir, skillId, platform) {
457
+ return path.join(specDir, "skills", skillId, `.render-hash-${platform}`);
458
+ }
459
+ /**
460
+ * Read the stored render hash for a skill on a specific platform
461
+ * AC: @platform-renderer-trait ac-6 - Read per-platform hash
462
+ * AC: @claude-code-renderer-extended ac-6 - fallback to legacy hash
463
+ */
464
+ export async function readPlatformRenderHash(specDir, skillId, platform) {
465
+ try {
466
+ const hashPath = getPlatformRenderHashPath(specDir, skillId, platform);
467
+ const content = await fs.readFile(hashPath, "utf-8");
468
+ return content.trim();
469
+ }
470
+ catch {
471
+ // Fall back to legacy hash (non-platform-specific)
472
+ return readRenderHash(specDir, skillId);
473
+ }
474
+ }
475
+ /**
476
+ * Migrate legacy .render-hash to platform-specific .render-hash-<platform>
477
+ * AC: @claude-code-renderer-extended ac-6 - hash migration
478
+ */
479
+ export async function migrateLegacyRenderHash(specDir, skillId, platform) {
480
+ const legacyHashPath = getRenderHashPath(specDir, skillId);
481
+ const platformHashPath = getPlatformRenderHashPath(specDir, skillId, platform);
482
+ try {
483
+ // Check if platform-specific hash already exists
484
+ await fs.access(platformHashPath);
485
+ return false; // Already migrated
486
+ }
487
+ catch {
488
+ // Platform-specific doesn't exist, check for legacy
489
+ }
490
+ try {
491
+ const legacyHash = await fs.readFile(legacyHashPath, "utf-8");
492
+ // Write to platform-specific location
493
+ await writePlatformRenderHash(specDir, skillId, platform, legacyHash.trim());
494
+ return true; // Migrated successfully
495
+ }
496
+ catch {
497
+ return false; // No legacy hash to migrate
498
+ }
499
+ }
500
+ /**
501
+ * Write the render hash for a skill on a specific platform
502
+ * AC: @platform-renderer-trait ac-6 - Store per-platform hash in .render-hash-<platform>
503
+ */
504
+ export async function writePlatformRenderHash(specDir, skillId, platform, hash) {
505
+ const hashPath = getPlatformRenderHashPath(specDir, skillId, platform);
506
+ await fs.mkdir(path.dirname(hashPath), { recursive: true });
507
+ await fs.writeFile(hashPath, hash + "\n", "utf-8");
508
+ }
509
+ /**
510
+ * Check if a rendered skill has drifted from its last render for a specific platform
511
+ * AC: @platform-renderer-trait ac-6 - Platform-specific drift detection
512
+ */
513
+ export async function checkPlatformSkillDrift(specDir, projectRoot, skillId, platform, outputDir, origin) {
514
+ // Core skills on claude-code are plugin-provided, not locally rendered
515
+ if (origin === "core" && platform === "claude-code" && !outputDir) {
516
+ return "plugin-provided";
517
+ }
518
+ // Determine the rendered path
519
+ let renderedPath;
520
+ if (outputDir) {
521
+ const subdir = getSkillSubdir(skillId, origin, platform);
522
+ renderedPath = path.join(projectRoot, outputDir, subdir, "SKILL.md");
523
+ }
524
+ else {
525
+ const platformOutputDir = getPlatformDefaultOutputDir(platform);
526
+ const subdir = getSkillSubdir(skillId, origin, platform);
527
+ renderedPath = path.join(projectRoot, platformOutputDir, subdir, "SKILL.md");
528
+ }
529
+ // Check if rendered file exists
530
+ let renderedContent;
531
+ try {
532
+ renderedContent = await fs.readFile(renderedPath, "utf-8");
533
+ }
534
+ catch {
535
+ return "not-rendered";
536
+ }
537
+ // AC: @claude-code-renderer-extended ac-6 - Migrate legacy hash if needed
538
+ await migrateLegacyRenderHash(specDir, skillId, platform);
539
+ // Get stored hash (try platform-specific first, then fall back to legacy)
540
+ const storedHash = await readPlatformRenderHash(specDir, skillId, platform);
541
+ if (!storedHash) {
542
+ return "no-hash";
543
+ }
544
+ // Compare hashes
545
+ const currentHash = computeContentHash(renderedContent);
546
+ return currentHash === storedHash ? "in-sync" : "drifted";
547
+ }
548
+ /**
549
+ * Get the default output directory for a platform
550
+ */
551
+ function getPlatformDefaultOutputDir(platform) {
552
+ switch (platform) {
553
+ case "claude-code":
554
+ return ".claude/skills";
555
+ case "codex":
556
+ return ".agents/skills";
557
+ default:
558
+ return `.${platform}/skills`;
559
+ }
560
+ }
561
+ /**
562
+ * Base render function that handles shared logic across all platform renderers:
563
+ * content loading, frontmatter stripping, idempotency check, file write,
564
+ * hash storage, and supporting directory copy.
565
+ *
566
+ * AC: @skill-module-split ac-2 - Shared render logic in base function
567
+ * AC: @platform-renderer-trait ac-1 through ac-6
568
+ */
569
+ export async function renderSkillBase(ctx, projectRoot, skill, options, config, defaultOutputDir, skillSubdir) {
570
+ const dryRun = options.dryRun ?? false;
571
+ const storeHash = options.storeHash ?? false;
572
+ const outputDir = options.outputDir || defaultOutputDir;
573
+ const targetDir = path.join(projectRoot, outputDir, skillSubdir || skill.id);
574
+ const targetSkillMd = path.join(targetDir, "SKILL.md");
575
+ const paths = [];
576
+ // Load source content
577
+ const sourceContent = await loadSkillContent(ctx, skill);
578
+ // Generate platform-specific frontmatter
579
+ const frontmatter = config.generateFrontmatter(skill);
580
+ // Build rendered content: frontmatter + marker + body
581
+ let renderedContent;
582
+ if (!sourceContent) {
583
+ renderedContent = `${frontmatter}\n${KSPEC_MANAGED_MARKER}\n\n# ${skill.name}\n\n${skill.description || ""}\n`;
584
+ }
585
+ else {
586
+ const frontmatterMatch = sourceContent.match(/^---\n[\s\S]*?\n---\n?/);
587
+ const contentWithoutFrontmatter = frontmatterMatch
588
+ ? sourceContent.slice(frontmatterMatch[0].length)
589
+ : sourceContent;
590
+ renderedContent = `${frontmatter}\n${KSPEC_MANAGED_MARKER}\n${contentWithoutFrontmatter}`;
591
+ }
592
+ // Idempotency check
593
+ let targetExists = false;
594
+ let targetContent = "";
595
+ try {
596
+ targetContent = await fs.readFile(targetSkillMd, "utf-8");
597
+ targetExists = true;
598
+ }
599
+ catch {
600
+ // Target doesn't exist
601
+ }
602
+ let action;
603
+ if (!targetExists) {
604
+ action = "created";
605
+ }
606
+ else if (contentsEqual(renderedContent, targetContent)) {
607
+ action = "unchanged";
608
+ }
609
+ else {
610
+ action = "updated";
611
+ }
612
+ // Write SKILL.md
613
+ if (!dryRun && action !== "unchanged") {
614
+ await fs.mkdir(targetDir, { recursive: true });
615
+ await fs.writeFile(targetSkillMd, renderedContent, "utf-8");
616
+ }
617
+ paths.push(targetSkillMd);
618
+ // Write additional files (e.g., sidecar for codex)
619
+ let additionalContentChanged = false;
620
+ if (config.writeAdditionalFiles) {
621
+ const additional = await config.writeAdditionalFiles(ctx, skill, targetDir, dryRun);
622
+ paths.push(...additional.paths);
623
+ additionalContentChanged = additional.contentChanged;
624
+ }
625
+ // Store per-platform hash
626
+ const contentChanged = action !== "unchanged" || additionalContentChanged;
627
+ if (!dryRun && storeHash && contentChanged) {
628
+ // Get additional content for hash (e.g., sidecar content)
629
+ const additionalHashContent = config.getAdditionalHashContent?.(renderedContent);
630
+ const hashContent = additionalHashContent
631
+ ? renderedContent + "\n" + additionalHashContent
632
+ : renderedContent;
633
+ const hash = computeContentHash(hashContent);
634
+ await writePlatformRenderHash(ctx.specDir, skill.id, config.platform, hash);
635
+ if (config.writeLegacyHash) {
636
+ await writeRenderHash(ctx.specDir, skill.id, hash);
637
+ }
638
+ }
639
+ // Copy supporting directories
640
+ const sourceSkillDir = path.join(ctx.specDir, "skills", skill.id);
641
+ const supportingDirsAction = await copySupportingDirectories(sourceSkillDir, targetDir, dryRun);
642
+ for (const [dirName, dirAction] of Object.entries(supportingDirsAction)) {
643
+ if (dirAction !== "skipped") {
644
+ paths.push(path.join(targetDir, dirName));
645
+ }
646
+ }
647
+ return {
648
+ id: skill.id,
649
+ platform: config.platform,
650
+ action,
651
+ paths,
652
+ supportingDirsAction,
653
+ };
654
+ }
655
+ // ============================================================================
656
+ // Claude Code Renderer (implements PlatformRenderer)
657
+ // ============================================================================
658
+ /**
659
+ * Migrate old plugin paths for core skills.
660
+ * Cleans up old render targets (.claude/skills/<id>/, .claude/skills/kspec/<id>/,
661
+ * .claude/plugins/kspec/) when kspec-managed marker is present.
662
+ * Called from renderer and from CLI after marketplace registration.
663
+ */
664
+ export async function migrateOldPluginPaths(projectRoot, skillId) {
665
+ const defaultOutputDir = ".claude/skills";
666
+ // Old flat path: .claude/skills/<id>/ (pre-#440)
667
+ const oldFlatPath = path.join(projectRoot, defaultOutputDir, skillId, "SKILL.md");
668
+ try {
669
+ const content = await fs.readFile(oldFlatPath, "utf-8");
670
+ if (content.includes(KSPEC_MANAGED_MARKER)) {
671
+ await fs.rm(path.join(projectRoot, defaultOutputDir, skillId), { recursive: true, force: true });
672
+ }
673
+ }
674
+ catch {
675
+ // Old path doesn't exist
676
+ }
677
+ // Old namespaced path: .claude/skills/kspec/<id>/ (PR #440)
678
+ const oldNamespacedPath = path.join(projectRoot, defaultOutputDir, "kspec", skillId, "SKILL.md");
679
+ try {
680
+ const content = await fs.readFile(oldNamespacedPath, "utf-8");
681
+ if (content.includes(KSPEC_MANAGED_MARKER)) {
682
+ await fs.rm(path.join(projectRoot, defaultOutputDir, "kspec", skillId), { recursive: true, force: true });
683
+ }
684
+ }
685
+ catch {
686
+ // Old path doesn't exist
687
+ }
688
+ // Old monolithic: .claude/skills/kspec/SKILL.md
689
+ const oldMonolithicPath = path.join(projectRoot, defaultOutputDir, "kspec", "SKILL.md");
690
+ try {
691
+ const content = await fs.readFile(oldMonolithicPath, "utf-8");
692
+ if (content.includes(KSPEC_MANAGED_MARKER)) {
693
+ await fs.rm(oldMonolithicPath, { force: true });
694
+ }
695
+ }
696
+ catch {
697
+ // Old path doesn't exist
698
+ }
699
+ // Old plugin render target: .claude/plugins/kspec/skills/<id>/
700
+ const oldPluginPath = path.join(projectRoot, PLUGIN_SKILLS_DIR, skillId, "SKILL.md");
701
+ try {
702
+ const content = await fs.readFile(oldPluginPath, "utf-8");
703
+ if (content.includes(KSPEC_MANAGED_MARKER)) {
704
+ await fs.rm(path.join(projectRoot, PLUGIN_SKILLS_DIR, skillId), { recursive: true, force: true });
705
+ }
706
+ }
707
+ catch {
708
+ // Old path doesn't exist
709
+ }
710
+ // Clean up empty parent directories
711
+ for (const dir of [
712
+ path.join(projectRoot, defaultOutputDir, "kspec"),
713
+ path.join(projectRoot, PLUGIN_SKILLS_DIR),
714
+ path.join(projectRoot, PLUGIN_DIR),
715
+ ]) {
716
+ try {
717
+ const entries = await fs.readdir(dir);
718
+ if (entries.length === 0) {
719
+ await fs.rm(dir, { recursive: true, force: true });
720
+ }
721
+ }
722
+ catch {
723
+ // Dir doesn't exist or not empty
724
+ }
725
+ }
726
+ }
727
+ /**
728
+ * Claude Code platform renderer implementation
729
+ * AC: @platform-renderer-trait ac-1 through ac-6
730
+ */
731
+ export const claudeCodeRenderer = {
732
+ platform: "claude-code",
733
+ defaultOutputDir: ".claude/skills",
734
+ async render(ctx, projectRoot, skill, options = {}) {
735
+ const dryRun = options.dryRun ?? false;
736
+ // AC: @skill-rendering ac-7 - Core skills are provided by the npm package plugin
737
+ // directory. Skip local rendering; migration cleanup is handled separately.
738
+ if (skill.origin === "core" && !options.outputDir) {
739
+ // Run migration cleanup for old paths
740
+ if (!dryRun) {
741
+ await migrateOldPluginPaths(projectRoot, skill.id);
742
+ }
743
+ return {
744
+ id: skill.id,
745
+ platform: this.platform,
746
+ action: "skipped",
747
+ paths: [],
748
+ skipReason: "core skill provided by npm package plugin",
749
+ skipCode: "plugin-provided",
750
+ };
751
+ }
752
+ // Project/local skills render to .claude/skills/
753
+ const result = await renderSkillBase(ctx, projectRoot, skill, options, {
754
+ platform: this.platform,
755
+ generateFrontmatter,
756
+ writeLegacyHash: true,
757
+ }, this.defaultOutputDir, skill.id);
758
+ return result;
759
+ },
760
+ async checkDrift(specDir, projectRoot, skillId, options) {
761
+ return checkPlatformSkillDrift(specDir, projectRoot, skillId, this.platform, options?.outputDir, options?.origin);
762
+ },
763
+ };
764
+ // ============================================================================
765
+ // Codex Renderer (implements PlatformRenderer)
766
+ // AC: @codex-renderer ac-1 through ac-6
767
+ // ============================================================================
768
+ /**
769
+ * Generate minimal YAML frontmatter for Codex (name + description only)
770
+ * AC: @codex-renderer ac-1 - Codex frontmatter contains only name and description
771
+ */
772
+ function generateCodexFrontmatter(skill) {
773
+ const frontmatter = {
774
+ name: skill.id,
775
+ description: skill.description || skill.name,
776
+ };
777
+ return `---\n${yaml.stringify(frontmatter).trim()}\n---`;
778
+ }
779
+ /**
780
+ * Generate Codex sidecar openai.yaml content from platform_config.codex
781
+ * AC: @codex-renderer ac-2 - sidecar agents/openai.yaml with Codex config fields
782
+ */
783
+ function generateCodexSidecarYaml(skill) {
784
+ const codexConfig = skill.platform_config?.codex;
785
+ if (!codexConfig) {
786
+ return null;
787
+ }
788
+ // Build the sidecar structure per Codex docs
789
+ const sidecar = {};
790
+ // Interface section (display_name, short_description, icons, colors)
791
+ const interfaceSection = {};
792
+ if (codexConfig.display_name) {
793
+ interfaceSection.display_name = codexConfig.display_name;
794
+ }
795
+ if (codexConfig.short_description) {
796
+ interfaceSection.short_description = codexConfig.short_description;
797
+ }
798
+ if (codexConfig.icon_small) {
799
+ interfaceSection.icon_small = codexConfig.icon_small;
800
+ }
801
+ if (codexConfig.icon_large) {
802
+ interfaceSection.icon_large = codexConfig.icon_large;
803
+ }
804
+ if (codexConfig.brand_color) {
805
+ interfaceSection.brand_color = codexConfig.brand_color;
806
+ }
807
+ if (codexConfig.default_prompt) {
808
+ interfaceSection.default_prompt = codexConfig.default_prompt;
809
+ }
810
+ if (Object.keys(interfaceSection).length > 0) {
811
+ sidecar.interface = interfaceSection;
812
+ }
813
+ // Policy section (allow_implicit_invocation)
814
+ if (codexConfig.allow_implicit_invocation !== undefined) {
815
+ sidecar.policy = {
816
+ allow_implicit_invocation: codexConfig.allow_implicit_invocation,
817
+ };
818
+ }
819
+ // Only return content if there's something to write
820
+ if (Object.keys(sidecar).length === 0) {
821
+ return null;
822
+ }
823
+ return yaml.stringify(sidecar);
824
+ }
825
+ /**
826
+ * Codex platform renderer implementation
827
+ * AC: @codex-renderer ac-1 - Creates .agents/skills/<id>/SKILL.md with minimal frontmatter
828
+ * AC: @codex-renderer ac-2 - Creates sidecar agents/openai.yaml when platform_config.codex exists
829
+ * AC: @codex-renderer ac-3 - No sidecar created when no platform_config.codex
830
+ * AC: @codex-renderer ac-4 - Rendered file contains <!-- kspec-managed --> marker
831
+ * AC: @codex-renderer ac-5 - Supporting directories copied to output
832
+ * AC: @codex-renderer ac-6 - Hash written to .render-hash-codex
833
+ */
834
+ export const codexRenderer = {
835
+ platform: "codex",
836
+ defaultOutputDir: ".agents/skills",
837
+ async render(ctx, projectRoot, skill, options = {}) {
838
+ // Pre-compute sidecar content so it's available for both hash and write
839
+ const sidecarContent = generateCodexSidecarYaml(skill);
840
+ const codexSubdir = getSkillSubdir(skill.id, skill.origin, "codex");
841
+ return renderSkillBase(ctx, projectRoot, skill, options, {
842
+ platform: this.platform,
843
+ generateFrontmatter: generateCodexFrontmatter,
844
+ // AC: @skill-drift-detection-improvements ac-1 - Include sidecar in hash
845
+ getAdditionalHashContent: () => sidecarContent,
846
+ // AC: @codex-renderer ac-2, ac-3 - Write sidecar agents/openai.yaml
847
+ writeAdditionalFiles: async (_ctx, _skill, targetDir, dryRun) => {
848
+ const paths = [];
849
+ let contentChanged = false;
850
+ if (sidecarContent) {
851
+ const sidecarDir = path.join(targetDir, "agents");
852
+ const sidecarPath = path.join(sidecarDir, "openai.yaml");
853
+ // Check existing sidecar
854
+ let sidecarExists = false;
855
+ let existingSidecar = "";
856
+ try {
857
+ existingSidecar = await fs.readFile(sidecarPath, "utf-8");
858
+ sidecarExists = true;
859
+ }
860
+ catch {
861
+ // Doesn't exist
862
+ }
863
+ const sidecarAction = !sidecarExists
864
+ ? "created"
865
+ : contentsEqual(sidecarContent, existingSidecar)
866
+ ? "unchanged"
867
+ : "updated";
868
+ if (!dryRun && sidecarAction !== "unchanged") {
869
+ await fs.mkdir(sidecarDir, { recursive: true });
870
+ await fs.writeFile(sidecarPath, sidecarContent, "utf-8");
871
+ }
872
+ paths.push(sidecarPath);
873
+ contentChanged = sidecarAction !== "unchanged";
874
+ }
875
+ return { paths, contentChanged };
876
+ },
877
+ }, this.defaultOutputDir, codexSubdir);
878
+ },
879
+ // AC: @skill-drift-detection-improvements ac-1 - Include sidecar content in drift hash
880
+ async checkDrift(specDir, projectRoot, skillId, options) {
881
+ const codexSubdir = getSkillSubdir(skillId, options?.origin, "codex");
882
+ const platformOutputDir = options?.outputDir || this.defaultOutputDir;
883
+ const skillDir = path.join(projectRoot, platformOutputDir, codexSubdir);
884
+ const renderedPath = path.join(skillDir, "SKILL.md");
885
+ // Check if rendered file exists
886
+ let renderedContent;
887
+ try {
888
+ renderedContent = await fs.readFile(renderedPath, "utf-8");
889
+ }
890
+ catch {
891
+ return "not-rendered";
892
+ }
893
+ // Migrate legacy hash if needed
894
+ await migrateLegacyRenderHash(specDir, skillId, this.platform);
895
+ // Get stored hash
896
+ const storedHash = await readPlatformRenderHash(specDir, skillId, this.platform);
897
+ if (!storedHash) {
898
+ return "no-hash";
899
+ }
900
+ // Read sidecar content if it exists
901
+ const sidecarPath = path.join(skillDir, "agents", "openai.yaml");
902
+ let sidecarContent = null;
903
+ try {
904
+ sidecarContent = await fs.readFile(sidecarPath, "utf-8");
905
+ }
906
+ catch {
907
+ // No sidecar file
908
+ }
909
+ // Combine content for hash (must match render-time computation)
910
+ const combinedContent = sidecarContent
911
+ ? renderedContent + "\n" + sidecarContent
912
+ : renderedContent;
913
+ const currentHash = computeContentHash(combinedContent);
914
+ return currentHash === storedHash ? "in-sync" : "drifted";
915
+ },
916
+ };
917
+ // ============================================================================
918
+ // Renderer Registry
919
+ // ============================================================================
920
+ /** Map of platform name to renderer implementation */
921
+ const rendererRegistry = new Map([
922
+ ["claude-code", claudeCodeRenderer],
923
+ ["codex", codexRenderer],
924
+ ]);
925
+ /**
926
+ * Get the renderer for a specific platform
927
+ */
928
+ export function getRenderer(platform) {
929
+ return rendererRegistry.get(platform);
930
+ }
931
+ /**
932
+ * Get all registered renderers
933
+ */
934
+ export function getAllRenderers() {
935
+ return Array.from(rendererRegistry.values());
936
+ }
937
+ /**
938
+ * Register a new platform renderer
939
+ */
940
+ export function registerRenderer(renderer) {
941
+ rendererRegistry.set(renderer.platform, renderer);
942
+ }
943
+ //# sourceMappingURL=skill-render.js.map