@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
@@ -8,10 +8,14 @@
8
8
  * - All kspec read/write operations target .kspec/
9
9
  * - Changes auto-commit to shadow branch
10
10
  */
11
- import * as fs from 'node:fs/promises';
12
- import * as path from 'node:path';
13
- import { execSync, exec } from 'node:child_process';
14
- import { promisify } from 'node:util';
11
+ import { exec, execSync } from "node:child_process";
12
+ import * as fs from "node:fs/promises";
13
+ import * as path from "node:path";
14
+ import { promisify } from "node:util";
15
+ import { fileURLToPath } from "node:url";
16
+ import { isBatchMode } from "../cli/batch-context.js";
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
15
19
  const execAsync = promisify(exec);
16
20
  // Import getVerboseMode for checking CLI --debug-shadow flag
17
21
  // We use a getter function to avoid issues with circular dependencies
@@ -29,17 +33,31 @@ export class ShadowError extends Error {
29
33
  super(message);
30
34
  this.code = code;
31
35
  this.suggestion = suggestion;
32
- this.name = 'ShadowError';
36
+ this.name = "ShadowError";
33
37
  }
34
38
  }
35
39
  /**
36
40
  * Default shadow branch name
37
41
  */
38
- export const SHADOW_BRANCH_NAME = 'kspec-meta';
42
+ export const SHADOW_BRANCH_NAME = "kspec-meta";
39
43
  /**
40
44
  * Default shadow worktree directory
41
45
  */
42
- export const SHADOW_WORKTREE_DIR = '.kspec';
46
+ export const SHADOW_WORKTREE_DIR = ".kspec";
47
+ /**
48
+ * Get effective branch name from options or default.
49
+ * AC: @config-shadow ac-7 — backward compat when called without config
50
+ */
51
+ function getBranchName(options) {
52
+ return options?.branchName ?? SHADOW_BRANCH_NAME;
53
+ }
54
+ /**
55
+ * Get effective directory name from options or default.
56
+ * AC: @config-shadow ac-7 — backward compat when called without config
57
+ */
58
+ function getDirectoryName(options) {
59
+ return options?.directory ?? SHADOW_WORKTREE_DIR;
60
+ }
43
61
  /**
44
62
  * Check if debug mode is enabled.
45
63
  * Debug mode can be enabled via:
@@ -49,7 +67,7 @@ export const SHADOW_WORKTREE_DIR = '.kspec';
49
67
  * When enabled, shadow branch operations output detailed information.
50
68
  */
