@kynetic-ai/spec 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (510) hide show
  1. package/README.md +250 -17
  2. package/dist/acp/client.d.ts +18 -4
  3. package/dist/acp/client.d.ts.map +1 -1
  4. package/dist/acp/client.js +44 -26
  5. package/dist/acp/client.js.map +1 -1
  6. package/dist/acp/framing.d.ts +2 -2
  7. package/dist/acp/framing.d.ts.map +1 -1
  8. package/dist/acp/framing.js +37 -29
  9. package/dist/acp/framing.js.map +1 -1
  10. package/dist/acp/index.d.ts +6 -7
  11. package/dist/acp/index.d.ts.map +1 -1
  12. package/dist/acp/index.js +3 -3
  13. package/dist/acp/index.js.map +1 -1
  14. package/dist/acp/types.d.ts +5 -5
  15. package/dist/acp/types.d.ts.map +1 -1
  16. package/dist/acp/types.js +18 -18
  17. package/dist/acp/types.js.map +1 -1
  18. package/dist/agents/adapters.d.ts.map +1 -1
  19. package/dist/agents/adapters.js +24 -13
  20. package/dist/agents/adapters.js.map +1 -1
  21. package/dist/agents/index.d.ts +2 -2
  22. package/dist/agents/index.js +2 -2
  23. package/dist/agents/spawner.d.ts +4 -4
  24. package/dist/agents/spawner.d.ts.map +1 -1
  25. package/dist/agents/spawner.js +6 -6
  26. package/dist/agents/spawner.js.map +1 -1
  27. package/dist/cli/batch-context.d.ts +43 -0
  28. package/dist/cli/batch-context.d.ts.map +1 -0
  29. package/dist/cli/batch-context.js +93 -0
  30. package/dist/cli/batch-context.js.map +1 -0
  31. package/dist/cli/batch-exec.d.ts +116 -0
  32. package/dist/cli/batch-exec.d.ts.map +1 -0
  33. package/dist/cli/batch-exec.js +694 -0
  34. package/dist/cli/batch-exec.js.map +1 -0
  35. package/dist/cli/batch.d.ts +4 -2
  36. package/dist/cli/batch.d.ts.map +1 -1
  37. package/dist/cli/batch.js +15 -14
  38. package/dist/cli/batch.js.map +1 -1
  39. package/dist/cli/command-annotations.d.ts +23 -0
  40. package/dist/cli/command-annotations.d.ts.map +1 -0
  41. package/dist/cli/command-annotations.js +27 -0
  42. package/dist/cli/command-annotations.js.map +1 -0
  43. package/dist/cli/commands/agents.d.ts +46 -0
  44. package/dist/cli/commands/agents.d.ts.map +1 -0
  45. package/dist/cli/commands/agents.js +377 -0
  46. package/dist/cli/commands/agents.js.map +1 -0
  47. package/dist/cli/commands/batch.d.ts +20 -0
  48. package/dist/cli/commands/batch.d.ts.map +1 -0
  49. package/dist/cli/commands/batch.js +214 -0
  50. package/dist/cli/commands/batch.js.map +1 -0
  51. package/dist/cli/commands/clone-for-testing.d.ts +1 -1
  52. package/dist/cli/commands/clone-for-testing.d.ts.map +1 -1
  53. package/dist/cli/commands/clone-for-testing.js +37 -47
  54. package/dist/cli/commands/clone-for-testing.js.map +1 -1
  55. package/dist/cli/commands/derive.d.ts +1 -1
  56. package/dist/cli/commands/derive.d.ts.map +1 -1
  57. package/dist/cli/commands/derive.js +140 -88
  58. package/dist/cli/commands/derive.js.map +1 -1
  59. package/dist/cli/commands/doctor.d.ts +11 -0
  60. package/dist/cli/commands/doctor.d.ts.map +1 -0
  61. package/dist/cli/commands/doctor.js +152 -0
  62. package/dist/cli/commands/doctor.js.map +1 -0
  63. package/dist/cli/commands/export.d.ts +12 -0
  64. package/dist/cli/commands/export.d.ts.map +1 -0
  65. package/dist/cli/commands/export.js +134 -0
  66. package/dist/cli/commands/export.js.map +1 -0
  67. package/dist/cli/commands/help.d.ts +1 -1
  68. package/dist/cli/commands/help.d.ts.map +1 -1
  69. package/dist/cli/commands/help.js +163 -37
  70. package/dist/cli/commands/help.js.map +1 -1
  71. package/dist/cli/commands/inbox.d.ts +1 -1
  72. package/dist/cli/commands/inbox.d.ts.map +1 -1
  73. package/dist/cli/commands/inbox.js +178 -56
  74. package/dist/cli/commands/inbox.js.map +1 -1
  75. package/dist/cli/commands/index.d.ts +31 -19
  76. package/dist/cli/commands/index.d.ts.map +1 -1
  77. package/dist/cli/commands/index.js +31 -19
  78. package/dist/cli/commands/index.js.map +1 -1
  79. package/dist/cli/commands/init.d.ts +5 -1
  80. package/dist/cli/commands/init.d.ts.map +1 -1
  81. package/dist/cli/commands/init.js +108 -57
  82. package/dist/cli/commands/init.js.map +1 -1
  83. package/dist/cli/commands/item.d.ts +1 -1
  84. package/dist/cli/commands/item.d.ts.map +1 -1
  85. package/dist/cli/commands/item.js +557 -274
  86. package/dist/cli/commands/item.js.map +1 -1
  87. package/dist/cli/commands/link.d.ts +1 -1
  88. package/dist/cli/commands/link.d.ts.map +1 -1
  89. package/dist/cli/commands/link.js +55 -46
  90. package/dist/cli/commands/link.js.map +1 -1
  91. package/dist/cli/commands/log.d.ts +1 -1
  92. package/dist/cli/commands/log.d.ts.map +1 -1
  93. package/dist/cli/commands/log.js +57 -51
  94. package/dist/cli/commands/log.js.map +1 -1
  95. package/dist/cli/commands/merge-driver.d.ts +19 -0
  96. package/dist/cli/commands/merge-driver.d.ts.map +1 -0
  97. package/dist/cli/commands/merge-driver.js +398 -0
  98. package/dist/cli/commands/merge-driver.js.map +1 -0
  99. package/dist/cli/commands/meta.d.ts +1 -1
  100. package/dist/cli/commands/meta.d.ts.map +1 -1
  101. package/dist/cli/commands/meta.js +533 -399
  102. package/dist/cli/commands/meta.js.map +1 -1
  103. package/dist/cli/commands/module.d.ts +1 -1
  104. package/dist/cli/commands/module.d.ts.map +1 -1
  105. package/dist/cli/commands/module.js +30 -25
  106. package/dist/cli/commands/module.js.map +1 -1
  107. package/dist/cli/commands/plan-import.d.ts +11 -0
  108. package/dist/cli/commands/plan-import.d.ts.map +1 -0
  109. package/dist/cli/commands/plan-import.js +516 -0
  110. package/dist/cli/commands/plan-import.js.map +1 -0
  111. package/dist/cli/commands/plan.d.ts +10 -0
  112. package/dist/cli/commands/plan.d.ts.map +1 -0
  113. package/dist/cli/commands/plan.js +421 -0
  114. package/dist/cli/commands/plan.js.map +1 -0
  115. package/dist/cli/commands/ralph.d.ts +1 -1
  116. package/dist/cli/commands/ralph.d.ts.map +1 -1
  117. package/dist/cli/commands/ralph.js +1097 -169
  118. package/dist/cli/commands/ralph.js.map +1 -1
  119. package/dist/cli/commands/refs.d.ts +13 -0
  120. package/dist/cli/commands/refs.d.ts.map +1 -0
  121. package/dist/cli/commands/refs.js +283 -0
  122. package/dist/cli/commands/refs.js.map +1 -0
  123. package/dist/cli/commands/search.d.ts +1 -1
  124. package/dist/cli/commands/search.d.ts.map +1 -1
  125. package/dist/cli/commands/search.js +199 -37
  126. package/dist/cli/commands/search.js.map +1 -1
  127. package/dist/cli/commands/serve.d.ts +10 -0
  128. package/dist/cli/commands/serve.d.ts.map +1 -0
  129. package/dist/cli/commands/serve.js +491 -0
  130. package/dist/cli/commands/serve.js.map +1 -0
  131. package/dist/cli/commands/session.d.ts +25 -6
  132. package/dist/cli/commands/session.d.ts.map +1 -1
  133. package/dist/cli/commands/session.js +811 -127
  134. package/dist/cli/commands/session.js.map +1 -1
  135. package/dist/cli/commands/setup-seeding.d.ts +81 -0
  136. package/dist/cli/commands/setup-seeding.d.ts.map +1 -0
  137. package/dist/cli/commands/setup-seeding.js +292 -0
  138. package/dist/cli/commands/setup-seeding.js.map +1 -0
  139. package/dist/cli/commands/setup.d.ts +77 -3
  140. package/dist/cli/commands/setup.d.ts.map +1 -1
  141. package/dist/cli/commands/setup.js +1233 -274
  142. package/dist/cli/commands/setup.js.map +1 -1
  143. package/dist/cli/commands/shadow.d.ts +1 -1
  144. package/dist/cli/commands/shadow.d.ts.map +1 -1
  145. package/dist/cli/commands/shadow.js +70 -50
  146. package/dist/cli/commands/shadow.js.map +1 -1
  147. package/dist/cli/commands/skill-crud.d.ts +58 -0
  148. package/dist/cli/commands/skill-crud.d.ts.map +1 -0
  149. package/dist/cli/commands/skill-crud.js +753 -0
  150. package/dist/cli/commands/skill-crud.js.map +1 -0
  151. package/dist/cli/commands/skill-diff.d.ts +27 -0
  152. package/dist/cli/commands/skill-diff.d.ts.map +1 -0
  153. package/dist/cli/commands/skill-diff.js +840 -0
  154. package/dist/cli/commands/skill-diff.js.map +1 -0
  155. package/dist/cli/commands/skill-install.d.ts +53 -0
  156. package/dist/cli/commands/skill-install.d.ts.map +1 -0
  157. package/dist/cli/commands/skill-install.js +452 -0
  158. package/dist/cli/commands/skill-install.js.map +1 -0
  159. package/dist/cli/commands/skill.d.ts +20 -0
  160. package/dist/cli/commands/skill.d.ts.map +1 -0
  161. package/dist/cli/commands/skill.js +36 -0
  162. package/dist/cli/commands/skill.js.map +1 -0
  163. package/dist/cli/commands/task.d.ts +1 -1
  164. package/dist/cli/commands/task.d.ts.map +1 -1
  165. package/dist/cli/commands/task.js +569 -346
  166. package/dist/cli/commands/task.js.map +1 -1
  167. package/dist/cli/commands/tasks.d.ts +26 -1
  168. package/dist/cli/commands/tasks.d.ts.map +1 -1
  169. package/dist/cli/commands/tasks.js +227 -122
  170. package/dist/cli/commands/tasks.js.map +1 -1
  171. package/dist/cli/commands/trait.d.ts +1 -1
  172. package/dist/cli/commands/trait.d.ts.map +1 -1
  173. package/dist/cli/commands/trait.js +166 -101
  174. package/dist/cli/commands/trait.js.map +1 -1
  175. package/dist/cli/commands/triage.d.ts +7 -0
  176. package/dist/cli/commands/triage.d.ts.map +1 -0
  177. package/dist/cli/commands/triage.js +569 -0
  178. package/dist/cli/commands/triage.js.map +1 -0
  179. package/dist/cli/commands/util.d.ts +7 -0
  180. package/dist/cli/commands/util.d.ts.map +1 -0
  181. package/dist/cli/commands/util.js +30 -0
  182. package/dist/cli/commands/util.js.map +1 -0
  183. package/dist/cli/commands/validate.d.ts +1 -1
  184. package/dist/cli/commands/validate.d.ts.map +1 -1
  185. package/dist/cli/commands/validate.js +264 -83
  186. package/dist/cli/commands/validate.js.map +1 -1
  187. package/dist/cli/commands/workflow.d.ts +16 -0
  188. package/dist/cli/commands/workflow.d.ts.map +1 -0
  189. package/dist/cli/commands/workflow.js +851 -0
  190. package/dist/cli/commands/workflow.js.map +1 -0
  191. package/dist/cli/exit-codes.d.ts +7 -0
  192. package/dist/cli/exit-codes.d.ts.map +1 -1
  193. package/dist/cli/exit-codes.js +26 -18
  194. package/dist/cli/exit-codes.js.map +1 -1
  195. package/dist/cli/help/content.d.ts.map +1 -1
  196. package/dist/cli/help/content.js +86 -71
  197. package/dist/cli/help/content.js.map +1 -1
  198. package/dist/cli/index.d.ts +1 -1
  199. package/dist/cli/index.d.ts.map +1 -1
  200. package/dist/cli/index.js +131 -19
  201. package/dist/cli/index.js.map +1 -1
  202. package/dist/cli/introspection.d.ts +6 -2
  203. package/dist/cli/introspection.d.ts.map +1 -1
  204. package/dist/cli/introspection.js +11 -8
  205. package/dist/cli/introspection.js.map +1 -1
  206. package/dist/cli/output.d.ts +64 -4
  207. package/dist/cli/output.d.ts.map +1 -1
  208. package/dist/cli/output.js +235 -85
  209. package/dist/cli/output.js.map +1 -1
  210. package/dist/cli/parse-utils.d.ts +21 -0
  211. package/dist/cli/parse-utils.d.ts.map +1 -0
  212. package/dist/cli/parse-utils.js +32 -0
  213. package/dist/cli/parse-utils.js.map +1 -0
  214. package/dist/cli/pid-utils.d.ts +72 -0
  215. package/dist/cli/pid-utils.d.ts.map +1 -0
  216. package/dist/cli/pid-utils.js +174 -0
  217. package/dist/cli/pid-utils.js.map +1 -0
  218. package/dist/cli/suggest.d.ts.map +1 -1
  219. package/dist/cli/suggest.js +1 -2
  220. package/dist/cli/suggest.js.map +1 -1
  221. package/dist/cli/validators.d.ts +43 -0
  222. package/dist/cli/validators.d.ts.map +1 -0
  223. package/dist/cli/validators.js +84 -0
  224. package/dist/cli/validators.js.map +1 -0
  225. package/dist/daemon/index.ts +52 -0
  226. package/dist/daemon/middleware/project-context.ts +126 -0
  227. package/dist/daemon/pid.ts +179 -0
  228. package/dist/daemon/project-context.ts +343 -0
  229. package/dist/daemon/routes/inbox.ts +164 -0
  230. package/dist/daemon/routes/items.ts +322 -0
  231. package/dist/daemon/routes/meta.ts +118 -0
  232. package/dist/daemon/routes/projects.ts +162 -0
  233. package/dist/daemon/routes/tasks.ts +327 -0
  234. package/dist/daemon/routes/triage.ts +468 -0
  235. package/dist/daemon/routes/validation.ts +248 -0
  236. package/dist/daemon/server.ts +408 -0
  237. package/dist/daemon/watcher.ts +195 -0
  238. package/dist/daemon/websocket/handler.ts +138 -0
  239. package/dist/daemon/websocket/heartbeat.ts +71 -0
  240. package/dist/daemon/websocket/pubsub.ts +125 -0
  241. package/dist/daemon/websocket/types.ts +66 -0
  242. package/dist/export/html.d.ts +19 -0
  243. package/dist/export/html.d.ts.map +1 -0
  244. package/dist/export/html.js +239 -0
  245. package/dist/export/html.js.map +1 -0
  246. package/dist/export/index.d.ts +10 -0
  247. package/dist/export/index.d.ts.map +1 -0
  248. package/dist/export/index.js +10 -0
  249. package/dist/export/index.js.map +1 -0
  250. package/dist/export/json.d.ts +24 -0
  251. package/dist/export/json.d.ts.map +1 -0
  252. package/dist/export/json.js +198 -0
  253. package/dist/export/json.js.map +1 -0
  254. package/dist/export/triage.d.ts +51 -0
  255. package/dist/export/triage.d.ts.map +1 -0
  256. package/dist/export/triage.js +83 -0
  257. package/dist/export/triage.js.map +1 -0
  258. package/dist/export/types.d.ts +122 -0
  259. package/dist/export/types.d.ts.map +1 -0
  260. package/dist/export/types.js +9 -0
  261. package/dist/export/types.js.map +1 -0
  262. package/dist/index.d.ts +2 -2
  263. package/dist/index.js +2 -2
  264. package/dist/lib/claude-plugin-registry.d.ts +66 -0
  265. package/dist/lib/claude-plugin-registry.d.ts.map +1 -0
  266. package/dist/lib/claude-plugin-registry.js +318 -0
  267. package/dist/lib/claude-plugin-registry.js.map +1 -0
  268. package/dist/merge/arrays.d.ts +87 -0
  269. package/dist/merge/arrays.d.ts.map +1 -0
  270. package/dist/merge/arrays.js +164 -0
  271. package/dist/merge/arrays.js.map +1 -0
  272. package/dist/merge/file-type.d.ts +32 -0
  273. package/dist/merge/file-type.d.ts.map +1 -0
  274. package/dist/merge/file-type.js +70 -0
  275. package/dist/merge/file-type.js.map +1 -0
  276. package/dist/merge/index.d.ts +14 -0
  277. package/dist/merge/index.d.ts.map +1 -0
  278. package/dist/merge/index.js +11 -0
  279. package/dist/merge/index.js.map +1 -0
  280. package/dist/merge/objects.d.ts +46 -0
  281. package/dist/merge/objects.d.ts.map +1 -0
  282. package/dist/merge/objects.js +193 -0
  283. package/dist/merge/objects.js.map +1 -0
  284. package/dist/merge/parse.d.ts +23 -0
  285. package/dist/merge/parse.d.ts.map +1 -0
  286. package/dist/merge/parse.js +78 -0
  287. package/dist/merge/parse.js.map +1 -0
  288. package/dist/merge/resolve.d.ts +66 -0
  289. package/dist/merge/resolve.d.ts.map +1 -0
  290. package/dist/merge/resolve.js +189 -0
  291. package/dist/merge/resolve.js.map +1 -0
  292. package/dist/merge/types.d.ts +82 -0
  293. package/dist/merge/types.d.ts.map +1 -0
  294. package/dist/merge/types.js +8 -0
  295. package/dist/merge/types.js.map +1 -0
  296. package/dist/parser/agent-data-sections.d.ts +53 -0
  297. package/dist/parser/agent-data-sections.d.ts.map +1 -0
  298. package/dist/parser/agent-data-sections.js +118 -0
  299. package/dist/parser/agent-data-sections.js.map +1 -0
  300. package/dist/parser/alignment.d.ts +4 -4
  301. package/dist/parser/alignment.d.ts.map +1 -1
  302. package/dist/parser/alignment.js +27 -22
  303. package/dist/parser/alignment.js.map +1 -1
  304. package/dist/parser/assess.d.ts +5 -5
  305. package/dist/parser/assess.d.ts.map +1 -1
  306. package/dist/parser/assess.js +36 -32
  307. package/dist/parser/assess.js.map +1 -1
  308. package/dist/parser/config.d.ts +351 -0
  309. package/dist/parser/config.d.ts.map +1 -0
  310. package/dist/parser/config.js +326 -0
  311. package/dist/parser/config.js.map +1 -0
  312. package/dist/parser/convention-validation.d.ts +1 -1
  313. package/dist/parser/convention-validation.d.ts.map +1 -1
  314. package/dist/parser/convention-validation.js +21 -16
  315. package/dist/parser/convention-validation.js.map +1 -1
  316. package/dist/parser/coverage-cache.d.ts +49 -0
  317. package/dist/parser/coverage-cache.d.ts.map +1 -0
  318. package/dist/parser/coverage-cache.js +123 -0
  319. package/dist/parser/coverage-cache.js.map +1 -0
  320. package/dist/parser/daemon-status.d.ts +37 -0
  321. package/dist/parser/daemon-status.d.ts.map +1 -0
  322. package/dist/parser/daemon-status.js +67 -0
  323. package/dist/parser/daemon-status.js.map +1 -0
  324. package/dist/parser/doctor.d.ts +107 -0
  325. package/dist/parser/doctor.d.ts.map +1 -0
  326. package/dist/parser/doctor.js +366 -0
  327. package/dist/parser/doctor.js.map +1 -0
  328. package/dist/parser/fix.d.ts +1 -1
  329. package/dist/parser/fix.d.ts.map +1 -1
  330. package/dist/parser/fix.js +31 -27
  331. package/dist/parser/fix.js.map +1 -1
  332. package/dist/parser/index.d.ts +16 -11
  333. package/dist/parser/index.d.ts.map +1 -1
  334. package/dist/parser/index.js +16 -11
  335. package/dist/parser/index.js.map +1 -1
  336. package/dist/parser/items.d.ts +8 -2
  337. package/dist/parser/items.d.ts.map +1 -1
  338. package/dist/parser/items.js +71 -35
  339. package/dist/parser/items.js.map +1 -1
  340. package/dist/parser/meta.d.ts +167 -9
  341. package/dist/parser/meta.d.ts.map +1 -1
  342. package/dist/parser/meta.js +379 -46
  343. package/dist/parser/meta.js.map +1 -1
  344. package/dist/parser/plan-document.d.ts +189 -0
  345. package/dist/parser/plan-document.d.ts.map +1 -0
  346. package/dist/parser/plan-document.js +340 -0
  347. package/dist/parser/plan-document.js.map +1 -0
  348. package/dist/parser/plans.d.ts +59 -0
  349. package/dist/parser/plans.d.ts.map +1 -0
  350. package/dist/parser/plans.js +239 -0
  351. package/dist/parser/plans.js.map +1 -0
  352. package/dist/parser/refs.d.ts +22 -9
  353. package/dist/parser/refs.d.ts.map +1 -1
  354. package/dist/parser/refs.js +102 -50
  355. package/dist/parser/refs.js.map +1 -1
  356. package/dist/parser/setup-status.d.ts +71 -0
  357. package/dist/parser/setup-status.d.ts.map +1 -0
  358. package/dist/parser/setup-status.js +269 -0
  359. package/dist/parser/setup-status.js.map +1 -0
  360. package/dist/parser/shadow.d.ts +150 -19
  361. package/dist/parser/shadow.d.ts.map +1 -1
  362. package/dist/parser/shadow.js +548 -187
  363. package/dist/parser/shadow.js.map +1 -1
  364. package/dist/parser/skill-render.d.ts +317 -0
  365. package/dist/parser/skill-render.d.ts.map +1 -0
  366. package/dist/parser/skill-render.js +943 -0
  367. package/dist/parser/skill-render.js.map +1 -0
  368. package/dist/parser/traits.d.ts +3 -3
  369. package/dist/parser/traits.d.ts.map +1 -1
  370. package/dist/parser/traits.js +2 -2
  371. package/dist/parser/traits.js.map +1 -1
  372. package/dist/parser/validate-skills.d.ts +32 -0
  373. package/dist/parser/validate-skills.d.ts.map +1 -0
  374. package/dist/parser/validate-skills.js +202 -0
  375. package/dist/parser/validate-skills.js.map +1 -0
  376. package/dist/parser/validate.d.ts +45 -3
  377. package/dist/parser/validate.d.ts.map +1 -1
  378. package/dist/parser/validate.js +622 -105
  379. package/dist/parser/validate.js.map +1 -1
  380. package/dist/parser/yaml.d.ts +83 -19
  381. package/dist/parser/yaml.d.ts.map +1 -1
  382. package/dist/parser/yaml.js +478 -173
  383. package/dist/parser/yaml.js.map +1 -1
  384. package/dist/ralph/cli-renderer.d.ts +8 -1
  385. package/dist/ralph/cli-renderer.d.ts.map +1 -1
  386. package/dist/ralph/cli-renderer.js +105 -34
  387. package/dist/ralph/cli-renderer.js.map +1 -1
  388. package/dist/ralph/events.d.ts +10 -10
  389. package/dist/ralph/events.d.ts.map +1 -1
  390. package/dist/ralph/events.js +277 -98
  391. package/dist/ralph/events.js.map +1 -1
  392. package/dist/ralph/index.d.ts +5 -2
  393. package/dist/ralph/index.d.ts.map +1 -1
  394. package/dist/ralph/index.js +9 -3
  395. package/dist/ralph/index.js.map +1 -1
  396. package/dist/ralph/loop-errors.d.ts +83 -0
  397. package/dist/ralph/loop-errors.d.ts.map +1 -0
  398. package/dist/ralph/loop-errors.js +150 -0
  399. package/dist/ralph/loop-errors.js.map +1 -0
  400. package/dist/ralph/subagent.d.ts +83 -0
  401. package/dist/ralph/subagent.d.ts.map +1 -0
  402. package/dist/ralph/subagent.js +174 -0
  403. package/dist/ralph/subagent.js.map +1 -0
  404. package/dist/ralph/wrap-up.d.ts +125 -0
  405. package/dist/ralph/wrap-up.d.ts.map +1 -0
  406. package/dist/ralph/wrap-up.js +270 -0
  407. package/dist/ralph/wrap-up.js.map +1 -0
  408. package/dist/schema/batch.d.ts +95 -0
  409. package/dist/schema/batch.d.ts.map +1 -0
  410. package/dist/schema/batch.js +24 -0
  411. package/dist/schema/batch.js.map +1 -0
  412. package/dist/schema/common.d.ts +2 -2
  413. package/dist/schema/common.d.ts.map +1 -1
  414. package/dist/schema/common.js +34 -31
  415. package/dist/schema/common.js.map +1 -1
  416. package/dist/schema/inbox.d.ts +12 -12
  417. package/dist/schema/inbox.js +4 -4
  418. package/dist/schema/inbox.js.map +1 -1
  419. package/dist/schema/index.d.ts +8 -5
  420. package/dist/schema/index.d.ts.map +1 -1
  421. package/dist/schema/index.js +8 -5
  422. package/dist/schema/index.js.map +1 -1
  423. package/dist/schema/meta.d.ts +1454 -27
  424. package/dist/schema/meta.d.ts.map +1 -1
  425. package/dist/schema/meta.js +198 -21
  426. package/dist/schema/meta.js.map +1 -1
  427. package/dist/schema/plan.d.ts +285 -0
  428. package/dist/schema/plan.d.ts.map +1 -0
  429. package/dist/schema/plan.js +81 -0
  430. package/dist/schema/plan.js.map +1 -0
  431. package/dist/schema/spec.d.ts +72 -33
  432. package/dist/schema/spec.d.ts.map +1 -1
  433. package/dist/schema/spec.js +22 -9
  434. package/dist/schema/spec.js.map +1 -1
  435. package/dist/schema/task.d.ts +172 -161
  436. package/dist/schema/task.d.ts.map +1 -1
  437. package/dist/schema/task.js +21 -12
  438. package/dist/schema/task.js.map +1 -1
  439. package/dist/schema/triage.d.ts +266 -0
  440. package/dist/schema/triage.d.ts.map +1 -0
  441. package/dist/schema/triage.js +134 -0
  442. package/dist/schema/triage.js.map +1 -0
  443. package/dist/sessions/index.d.ts +2 -2
  444. package/dist/sessions/index.d.ts.map +1 -1
  445. package/dist/sessions/index.js +3 -3
  446. package/dist/sessions/index.js.map +1 -1
  447. package/dist/sessions/store.d.ts +233 -1
  448. package/dist/sessions/store.d.ts.map +1 -1
  449. package/dist/sessions/store.js +628 -31
  450. package/dist/sessions/store.js.map +1 -1
  451. package/dist/sessions/types.d.ts +10 -10
  452. package/dist/sessions/types.d.ts.map +1 -1
  453. package/dist/sessions/types.js +10 -9
  454. package/dist/sessions/types.js.map +1 -1
  455. package/dist/strings/errors.d.ts +51 -0
  456. package/dist/strings/errors.d.ts.map +1 -1
  457. package/dist/strings/errors.js +136 -106
  458. package/dist/strings/errors.js.map +1 -1
  459. package/dist/strings/guidance.d.ts.map +1 -1
  460. package/dist/strings/guidance.js +16 -16
  461. package/dist/strings/guidance.js.map +1 -1
  462. package/dist/strings/index.d.ts +4 -4
  463. package/dist/strings/index.d.ts.map +1 -1
  464. package/dist/strings/index.js +4 -4
  465. package/dist/strings/index.js.map +1 -1
  466. package/dist/strings/labels.d.ts +4 -0
  467. package/dist/strings/labels.d.ts.map +1 -1
  468. package/dist/strings/labels.js +45 -41
  469. package/dist/strings/labels.js.map +1 -1
  470. package/dist/strings/validation.d.ts.map +1 -1
  471. package/dist/strings/validation.js +71 -71
  472. package/dist/strings/validation.js.map +1 -1
  473. package/dist/utils/commit.d.ts +1 -1
  474. package/dist/utils/commit.d.ts.map +1 -1
  475. package/dist/utils/commit.js +28 -26
  476. package/dist/utils/commit.js.map +1 -1
  477. package/dist/utils/git.d.ts +1 -1
  478. package/dist/utils/git.d.ts.map +1 -1
  479. package/dist/utils/git.js +40 -38
  480. package/dist/utils/git.js.map +1 -1
  481. package/dist/utils/grep.js +11 -11
  482. package/dist/utils/grep.js.map +1 -1
  483. package/dist/utils/index.d.ts +7 -7
  484. package/dist/utils/index.d.ts.map +1 -1
  485. package/dist/utils/index.js +4 -4
  486. package/dist/utils/index.js.map +1 -1
  487. package/dist/utils/time.d.ts.map +1 -1
  488. package/dist/utils/time.js +10 -10
  489. package/dist/utils/time.js.map +1 -1
  490. package/package.json +28 -5
  491. package/plugin/.claude-plugin/marketplace.json +17 -0
  492. package/plugin/.claude-plugin/plugin.json +5 -0
  493. package/plugin/plugins/kspec/skills/help/SKILL.md +42 -0
  494. package/plugin/plugins/kspec/skills/triage/SKILL.md +206 -0
  495. package/plugin/plugins/kspec/skills/triage/docs/automation.md +120 -0
  496. package/plugin/plugins/kspec/skills/triage/docs/inbox.md +144 -0
  497. package/plugin/plugins/kspec/skills/triage/docs/observations.md +85 -0
  498. package/templates/agents-sections/01-quick-start.md +22 -0
  499. package/templates/agents-sections/02-shadow-branch.md +34 -0
  500. package/templates/agents-sections/03-task-lifecycle.md +48 -0
  501. package/templates/agents-sections/04-pr-workflow.md +17 -0
  502. package/templates/agents-sections/05-commit-convention.md +27 -0
  503. package/templates/agents-sections/06-ralph-loop.md +45 -0
  504. package/templates/hooks/pre-commit +34 -0
  505. package/templates/skills/help/SKILL.md +37 -0
  506. package/templates/skills/manifest.yaml +15 -0
  507. package/templates/skills/triage/SKILL.md +199 -0
  508. package/templates/skills/triage/docs/automation.md +120 -0
  509. package/templates/skills/triage/docs/inbox.md +144 -0
  510. package/templates/skills/triage/docs/observations.md +85 -0
@@ -1,11 +1,45 @@
1
- import * as fs from 'node:fs/promises';
2
- import * as path from 'node:path';
3
- import * as os from 'node:os';
4
- import * as readline from 'node:readline/promises';
5
- import { success, error, warn } from '../output.js';
6
- import { errors } from '../../strings/index.js';
7
- import { getShadowStatus, repairShadow, getGitRoot, SHADOW_BRANCH_NAME, } from '../../parser/shadow.js';
8
- import { EXIT_CODES } from '../exit-codes.js';
1
+ /**
2
+ * Enhanced setup command for kspec agent integration.
3
+ *
4
+ * Orchestrates the full onboarding pipeline:
5
+ * - Detect agent environment (Claude Code, Cline, etc.)
6
+ * - Install hooks (UserPromptSubmit, Stop, PreToolUse guards)
7
+ * - Render skills from .kspec/skills/ to .claude/skills/
8
+ * - Generate kspec-agents.md
9
+ *
10
+ * AC: @enhanced-setup ac-1 - summary displayed listing each step performed
11
+ * AC: @enhanced-setup ac-2 - all hook entries (UserPromptSubmit, Stop, PreToolUse) present
12
+ * AC: @enhanced-setup ac-3 - rendered skill files exist for each skill targeting claude-code
13
+ * AC: @enhanced-setup ac-4 - kspec-agents.md exists after setup
14
+ * AC: @enhanced-setup ac-5 - --skip-skills flag skips skill rendering
15
+ * AC: @enhanced-setup ac-6 - --dry-run displays planned actions without changes
16
+ * AC: @enhanced-setup ac-7 - --status reports current state including agent detected
17
+ * AC: @enhanced-setup ac-8 - --status shows hooks status, skills rendered count, agents.md freshness
18
+ * AC: @enhanced-setup ac-9 - skills referenced by ralph (task-work, reflect) are present
19
+ */
20
+ import * as fs from "node:fs/promises";
21
+ import * as os from "node:os";
22
+ import * as path from "node:path";
23
+ import * as readline from "node:readline/promises";
24
+ import chalk from "chalk";
25
+ import { getGitRoot, getShadowStatus, repairShadow, SHADOW_BRANCH_NAME, } from "../../parser/shadow.js";
26
+ import { errors } from "../../strings/index.js";
27
+ import { EXIT_CODES } from "../exit-codes.js";
28
+ import { error, output, success, warn } from "../output.js";
29
+ /**
30
+ * Log a message at debug level (only when KSPEC_DEBUG=1)
31
+ * AC: @setup-pipeline-unification ac-4
32
+ */
33
+ function debugLog(message, detail) {
34
+ if (process.env.KSPEC_DEBUG === "1") {
35
+ if (detail) {
36
+ console.error(`[DEBUG] setup: ${message}`, detail);
37
+ }
38
+ else {
39
+ console.error(`[DEBUG] setup: ${message}`);
40
+ }
41
+ }
42
+ }
9
43
  /**
10
44
  * Detect which agent environment we're running in.
11
45
  * Returns the detected agent type and confidence level.
@@ -17,23 +51,29 @@ export function detectAgent() {
17
51
  // CLAUDECODE=1 is set in CLI sessions
18
52
  // CLAUDE_CODE_ENTRYPOINT indicates entry point (cli, etc.)
19
53
  // CLAUDE_PROJECT_DIR is set in some contexts
20
- if (process.env.CLAUDECODE === '1' || process.env.CLAUDE_CODE_ENTRYPOINT || process.env.CLAUDE_PROJECT_DIR) {
54
+ if (process.env.CLAUDECODE === "1" ||
55
+ process.env.CLAUDE_CODE_ENTRYPOINT ||
56
+ process.env.CLAUDE_PROJECT_DIR) {
21
57
  return {
22
- type: 'claude-code',
23
- confidence: 'high',
24
- configPath: path.join(os.homedir(), '.claude', 'settings.json'),
58
+ type: "claude-code",
59
+ confidence: "high",
60
+ configPath: path.join(os.homedir(), ".claude", "settings.json"),
25
61
  envVars: {
26
62
  ...(process.env.CLAUDECODE && { CLAUDECODE: process.env.CLAUDECODE }),
27
- ...(process.env.CLAUDE_CODE_ENTRYPOINT && { CLAUDE_CODE_ENTRYPOINT: process.env.CLAUDE_CODE_ENTRYPOINT }),
28
- ...(process.env.CLAUDE_PROJECT_DIR && { CLAUDE_PROJECT_DIR: process.env.CLAUDE_PROJECT_DIR }),
63
+ ...(process.env.CLAUDE_CODE_ENTRYPOINT && {
64
+ CLAUDE_CODE_ENTRYPOINT: process.env.CLAUDE_CODE_ENTRYPOINT,
65
+ }),
66
+ ...(process.env.CLAUDE_PROJECT_DIR && {
67
+ CLAUDE_PROJECT_DIR: process.env.CLAUDE_PROJECT_DIR,
68
+ }),
29
69
  },
30
70
  };
31
71
  }
32
72
  // Cline: CLINE_ACTIVE is set when running in Cline terminal
33
73
  if (process.env.CLINE_ACTIVE) {
34
74
  return {
35
- type: 'cline',
36
- confidence: 'high',
75
+ type: "cline",
76
+ confidence: "high",
37
77
  // Cline uses VS Code settings, but env vars should be in shell profile
38
78
  configPath: undefined,
39
79
  envVars: { CLINE_ACTIVE: process.env.CLINE_ACTIVE },
@@ -42,63 +82,64 @@ export function detectAgent() {
42
82
  // GitHub Copilot CLI: Check for copilot-specific markers
43
83
  if (process.env.COPILOT_MODEL || process.env.GH_TOKEN) {
44
84
  return {
45
- type: 'copilot-cli',
46
- confidence: 'medium',
47
- configPath: path.join(os.homedir(), '.copilot', 'config.json'),
85
+ type: "copilot-cli",
86
+ confidence: "medium",
87
+ configPath: path.join(os.homedir(), ".copilot", "config.json"),
48
88
  };
49
89
  }
50
90
  // Aider: Check for AIDER_* env vars
51
91
  if (process.env.AIDER_MODEL || process.env.AIDER_DARK_MODE !== undefined) {
52
92
  return {
53
- type: 'aider',
54
- confidence: 'high',
55
- configPath: path.join(os.homedir(), '.aider.conf.yml'),
93
+ type: "aider",
94
+ confidence: "high",
95
+ configPath: path.join(os.homedir(), ".aider.conf.yml"),
56
96
  };
57
97
  }
58
98
  // OpenCode: Check for OPENCODE_* env vars
59
99
  if (process.env.OPENCODE_CONFIG_DIR || process.env.OPENCODE_CONFIG) {
60
100
  return {
61
- type: 'opencode',
62
- confidence: 'high',
63
- configPath: process.env.OPENCODE_CONFIG || path.join(os.homedir(), '.config', 'opencode', 'opencode.json'),
101
+ type: "opencode",
102
+ confidence: "high",
103
+ configPath: process.env.OPENCODE_CONFIG ||
104
+ path.join(os.homedir(), ".config", "opencode", "opencode.json"),
64
105
  };
65
106
  }
66
107
  // Gemini CLI: GEMINI_CLI=1 is set when running in Gemini CLI
67
- if (process.env.GEMINI_CLI === '1') {
108
+ if (process.env.GEMINI_CLI === "1") {
68
109
  return {
69
- type: 'gemini-cli',
70
- confidence: 'high',
71
- configPath: path.join(os.homedir(), '.gemini', 'settings.json'),
72
- envVars: { GEMINI_CLI: '1' },
110
+ type: "gemini-cli",
111
+ confidence: "high",
112
+ configPath: path.join(os.homedir(), ".gemini", "settings.json"),
113
+ envVars: { GEMINI_CLI: "1" },
73
114
  };
74
115
  }
75
116
  // Codex CLI: CODEX_SANDBOX is set when running in sandbox
76
117
  if (process.env.CODEX_SANDBOX) {
77
118
  return {
78
- type: 'codex-cli',
79
- confidence: 'high',
80
- configPath: path.join(os.homedir(), '.codex', 'config.toml'),
119
+ type: "codex-cli",
120
+ confidence: "high",
121
+ configPath: path.join(os.homedir(), ".codex", "config.toml"),
81
122
  envVars: { CODEX_SANDBOX: process.env.CODEX_SANDBOX },
82
123
  };
83
124
  }
84
125
  // Amp (Sourcegraph): Check for AMP_API_KEY or AMP_TOOLBOX
85
126
  if (process.env.AMP_API_KEY || process.env.AMP_TOOLBOX) {
86
127
  return {
87
- type: 'amp',
88
- confidence: 'medium',
89
- configPath: path.join(os.homedir(), '.config', 'amp', 'settings.json'),
128
+ type: "amp",
129
+ confidence: "medium",
130
+ configPath: path.join(os.homedir(), ".config", "amp", "settings.json"),
90
131
  };
91
132
  }
92
133
  return {
93
- type: 'unknown',
94
- confidence: 'low',
134
+ type: "unknown",
135
+ confidence: "low",
95
136
  };
96
137
  }
97
138
  /**
98
139
  * Install KSPEC_AUTHOR config for Claude Code (global settings)
99
140
  */