51
69
  export function isDebugMode(verboseFlag) {
52
- if (process.env.KSPEC_DEBUG === '1') {
70
+ if (process.env.KSPEC_DEBUG === "1") {
53
71
  return true;
54
72
  }
55
73
  if (verboseFlag === true) {
@@ -66,10 +84,10 @@ export function isDebugMode(verboseFlag) {
66
84
  */
67
85
  export async function isGitRepo(dir) {
68
86
  try {
69
- execSync('git rev-parse --git-dir', {
87
+ execSync("git rev-parse --git-dir", {
70
88
  cwd: dir,
71
- stdio: ['pipe', 'pipe', 'pipe'],
72
- encoding: 'utf-8',
89
+ stdio: ["pipe", "pipe", "pipe"],
90
+ encoding: "utf-8",
73
91
  });
74
92
  return true;
75
93
  }
@@ -82,10 +100,10 @@ export async function isGitRepo(dir) {
82
100
  */
83
101
  export function getGitRoot(dir) {
84
102
  try {
85
- const result = execSync('git rev-parse --show-toplevel', {
103
+ const result = execSync("git rev-parse --show-toplevel", {
86
104
  cwd: dir,
87
- stdio: ['pipe', 'pipe', 'pipe'],
88
- encoding: 'utf-8',
105
+ stdio: ["pipe", "pipe", "pipe"],
106
+ encoding: "utf-8",
89
107
  }).trim();
90
108
  return result;
91
109
  }
@@ -100,7 +118,7 @@ export async function branchExists(dir, branchName) {
100
118
  try {
101
119
  execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, {
102
120
  cwd: dir,
103
- stdio: ['pipe', 'pipe', 'pipe'],
121
+ stdio: ["pipe", "pipe", "pipe"],
104
122
  });
105
123
  return true;
106
124
  }
@@ -114,12 +132,12 @@ export async function branchExists(dir, branchName) {
114
132
  export async function isValidWorktree(worktreeDir) {
115
133
  try {
116
134
  // Check if .git file exists (worktrees have a .git file, not directory)
117
- const gitPath = path.join(worktreeDir, '.git');
135
+ const gitPath = path.join(worktreeDir, ".git");
118
136
  const stat = await fs.stat(gitPath);
119
137
  if (stat.isFile()) {
120
138
  // Read the .git file to verify it points to a worktree
121
- const content = await fs.readFile(gitPath, 'utf-8');
122
- return content.trim().startsWith('gitdir:');
139
+ const content = await fs.readFile(gitPath, "utf-8");
140
+ return content.trim().startsWith("gitdir:");
123
141
  }
124
142
  return false;
125
143
  }
@@ -131,35 +149,66 @@ export async function isValidWorktree(worktreeDir) {
131
149
  * Detect if running from inside the shadow worktree directory.
132
150
  * Returns the main project root if detected, null otherwise.
133
151
  *
152
+ * AC: @config-shadow ac-8 — detects custom worktree directories using git metadata
153
+ *
134
154
  * Detection logic:
135
155
  * 1. Check if .git is a file (worktrees have .git files, not directories)
136
156
  * 2. Read the gitdir reference from the .git file
137
- * 3. Check if it points to a worktree for .kspec (pattern: <project>/.git/worktrees/-kspec)
157
+ * 3. Check if it points to a worktree for kspec (pattern: <project>/.git/worktrees/...)
158
+ *
159
+ * For custom directories, we detect ANY worktree that:
160
+ * - Has a kspec manifest in it (indicating it's a kspec shadow worktree)
161
+ * - Or has a worktree name containing "kspec" or the configured directory name
162
+ *
163
+ * @param cwd Current working directory
164
+ * @param configuredDirectory Optional configured directory name for matching
138
165
  */
139
- export async function detectRunningFromShadowWorktree(cwd) {
166
+ export async function detectRunningFromShadowWorktree(cwd, configuredDirectory) {
140
167
  try {
141
- const gitPath = path.join(cwd, '.git');
168
+ const gitPath = path.join(cwd, ".git");
142
169
  const stat = await fs.stat(gitPath);
143
170
  // Worktrees have a .git file, not directory
144
171
  if (!stat.isFile()) {
145
172
  return null;
146
173
  }
147
- const content = await fs.readFile(gitPath, 'utf-8');
174
+ const content = await fs.readFile(gitPath, "utf-8");
148
175
  const match = content.trim().match(/^gitdir:\s*(.+)$/);
149
176
  if (!match) {
150
177
  return null;
151
178
  }
152
179
  const gitdir = match[1];
153
- // Check if this is a shadow worktree (pattern: <project>/.git/worktrees/-kspec)
154
- if (gitdir.includes('.git/worktrees/')) {
180
+ // Check if this is a worktree (pattern: <project>/.git/worktrees/<name>)
181
+ if (gitdir.includes(".git/worktrees/")) {
155
182
  const worktreesMatch = gitdir.match(/^(.+)\/\.git\/worktrees\//);
156
183
  if (worktreesMatch) {
157
184
  const mainProjectRoot = worktreesMatch[1];
158
185
  const cwdBase = path.basename(cwd);
159
186
  const worktreeName = path.basename(gitdir);
160
- if (cwdBase === SHADOW_WORKTREE_DIR || worktreeName.includes('kspec')) {
187
+ // AC: ac-8 check multiple patterns for shadow worktree detection
188
+ const directoryToCheck = configuredDirectory || SHADOW_WORKTREE_DIR;
189
+ // Check if directory name matches default, configured, or worktree contains "kspec"
190
+ if (cwdBase === SHADOW_WORKTREE_DIR ||
191
+ cwdBase === directoryToCheck ||
192
+ worktreeName.includes("kspec")) {
161
193
  return mainProjectRoot;
162
194
  }
195
+ // Additional check: see if this directory has a kspec manifest
196
+ // This catches custom directories that don't have "kspec" in the name
197
+ try {
198
+ const files = await fs.readdir(cwd);
199
+ const hasKspecManifest = files.some((f) => (f.endsWith(".yaml") || f.endsWith(".yml")) &&
200
+ !f.includes(".tasks.") &&
201
+ !f.includes(".inbox."));
202
+ // Check for modules directory or tasks file as additional signals
203
+ const hasModules = files.includes("modules");
204
+ const hasTasksFile = files.some((f) => f.includes(".tasks."));
205
+ if (hasKspecManifest && (hasModules || hasTasksFile)) {
206
+ return mainProjectRoot;
207
+ }
208
+ }
209
+ catch {
210
+ // Ignore read errors
211
+ }
163
212
  }
164
213
  }
165
214
  return null;
@@ -170,14 +219,22 @@ export async function detectRunningFromShadowWorktree(cwd) {
170
219
  }
171
220
  /**
172
221
  * Detect shadow branch configuration from a directory.
173
- * Returns shadow config if .kspec/ exists and is valid.
222
+ * Returns shadow config if worktree directory exists and is valid.
223
+ *
224
+ * AC: @config-shadow ac-1 ac-2 — uses configured branch/directory names
225
+ * AC: @config-shadow ac-7 — defaults to constants when options not provided
226
+ *
227
+ * @param startDir Directory to start detection from
228
+ * @param options Optional shadow configuration (branch name, directory)
174
229
  */
175
- export async function detectShadow(startDir) {
230
+ export async function detectShadow(startDir, options) {
176
231
  const gitRoot = getGitRoot(startDir);
177
232
  if (!gitRoot) {
178
233
  return null;
179
234
  }
180
- const worktreeDir = path.join(gitRoot, SHADOW_WORKTREE_DIR);
235
+ const directoryName = getDirectoryName(options);
236
+ const branchName = getBranchName(options);
237
+ const worktreeDir = path.join(gitRoot, directoryName);
181
238
  try {
182
239
  await fs.access(worktreeDir);
183
240
  // Verify it's a valid worktree
@@ -185,7 +242,7 @@ export async function detectShadow(startDir) {
185
242
  return {
186
243
  enabled: true,
187
244
  worktreeDir,
188
- branchName: SHADOW_BRANCH_NAME,
245
+ branchName,
189
246
  projectRoot: gitRoot,
190
247
  };
191
248
  }
@@ -193,29 +250,38 @@ export async function detectShadow(startDir) {
193
250
  return null;
194
251
  }
195
252
  catch {
196
- // .kspec/ doesn't exist
253
+ // Worktree directory doesn't exist
197
254
  return null;
198
255
  }
199
256
  }
200
257
  /**
201
- * Get detailed shadow branch status
258
+ * Get detailed shadow branch status.
259
+ *
260
+ * AC: @config-shadow ac-1 ac-2 — uses configured branch/directory names
261
+ * AC: @config-shadow ac-7 — defaults to constants when options not provided
262
+ *
263
+ * @param projectRoot Git repository root
264
+ * @param options Optional shadow configuration
202
265
  */
203
- export async function getShadowStatus(projectRoot) {
204
- const worktreeDir = path.join(projectRoot, SHADOW_WORKTREE_DIR);
266
+ export async function getShadowStatus(projectRoot, options) {
267
+ const directoryName = getDirectoryName(options);
268
+ const branchName = getBranchName(options);
269
+ const worktreeDir = path.join(projectRoot, directoryName);
205
270
  const status = {
206
271
  exists: false,
207
272
  healthy: false,
208
273
  branchExists: false,
209
274
  worktreeExists: false,
210
275
  worktreeLinked: false,
276
+ artifactsDirExists: false,
211
277
  };
212
278
  // Check if we're in a git repo
213
279
  if (!(await isGitRepo(projectRoot))) {
214
- status.error = 'Not a git repository';
280
+ status.error = "Not a git repository";
215
281
  return status;
216
282
  }
217
283
  // Check if branch exists
218
- status.branchExists = await branchExists(projectRoot, SHADOW_BRANCH_NAME);
284
+ status.branchExists = await branchExists(projectRoot, branchName);
219
285
  // Check if worktree directory exists
220
286
  try {
221
287
  await fs.access(worktreeDir);
@@ -227,37 +293,112 @@ export async function getShadowStatus(projectRoot) {
227
293
  // Check if worktree is properly linked
228
294
  if (status.worktreeExists) {
229
295
  status.worktreeLinked = await isValidWorktree(worktreeDir);
296
+ // AC: @artifacts-directory ac-doctor-checks
297
+ try {
298
+ await fs.access(path.join(worktreeDir, "artifacts"));
299
+ status.artifactsDirExists = true;
300
+ }
301
+ catch {
302
+ status.artifactsDirExists = false;
303
+ }
230
304
  }
231
305
  // Determine overall status
232
306
  status.exists = status.branchExists || status.worktreeExists;
233
- status.healthy = status.branchExists && status.worktreeExists && status.worktreeLinked;
307
+ status.healthy =
308
+ status.branchExists && status.worktreeExists && status.worktreeLinked;
234
309
  if (!status.healthy && status.exists) {
235
310
  if (!status.branchExists) {
236
- status.error = 'Shadow branch missing but worktree exists';
311
+ status.error = "Shadow branch missing but worktree exists";
237
312
  }
238
313
  else if (!status.worktreeExists) {
239
- status.error = 'Shadow branch exists but worktree missing';
314
+ status.error = "Shadow branch exists but worktree missing";
240
315
  }
241
316
  else if (!status.worktreeLinked) {
242
- status.error = 'Worktree exists but not properly linked';
317
+ status.error = "Worktree exists but not properly linked";
243
318
  }
244
319
  }
245
320
  return status;
246
321
  }
322
+ /**
323
+ * Check if detected shadow branch settings match the configuration.
324
+ *
325
+ * AC: @config-shadow ac-9 — detect mismatch and guide user to migrate
326
+ *
327
+ * This function detects when:
328
+ * - A shadow branch exists with default settings (kspec-meta, .kspec)
329
+ * - But config specifies different settings
330
+ *
331
+ * @param projectRoot Git repository root
332
+ * @param configuredBranch Configured branch name
333
+ * @param configuredDirectory Configured directory name
334
+ */
335
+ export async function checkConfigMismatch(projectRoot, configuredBranch, configuredDirectory) {
336
+ const result = { hasMismatch: false };
337
+ // First check if default shadow exists
338
+ const defaultStatus = await getShadowStatus(projectRoot, {
339
+ branchName: SHADOW_BRANCH_NAME,
340
+ directory: SHADOW_WORKTREE_DIR,
341
+ });
342
+ if (!defaultStatus.healthy) {
343
+ // No existing shadow with defaults - no mismatch possible
344
+ return result;
345
+ }
346
+ // Check if configured settings differ from defaults
347
+ const branchDiffers = configuredBranch !== SHADOW_BRANCH_NAME;
348
+ const directoryDiffers = configuredDirectory !== SHADOW_WORKTREE_DIR;
349
+ if (!branchDiffers && !directoryDiffers) {
350
+ // Config matches defaults - no mismatch
351
+ return result;
352
+ }
353
+ // There's a mismatch - existing shadow uses defaults but config specifies different values
354
+ result.hasMismatch = true;
355
+ if (branchDiffers) {
356
+ result.branchMismatch = {
357
+ detected: SHADOW_BRANCH_NAME,
358
+ configured: configuredBranch,
359
+ };
360
+ }
361
+ if (directoryDiffers) {
362
+ result.directoryMismatch = {
363
+ detected: SHADOW_WORKTREE_DIR,
364
+ configured: configuredDirectory,
365
+ };
366
+ }
367
+ // Build guidance message
368
+ const parts = [];
369
+ if (result.branchMismatch) {
370
+ parts.push(`branch "${SHADOW_BRANCH_NAME}" (config wants "${configuredBranch}")`);
371
+ }
372
+ if (result.directoryMismatch) {
373
+ parts.push(`directory "${SHADOW_WORKTREE_DIR}" (config wants "${configuredDirectory}")`);
374
+ }
375
+ result.guidance = [
376
+ `Shadow branch exists with ${parts.join(" and ")}.`,
377
+ "",
378
+ "To migrate to configured settings:",
379
+ " 1. Export your specs: kspec export --all > backup.yaml",
380
+ " 2. Remove existing shadow: rm -rf .kspec && git branch -D kspec-meta",
381
+ " 3. Re-initialize: kspec init",
382
+ " 4. Import specs: kspec import backup.yaml",
383
+ "",
384
+ "Or update kspec.config.yaml to match existing settings.",
385
+ ].join("\n");
386
+ return result;
387
+ }
247
388
  /**
248
389
  * Create an appropriate ShadowError based on status
249
390
  */
250
391
  export function createShadowError(status) {
251
392
  if (!status.branchExists && !status.worktreeExists) {
252
- return new ShadowError('Shadow branch not initialized', 'NOT_INITIALIZED', 'Run `kspec init` to create shadow branch and worktree.');
393
+ return new ShadowError("Shadow branch not initialized", "NOT_INITIALIZED", "Run `kspec init` to create shadow branch and worktree.");
253
394
  }
254
395
  if (status.branchExists && !status.worktreeExists) {
255
- return new ShadowError('.kspec/ directory missing', 'DIRECTORY_MISSING', 'Run `kspec shadow repair` to recreate the worktree.');
396
+ return new ShadowError(".kspec/ directory missing", "DIRECTORY_MISSING", "Run `kspec shadow repair` to recreate the worktree.");
256
397
  }
257
398
  if (status.worktreeExists && !status.worktreeLinked) {
258
- return new ShadowError('Worktree disconnected from git', 'WORKTREE_DISCONNECTED', 'Run `kspec shadow repair` to fix the worktree link.');
399
+ return new ShadowError("Worktree disconnected from git", "WORKTREE_DISCONNECTED", "Run `kspec shadow repair` to fix the worktree link.");
259
400
  }
260
- return new ShadowError(status.error || 'Unknown shadow branch error', 'GIT_ERROR', 'Check git status and try `kspec shadow repair`.');
401
+ return new ShadowError(status.error || "Unknown shadow branch error", "GIT_ERROR", "Check git status and try `kspec shadow repair`.");
261
402
  }
262
403
  /**
263
404
  * Auto-commit changes to shadow branch.
@@ -275,18 +416,18 @@ export async function shadowAutoCommit(worktreeDir, message, verbose) {
275
416
  console.error(`[DEBUG] Shadow auto-commit: git add -A (cwd: ${worktreeDir})`);
276
417
  }
277
418
  // Stage all changes
278
- execSync('git add -A', {
419
+ execSync("git add -A", {
279
420
  cwd: worktreeDir,
280
- stdio: ['pipe', 'pipe', 'pipe'],
421
+ stdio: ["pipe", "pipe", "pipe"],
281
422
  });
282
423
  // Check if there are staged changes
283
424
  try {
284
425
  if (debug) {
285
426
  console.error(`[DEBUG] Shadow auto-commit: git diff --cached --quiet`);
286
427
  }
287
- execSync('git diff --cached --quiet', {
428
+ execSync("git diff --cached --quiet", {
288
429
  cwd: worktreeDir,
289
- stdio: ['pipe', 'pipe', 'pipe'],
430
+ stdio: ["pipe", "pipe", "pipe"],
290
431
  });
291
432
  // No error = no changes
292
433
  if (debug) {
@@ -304,8 +445,8 @@ export async function shadowAutoCommit(worktreeDir, message, verbose) {
304
445
  // Set KSPEC_SHADOW_COMMIT=1 to signal authorized commit to git hooks
305
446
  execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, {
306
447
  cwd: worktreeDir,
307
- stdio: ['pipe', 'pipe', 'pipe'],
308
- env: { ...process.env, KSPEC_SHADOW_COMMIT: '1' },
448
+ stdio: ["pipe", "pipe", "pipe"],
449
+ env: { ...process.env, KSPEC_SHADOW_COMMIT: "1" },
309
450
  });
310
451
  if (debug) {
311
452
  console.error(`[DEBUG] Shadow auto-commit: Success`);
@@ -315,7 +456,7 @@ export async function shadowAutoCommit(worktreeDir, message, verbose) {
315
456
  catch (error) {
316
457
  // AC: Only log error if debug mode enabled
317
458
  if (debug) {
318
- console.error('Shadow auto-commit failed:', error);
459
+ console.error("Shadow auto-commit failed:", error);
319
460
  }
320
461
  return false;
321
462
  }
@@ -326,36 +467,36 @@ export async function shadowAutoCommit(worktreeDir, message, verbose) {
326
467
  export function generateCommitMessage(operation, ref, detail) {
327
468
  const parts = [];
328
469
  switch (operation) {
329
- case 'task-start':
470
+ case "task-start":
330
471
  parts.push(`Start @${ref}`);
331
472
  break;
332
- case 'task-complete':
473
+ case "task-complete":
333
474
  parts.push(`Complete @${ref}`);
334
475
  if (detail)
335
476
  parts.push(`: ${detail}`);
336
477
  break;
337
- case 'task-note':
478
+ case "task-note":
338
479
  parts.push(`Note on @${ref}`);
339
480
  break;
340
- case 'task-add':
481
+ case "task-add":
341
482
  parts.push(`Add task: ${detail || ref}`);
342
483
  break;
343
- case 'inbox-add':
344
- parts.push(`Inbox: ${detail?.slice(0, 50)}${(detail?.length || 0) > 50 ? '...' : ''}`);
484
+ case "inbox-add":
485
+ parts.push(`Inbox: ${detail?.slice(0, 50)}${(detail?.length || 0) > 50 ? "..." : ""}`);
345
486
  break;
346
- case 'inbox-promote':
487
+ case "inbox-promote":
347
488
  parts.push(`Promote to @${ref}`);
348
489
  break;
349
- case 'item-add':
490
+ case "item-add":
350
491
  parts.push(`Add @${ref}`);
351
492
  break;
352
- case 'item-set':
493
+ case "item-set":
353
494
  parts.push(`Update @${ref}`);
354
495
  break;
355
- case 'item-delete':
496
+ case "item-delete":
356
497
  parts.push(`Delete @${ref}`);
357
498
  break;
358
- case 'derive':
499
+ case "derive":
359
500
  parts.push(`Derive from @${ref}`);
360
501
  break;
361
502
  default:
@@ -363,11 +504,13 @@ export function generateCommitMessage(operation, ref, detail) {
363
504
  if (ref)
364
505
  parts.push(` @${ref}`);
365
506
  }
366
- return parts.join('');
507
+ return parts.join("");
367
508
  }
368
509
  /**
369
510
  * Resolve a path relative to shadow worktree if enabled.
370
511
  * Falls back to original path if shadow is not enabled.
512
+ *
513
+ * Uses the worktreeDir from shadowConfig for custom directory support.
371
514
  */
372
515
  export function resolveShadowPath(originalPath, shadowConfig, projectRoot) {
373
516
  if (!shadowConfig?.enabled) {
@@ -375,17 +518,21 @@ export function resolveShadowPath(originalPath, shadowConfig, projectRoot) {
375
518
  }
376
519
  // If the path is within the project root, rewrite to shadow worktree
377
520
  const relativePath = path.relative(projectRoot, originalPath);
378
- // Skip if path is outside project or already in .kspec
379
- if (relativePath.startsWith('..') || relativePath.startsWith(SHADOW_WORKTREE_DIR)) {
521
+ // Get the directory name from the worktree path (supports custom directories)
522
+ const worktreeDirName = path.basename(shadowConfig.worktreeDir);
523
+ // Skip if path is outside project or already in shadow worktree
524
+ if (relativePath.startsWith("..") ||
525
+ relativePath.startsWith(worktreeDirName)) {
380
526
  return originalPath;
381
527
  }
382
- // Handle spec/ -> .kspec/ mapping
383
- if (relativePath.startsWith('spec/') || relativePath.startsWith('spec\\')) {
528
+ // Handle spec/ -> shadow worktree mapping
529
+ if (relativePath.startsWith("spec/") || relativePath.startsWith("spec\\")) {
384
530
  const specRelative = relativePath.slice(5); // Remove 'spec/'
385
531
  return path.join(shadowConfig.worktreeDir, specRelative);
386
532
  }
387
- // For task/inbox files at root, move to .kspec
388
- if (relativePath.endsWith('.tasks.yaml') || relativePath.endsWith('.inbox.yaml')) {
533
+ // For task/inbox files at root, move to shadow worktree
534
+ if (relativePath.endsWith(".tasks.yaml") ||
535
+ relativePath.endsWith(".inbox.yaml")) {
389
536
  return path.join(shadowConfig.worktreeDir, relativePath);
390
537
  }
391
538
  return originalPath;
@@ -402,12 +549,16 @@ export function resolveShadowPath(originalPath, shadowConfig, projectRoot) {
402
549
  * @returns true if committed, false if shadow not enabled or nothing to commit
403
550
  */
404
551
  export async function commitIfShadow(shadowConfig, operation, ref, detail, verbose) {
552
+ // Suppress auto-commits during atomic batch execution
553
+ if (isBatchMode()) {
554
+ return false;
555
+ }
405
556
  if (!shadowConfig?.enabled) {
406
557
  return false;
407
558
  }
408
559
  const message = generateCommitMessage(operation, ref, detail);
409
560
  const committed = await shadowAutoCommit(shadowConfig.worktreeDir, message, verbose);
410
- // AC-1: Fire-and-forget push after each commit
561
+ // AC: @shadow-sync ac-1 - Fire-and-forget push after each commit
411
562
  if (committed) {
412
563
  shadowPushAsync(shadowConfig.worktreeDir, verbose);
413
564
  }
@@ -438,7 +589,7 @@ export function formatShadowError(error) {
438
589
  /**
439
590
  * Check if a remote exists (default: origin)
440
591
  */
441
- export async function hasRemote(projectRoot, remoteName = 'origin') {
592
+ export async function hasRemote(projectRoot, remoteName = "origin") {
442
593
  try {
443
594
  const { stdout } = await execAsync(`git remote get-url ${remoteName}`, {
444
595
  cwd: projectRoot,
@@ -452,11 +603,11 @@ export async function hasRemote(projectRoot, remoteName = 'origin') {
452
603
  /**
453
604
  * Check if a branch exists on a remote
454
605
  */
455
- export async function remoteBranchExists(projectRoot, branchName, remoteName = 'origin') {
606
+ export async function remoteBranchExists(projectRoot, branchName, remoteName = "origin") {
456
607
  try {
457
608
  execSync(`git show-ref --verify --quiet refs/remotes/${remoteName}/${branchName}`, {
458
609
  cwd: projectRoot,
459
- stdio: ['pipe', 'pipe', 'pipe'],
610
+ stdio: ["pipe", "pipe", "pipe"],
460
611
  });
461
612
  return true;
462
613
  }
@@ -468,7 +619,7 @@ export async function remoteBranchExists(projectRoot, branchName, remoteName = '
468
619
  * Fetch from remote to ensure refs are up to date.
469
620
  * Returns true if fetch succeeded, false otherwise.
470
621
  */
471
- export async function fetchRemote(projectRoot, remoteName = 'origin') {
622
+ export async function fetchRemote(projectRoot, remoteName = "origin") {
472
623
  try {
473
624
  await execAsync(`git fetch ${remoteName}`, {
474
625
  cwd: projectRoot,
@@ -482,10 +633,18 @@ export async function fetchRemote(projectRoot, remoteName = 'origin') {
482
633
  /**
483
634
  * Push shadow branch to remote with tracking.
484
635
  * Returns true if push succeeded, false otherwise.
636
+ *
637
+ * AC: @config-shadow ac-3 — uses configured remote name
638
+ * AC: @config-shadow ac-7 — defaults to origin when not provided
639
+ *
640
+ * @param worktreeDir Path to shadow worktree
641
+ * @param remoteName Remote name (default: origin)
642
+ * @param options Optional shadow configuration
485
643
  */
486
- export async function pushShadowBranch(worktreeDir, remoteName = 'origin') {
644
+ export async function pushShadowBranch(worktreeDir, remoteName = "origin", options) {
645
+ const branchName = getBranchName(options);
487
646
  try {
488
- await execAsync(`git push -u ${remoteName} ${SHADOW_BRANCH_NAME}`, {
647
+ await execAsync(`git push -u ${remoteName} ${branchName}`, {
489
648
  cwd: worktreeDir,
490
649
  });
491
650
  return true;
@@ -497,10 +656,16 @@ export async function pushShadowBranch(worktreeDir, remoteName = 'origin') {
497
656
  /**
498
657
  * Check if shadow branch has remote tracking configured.
499
658
  * AC-4: Used to determine whether sync should be attempted.
659
+ *
660
+ * AC: @config-shadow ac-7 — backward compat when called without config
661
+ *
662
+ * @param worktreeDir Path to shadow worktree
663
+ * @param options Optional shadow configuration
500
664
  */
501
- export async function hasRemoteTracking(worktreeDir) {
665
+ export async function hasRemoteTracking(worktreeDir, options) {
666
+ const branchName = getBranchName(options);
502
667
  try {
503
- const { stdout } = await execAsync(`git config branch.${SHADOW_BRANCH_NAME}.remote`, { cwd: worktreeDir });
668
+ const { stdout } = await execAsync(`git config branch.${branchName}.remote`, { cwd: worktreeDir });
504
669
  return stdout.trim().length > 0;
505
670
  }
506
671
  catch {
@@ -512,27 +677,80 @@ export async function hasRemoteTracking(worktreeDir) {
512
677
  * AC-8: If shadow has no tracking but main branch has origin remote,
513
678
  * automatically configure tracking to origin/kspec-meta.
514
679
  *
515
- * @param worktreeDir Path to .kspec/ worktree
680
+ * AC: @config-shadow ac-3 ac-4 ac-5 handles different remote types
681
+ * AC: @config-shadow ac-6 — error with guidance if named remote doesn't exist
682
+ * AC: @config-shadow ac-7 — backward compat when called without config
683
+ *
684
+ * @param worktreeDir Path to shadow worktree
516
685
  * @param projectRoot Git repository root
517
- * @returns true if tracking is now configured (was already or just set up)
686
+ * @param options Optional shadow configuration
687
+ * @returns Result with success status and error details if applicable
518
688
  */
519
- export async function ensureRemoteTracking(worktreeDir, projectRoot) {
689
+ export async function ensureRemoteTracking(worktreeDir, projectRoot, options) {
690
+ const branchName = getBranchName(options);
520
691
  // Check if already has tracking
521
- if (await hasRemoteTracking(worktreeDir)) {
522
- return true;
692
+ if (await hasRemoteTracking(worktreeDir, options)) {
693
+ return { success: true };
694
+ }
695
+ // Determine remote name to use
696
+ let remoteName = "origin";
697
+ if (options?.remote) {
698
+ const remoteType = options.remoteType ?? "named";
699
+ if (remoteType === "named") {
700
+ // AC: ac-3 — use the named remote directly
701
+ remoteName = options.remote;
702
+ // AC: ac-6 — verify the named remote exists with guidance
703
+ if (!(await hasRemote(projectRoot, remoteName))) {
704
+ // Named remote doesn't exist - provide helpful guidance
705
+ return {
706
+ success: false,
707
+ missingRemote: remoteName,
708
+ guidance: `Remote '${remoteName}' does not exist. To fix this:\n` +
709
+ ` 1. Add the remote: git remote add ${remoteName} <url>\n` +
710
+ ` 2. Or update kspec.config.yaml to use an existing remote\n` +
711
+ ` 3. Or remove shadow.remote to use the default 'origin' remote`,
712
+ };
713
+ }
714
+ }
715
+ else if (remoteType === "path" || remoteType === "url") {
716
+ // AC: ac-4 ac-5 — add a git remote for path/URL if not already present
717
+ const specRemoteName = "kspec-specs";
718
+ const hasSpecsRemote = await hasRemote(projectRoot, specRemoteName);
719
+ if (!hasSpecsRemote) {
720
+ try {
721
+ // Expand tilde for paths if needed
722
+ let remoteTarget = options.remote;
723
+ if (remoteType === "path" && remoteTarget.startsWith("~")) {
724
+ remoteTarget = remoteTarget.replace(/^~/, process.env.HOME || process.env.USERPROFILE || "~");
725
+ }
726
+ await execAsync(`git remote add ${specRemoteName} "${remoteTarget}"`, {
727
+ cwd: projectRoot,
728
+ });
729
+ }
730
+ catch {
731
+ // Remote add failed - may already exist with different URL
732
+ return { success: false };
733
+ }
734
+ }
735
+ remoteName = specRemoteName;
736
+ }
523
737
  }
524
- // Check if main branch has origin remote
525
- if (!(await hasRemote(projectRoot))) {
526
- return false;
738
+ else {
739
+ // No remote configured - check if main branch has origin
740
+ if (!(await hasRemote(projectRoot))) {
741
+ return { success: false };
742
+ }
527
743
  }
528
- // Set up tracking for shadow branch to origin/kspec-meta
744
+ // Set up tracking for shadow branch
529
745
  try {
530
- await execAsync(`git config branch.${SHADOW_BRANCH_NAME}.remote origin`, { cwd: worktreeDir });
531
- await execAsync(`git config branch.${SHADOW_BRANCH_NAME}.merge refs/heads/${SHADOW_BRANCH_NAME}`, { cwd: worktreeDir });
532
- return true;
746
+ await execAsync(`git config branch.${branchName}.remote ${remoteName}`, {
747
+ cwd: worktreeDir,
748
+ });
749
+ await execAsync(`git config branch.${branchName}.merge refs/heads/${branchName}`, { cwd: worktreeDir });
750
+ return { success: true };
533
751
  }
534
752
  catch {
535
- return false;
753
+ return { success: false };
536
754
  }
537
755
  }
538
756
  /**
@@ -541,36 +759,46 @@ export async function ensureRemoteTracking(worktreeDir, projectRoot) {
541
759
  * AC-8: Automatically sets up tracking if main branch has remote.
542
760
  * Silently ignores errors - the local commit succeeded regardless.
543
761
  *
544
- * @param worktreeDir Path to .kspec/ worktree
762
+ * AC: @config-shadow ac-7 backward compat when called without config
763
+ *
764
+ * @param worktreeDir Path to shadow worktree
545
765
  * @param verbose Enable debug output
766
+ * @param options Optional shadow configuration
546
767
  */
547
- export async function shadowPushAsync(worktreeDir, verbose) {
768
+ export async function shadowPushAsync(worktreeDir, verbose, options) {
548
769
  const debug = isDebugMode(verbose);
549
- // AC-8: Auto-configure tracking if main has remote but shadow doesn't
770
+ // AC: @shadow-sync ac-8 - Auto-configure tracking if main has remote but shadow doesn't
550
771
  const projectRoot = path.dirname(worktreeDir);
551
- await ensureRemoteTracking(worktreeDir, projectRoot);
772
+ const trackingResult = await ensureRemoteTracking(worktreeDir, projectRoot, options);
773
+ // AC: @config-shadow ac-6 — log guidance if named remote doesn't exist
774
+ if (!trackingResult.success && trackingResult.missingRemote) {
775
+ if (debug) {
776
+ console.error(`[DEBUG] Shadow push: ${trackingResult.guidance}`);
777
+ }
778
+ return;
779
+ }
552
780
  // Check if tracking is configured before attempting push
553
- if (!(await hasRemoteTracking(worktreeDir))) {
781
+ if (!(await hasRemoteTracking(worktreeDir, options))) {
554
782
  if (debug) {
555
- console.error('[DEBUG] Shadow push: No remote tracking configured, skipping');
783
+ console.error("[DEBUG] Shadow push: No remote tracking configured, skipping");
556
784
  }
557
- return; // AC-4: silently skip if no tracking
785
+ return; // AC: @shadow-sync ac-4 - silently skip if no tracking
558
786
  }
559
787
  try {
560
788
  if (debug) {
561
789
  console.error(`[DEBUG] Shadow push: git push (cwd: ${worktreeDir})`);
562
790
  }
563
791
  // Don't await - fire and forget
564
- execAsync('git push', { cwd: worktreeDir }).catch((err) => {
792
+ execAsync("git push", { cwd: worktreeDir }).catch((err) => {
565
793
  if (debug) {
566
- console.error('[DEBUG] Shadow push failed:', err);
794
+ console.error("[DEBUG] Shadow push failed:", err);
567
795
  }
568
796
  // Silently ignore push failures - local state is correct
569
797
  });
570
798
  }
571
799
  catch (err) {
572
800
  if (debug) {
573
- console.error('[DEBUG] Shadow push error:', err);
801
+ console.error("[DEBUG] Shadow push error:", err);
574
802
  }
575
803
  }
576
804
  }
@@ -580,26 +808,37 @@ export async function shadowPushAsync(worktreeDir, verbose) {
580
808
  * AC-6: Uses --ff-only first, falls back to --rebase.
581
809
  * AC-3: On conflict, returns failure with suggestion.
582
810
  * AC-8: Automatically sets up tracking if main branch has remote.
811
+ *
812
+ * AC: @config-shadow ac-7 — backward compat when called without config
813
+ *
814
+ * @param worktreeDir Path to shadow worktree
815
+ * @param options Optional shadow configuration
583
816
  */
584
- export async function shadowPull(worktreeDir) {
817
+ export async function shadowPull(worktreeDir, options) {
818
+ const branchName = getBranchName(options);
585
819
  const result = {
586
820
  success: false,
587
821
  pulled: false,
588
822
  pushed: false,
589
823
  hadConflict: false,
590
824
  };
591
- // AC-8: Auto-configure tracking if main has remote but shadow doesn't
825
+ // AC: @shadow-sync ac-8 - Auto-configure tracking if main has remote but shadow doesn't
592
826
  const projectRoot = path.dirname(worktreeDir);
593
- await ensureRemoteTracking(worktreeDir, projectRoot);
594
- // AC-4: Skip if no remote tracking
595
- if (!(await hasRemoteTracking(worktreeDir))) {
827
+ const trackingResult = await ensureRemoteTracking(worktreeDir, projectRoot, options);
828
+ // AC: @config-shadow ac-6 — error with guidance if named remote doesn't exist
829
+ if (!trackingResult.success && trackingResult.missingRemote) {
830
+ result.error = trackingResult.guidance;
831
+ return result;
832
+ }
833
+ // AC: @shadow-sync ac-4 - Skip if no remote tracking
834
+ if (!(await hasRemoteTracking(worktreeDir, options))) {
596
835
  result.success = true;
597
836
  return result;
598
837
  }
599
838
  // Check if remote branch exists before attempting pull
600
839
  // Fetch first to ensure refs are up to date
601
840
  await fetchRemote(projectRoot);
602
- const remoteHasBranch = await remoteBranchExists(projectRoot, SHADOW_BRANCH_NAME);
841
+ const remoteHasBranch = await remoteBranchExists(projectRoot, branchName);
603
842
  if (!remoteHasBranch) {
604
843
  // Remote branch doesn't exist yet - nothing to pull, but success
605
844
  result.success = true;
@@ -607,7 +846,7 @@ export async function shadowPull(worktreeDir) {
607
846
  }
608
847
  try {
609
848
  // Try fast-forward only first (cleanest)
610
- await execAsync('git pull --ff-only', { cwd: worktreeDir });
849
+ await execAsync("git pull --ff-only", { cwd: worktreeDir });
611
850
  result.success = true;
612
851
  result.pulled = true;
613
852
  return result;
@@ -616,8 +855,8 @@ export async function shadowPull(worktreeDir) {
616
855
  // Fast-forward failed, try rebase
617
856
  }
618
857
  try {
619
- // AC-6: Fall back to rebase
620
- await execAsync('git pull --rebase', { cwd: worktreeDir });
858
+ // AC: @shadow-sync ac-6 - Fall back to rebase
859
+ await execAsync("git pull --rebase", { cwd: worktreeDir });
621
860
  result.success = true;
622
861
  result.pulled = true;
623
862
  return result;
@@ -625,31 +864,36 @@ export async function shadowPull(worktreeDir) {
625
864
  catch {
626
865
  // Rebase failed - likely conflict
627
866
  }
628
- // AC-3: Conflict detected - abort rebase and report
867
+ // AC: @shadow-sync ac-3 - Conflict detected - abort rebase and report
629
868
  try {
630
- await execAsync('git rebase --abort', { cwd: worktreeDir });
869
+ await execAsync("git rebase --abort", { cwd: worktreeDir });
631
870
  }
632
871
  catch {
633
872
  // May not be in rebase state, ignore
634
873
  }
635
874
  result.hadConflict = true;
636
- result.error = 'Sync conflict detected. Run `kspec shadow resolve` to fix.';
875
+ result.error = "Sync conflict detected. Run `kspec shadow resolve` to fix.";
637
876
  return result;
638
877
  }
639
878
  /**
640
879
  * Full sync operation: pull then push.
641
880
  * Used by session start and explicit sync commands.
881
+ *
882
+ * AC: @config-shadow ac-7 — backward compat when called without config
883
+ *
884
+ * @param worktreeDir Path to shadow worktree
885
+ * @param options Optional shadow configuration
642
886
  */
643
- export async function shadowSync(worktreeDir) {
887
+ export async function shadowSync(worktreeDir, options) {
644
888
  // First pull
645
- const pullResult = await shadowPull(worktreeDir);
889
+ const pullResult = await shadowPull(worktreeDir, options);
646
890
  if (!pullResult.success) {
647
891
  return pullResult;
648
892
  }
649
893
  // Then push (only if tracking configured, checked inside)
650
- if (await hasRemoteTracking(worktreeDir)) {
894
+ if (await hasRemoteTracking(worktreeDir, options)) {
651
895
  try {
652
- await execAsync('git push', { cwd: worktreeDir });
896
+ await execAsync("git push", { cwd: worktreeDir });
653
897
  pullResult.pushed = true;
654
898
  }
655
899
  catch {
@@ -665,7 +909,7 @@ export async function shadowSync(worktreeDir) {
665
909
  async function hasUncommittedGitignore(projectRoot) {
666
910
  try {
667
911
  // Check both staged and unstaged changes to .gitignore
668
- const { stdout } = await execAsync('git status --porcelain .gitignore', {
912
+ const { stdout } = await execAsync("git status --porcelain .gitignore", {
669
913
  cwd: projectRoot,
670
914
  });
671
915
  return stdout.trim().length > 0;
@@ -675,41 +919,51 @@ async function hasUncommittedGitignore(projectRoot) {
675
919
  }
676
920
  }
677
921
  /**
678
- * Commit only .gitignore with a message
922
+ * Commit only .gitignore with a message.
923
+ *
924
+ * @param projectRoot Git repository root
925
+ * @param directoryName Shadow directory name (for commit message)
679
926
  */
680
- async function commitGitignore(projectRoot) {
681
- await execAsync('git add .gitignore', { cwd: projectRoot });
682
- await execAsync('git commit -m "chore: add .kspec/ to .gitignore for shadow branch"', {
927
+ async function commitGitignore(projectRoot, directoryName) {
928
+ await execAsync("git add .gitignore", { cwd: projectRoot });
929
+ await execAsync(`git commit -m "chore: add ${directoryName}/ to .gitignore for shadow branch"`, {
683
930
  cwd: projectRoot,
684
931
  });
685
932
  }
686
933
  /**
687
- * Add .kspec/ to .gitignore if not already present.
934
+ * Add shadow directory to .gitignore if not already present.
688
935
  * Fails if .gitignore has uncommitted changes.
689
936
  * Commits the change after adding.
937
+ *
938
+ * AC: @config-shadow ac-2 — uses configured directory name
939
+ * AC: @config-shadow ac-7 — defaults to .kspec when not provided
940
+ *
941
+ * @param projectRoot Git repository root
942
+ * @param options Optional shadow configuration
690
943
  */
691
- async function ensureGitignore(projectRoot) {
692
- const gitignorePath = path.join(projectRoot, '.gitignore');
693
- const entry = `${SHADOW_WORKTREE_DIR}/`;
944
+ async function ensureGitignore(projectRoot, options) {
945
+ const directoryName = getDirectoryName(options);
946
+ const gitignorePath = path.join(projectRoot, ".gitignore");
947
+ const entry = `${directoryName}/`;
694
948
  // Fail fast if .gitignore has uncommitted changes
695
949
  if (await hasUncommittedGitignore(projectRoot)) {
696
- throw new ShadowError('.gitignore has uncommitted changes', 'GIT_ERROR', 'Commit or stash your .gitignore changes before running kspec init.');
950
+ throw new ShadowError(".gitignore has uncommitted changes", "GIT_ERROR", "Commit or stash your .gitignore changes before running kspec init.");
697
951
  }
698
952
  try {
699
- let content = '';
953
+ let content = "";
700
954
  try {
701
- content = await fs.readFile(gitignorePath, 'utf-8');
955
+ content = await fs.readFile(gitignorePath, "utf-8");
702
956
  }
703
957
  catch {
704
958
  // File doesn't exist, will create
705
959
  }
706
960
  // Check if already present (handle various formats)
707
- const lines = content.split('\n');
961
+ const lines = content.split("\n");
708
962
  const patterns = [
709
- SHADOW_WORKTREE_DIR,
710
- `${SHADOW_WORKTREE_DIR}/`,
711
- `/${SHADOW_WORKTREE_DIR}`,
712
- `/${SHADOW_WORKTREE_DIR}/`,
963
+ directoryName,
964
+ `${directoryName}/`,
965
+ `/${directoryName}`,
966
+ `/${directoryName}/`,
713
967
  ];
714
968
  for (const line of lines) {
715
969
  const trimmed = line.trim();
@@ -718,19 +972,19 @@ async function ensureGitignore(projectRoot) {
718
972
  }
719
973
  }
720
974
  // Add to gitignore
721
- const newContent = content.endsWith('\n') || content === ''
975
+ const newContent = content.endsWith("\n") || content === ""
722
976
  ? `${content}${entry}\n`
723
977
  : `${content}\n${entry}\n`;
724
- await fs.writeFile(gitignorePath, newContent, 'utf-8');
978
+ await fs.writeFile(gitignorePath, newContent, "utf-8");
725
979
  // Commit the change
726
- await commitGitignore(projectRoot);
980
+ await commitGitignore(projectRoot, directoryName);
727
981
  return true;
728
982
  }
729
983
  catch (error) {
730
984
  if (error instanceof ShadowError) {
731
985
  throw error;
732
986
  }
733
- throw new ShadowError(`Failed to update .gitignore: ${error}`, 'GIT_ERROR', 'Check file permissions for .gitignore');
987
+ throw new ShadowError(`Failed to update .gitignore: ${error}`, "GIT_ERROR", "Check file permissions for .gitignore");
734
988
  }
735
989
  }
736
990
  /**
@@ -752,12 +1006,6 @@ project:
752
1006
  # Module includes
753
1007
  includes:
754
1008
  - modules/main.yaml
755
-
756
- # Configuration
757
- config:
758
- validation:
759
- strict_refs: true
760
- require_acceptance: false
761
1009
  `;
762
1010
  }
763
1011
  /**
@@ -790,6 +1038,13 @@ function generateShadowInbox() {
790
1038
  items: []
791
1039
  `;
792
1040
  }
1041
+ /**
1042
+ * Get the kspec package root directory.
1043
+ * Navigates from dist/parser/ to package root.
1044
+ */
1045
+ function getPackageRoot() {
1046
+ return path.resolve(__dirname, "..", "..");
1047
+ }
793
1048
  /**
794
1049
  * Install pre-commit hook to protect kspec-meta branch.
795
1050
  * Hook prevents direct commits to shadow branch unless KSPEC_SHADOW_COMMIT=1.
@@ -797,15 +1052,19 @@ items: []
797
1052
  * Note: Git worktrees use hooks from the main .git/hooks directory (via commondir),
798
1053
  * not from .git/worktrees/-kspec/hooks. So we install to main hooks directory.
799
1054
  *
1055
+ * The hook source is located in the kspec package's templates/hooks/ directory.
1056
+ *
800
1057
  * @param projectRoot Git repository root
801
1058
  * @returns true if hook was installed, false if already exists
802
1059
  */
803
1060
  async function installShadowHook(projectRoot) {
804
- const hooksDir = path.join(projectRoot, '.git', 'hooks');
805
- const hookPath = path.join(hooksDir, 'pre-commit');
806
- const sourceHookPath = path.join(projectRoot, 'hooks', 'pre-commit');
1061
+ const hooksDir = path.join(projectRoot, ".git", "hooks");
1062
+ const hookPath = path.join(hooksDir, "pre-commit");
1063
+ // Look for hook in package templates directory
1064
+ const packageRoot = getPackageRoot();
1065
+ const sourceHookPath = path.join(packageRoot, "templates", "hooks", "pre-commit");
807
1066
  try {
808
- // Check if source hook exists
1067
+ // Check if source hook exists in package templates
809
1068
  try {
810
1069
  await fs.access(sourceHookPath);
811
1070
  }
@@ -822,12 +1081,12 @@ async function installShadowHook(projectRoot) {
822
1081
  catch {
823
1082
  // Hook doesn't exist - install it
824
1083
  }
825
- // Copy hook from source
826
- const hookContent = await fs.readFile(sourceHookPath, 'utf-8');
1084
+ // Copy hook from package templates
1085
+ const hookContent = await fs.readFile(sourceHookPath, "utf-8");
827
1086
  await fs.writeFile(hookPath, hookContent, { mode: 0o755 });
828
1087
  return true;
829
1088
  }
830
- catch (error) {
1089
+ catch (_error) {
831
1090
  // Silently fail - hook installation is optional
832
1091
  return false;
833
1092
  }
@@ -838,13 +1097,87 @@ async function installShadowHook(projectRoot) {
838
1097
  function toSlug(projectName) {
839
1098
  return projectName
840
1099
  .toLowerCase()
841
- .replace(/[^a-z0-9]+/g, '-')
842
- .replace(/^-|-$/g, '');
1100
+ .replace(/[^a-z0-9]+/g, "-")
1101
+ .replace(/^-|-$/g, "");
1102
+ }
1103
+ /**
1104
+ * Configure git merge driver for kspec YAML files.
1105
+ * AC: @yaml-merge-driver ac-12
1106
+ *
1107
+ * Configures the merge driver in .git/config and adds .gitattributes in the shadow branch.
1108
+ *
1109
+ * @param projectRoot Git repository root
1110
+ * @param worktreeDir Path to shadow worktree directory
1111
+ * @returns true if configuration was successful
1112
+ */
1113
+ async function configureMergeDriver(projectRoot, worktreeDir) {
1114
+ try {
1115
+ // Step 1: Configure merge driver in .git/config
1116
+ const kspecPath = execSync("which kspec", {
1117
+ encoding: "utf-8",
1118
+ stdio: ["pipe", "pipe", "pipe"],
1119
+ }).trim();
1120
+ // Add merge driver configuration to git config
1121
+ try {
1122
+ execSync(`git config merge.kspec.name "Kspec YAML semantic merge driver"`, {
1123
+ cwd: projectRoot,
1124
+ stdio: ["pipe", "pipe", "pipe"],
1125
+ });
1126
+ execSync(`git config merge.kspec.driver "${kspecPath} merge-driver %O %A %B --non-interactive"`, {
1127
+ cwd: projectRoot,
1128
+ stdio: ["pipe", "pipe", "pipe"],
1129
+ });
1130
+ }
1131
+ catch (error) {
1132
+ // Config may fail if already set - check if it's set correctly
1133
+ try {
1134
+ const existingDriver = execSync("git config merge.kspec.driver", {
1135
+ cwd: projectRoot,
1136
+ encoding: "utf-8",
1137
+ stdio: ["pipe", "pipe", "pipe"],
1138
+ }).trim();
1139
+ if (!existingDriver.includes("kspec merge-driver")) {
1140
+ throw new Error("Merge driver config exists but is incorrect");
1141
+ }
1142
+ }
1143
+ catch {
1144
+ throw error; // Re-throw original error
1145
+ }
1146
+ }
1147
+ // Step 2: Create .gitattributes in shadow branch
1148
+ const gitattributesPath = path.join(worktreeDir, ".gitattributes");
1149
+ // Check if .gitattributes exists
1150
+ let existingContent = "";
1151
+ try {
1152
+ existingContent = await fs.readFile(gitattributesPath, "utf-8");
1153
+ }
1154
+ catch {
1155
+ // File doesn't exist, that's fine
1156
+ }
1157
+ // Check if merge driver is already configured
1158
+ if (!existingContent.includes("merge=kspec")) {
1159
+ const attributesContent = existingContent
1160
+ ? existingContent + "\n"
1161
+ : "# Git attributes for kspec\n\n";
1162
+ await fs.writeFile(gitattributesPath, attributesContent + "*.yaml merge=kspec\n*.yml merge=kspec\n", "utf-8");
1163
+ // Commit .gitattributes to shadow branch
1164
+ await shadowAutoCommit(worktreeDir, "Configure kspec merge driver");
1165
+ }
1166
+ return true;
1167
+ }
1168
+ catch (_error) {
1169
+ // Silently fail - merge driver configuration is optional
1170
+ return false;
1171
+ }
843
1172
  }
844
1173
  /**
845
1174
  * Initialize shadow branch and worktree.
846
1175
  * Creates orphan branch, worktree, updates gitignore, and creates initial structure.
847
1176
  *
1177
+ * AC: @config-shadow ac-1 — creates orphan branch with configured name
1178
+ * AC: @config-shadow ac-2 — creates worktree at configured directory
1179
+ * AC: @config-shadow ac-7 — defaults to constants when options not provided
1180
+ *
848
1181
  * @param projectRoot Git repository root
849
1182
  * @param options Initialization options
850
1183
  * @returns Result indicating what was created
@@ -862,12 +1195,15 @@ export async function initializeShadow(projectRoot, options = {}) {
862
1195
  };
863
1196
  // Check if we're in a git repo
864
1197
  if (!(await isGitRepo(projectRoot))) {
865
- result.error = 'Not a git repository';
1198
+ result.error = "Not a git repository";
866
1199
  return result;
867
1200
  }
868
- const worktreeDir = path.join(projectRoot, SHADOW_WORKTREE_DIR);
869
- // Check current status
870
- const status = await getShadowStatus(projectRoot);
1201
+ // AC: ac-1 ac-2 — use configured branch/directory or defaults
1202
+ const branchName = getBranchName(options.shadow);
1203
+ const directoryName = getDirectoryName(options.shadow);
1204
+ const worktreeDir = path.join(projectRoot, directoryName);
1205
+ // Check current status with configured options
1206
+ const status = await getShadowStatus(projectRoot, options.shadow);
871
1207
  // Handle existing shadow branch
872
1208
  if (status.healthy && !options.force) {
873
1209
  result.alreadyExists = true;
@@ -875,20 +1211,27 @@ export async function initializeShadow(projectRoot, options = {}) {
875
1211
  return result;
876
1212
  }
877
1213
  // Derive project name if not provided
878
- const projectName = options.projectName || path.basename(projectRoot)
879
- .replace(/[-_]/g, ' ')
880
- .replace(/\b\w/g, (c) => c.toUpperCase());
1214
+ const projectName = options.projectName ||
1215
+ path
1216
+ .basename(projectRoot)
1217
+ .replace(/[-_]/g, " ")
1218
+ .replace(/\b\w/g, (c) => c.toUpperCase());
881
1219
  const slug = toSlug(projectName);
1220
+ // Determine remote name to use
1221
+ let remoteName = "origin";
1222
+ if (options.shadow?.remote && options.shadow.remoteType === "named") {
1223
+ remoteName = options.shadow.remote;
1224
+ }
882
1225
  // Check for remote shadow branch (AC-4: fetch to ensure refs are up to date)
883
- const remoteExists = await hasRemote(projectRoot);
1226
+ const remoteExists = await hasRemote(projectRoot, remoteName);
884
1227
  let remoteHasShadow = false;
885
1228
  if (remoteExists) {
886
- await fetchRemote(projectRoot); // Best effort, ignore failures
887
- remoteHasShadow = await remoteBranchExists(projectRoot, SHADOW_BRANCH_NAME);
1229
+ await fetchRemote(projectRoot, remoteName); // Best effort, ignore failures
1230
+ remoteHasShadow = await remoteBranchExists(projectRoot, branchName, remoteName);
888
1231
  }
889
1232
  try {
890
- // Step 1: Update .gitignore first (before creating .kspec/)
891
- result.gitignoreUpdated = await ensureGitignore(projectRoot);
1233
+ // Step 1: Update .gitignore first (before creating worktree)
1234
+ result.gitignoreUpdated = await ensureGitignore(projectRoot, options.shadow);
892
1235
  // Step 2: Create worktree with orphan branch (or attach to existing branch)
893
1236
  if (!status.worktreeExists || !status.worktreeLinked) {
894
1237
  // Remove existing directory if present but not linked
@@ -897,7 +1240,7 @@ export async function initializeShadow(projectRoot, options = {}) {
897
1240
  }
898
1241
  // Remove stale worktree reference if any
899
1242
  try {
900
- await execAsync(`git worktree remove ${SHADOW_WORKTREE_DIR} --force`, {
1243
+ await execAsync(`git worktree remove "${directoryName}" --force`, {
901
1244
  cwd: projectRoot,
902
1245
  });
903
1246
  }
@@ -905,27 +1248,28 @@ export async function initializeShadow(projectRoot, options = {}) {
905
1248
  // Ignore - worktree may not exist in git's list
906
1249
  }
907
1250
  if (remoteHasShadow) {
908
- // AC-1: Remote has shadow branch - create worktree from it with tracking
909
- await execAsync(`git worktree add ${SHADOW_WORKTREE_DIR} ${SHADOW_BRANCH_NAME}`, { cwd: projectRoot });
1251
+ // AC: @shadow-init-remote ac-1 - Remote has shadow branch - create worktree from it with tracking
1252
+ await execAsync(`git worktree add "${directoryName}" ${branchName}`, { cwd: projectRoot });
910
1253
  // Set up tracking for the branch
911
- await execAsync(`git branch --set-upstream-to=origin/${SHADOW_BRANCH_NAME} ${SHADOW_BRANCH_NAME}`, { cwd: projectRoot });
1254
+ await execAsync(`git branch --set-upstream-to=${remoteName}/${branchName} ${branchName}`, { cwd: projectRoot });
912
1255
  result.createdFromRemote = true;
913
1256
  }
914
1257
  else if (!status.branchExists) {
915
- // AC-2/AC-3: No remote branch or no remote - create orphan branch
916
- await execAsync(`git worktree add --orphan -b ${SHADOW_BRANCH_NAME} ${SHADOW_WORKTREE_DIR}`, { cwd: projectRoot });
1258
+ // AC: @shadow-init-remote ac-2 ac-3 - No remote branch or no remote - create orphan branch
1259
+ // AC: @config-shadow ac-1 use configured branch name
1260
+ await execAsync(`git worktree add --orphan -b ${branchName} "${directoryName}"`, { cwd: projectRoot });
917
1261
  result.branchCreated = true;
918
1262
  }
919
1263
  else {
920
1264
  // Attach to existing local branch
921
- await execAsync(`git worktree add ${SHADOW_WORKTREE_DIR} ${SHADOW_BRANCH_NAME}`, { cwd: projectRoot });
1265
+ await execAsync(`git worktree add "${directoryName}" ${branchName}`, { cwd: projectRoot });
922
1266
  }
923
1267
  result.worktreeCreated = true;
924
1268
  }
925
1269
  // Step 3: Create initial structure if empty (only for new branches, not remote)
926
1270
  const manifestPath = path.join(worktreeDir, `${slug}.yaml`);
927
- const modulesDir = path.join(worktreeDir, 'modules');
928
- const moduleFilePath = path.join(modulesDir, 'main.yaml');
1271
+ const modulesDir = path.join(worktreeDir, "modules");
1272
+ const moduleFilePath = path.join(modulesDir, "main.yaml");
929
1273
  const tasksPath = path.join(worktreeDir, `${slug}.tasks.yaml`);
930
1274
  const inboxPath = path.join(worktreeDir, `${slug}.inbox.yaml`);
931
1275
  let filesCreated = false;
@@ -933,18 +1277,24 @@ export async function initializeShadow(projectRoot, options = {}) {
933
1277
  try {
934
1278
  // Look for any .yaml manifest file (project name may differ)
935
1279
  const files = await fs.readdir(worktreeDir);
936
- const hasManifest = files.some(f => f.endsWith('.yaml') && !f.includes('.tasks.') && !f.includes('.inbox.'));
1280
+ const hasManifest = files.some((f) => f.endsWith(".yaml") &&
1281
+ !f.includes(".tasks.") &&
1282
+ !f.includes(".inbox."));
937
1283
  if (!hasManifest) {
938
- throw new Error('No manifest found');
1284
+ throw new Error("No manifest found");
939
1285
  }
940
1286
  }
941
1287
  catch {
942
1288
  // Manifest doesn't exist, create initial structure
943
1289
  await fs.mkdir(modulesDir, { recursive: true });
944
- await fs.writeFile(manifestPath, generateShadowManifest(projectName), 'utf-8');
945
- await fs.writeFile(moduleFilePath, generateShadowModule(projectName), 'utf-8');
946
- await fs.writeFile(tasksPath, generateShadowTasks(projectName), 'utf-8');
947
- await fs.writeFile(inboxPath, generateShadowInbox(), 'utf-8');
1290
+ await fs.writeFile(manifestPath, generateShadowManifest(projectName), "utf-8");
1291
+ await fs.writeFile(moduleFilePath, generateShadowModule(projectName), "utf-8");
1292
+ await fs.writeFile(tasksPath, generateShadowTasks(projectName), "utf-8");
1293
+ await fs.writeFile(inboxPath, generateShadowInbox(), "utf-8");
1294
+ // AC: @artifacts-directory ac-init-creates, ac-gitignore-entry
1295
+ const artifactsDir = path.join(worktreeDir, "artifacts");
1296
+ await fs.mkdir(artifactsDir, { recursive: true });
1297
+ await fs.writeFile(path.join(worktreeDir, ".gitignore"), "# Ephemeral artifacts - reports, exports, generated files\n# Not tracked in shadow branch\nartifacts/\n", "utf-8");
948
1298
  filesCreated = true;
949
1299
  }
950
1300
  // Step 4: Initial commit if files were created
@@ -953,10 +1303,13 @@ export async function initializeShadow(projectRoot, options = {}) {
953
1303
  }
954
1304
  // Step 5: AC-2: Push new branch to remote to establish tracking
955
1305
  if (result.branchCreated && remoteExists && !remoteHasShadow) {
956
- result.pushedToRemote = await pushShadowBranch(worktreeDir);
1306
+ result.pushedToRemote = await pushShadowBranch(worktreeDir, remoteName, options.shadow);
957
1307
  }
958
1308
  // Step 6: Install pre-commit hook to protect shadow branch
959
1309
  await installShadowHook(projectRoot);
1310
+ // Step 7: Configure merge driver for semantic YAML merging
1311
+ // AC: @yaml-merge-driver ac-12
1312
+ await configureMergeDriver(projectRoot, worktreeDir);
960
1313
  result.success = true;
961
1314
  return result;
962
1315
  }
@@ -969,11 +1322,16 @@ export async function initializeShadow(projectRoot, options = {}) {
969
1322
  * Repair a broken shadow branch setup.
970
1323
  * Handles cases where worktree is disconnected or directory is missing.
971
1324
  *
1325
+ * AC: @config-shadow ac-7 — backward compat when called without config
1326
+ *
972
1327
  * @param projectRoot Git repository root
1328
+ * @param options Optional shadow configuration
973
1329
  * @returns Result indicating what was repaired
974
1330
  */
975
- export async function repairShadow(projectRoot) {
976
- const status = await getShadowStatus(projectRoot);
1331
+ export async function repairShadow(projectRoot, options) {
1332
+ const branchName = getBranchName(options);
1333
+ const directoryName = getDirectoryName(options);
1334
+ const status = await getShadowStatus(projectRoot, options);
977
1335
  if (status.healthy) {
978
1336
  return {
979
1337
  success: true,
@@ -997,15 +1355,15 @@ export async function repairShadow(projectRoot) {
997
1355
  alreadyExists: false,
998
1356
  createdFromRemote: false,
999
1357
  pushedToRemote: false,
1000
- error: 'Shadow branch does not exist. Run `kspec init` instead.',
1358
+ error: "Shadow branch does not exist. Run `kspec init` instead.",
1001
1359
  };
1002
1360
  }
1003
1361
  // Branch exists but worktree is broken - repair it
1004
- const worktreeDir = path.join(projectRoot, SHADOW_WORKTREE_DIR);
1362
+ const worktreeDir = path.join(projectRoot, directoryName);
1005
1363
  try {
1006
1364
  // Remove stale worktree reference
1007
1365
  try {
1008
- await execAsync(`git worktree remove ${SHADOW_WORKTREE_DIR} --force`, {
1366
+ await execAsync(`git worktree remove "${directoryName}" --force`, {
1009
1367
  cwd: projectRoot,
1010
1368
  });
1011
1369
  }
@@ -1016,15 +1374,18 @@ export async function repairShadow(projectRoot) {
1016
1374
  await fs.rm(worktreeDir, { recursive: true, force: true });
1017
1375
  // Prune stale worktree references (cleans up orphaned entries)
1018
1376
  try {
1019
- await execAsync('git worktree prune', { cwd: projectRoot });
1377
+ await execAsync("git worktree prune", { cwd: projectRoot });
1020
1378
  }
1021
1379
  catch {
1022
1380
  // Ignore - prune is best-effort
1023
1381
  }
1024
1382
  // Recreate worktree
1025
- await execAsync(`git worktree add ${SHADOW_WORKTREE_DIR} ${SHADOW_BRANCH_NAME}`, { cwd: projectRoot });
1383
+ await execAsync(`git worktree add "${directoryName}" ${branchName}`, { cwd: projectRoot });
1026
1384
  // Install pre-commit hook
1027
1385
  await installShadowHook(projectRoot);
1386
+ // AC: @artifacts-directory ac-repair-recreates
1387
+ const artifactsDir = path.join(worktreeDir, "artifacts");
1388
+ await fs.mkdir(artifactsDir, { recursive: true });
1028
1389
  return {
1029
1390
  success: true,
1030
1391
  branchCreated: false,