100
141
  async function installClaudeCodeConfig(author) {
101
- const configPath = path.join(os.homedir(), '.claude', 'settings.json');
142
+ const configPath = path.join(os.homedir(), ".claude", "settings.json");
102
143
  const configDir = path.dirname(configPath);
103
144
  try {
104
145
  // Ensure directory exists
@@ -106,56 +147,255 @@ async function installClaudeCodeConfig(author) {
106
147
  // Read existing config or start fresh
107
148
  let config = {};
108
149
  try {
109
- const existing = await fs.readFile(configPath, 'utf-8');
150
+ const existing = await fs.readFile(configPath, "utf-8");
110
151
  config = JSON.parse(existing);
111
152
  }
112
- catch {
113
- // File doesn't exist or invalid JSON, start fresh
153
+ catch (err) {
154
+ debugLog("No existing Claude Code config, starting fresh", err);
114
155
  }
115
156
  // Merge env settings
116
157
  const env = config.env || {};
117
158
  env.KSPEC_AUTHOR = author;
118
159
  config.env = env;
119
160
  // Write back
120
- await fs.writeFile(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
161
+ await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
121
162
  return true;
122
163
  }
123
164
  catch (err) {
165
+ debugLog("Failed to install Claude Code config", err);
124
166
  return false;
125
167
  }
126
168
  }
169
+ /**
170
+ * PreToolUse guard hook scripts
171
+ * These are the shell scripts that will be installed to .claude/hooks/
172
+ */
173
+ const GUARD_SCRIPTS = {
174
+ "kspec-worktree-guard.sh": `#!/bin/bash
175
+ # Guard against dangerous git operations in .kspec worktree
176
+ #
177
+ # This hook prevents accidentally creating branches or switching
178
+ # branches in the .kspec worktree, which should always stay on kspec-meta.
179
+
180
+ # Read the tool input from stdin
181
+ INPUT=$(cat)
182
+
183
+ # Extract the command from the JSON input
184
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
185
+
186
+ # If no command, allow (not a Bash tool call)
187
+ if [ -z "$COMMAND" ]; then
188
+ echo '{"decision": "allow"}'
189
+ exit 0
190
+ fi
191
+
192
+ # Two views of the command for safe matching:
193
+ # 1. UNQUOTED: remove quote chars (keeps content) to catch split-quote
194
+ # bypasses like: git "reset" --hard → git reset --hard
195
+ # 2. STRIPPED: remove entire quoted strings to ignore patterns in args
196
+ # like: echo "git reset" → echo
197
+ UNQUOTED=$(echo "$COMMAND" | sed 's/["\\x27]//g')
198
+ STRIPPED=$(echo "$COMMAND" | sed -e "s/\\x27[^\\x27]*\\x27//g" -e 's/"[^"]*"//g')
199
+ # First command word (handles leading whitespace)
200
+ FIRST_CMD=$(echo "$COMMAND" | sed 's/^[[:space:]]*//' | cut -d' ' -f1)
201
+
202
+ # Block deleting kspec-meta from anywhere (check unquoted to catch bypasses)
203
+ if [[ "$UNQUOTED" == *"git branch -d kspec-meta"* || "$UNQUOTED" == *"git branch -D kspec-meta"* ]]; then
204
+ cat <<EOF
205
+ {
206
+ "decision": "block",
207
+ "reason": "[kspec-worktree-guard] BLOCKED: Cannot delete kspec-meta branch. This is the main branch for the .kspec worktree."
208
+ }
209
+ EOF
210
+ exit 0
211
+ fi
212
+
213
+ # Get cwd from hook input (not pwd - hook runs in different context)
214
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
215
+ IN_KSPEC=false
216
+
217
+ if [[ "$CWD" == *"/.kspec"* || "$CWD" == *"/.kspec" ]]; then
218
+ IN_KSPEC=true
219
+ fi
220
+
221
+ # Also check if command contains cd to .kspec
222
+ if [[ "$COMMAND" == *"cd "*".kspec"* || "$COMMAND" == *"cd .kspec"* ]]; then
223
+ IN_KSPEC=true
224
+ fi
225
+
226
+ if [ "$IN_KSPEC" = false ]; then
227
+ echo '{"decision": "allow"}'
228
+ exit 0
229
+ fi
230
+
231
+ # Dangerous patterns in .kspec (branch creation/modification/history rewriting)
232
+ # Note: "git checkout kspec-meta" is safe and allowed
233
+ DANGEROUS_PATTERNS=(
234
+ # Branch creation
235
+ "git checkout -b"
236
+ "git checkout -B"
237
+ "git branch -c"
238
+ "git branch -C"
239
+ "git branch -m"
240
+ "git branch -M"
241
+ "git switch -c"
242
+ "git switch -C"
243
+ "git switch --create"
244
+ # History rewriting - these can cause conflicts with active sessions
245
+ "git reset"
246
+ "git rebase"
247
+ "git cherry-pick"
248
+ "git commit --amend"
249
+ # Force push
250
+ "git push --force"
251
+ "git push -f"
252
+ # Discarding changes
253
+ "git stash"
254
+ "git clean"
255
+ "git checkout -- "
256
+ "git restore"
257
+ )
258
+
259
+ for pattern in "\${DANGEROUS_PATTERNS[@]}"; do
260
+ # Block if:
261
+ # - Pattern matches UNQUOTED AND first command is "git" (catches split-quote bypasses), OR
262
+ # - Pattern matches STRIPPED (actual command outside any quotes)
263
+ # This allows: echo "git reset", grep "git stash" (first cmd is echo/grep, pattern not in STRIPPED)
264
+ # This blocks: git reset, git "reset" --hard, git st'ash' (first cmd is git OR pattern in STRIPPED)
265
+ if [[ "$STRIPPED" == *"$pattern"* ]] || { [[ "$UNQUOTED" == *"$pattern"* ]] && [[ "$FIRST_CMD" == "git" ]]; }; then
266
+ cat <<EOF
267
+ {
268
+ "decision": "block",
269
+ "reason": "[kspec-worktree-guard] BLOCKED: Dangerous git operation in .kspec worktree. This worktree contains active session data and must stay on kspec-meta. Operations like reset, rebase, stash, and clean can corrupt session files."
270
+ }
271
+ EOF
272
+ exit 0
273
+ fi
274
+ done
275
+
276
+ # Allow all other commands
277
+ echo '{"decision": "allow"}'
278
+ `,
279
+ "ralph-task-limit-guard.sh": `#!/bin/bash
280
+ # Ralph task limit guard - blocks task start when limit reached
281
+ #
282
+ # This hook provides hard enforcement of the --max-tasks limit.
283
+ # Ralph writes a marker file when the limit is reached; this hook
284
+ # blocks 'kspec task start' commands when that marker exists.
285
+
286
+ # Marker file location (relative to project root)
287
+ MARKER_FILE=".claude/ralph-task-limit.json"
288
+
289
+ # Read the tool input from stdin
290
+ INPUT=$(cat)
291
+
292
+ # Extract the command from the JSON input
293
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
294
+
295
+ # If no command, allow (not a Bash tool call)
296
+ if [ -z "$COMMAND" ]; then
297
+ echo '{"decision": "allow"}'
298
+ exit 0
299
+ fi
300
+
301
+ # Only check commands that match "kspec task start"
302
+ if [[ ! "$COMMAND" =~ kspec[[:space:]]+task[[:space:]]+start ]]; then
303
+ echo '{"decision": "allow"}'
304
+ exit 0
305
+ fi
306
+
307
+ # Get cwd from hook input to find marker file
308
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
309
+ if [ -z "$CWD" ]; then
310
+ CWD="$PWD"
311
+ fi
312
+
313
+ # Find project root by walking up looking for .claude directory
314
+ PROJECT_ROOT="$CWD"
315
+ while [ "$PROJECT_ROOT" != "/" ]; do
316
+ if [ -d "$PROJECT_ROOT/.claude" ]; then
317
+ break
318
+ fi
319
+ PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
320
+ done
321
+
322
+ if [ "$PROJECT_ROOT" = "/" ]; then
323
+ # No .claude directory found, allow
324
+ echo '{"decision": "allow"}'
325
+ exit 0
326
+ fi
327
+
328
+ MARKER_PATH="$PROJECT_ROOT/$MARKER_FILE"
329
+
330
+ # Check if marker file exists
331
+ if [ ! -f "$MARKER_PATH" ]; then
332
+ echo '{"decision": "allow"}'
333
+ exit 0
334
+ fi
335
+
336
+ # Read marker file and check if active
337
+ ACTIVE=$(jq -r '.active // false' "$MARKER_PATH" 2>/dev/null)
338
+ if [ "$ACTIVE" != "true" ]; then
339
+ echo '{"decision": "allow"}'
340
+ exit 0
341
+ fi
342
+
343
+ # Extract limit info for error message
344
+ MAX=$(jq -r '.max // "?"' "$MARKER_PATH" 2>/dev/null)
345
+ COMPLETED=$(jq -r '.completed // "?"' "$MARKER_PATH" 2>/dev/null)
346
+
347
+ # Block the command
348
+ cat <<EOF
349
+ {
350
+ "decision": "block",
351
+ "reason": "[ralph-task-limit-guard] BLOCKED: Task limit reached (\${COMPLETED}/\${MAX} tasks completed this iteration). This limit was set by --max-tasks. Please wrap up current work and let the iteration end naturally. Do not attempt to start new tasks."
352
+ }
353
+ EOF
354
+ exit 0
355
+ `,
356
+ };
127
357
  /**
128
358
  * Install hooks to project-level Claude Code settings (.claude/settings.json)
359
+ * AC: @enhanced-setup ac-2 - all hook entries present
129
360
  */
130
- async function installClaudeCodeHooks(projectDir) {
131
- const configPath = path.join(projectDir, '.claude', 'settings.json');
361
+ async function installClaudeCodeHooks(projectDir, dryRun = false) {
362
+ const configPath = path.join(projectDir, ".claude", "settings.json");
132
363
  const configDir = path.dirname(configPath);
133
- const result = { stop: false, promptCheck: false };
364
+ const hooksDir = path.join(projectDir, ".claude", "hooks");
365
+ const result = {
366
+ promptCheck: false,
367
+ stop: false,
368
+ preToolUse: false,
369
+ guardsCreated: [],
370
+ };
134
371
  try {
135
- // Ensure directory exists
136
- await fs.mkdir(configDir, { recursive: true });
372
+ // Ensure directories exist
373
+ if (!dryRun) {
374
+ await fs.mkdir(configDir, { recursive: true });
375
+ await fs.mkdir(hooksDir, { recursive: true });
376
+ }
137
377
  // Read existing config or start fresh
138
378
  let config = {};
139
379
  try {
140
- const existing = await fs.readFile(configPath, 'utf-8');
380
+ const existing = await fs.readFile(configPath, "utf-8");
141
381
  config = JSON.parse(existing);
142
382
  }
143
- catch {
144
- // File doesn't exist or invalid JSON, start fresh
383
+ catch (err) {
384
+ debugLog("No existing hooks config, starting fresh", err);
145
385
  }
146
386
  // Get or create hooks object
147
387
  const hooks = config.hooks || {};
148
388
  // Install UserPromptSubmit hook (spec-first reminder)
149
- const promptCheckCommand = 'npx kspec session prompt-check';
389
+ const promptCheckCommand = "kspec session prompt-check";
150
390
  const existingPromptHooks = hooks.UserPromptSubmit;
151
- const promptAlreadyInstalled = existingPromptHooks?.some((entry) => entry.hooks?.some((hook) => hook.command?.includes('session prompt-check')));
391
+ const promptAlreadyInstalled = existingPromptHooks?.some((entry) => entry.hooks?.some((hook) => hook.command?.includes("session prompt-check")));
152
392
  if (!promptAlreadyInstalled) {
153
393
  hooks.UserPromptSubmit = [
154
394
  ...(existingPromptHooks || []),
155
395
  {
156
396
  hooks: [
157
397
  {
158
- type: 'command',
398
+ type: "command",
159
399
  command: promptCheckCommand,
160
400
  },
161
401
  ],
@@ -167,17 +407,17 @@ async function installClaudeCodeHooks(projectDir) {
167
407
  result.promptCheck = true; // Already configured
168
408
  }
169
409
  // Install Stop hook (checkpoint)
170
- const stopHookCommand = 'npx kspec session checkpoint --json';
410
+ const stopHookCommand = "kspec session checkpoint --json";
171
411
  const existingStopHooks = hooks.Stop;
172
- const stopAlreadyInstalled = existingStopHooks?.some((entry) => entry.hooks?.some((hook) => hook.command?.includes('session checkpoint')));
412
+ const stopAlreadyInstalled = existingStopHooks?.some((entry) => entry.hooks?.some((hook) => hook.command?.includes("session checkpoint")));
173
413
  if (!stopAlreadyInstalled) {
174
414
  hooks.Stop = [
175
415
  ...(existingStopHooks || []),
176
416
  {
177
- matcher: '',
417
+ matcher: "",
178
418
  hooks: [
179
419
  {
180
- type: 'command',
420
+ type: "command",
181
421
  command: stopHookCommand,
182
422
  },
183
423
  ],
@@ -188,65 +428,46 @@ async function installClaudeCodeHooks(projectDir) {
188
428
  else {
189
429
  result.stop = true; // Already configured
190
430
  }
191
- config.hooks = hooks;
192
- // Write back
193
- await fs.writeFile(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
194
- return result;
195
- }
196
- catch {
197
- return result;
198
- }
199
- }
200
- /**
201
- * Install stop hook to project-level Claude Code settings (.claude/settings.json)
202
- * @deprecated Use installClaudeCodeHooks instead
203
- */
204
- async function installClaudeCodeStopHook(projectDir) {
205
- const configPath = path.join(projectDir, '.claude', 'settings.json');
206
- const configDir = path.dirname(configPath);
207
- try {
208
- // Ensure directory exists
209
- await fs.mkdir(configDir, { recursive: true });
210
- // Read existing config or start fresh
211
- let config = {};
212
- try {
213
- const existing = await fs.readFile(configPath, 'utf-8');
214
- config = JSON.parse(existing);
431
+ // AC: @enhanced-setup ac-2 - Install PreToolUse hooks with guards
432
+ const existingPreToolUseHooks = hooks.PreToolUse;
433
+ // Check if our guards are already installed
434
+ const guardHookCommands = Object.keys(GUARD_SCRIPTS).map((name) => `.claude/hooks/${name}`);
435
+ const guardsAlreadyInstalled = existingPreToolUseHooks?.some((entry) => entry.hooks?.some((hook) => guardHookCommands.some((cmd) => hook.command?.includes(cmd))));
436
+ if (!guardsAlreadyInstalled) {
437
+ // Create guard script files
438
+ for (const [name, content] of Object.entries(GUARD_SCRIPTS)) {
439
+ const scriptPath = path.join(hooksDir, name);
440
+ if (!dryRun) {
441
+ await fs.writeFile(scriptPath, content, { mode: 0o755 });
442
+ }
443
+ result.guardsCreated.push(name);
444
+ }
445
+ // Add PreToolUse hook entry
446
+ hooks.PreToolUse = [
447
+ ...(existingPreToolUseHooks || []),
448
+ {
449
+ matcher: "Bash",
450
+ hooks: Object.keys(GUARD_SCRIPTS).map((name) => ({
451
+ type: "command",
452
+ command: `.claude/hooks/${name}`,
453
+ })),
454
+ },
455
+ ];
456
+ result.preToolUse = true;
215
457
  }
216
- catch {
217
- // File doesn't exist or invalid JSON, start fresh
458
+ else {
459
+ result.preToolUse = true; // Already configured
218
460
  }
219
- // Build the stop hook command
220
- const stopHookCommand = 'npx kspec session checkpoint --json';
221
- // Get or create hooks object
222
- const hooks = config.hooks || {};
223
- // Check if Stop hook already exists with our command
224
- const existingStopHooks = hooks.Stop;
225
- const alreadyInstalled = existingStopHooks?.some((entry) => entry.hooks?.some((hook) => hook.command?.includes('session checkpoint')));
226
- if (alreadyInstalled) {
227
- return true; // Already configured
228
- }
229
- // Add our stop hook using Claude Code hooks format
230
- // Note: matcher field is required even if empty string
231
- hooks.Stop = [
232
- ...(existingStopHooks || []),
233
- {
234
- matcher: '',
235
- hooks: [
236
- {
237
- type: 'command',
238
- command: stopHookCommand,
239
- },
240
- ],
241
- },
242
- ];
243
461
  config.hooks = hooks;
244
462
  // Write back
245
- await fs.writeFile(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
246
- return true;
463
+ if (!dryRun) {
464
+ await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
465
+ }
466
+ return result;
247
467
  }
248
- catch {
249
- return false;
468
+ catch (err) {
469
+ debugLog("installClaudeCodeHooks failed", err);
470
+ return result;
250
471
  }
251
472
  }
252
473
  /**
@@ -254,23 +475,23 @@ async function installClaudeCodeStopHook(projectDir) {
254
475
  * Aider uses `set-env:` for environment variables in list format
255
476
  */
256
477
  async function installAiderConfig(author) {
257
- const configPath = path.join(os.homedir(), '.aider.conf.yml');
478
+ const configPath = path.join(os.homedir(), ".aider.conf.yml");
258
479
  try {
259
- let content = '';
480
+ let content = "";
260
481
  try {
261
- content = await fs.readFile(configPath, 'utf-8');
482
+ content = await fs.readFile(configPath, "utf-8");
262
483
  }
263
- catch {
264
- // File doesn't exist, start fresh
484
+ catch (err) {
485
+ debugLog("No existing Aider config, starting fresh", err);
265
486
  }
266
487
  // Check if KSPEC_AUTHOR is already set
267
- if (content.includes('KSPEC_AUTHOR')) {
488
+ if (content.includes("KSPEC_AUTHOR")) {
268
489
  // Replace existing value (handles both old and new format)
269
490
  content = content.replace(/^(\s*-?\s*KSPEC_AUTHOR\s*[=:]\s*).*$/m, ` - KSPEC_AUTHOR=${author}`);
270
491
  }
271
492
  else {
272
493
  // Add to set-env section or create it
273
- if (content.includes('set-env:')) {
494
+ if (content.includes("set-env:")) {
274
495
  // Append to existing set-env section
275
496
  content = content.replace(/(set-env:\s*\n)/m, `$1 - KSPEC_AUTHOR=${author}\n`);
276
497
  }
@@ -279,10 +500,11 @@ async function installAiderConfig(author) {
279
500
  content += `\n# kspec author for note attribution\nset-env:\n - KSPEC_AUTHOR=${author}\n`;
280
501
  }
281
502
  }
282
- await fs.writeFile(configPath, content, 'utf-8');
503
+ await fs.writeFile(configPath, content, "utf-8");
283
504
  return true;
284
505
  }
285
- catch {
506
+ catch (err) {
507
+ debugLog("installAiderConfig failed", err);
286
508
  return false;
287
509
  }
288
510
  }
@@ -295,19 +517,20 @@ async function installGenericJsonConfig(configPath, author) {
295
517
  await fs.mkdir(configDir, { recursive: true });
296
518
  let config = {};
297
519
  try {
298
- const existing = await fs.readFile(configPath, 'utf-8');
520
+ const existing = await fs.readFile(configPath, "utf-8");
299
521
  config = JSON.parse(existing);
300
522
  }
301
- catch {
302
- // Start fresh
523
+ catch (err) {
524
+ debugLog(`No existing config at ${configPath}, starting fresh`, err);
303
525
  }
304
526
  const env = config.env || {};
305
527
  env.KSPEC_AUTHOR = author;
306
528
  config.env = env;
307
- await fs.writeFile(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
529
+ await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
308
530
  return true;
309
531
  }
310
- catch {
532
+ catch (err) {
533
+ debugLog(`installGenericJsonConfig failed for ${configPath}`, err);
311
534
  return false;
312
535
  }
313
536
  }
@@ -316,26 +539,26 @@ async function installGenericJsonConfig(configPath, author) {
316
539
  */
317
540
  function getDefaultAuthor(agentType) {
318
541
  switch (agentType) {
319
- case 'claude-code':
320
- return '@claude';
321
- case 'cline':
322
- return '@cline';
323
- case 'roo-code':
324
- return '@roo';
325
- case 'copilot-cli':
326
- return '@copilot';
327
- case 'gemini-cli':
328
- return '@gemini';
329
- case 'codex-cli':
330
- return '@codex';
331
- case 'aider':
332
- return '@aider';
333
- case 'opencode':
334
- return '@opencode';
335
- case 'amp':
336
- return '@amp';
542
+ case "claude-code":
543
+ return "@claude";
544
+ case "cline":
545
+ return "@cline";
546
+ case "roo-code":
547
+ return "@roo";
548
+ case "copilot-cli":
549
+ return "@copilot";
550
+ case "gemini-cli":
551
+ return "@gemini";
552
+ case "codex-cli":
553
+ return "@codex";
554
+ case "aider":
555
+ return "@aider";
556
+ case "opencode":
557
+ return "@opencode";
558
+ case "amp":
559
+ return "@amp";
337
560
  default:
338
- return '@agent';
561
+ return "@agent";
339
562
  }
340
563
  }
341
564
  /**
@@ -348,7 +571,7 @@ async function promptYesNo(question) {
348
571
  });
349
572
  try {
350
573
  const answer = await rl.question(`${question} `);
351
- return answer.toLowerCase() === 'y';
574
+ return answer.toLowerCase() === "y";
352
575
  }
353
576
  finally {
354
577
  rl.close();
@@ -379,7 +602,7 @@ async function ensureWorktree(autoWorktree) {
379
602
  console.log(`Detected ${SHADOW_BRANCH_NAME} branch without .kspec worktree. Creating...`);
380
603
  const result = await repairShadow(projectRoot);
381
604
  if (result.success) {
382
- success('Created .kspec worktree');
605
+ success("Created .kspec worktree");
383
606
  return true;
384
607
  }
385
608
  else {
@@ -390,10 +613,10 @@ async function ensureWorktree(autoWorktree) {
390
613
  // AC: detect-existing-repo - prompt user
391
614
  const shouldCreate = await promptYesNo(`${SHADOW_BRANCH_NAME} branch exists but .kspec worktree is missing. Create it? (y/N)`);
392
615
  if (shouldCreate) {
393
- console.log('Creating .kspec worktree...');
616
+ console.log("Creating .kspec worktree...");
394
617
  const result = await repairShadow(projectRoot);
395
618
  if (result.success) {
396
- success('Created .kspec worktree');
619
+ success("Created .kspec worktree");
397
620
  return true;
398
621
  }
399
622
  else {
@@ -402,7 +625,7 @@ async function ensureWorktree(autoWorktree) {
402
625
  }
403
626
  }
404
627
  else {
405
- warn('Skipping worktree creation');
628
+ warn("Skipping worktree creation");
406
629
  return false;
407
630
  }
408
631
  }
@@ -414,76 +637,837 @@ async function ensureWorktree(autoWorktree) {
414
637
  */
415
638
  function printManualInstructions(agentType) {
416
639
  const author = getDefaultAuthor(agentType);
417
- console.log('\nManual setup instructions:\n');
640
+ console.log("\nManual setup instructions:\n");
418
641
  switch (agentType) {
419
- case 'claude-code':
420
- console.log('Add to ~/.claude/settings.json:');
421
- console.log('```json');
642
+ case "claude-code":
643
+ console.log("Add to ~/.claude/settings.json:");
644
+ console.log("```json");
422
645
  console.log(JSON.stringify({ env: { KSPEC_AUTHOR: author } }, null, 2));
423
- console.log('```');
646
+ console.log("```");
424
647
  break;
425
- case 'cline':
426
- case 'roo-code':
427
- console.log('Add to your shell profile (~/.bashrc, ~/.zshrc):');
428
- console.log('```bash');
648
+ case "cline":
649
+ case "roo-code":
650
+ console.log("Add to your shell profile (~/.bashrc, ~/.zshrc):");
651
+ console.log("```bash");
429
652
  console.log(`export KSPEC_AUTHOR="${author}"`);
430
- console.log('```');
431
- console.log('\nThis will be inherited by terminals spawned by the VS Code extension.');
653
+ console.log("```");
654
+ console.log("\nThis will be inherited by terminals spawned by the VS Code extension.");
432
655
  break;
433
- case 'copilot-cli':
434
- console.log('Add to ~/.copilot/config.json:');
435
- console.log('```json');
656
+ case "copilot-cli":
657
+ console.log("Add to ~/.copilot/config.json:");
658
+ console.log("```json");
436
659
  console.log(JSON.stringify({ env: { KSPEC_AUTHOR: author } }, null, 2));
437
- console.log('```');
660
+ console.log("```");
438
661
  break;
439
- case 'aider':
440
- console.log('Add to ~/.aider.conf.yml:');
441
- console.log('```yaml');
442
- console.log('set-env:');
662
+ case "aider":
663
+ console.log("Add to ~/.aider.conf.yml:");
664
+ console.log("```yaml");
665
+ console.log("set-env:");
443
666
  console.log(` - KSPEC_AUTHOR=${author}`);
444
- console.log('```');
667
+ console.log("```");
445
668
  break;
446
- case 'codex-cli':
447
- console.log('Add to ~/.codex/config.toml:');
448
- console.log('```toml');
449
- console.log('[shell_environment_policy]');
669
+ case "codex-cli":
670
+ console.log("Add to ~/.codex/config.toml:");
671
+ console.log("```toml");
672
+ console.log("[shell_environment_policy]");
450
673
  console.log(`set = { KSPEC_AUTHOR = "${author}" }`);
451
- console.log('```');
674
+ console.log("```");
452
675
  break;
453
- case 'opencode':
454
- console.log('Add to ~/.config/opencode/opencode.json:');
455
- console.log('```json');
676
+ case "opencode":
677
+ console.log("Add to ~/.config/opencode/opencode.json:");
678
+ console.log("```json");
456
679
  console.log(JSON.stringify({ env: { KSPEC_AUTHOR: author } }, null, 2));
457
- console.log('```');
680
+ console.log("```");
458
681
  break;
459
- case 'amp':
460
- console.log('Add to ~/.config/amp/settings.json:');
461
- console.log('```json');
682
+ case "amp":
683
+ console.log("Add to ~/.config/amp/settings.json:");
684
+ console.log("```json");
462
685
  console.log(JSON.stringify({ env: { KSPEC_AUTHOR: author } }, null, 2));
463
- console.log('```');
686
+ console.log("```");
464
687
  break;
465
688
  default:
466
- console.log('Set the KSPEC_AUTHOR environment variable:');
467
- console.log('```bash');
689
+ console.log("Set the KSPEC_AUTHOR environment variable:");
690
+ console.log("```bash");
468
691
  console.log(`export KSPEC_AUTHOR="${author}"`);
469
- console.log('```');
470
- console.log('\nOr add to your shell profile (~/.bashrc, ~/.zshrc, etc.)');
692
+ console.log("```");
693
+ console.log("\nOr add to your shell profile (~/.bashrc, ~/.zshrc, etc.)");
694
+ }
695
+ }
696
+ /**
697
+ * Check the current setup status
698
+ * AC: @enhanced-setup ac-7, ac-8
699
+ */
700
+ async function getSetupStatus(projectDir) {
701
+ const detected = detectAgent();
702
+ const configPath = path.join(projectDir, ".claude", "settings.json");
703
+ const hooksDir = path.join(projectDir, ".claude", "hooks");
704
+ const agentsMdPath = path.join(projectDir, "kspec-agents.md");
705
+ const hashPath = path.join(projectDir, ".kspec", ".kspec-agents-hash");
706
+ const skillsDir = path.join(projectDir, ".claude", "skills");
707
+ // Check hooks
708
+ const hooks = {
709
+ promptCheck: false,
710
+ stop: false,
711
+ preToolUse: false,
712
+ guardsPresent: [],
713
+ };
714
+ try {
715
+ const configContent = await fs.readFile(configPath, "utf-8");
716
+ const config = JSON.parse(configContent);
717
+ const hooksConfig = config.hooks || {};
718
+ // Check UserPromptSubmit
719
+ const promptHooks = hooksConfig.UserPromptSubmit;
720
+ hooks.promptCheck = promptHooks?.some((entry) => entry.hooks?.some((h) => h.command?.includes("prompt-check"))) ?? false;
721
+ // Check Stop
722
+ const stopHooks = hooksConfig.Stop;
723
+ hooks.stop = stopHooks?.some((entry) => entry.hooks?.some((h) => h.command?.includes("checkpoint"))) ?? false;
724
+ // Check PreToolUse
725
+ const preToolUseHooks = hooksConfig.PreToolUse;
726
+ hooks.preToolUse = preToolUseHooks?.some((entry) => entry.hooks?.some((h) => h.command?.includes(".claude/hooks/"))) ?? false;
727
+ }
728
+ catch (err) {
729
+ debugLog("Failed to read hooks config for status", err);
730
+ }
731
+ // Check guard scripts
732
+ try {
733
+ const guardFiles = await fs.readdir(hooksDir);
734
+ for (const name of Object.keys(GUARD_SCRIPTS)) {
735
+ if (guardFiles.includes(name)) {
736
+ hooks.guardsPresent.push(name);
737
+ }
738
+ }
739
+ }
740
+ catch (err) {
741
+ debugLog("Hooks dir doesn't exist", err);
742
+ }
743
+ // Check skills
744
+ const skills = {
745
+ total: 0,
746
+ rendered: 0,
747
+ drifted: 0,
748
+ };
749
+ // Helper to scan a directory for skill subdirs with kspec-managed SKILL.md
750
+ async function scanForSkills(baseDir) {
751
+ try {
752
+ const dirs = await fs.readdir(baseDir, { withFileTypes: true });
753
+ for (const dir of dirs) {
754
+ if (dir.isDirectory()) {
755
+ const skillMdPath = path.join(baseDir, dir.name, "SKILL.md");
756
+ try {
757
+ const content = await fs.readFile(skillMdPath, "utf-8");
758
+ if (content.includes("<!-- kspec-managed -->")) {
759
+ skills.total++;
760
+ skills.rendered++;
761
+ }
762
+ }
763
+ catch (_noSkillMd) {
764
+ // No SKILL.md
765
+ }
766
+ }
767
+ }
768
+ }
769
+ catch (_notReadable) {
770
+ // Directory doesn't exist
771
+ }
772
+ }
773
+ // Scan .claude/skills/ (project/local skills)
774
+ await scanForSkills(skillsDir);
775
+ // Check plugin marketplace health
776
+ // AC: @enhanced-setup ac-7, ac-8
777
+ const plugin = {
778
+ marketplaceRegistered: false,
779
+ marketplaceHealthy: false,
780
+ pluginEnabled: false,
781
+ };
782
+ try {
783
+ const { checkMarketplaceHealth } = await import("../../lib/claude-plugin-registry.js");
784
+ const health = await checkMarketplaceHealth();
785
+ plugin.marketplaceRegistered = health.status !== "missing";
786
+ plugin.marketplaceHealthy = health.status === "healthy";
787
+ plugin.registeredPath = health.registeredPath;
788
+ plugin.healthMessage = health.message;
789
+ }
790
+ catch (err) {
791
+ debugLog("Could not check marketplace health", err);
792
+ plugin.healthMessage = "Health check unavailable";
793
+ }
794
+ // Check if plugin is enabled in project settings
795
+ try {
796
+ const configContent = await fs.readFile(configPath, "utf-8");
797
+ const config = JSON.parse(configContent);
798
+ plugin.pluginEnabled = config.enabledPlugins?.["kspec@kspec-plugins"] === true;
799
+ }
800
+ catch (err) {
801
+ debugLog("Could not check plugin enablement", err);
802
+ }
803
+ // Check agents.md
804
+ const agentsMd = {
805
+ exists: false,
806
+ status: "missing",
807
+ };
808
+ try {
809
+ await fs.access(agentsMdPath);
810
+ agentsMd.exists = true;
811
+ try {
812
+ const hashContent = await fs.readFile(hashPath, "utf-8");
813
+ const hashData = JSON.parse(hashContent);
814
+ agentsMd.generatedAt = hashData.generatedAt;
815
+ // AC: @cross-platform-and-version-robustness ac-4
816
+ // Compare stored hash against current meta to detect staleness
817
+ try {
818
+ const { initContext, loadMetaContext } = await import("../../parser/index.js");
819
+ const { computeMetaHash, loadTemplateSections, getPackageRoot } = await import("./agents.js");
820
+ const ctx = await initContext();
821
+ if (ctx.manifestPath) {
822
+ const metaCtx = await loadMetaContext(ctx);
823
+ let templateSections = [];
824
+ try {
825
+ templateSections = await loadTemplateSections(getPackageRoot());
826
+ }
827
+ catch (err) {
828
+ debugLog("Templates not available for staleness check", err);
829
+ }
830
+ const currentHash = computeMetaHash(metaCtx.skills, metaCtx.conventions, metaCtx.workflows, templateSections);
831
+ agentsMd.status = hashData.metaHash === currentHash ? "current" : "stale";
832
+ }
833
+ else {
834
+ // AC: @doctor-command ac-staleness-unknown — no manifest means we can't determine staleness
835
+ agentsMd.status = "unknown";
836
+ }
837
+ }
838
+ catch (err) {
839
+ // AC: @doctor-command ac-staleness-unknown — hash computation failed
840
+ debugLog("Could not compute meta hash for staleness check", err);
841
+ agentsMd.status = "unknown";
842
+ }
843
+ }
844
+ catch (err) {
845
+ debugLog("Hash file missing or invalid, marking stale", err);
846
+ agentsMd.status = "stale";
847
+ }
848
+ }
849
+ catch (err) {
850
+ debugLog("kspec-agents.md doesn't exist", err);
851
+ }
852
+ // Check seeding state
853
+ const seeding = {
854
+ permissionsSeeded: false,
855
+ memorySeeded: false,
856
+ };
857
+ try {
858
+ const configContent = await fs.readFile(path.join(projectDir, ".claude", "settings.json"), "utf-8");
859
+ const config = JSON.parse(configContent);
860
+ seeding.permissionsSeeded = !!config.permissions;
861
+ }
862
+ catch (err) {
863
+ debugLog("Could not check permissions seeding state", err);
864
+ }
865
+ if (detected.type === "claude-code") {
866
+ try {
867
+ const { claudeCodeMemoryWriter } = await import("./setup-seeding.js");
868
+ const memoryExists = await claudeCodeMemoryWriter.exists(projectDir);
869
+ seeding.memorySeeded = memoryExists;
870
+ if (memoryExists) {
871
+ seeding.memoryPath = claudeCodeMemoryWriter.getMemoryPath(projectDir);
872
+ }
873
+ }
874
+ catch (err) {
875
+ debugLog("Could not check memory seeding state", err);
876
+ }
877
+ }
878
+ return {
879
+ agent: {
880
+ detected: detected.type,
881
+ confidence: detected.confidence,
882
+ },
883
+ hooks,
884
+ skills,
885
+ plugin,
886
+ agentsMd,
887
+ seeding,
888
+ };
889
+ }
890
+ /**
891
+ * Render skills using the platform renderer registry
892
+ * AC: @setup-pipeline-unification ac-2 - uses getRenderer/getAllRenderers, not legacy renderClaudeCodeSkill
893
+ * AC: @setup-pipeline-unification ac-4 - errors logged at debug level
894
+ */
895
+ async function renderSkillsForSetup(projectDir, dryRun) {
896
+ // Dynamically import to avoid circular dependencies
897
+ const { initContext, loadMetaContext } = await import("../../parser/index.js");
898
+ const { getRenderer } = await import("../../parser/skill-render.js");
899
+ try {
900
+ const ctx = await initContext();
901
+ if (!ctx.manifestPath) {
902
+ return { rendered: 0, skipped: 0, pluginProvided: 0, skillIds: [] };
903
+ }
904
+ const metaCtx = await loadMetaContext(ctx);
905
+ // Collect all skills that have a registered renderer for their platform
906
+ const skillsToRender = [];
907
+ for (const skill of metaCtx.skills) {
908
+ for (const platform of skill.platforms) {
909
+ const renderer = getRenderer(platform);
910
+ if (renderer) {
911
+ skillsToRender.push({ skill, platform });
912
+ }
913
+ }
914
+ }
915
+ if (skillsToRender.length === 0) {
916
+ return { rendered: 0, skipped: 0, pluginProvided: 0, skillIds: [] };
917
+ }
918
+ let rendered = 0;
919
+ let skipped = 0;
920
+ let pluginProvided = 0;
921
+ const skillIds = [];
922
+ for (const { skill, platform } of skillsToRender) {
923
+ const renderer = getRenderer(platform);
924
+ try {
925
+ const result = await renderer.render(ctx, projectDir, skill, {
926
+ dryRun,
927
+ });
928
+ if (result.action === "created" || result.action === "updated") {
929
+ rendered++;
930
+ if (!skillIds.includes(skill.id)) {
931
+ skillIds.push(skill.id);
932
+ }
933
+ }
934
+ else if (result.action === "skipped" && result.skipCode === "plugin-provided") {
935
+ pluginProvided++;
936
+ }
937
+ else {
938
+ skipped++;
939
+ }
940
+ }
941
+ catch (err) {
942
+ debugLog(`Failed to render skill ${skill.id} for ${platform}`, err);
943
+ skipped++;
944
+ }
945
+ }
946
+ return { rendered, skipped, pluginProvided, skillIds };
947
+ }
948
+ catch (err) {
949
+ debugLog("renderSkillsForSetup failed", err);
950
+ return { rendered: 0, skipped: 0, pluginProvided: 0, skillIds: [] };
951
+ }
952
+ }
953
+ /**
954
+ * Generate kspec-agents.md using the canonical implementation from agents.ts
955
+ * AC: @setup-pipeline-unification ac-1 - calls generateAgentsContent() from agents.ts
956
+ * AC: @setup-pipeline-unification ac-4 - errors logged at debug level
957
+ */
958
+ async function generateAgentInstructions(projectDir, dryRun) {
959
+ const outputPath = path.join(projectDir, "kspec-agents.md");
960
+ const hashPath = path.join(projectDir, ".kspec", ".kspec-agents-hash");
961
+ // Dynamically import to avoid circular dependencies
962
+ const { initContext, loadMetaContext } = await import("../../parser/index.js");
963
+ const { generateAgentsContent, loadTemplateSections, getPackageRoot, computeMetaHash, } = await import("./agents.js");
964
+ try {
965
+ const ctx = await initContext();
966
+ if (!ctx.manifestPath) {
967
+ return { success: false, path: outputPath };
968
+ }
969
+ const metaCtx = await loadMetaContext(ctx);
970
+ const timestamp = new Date().toISOString();
971
+ // Load templates using the canonical implementation
972
+ let templateSections = [];
973
+ try {
974
+ templateSections = await loadTemplateSections(getPackageRoot());
975
+ }
976
+ catch (err) {
977
+ debugLog("Failed to load template sections", err);
978
+ }
979
+ // Generate content using the canonical implementation from agents.ts
980
+ const content = await generateAgentsContent(metaCtx.skills, metaCtx.conventions, metaCtx.workflows, timestamp, templateSections);
981
+ if (!dryRun) {
982
+ await fs.writeFile(outputPath, content, "utf-8");
983
+ // Write hash for freshness tracking using the canonical hash function
984
+ const metaHash = computeMetaHash(metaCtx.skills, metaCtx.conventions, metaCtx.workflows, templateSections);
985
+ await fs.mkdir(path.dirname(hashPath), { recursive: true });
986
+ // Dynamically import version to avoid top-level require
987
+ const { createRequire } = await import("node:module");
988
+ const req = createRequire(import.meta.url);
989
+ const { version } = req("../../../package.json");
990
+ await fs.writeFile(hashPath, JSON.stringify({
991
+ metaHash,
992
+ generatedAt: timestamp,
993
+ version,
994
+ }, null, 2), "utf-8");
995
+ }
996
+ return { success: true, path: outputPath };
997
+ }
998
+ catch (err) {
999
+ debugLog("generateAgentInstructions failed", err);
1000
+ return { success: false, path: outputPath };
1001
+ }
1002
+ }
1003
+ /**
1004
+ * Install core skills for the setup pipeline
1005
+ * AC: @init-setup-integration ac-2 - core skills installed
1006
+ */
1007
+ async function installCoreSkillsForSetup(projectDir, dryRun) {
1008
+ // Dynamically import to avoid circular dependencies
1009
+ const { initContext, loadMetaContext, saveMetaItem, getSkillContentPath, } = await import("../../parser/index.js");
1010
+ const { commitIfShadow } = await import("../../parser/shadow.js");
1011
+ const { SkillSchema } = await import("../../schema/index.js");
1012
+ const { loadCoreSkillsManifest, copyCoreSkillFiles, getKspecPackageVersion, } = await import("./skill.js");
1013
+ const { ulid } = await import("ulid");
1014
+ let installed = 0;
1015
+ let skipped = 0;
1016
+ try {
1017
+ const ctx = await initContext();
1018
+ if (!ctx.manifestPath) {
1019
+ return { installed: 0, skipped: 0 };
1020
+ }
1021
+ const metaCtx = await loadMetaContext(ctx);
1022
+ const coreSkills = await loadCoreSkillsManifest();
1023
+ // AC: @cross-platform-and-version-robustness ac-3
1024
+ const kspecVersion = await getKspecPackageVersion();
1025
+ if (!kspecVersion) {
1026
+ debugLog("Could not determine kspec version — skills installed without version tracking");
1027
+ }
1028
+ for (const coreSkill of coreSkills) {
1029
+ // Check if skill exists
1030
+ const existingSkill = metaCtx.skills.find((s) => s.id === coreSkill.id);
1031
+ if (existingSkill && existingSkill.origin !== "core") {
1032
+ // Custom/project skill exists, skip
1033
+ skipped++;
1034
+ continue;
1035
+ }
1036
+ // Build skill data
1037
+ const skillData = {
1038
+ _ulid: existingSkill?._ulid || ulid(),
1039
+ id: coreSkill.id,
1040
+ name: coreSkill.name,
1041
+ description: coreSkill.description,
1042
+ origin: "core",
1043
+ ...(kspecVersion && { version: kspecVersion }),
1044
+ platforms: coreSkill.platforms || ["claude-code"],
1045
+ depends_on: [],
1046
+ tags: ["core"],
1047
+ };
1048
+ const parsed = SkillSchema.safeParse(skillData);
1049
+ if (!parsed.success) {
1050
+ skipped++;
1051
+ continue;
1052
+ }
1053
+ if (!dryRun) {
1054
+ await saveMetaItem(ctx, parsed.data, "skill");
1055
+ // Copy skill files (SKILL.md + supporting dirs)
1056
+ const targetDir = path.dirname(getSkillContentPath(ctx, parsed.data.id));
1057
+ await copyCoreSkillFiles(coreSkill.id, targetDir);
1058
+ }
1059
+ installed++;
1060
+ }
1061
+ // Commit changes
1062
+ if (!dryRun && installed > 0) {
1063
+ const ctx2 = await initContext();
1064
+ await commitIfShadow(ctx2.shadow, "skill-install-core", `${installed} core skills`);
1065
+ }
1066
+ // AC: @core-skill-install ac-6, ac-7 - Register marketplace and enable plugin
1067
+ let marketplaceResult;
1068
+ let enableResult;
1069
+ if (!dryRun) {
1070
+ const { registerCorePluginMarketplace, enablePluginInProject, } = await import("../../lib/claude-plugin-registry.js");
1071
+ marketplaceResult = await registerCorePluginMarketplace();
1072
+ enableResult = await enablePluginInProject(projectDir);
1073
+ }
1074
+ return {
1075
+ installed,
1076
+ skipped,
1077
+ marketplaceRegistered: marketplaceResult?.success ?? false,
1078
+ pluginEnabled: enableResult?.success ?? false,
1079
+ marketplaceMessage: marketplaceResult?.message,
1080
+ enableMessage: enableResult?.message,
1081
+ };
1082
+ }
1083
+ catch (err) {
1084
+ debugLog("installCoreSkillsForSetup failed", err);
1085
+ return { installed: 0, skipped: 0 };
1086
+ }
1087
+ }
1088
+ /**
1089
+ * Run the full setup pipeline programmatically.
1090
+ * Used by both 'kspec setup' command and 'kspec init --setup'.
1091
+ * AC: @init-setup-integration ac-2, ac-3
1092
+ */
1093
+ export async function runSetupPipeline(projectDir, options) {
1094
+ const dryRun = options.dryRun ?? false;
1095
+ const skipSkills = options.skipSkills ?? false;
1096
+ const installHooksFlag = options.installHooks ?? true;
1097
+ const steps = [];
1098
+ let coreSkillsInstalled = 0;
1099
+ let skillsRendered = 0;
1100
+ let hooksInstalled = false;
1101
+ let agentsMdGenerated = false;
1102
+ let permissionsSeeded = false;
1103
+ let memorySeeded = false;
1104
+ try {
1105
+ const detected = detectAgent();
1106
+ // Step 1: Agent detection
1107
+ steps.push({
1108
+ name: "Agent detection",
1109
+ status: "done",
1110
+ message: `${detected.type} (${detected.confidence} confidence)`,
1111
+ });
1112
+ // Step 2: Install core skills
1113
+ // AC: @init-setup-integration ac-2 - core skills installed in .kspec/skills/
1114
+ const coreResult = await installCoreSkillsForSetup(projectDir, dryRun);
1115
+ coreSkillsInstalled = coreResult.installed;
1116
+ if (coreResult.installed > 0 || coreResult.skipped > 0) {
1117
+ steps.push({
1118
+ name: "Install core skills",
1119
+ status: "done",
1120
+ message: `${coreResult.installed} installed, ${coreResult.skipped} skipped`,
1121
+ });
1122
+ }
1123
+ else {
1124
+ steps.push({
1125
+ name: "Install core skills",
1126
+ status: "skipped",
1127
+ message: "No core skills found in package",
1128
+ });
1129
+ }
1130
+ // Step 2b: Register plugin marketplace (reports result from installCoreSkillsForSetup)
1131
+ // AC: @core-skill-install ac-6, ac-7
1132
+ if (!dryRun && (coreResult.installed > 0 || coreResult.skipped > 0)) {
1133
+ const bothOk = coreResult.marketplaceRegistered && coreResult.pluginEnabled;
1134
+ if (bothOk) {
1135
+ steps.push({
1136
+ name: "Register plugin marketplace",
1137
+ status: "done",
1138
+ message: "marketplace registered, plugin enabled",
1139
+ });
1140
+ }
1141
+ else {
1142
+ const failures = [];
1143
+ if (!coreResult.marketplaceRegistered) {
1144
+ failures.push(coreResult.marketplaceMessage || "marketplace registration failed");
1145
+ }
1146
+ if (!coreResult.pluginEnabled) {
1147
+ failures.push(coreResult.enableMessage || "plugin enablement failed");
1148
+ }
1149
+ steps.push({
1150
+ name: "Register plugin marketplace",
1151
+ status: "failed",
1152
+ message: failures.join("; "),
1153
+ });
1154
+ }
1155
+ }
1156
+ // Step 3: Install hooks (Claude Code only)
1157
+ // AC: @init-setup-integration ac-3 - hooks present
1158
+ if (detected.type === "claude-code" && installHooksFlag) {
1159
+ const hooksResult = await installClaudeCodeHooks(projectDir, dryRun);
1160
+ const installedHooks = [];
1161
+ if (hooksResult.promptCheck)
1162
+ installedHooks.push("UserPromptSubmit");
1163
+ if (hooksResult.stop)
1164
+ installedHooks.push("Stop");
1165
+ if (hooksResult.preToolUse)
1166
+ installedHooks.push("PreToolUse");
1167
+ hooksInstalled =
1168
+ hooksResult.promptCheck || hooksResult.stop || hooksResult.preToolUse;
1169
+ steps.push({
1170
+ name: "Install hooks",
1171
+ status: "done",
1172
+ message: installedHooks.join(", "),
1173
+ details: {
1174
+ guards: hooksResult.guardsCreated,
1175
+ },
1176
+ });
1177
+ }
1178
+ else if (!installHooksFlag) {
1179
+ steps.push({
1180
+ name: "Install hooks",
1181
+ status: "skipped",
1182
+ message: "--no-hooks flag",
1183
+ });
1184
+ }
1185
+ else {
1186
+ steps.push({
1187
+ name: "Install hooks",
1188
+ status: "skipped",
1189
+ message: `not applicable for ${detected.type}`,
1190
+ });
1191
+ }
1192
+ // Step 3a: Ensure artifacts directory exists
1193
+ // AC: @artifacts-directory ac-setup-ensures
1194
+ {
1195
+ const artifactsDir = path.join(projectDir, ".kspec", "artifacts");
1196
+ let artifactsCreated = false;
1197
+ try {
1198
+ await fs.access(artifactsDir);
1199
+ }
1200
+ catch (_e) {
1201
+ if (!dryRun) {
1202
+ await fs.mkdir(artifactsDir, { recursive: true });
1203
+ }
1204
+ artifactsCreated = true;
1205
+ }
1206
+ steps.push({
1207
+ name: "Ensure artifacts directory",
1208
+ status: artifactsCreated ? "done" : "skipped",
1209
+ message: artifactsCreated ? "created .kspec/artifacts/" : "already exists",
1210
+ });
1211
+ }
1212
+ // Step 3b: Seed permissions (Claude Code only)
1213
+ // AC: @new-project-bootstrapping ac-1
1214
+ {
1215
+ const { seedPermissions } = await import("./setup-seeding.js");
1216
+ const permResult = await seedPermissions(projectDir, detected.type, {
1217
+ dryRun,
1218
+ force: options.force,
1219
+ });
1220
+ permissionsSeeded = permResult.seeded;
1221
+ steps.push({
1222
+ name: "Seed permissions",
1223
+ status: permResult.seeded ? "done" : "skipped",
1224
+ message: permResult.message,
1225
+ });
1226
+ }
1227
+ // Step 3c: Seed memory (platform-extensible)
1228
+ // AC: @new-project-bootstrapping ac-2
1229
+ {
1230
+ const { seedMemory } = await import("./setup-seeding.js");
1231
+ const memResult = await seedMemory(projectDir, detected.type, {
1232
+ dryRun,
1233
+ force: options.force,
1234
+ });
1235
+ memorySeeded = memResult.seeded;
1236
+ steps.push({
1237
+ name: "Seed memory",
1238
+ status: memResult.seeded ? "done" : "skipped",
1239
+ message: memResult.seeded ? memResult.path : memResult.message,
1240
+ });
1241
+ }
1242
+ // Step 4: Render skills
1243
+ // AC: @init-setup-integration ac-3 - rendered skill files present
1244
+ if (!skipSkills) {
1245
+ const skillsResult = await renderSkillsForSetup(projectDir, dryRun);
1246
+ skillsRendered = skillsResult.rendered;
1247
+ if (skillsResult.rendered > 0 || skillsResult.skipped > 0 || skillsResult.pluginProvided > 0) {
1248
+ const parts = [];
1249
+ if (skillsResult.rendered > 0)
1250
+ parts.push(`${skillsResult.rendered} rendered`);
1251
+ if (skillsResult.pluginProvided > 0)
1252
+ parts.push(`${skillsResult.pluginProvided} plugin-provided`);
1253
+ if (skillsResult.skipped > 0)
1254
+ parts.push(`${skillsResult.skipped} unchanged`);
1255
+ steps.push({
1256
+ name: "Render skills",
1257
+ status: "done",
1258
+ message: parts.join(", "),
1259
+ details: {
1260
+ skillIds: skillsResult.skillIds,
1261
+ },
1262
+ });
1263
+ }
1264
+ else {
1265
+ steps.push({
1266
+ name: "Render skills",
1267
+ status: "skipped",
1268
+ message: "No claude-code skills in meta",
1269
+ });
1270
+ }
1271
+ }
1272
+ else {
1273
+ steps.push({
1274
+ name: "Render skills",
1275
+ status: "skipped",
1276
+ message: "--skip-skills flag",
1277
+ });
1278
+ }
1279
+ // Step 5: Generate kspec-agents.md
1280
+ // AC: @init-setup-integration ac-3 - kspec-agents.md present
1281
+ const agentsResult = await generateAgentInstructions(projectDir, dryRun);
1282
+ agentsMdGenerated = agentsResult.success;
1283
+ if (agentsResult.success) {
1284
+ steps.push({
1285
+ name: "Generate kspec-agents.md",
1286
+ status: "done",
1287
+ message: agentsResult.path,
1288
+ });
1289
+ }
1290
+ else {
1291
+ steps.push({
1292
+ name: "Generate kspec-agents.md",
1293
+ status: "failed",
1294
+ message: "No kspec project found",
1295
+ });
1296
+ }
1297
+ // Step 6: Configure author (optional, used by setup command)
1298
+ if (options.configureAuthor) {
1299
+ const author = options.author || getDefaultAuthor(detected.type);
1300
+ if (!options.force && process.env.KSPEC_AUTHOR) {
1301
+ steps.push({
1302
+ name: "Configure author",
1303
+ status: "skipped",
1304
+ message: `KSPEC_AUTHOR already set to "${process.env.KSPEC_AUTHOR}"`,
1305
+ });
1306
+ }
1307
+ else {
1308
+ let authorInstalled = false;
1309
+ switch (detected.type) {
1310
+ case "claude-code":
1311
+ if (!dryRun) {
1312
+ authorInstalled = await installClaudeCodeConfig(author);
1313
+ }
1314
+ else {
1315
+ authorInstalled = true;
1316
+ }
1317
+ break;
1318
+ case "aider":
1319
+ if (!dryRun) {
1320
+ authorInstalled = await installAiderConfig(author);
1321
+ }
1322
+ else {
1323
+ authorInstalled = true;
1324
+ }
1325
+ break;
1326
+ default:
1327
+ break;
1328
+ }
1329
+ if (authorInstalled) {
1330
+ steps.push({
1331
+ name: "Configure author",
1332
+ status: "done",
1333
+ message: `KSPEC_AUTHOR="${author}"`,
1334
+ });
1335
+ }
1336
+ }
1337
+ }
1338
+ // Output summary
1339
+ if (!dryRun) {
1340
+ console.log(chalk.bold("kspec Setup Summary\n"));
1341
+ for (const step of steps) {
1342
+ const icon = step.status === "done"
1343
+ ? chalk.green("✓")
1344
+ : step.status === "skipped"
1345
+ ? chalk.gray("○")
1346
+ : chalk.red("✗");
1347
+ const statusText = step.status === "done"
1348
+ ? ""
1349
+ : step.status === "skipped"
1350
+ ? chalk.gray(" (skipped)")
1351
+ : chalk.red(" (failed)");
1352
+ console.log(`${icon} ${step.name}${statusText}`);
1353
+ if (step.message) {
1354
+ console.log(chalk.gray(` ${step.message}`));
1355
+ }
1356
+ }
1357
+ }
1358
+ const success = steps.every((s) => s.status !== "failed");
1359
+ return {
1360
+ success,
1361
+ steps,
1362
+ coreSkillsInstalled,
1363
+ skillsRendered,
1364
+ hooksInstalled,
1365
+ agentsMdGenerated,
1366
+ permissionsSeeded,
1367
+ memorySeeded,
1368
+ };
1369
+ }
1370
+ catch (err) {
1371
+ debugLog("runSetupPipeline failed", err);
1372
+ return {
1373
+ success: false,
1374
+ steps,
1375
+ coreSkillsInstalled,
1376
+ skillsRendered,
1377
+ hooksInstalled,
1378
+ agentsMdGenerated,
1379
+ permissionsSeeded,
1380
+ memorySeeded,
1381
+ };
471
1382
  }
472
1383
  }
473
1384
  /**
474
1385
  * Register the 'setup' command
1386
+ * AC: @enhanced-setup ac-1 through ac-9
475
1387
  */
476
1388
  export function registerSetupCommand(program) {
477
1389
  program
478
- .command('setup')
479
- .description('Configure agent environment for kspec')
480
- .option('--dry-run', 'Show what would be done without making changes')
481
- .option('--author <author>', 'Custom author string (default: auto-detected based on agent)')
482
- .option('--no-hooks', 'Skip installing Claude Code stop hook')
483
- .option('--force', 'Overwrite existing configuration')
484
- .option('--auto-worktree', 'Automatically create .kspec worktree if kspec-meta branch exists')
1390
+ .command("setup")
1391
+ .description("Configure agent environment for kspec (orchestrated pipeline)")
1392
+ .option("--dry-run", "Show what would be done without making changes")
1393
+ .option("--author <author>", "Custom author string (default: auto-detected based on agent)")
1394
+ .option("--no-hooks", "Skip installing hooks")
1395
+ .option("--skip-skills", "Skip rendering skills")
1396
+ .option("--status", "Report current setup state without making changes")
1397
+ .option("--force", "Overwrite existing configuration")
1398
+ .option("--auto-worktree", "Automatically create .kspec worktree if kspec-meta branch exists")
485
1399
  .action(async (options) => {
486
1400
  try {
1401
+ const projectDir = process.cwd();
1402
+ // AC: @enhanced-setup ac-7, ac-8 - --status mode
1403
+ if (options.status) {
1404
+ const status = await getSetupStatus(projectDir);
1405
+ output(status, () => {
1406
+ console.log(chalk.bold("kspec Setup Status\n"));
1407
+ // Agent detection
1408
+ console.log(chalk.gray("Agent:"));
1409
+ console.log(` Detected: ${status.agent.detected} (${status.agent.confidence} confidence)`);
1410
+ console.log();
1411
+ // Hooks status
1412
+ console.log(chalk.gray("Hooks:"));
1413
+ console.log(` UserPromptSubmit: ${status.hooks.promptCheck ? chalk.green("✓") : chalk.red("✗")}`);
1414
+ console.log(` Stop: ${status.hooks.stop ? chalk.green("✓") : chalk.red("✗")}`);
1415
+ console.log(` PreToolUse: ${status.hooks.preToolUse ? chalk.green("✓") : chalk.red("✗")}`);
1416
+ if (status.hooks.guardsPresent.length > 0) {
1417
+ console.log(` Guards: ${status.hooks.guardsPresent.join(", ")}`);
1418
+ }
1419
+ console.log();
1420
+ // Skills status
1421
+ console.log(chalk.gray("Skills:"));
1422
+ console.log(` Rendered: ${status.skills.rendered}`);
1423
+ if (status.skills.drifted > 0) {
1424
+ console.log(` Drifted: ${chalk.yellow(status.skills.drifted.toString())}`);
1425
+ }
1426
+ console.log();
1427
+ // Plugin marketplace status
1428
+ // AC: @enhanced-setup ac-7, ac-8
1429
+ console.log(chalk.gray("Plugin:"));
1430
+ console.log(` Marketplace: ${status.plugin.marketplaceRegistered ? (status.plugin.marketplaceHealthy ? chalk.green("healthy") : chalk.yellow("registered")) : chalk.red("not registered")}`);
1431
+ console.log(` Enabled: ${status.plugin.pluginEnabled ? chalk.green("✓") : chalk.red("✗")}`);
1432
+ if (status.plugin.registeredPath) {
1433
+ console.log(chalk.gray(` Path: ${status.plugin.registeredPath}`));
1434
+ }
1435
+ if (status.plugin.healthMessage && !status.plugin.marketplaceHealthy) {
1436
+ console.log(chalk.yellow(` ${status.plugin.healthMessage}`));
1437
+ }
1438
+ console.log();
1439
+ // Agents.md status
1440
+ console.log(chalk.gray("kspec-agents.md:"));
1441
+ if (status.agentsMd.exists) {
1442
+ // AC: @doctor-command ac-staleness-unknown — show appropriate color for unknown status
1443
+ const statusColor = status.agentsMd.status === "current"
1444
+ ? chalk.green
1445
+ : status.agentsMd.status === "unknown"
1446
+ ? chalk.yellow
1447
+ : chalk.yellow;
1448
+ console.log(` Status: ${statusColor(status.agentsMd.status)}`);
1449
+ if (status.agentsMd.status === "unknown") {
1450
+ console.log(chalk.gray(" Could not determine staleness (no manifest or hash unavailable)"));
1451
+ }
1452
+ if (status.agentsMd.generatedAt) {
1453
+ console.log(` Generated: ${status.agentsMd.generatedAt}`);
1454
+ }
1455
+ }
1456
+ else {
1457
+ console.log(` Status: ${chalk.red("missing")}`);
1458
+ console.log(chalk.gray(" Run 'kspec setup' to generate"));
1459
+ }
1460
+ console.log();
1461
+ // Seeding status
1462
+ console.log(chalk.gray("Seeding:"));
1463
+ console.log(` Permissions: ${status.seeding.permissionsSeeded ? chalk.green("✓") : chalk.gray("○")}`);
1464
+ console.log(` Memory: ${status.seeding.memorySeeded ? chalk.green("✓") : chalk.gray("○")}`);
1465
+ if (status.seeding.memoryPath) {
1466
+ console.log(chalk.gray(` Path: ${status.seeding.memoryPath}`));
1467
+ }
1468
+ });
1469
+ return;
1470
+ }
487
1471
  // AC: detect-existing-repo, auto-worktree-flag, worktree-already-exists
488
1472
  const worktreeReady = await ensureWorktree(options.autoWorktree || false);
489
1473
  if (!worktreeReady) {
@@ -491,91 +1475,66 @@ export function registerSetupCommand(program) {
491
1475
  process.exit(EXIT_CODES.ERROR);
492
1476
  }
493
1477
  const detected = detectAgent();
494
- const projectDir = process.cwd();
495
- console.log(`Detected agent: ${detected.type} (confidence: ${detected.confidence})`);
496
- if (detected.type === 'unknown') {
497
- warn('Could not auto-detect agent environment');
498
- printManualInstructions('unknown');
1478
+ const dryRun = options.dryRun || false;
1479
+ if (detected.type === "unknown") {
1480
+ warn("Could not auto-detect agent environment");
1481
+ printManualInstructions("unknown");
499
1482
  return;
500
1483
  }
501
- const author = options.author || getDefaultAuthor(detected.type);
502
- const installHooks = options.hooks !== false && detected.type === 'claude-code';
503
- if (options.dryRun) {
504
- console.log(`\nWould configure:`);
505
- console.log(` Agent: ${detected.type}`);
506
- console.log(` Author: ${author}`);
507
- if (detected.configPath) {
508
- console.log(` Global config: ${detected.configPath}`);
509
- }
510
- if (installHooks) {
511
- console.log(` Project config: ${path.join(projectDir, '.claude', 'settings.json')}`);
512
- console.log(` Hooks: UserPromptSubmit (prompt-check), Stop (checkpoint)`);
1484
+ // AC: @setup-pipeline-unification ac-3 - delegate to runSetupPipeline()
1485
+ // One code path for both 'kspec setup' and 'kspec init --setup'
1486
+ const result = await runSetupPipeline(projectDir, {
1487
+ dryRun,
1488
+ skipSkills: options.skipSkills || false,
1489
+ installHooks: options.hooks !== false,
1490
+ force: options.force || false,
1491
+ author: options.author,
1492
+ configureAuthor: true,
1493
+ });
1494
+ // AC: @enhanced-setup ac-1 - Display summary
1495
+ // AC: @enhanced-setup ac-6 - dry-run displays planned actions
1496
+ output({
1497
+ dry_run: dryRun,
1498
+ steps: result.steps.map((s) => ({
1499
+ name: s.name,
1500
+ status: s.status,
1501
+ message: s.message,
1502
+ details: s.details,
1503
+ })),
1504
+ }, () => {
1505
+ if (dryRun) {
1506
+ console.log(chalk.yellow("DRY RUN - No changes made\n"));
513
1507
  }
514
- return;
515
- }
516
- // Check if already configured
517
- if (!options.force && process.env.KSPEC_AUTHOR) {
518
- warn(`KSPEC_AUTHOR is already set to "${process.env.KSPEC_AUTHOR}"`);
519
- console.log('Use --force to overwrite');
520
- return;
521
- }
522
- // Install config based on agent type
523
- let installed = false;
524
- let hooksInstalled = false;
525
- let hooksResult = null;
526
- switch (detected.type) {
527
- case 'claude-code':
528
- installed = await installClaudeCodeConfig(author);
529
- if (installHooks) {
530
- hooksResult = await installClaudeCodeHooks(projectDir);
531
- hooksInstalled = hooksResult.stop || hooksResult.promptCheck;
532
- }
533
- break;
534
- case 'aider':
535
- installed = await installAiderConfig(author);
536
- break;
537
- case 'cline':
538
- case 'roo-code':
539
- // These VS Code extensions use shell env vars, not config files
540
- // Can't auto-install, must print instructions
541
- printManualInstructions(detected.type);
542
- return;
543
- case 'copilot-cli':
544
- case 'gemini-cli':
545
- case 'opencode':
546
- case 'amp':
547
- if (detected.configPath) {
548
- installed = await installGenericJsonConfig(detected.configPath, author);
1508
+ // Pipeline already prints the summary when not dry-run
1509
+ // For dry-run, print it here since the pipeline skips output
1510
+ if (dryRun) {
1511
+ console.log(chalk.bold("kspec Setup Summary\n"));
1512
+ for (const step of result.steps) {
1513
+ const icon = step.status === "done"
1514
+ ? chalk.green("✓")
1515
+ : step.status === "skipped"
1516
+ ? chalk.gray("○")
1517
+ : chalk.red("✗");
1518
+ const statusText = step.status === "done"
1519
+ ? ""
1520
+ : step.status === "skipped"
1521
+ ? chalk.gray(" (skipped)")
1522
+ : chalk.red(" (failed)");
1523
+ console.log(`${icon} ${step.name}${statusText}`);
1524
+ if (step.message) {
1525
+ console.log(chalk.gray(` ${step.message}`));
1526
+ }
549
1527
  }
550
- break;
551
- case 'codex-cli':
552
- // Codex uses TOML config, would need special handling
553
- // For now, print manual instructions
554
- printManualInstructions(detected.type);
555
- return;
556
- }
557
- if (installed) {
558
- success(`Configured ${detected.type} with KSPEC_AUTHOR="${author}"`, {
559
- agent: detected.type,
560
- author,
561
- configPath: detected.configPath,
562
- });
563
- if (hooksInstalled && hooksResult) {
564
- const installedHooks = [];
565
- if (hooksResult.promptCheck)
566
- installedHooks.push('UserPromptSubmit (prompt-check)');
567
- if (hooksResult.stop)
568
- installedHooks.push('Stop (checkpoint)');
569
- success(`Installed hooks to .claude/settings.json`, {
570
- hooks: installedHooks,
571
- });
572
1528
  }
573
- console.log('\nRestart your agent session for changes to take effect.');
574
- }
575
- else {
576
- error(errors.failures.installConfig(detected.type));
577
- printManualInstructions(detected.type);
578
- }
1529
+ console.log();
1530
+ if (dryRun) {
1531
+ console.log(chalk.yellow("Run without --dry-run to apply changes."));
1532
+ }
1533
+ else {
1534
+ console.log(chalk.green("Setup complete."));
1535
+ console.log(chalk.gray("Restart your agent session for changes to take effect."));
1536
+ }
1537
+ });
579
1538
  }
580
1539
  catch (err) {
581
1540
  error(errors.failures.setupFailed, err);