@specforge/cli 0.1.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 (380) hide show
  1. package/README.md +781 -0
  2. package/bin/specforge +21 -0
  3. package/dist/channel.d.ts +27 -0
  4. package/dist/channel.d.ts.map +1 -0
  5. package/dist/channel.js +22 -0
  6. package/dist/channel.js.map +1 -0
  7. package/dist/cli/commands/configure.d.ts +16 -0
  8. package/dist/cli/commands/configure.d.ts.map +1 -0
  9. package/dist/cli/commands/configure.js +186 -0
  10. package/dist/cli/commands/configure.js.map +1 -0
  11. package/dist/cli/commands/configure.types.d.ts +110 -0
  12. package/dist/cli/commands/configure.types.d.ts.map +1 -0
  13. package/dist/cli/commands/configure.types.js +82 -0
  14. package/dist/cli/commands/configure.types.js.map +1 -0
  15. package/dist/cli/commands/debug/call.d.ts +14 -0
  16. package/dist/cli/commands/debug/call.d.ts.map +1 -0
  17. package/dist/cli/commands/debug/call.js +96 -0
  18. package/dist/cli/commands/debug/call.js.map +1 -0
  19. package/dist/cli/commands/debug/index.d.ts +12 -0
  20. package/dist/cli/commands/debug/index.d.ts.map +1 -0
  21. package/dist/cli/commands/debug/index.js +14 -0
  22. package/dist/cli/commands/debug/index.js.map +1 -0
  23. package/dist/cli/commands/debug/register.d.ts +11 -0
  24. package/dist/cli/commands/debug/register.d.ts.map +1 -0
  25. package/dist/cli/commands/debug/register.js +78 -0
  26. package/dist/cli/commands/debug/register.js.map +1 -0
  27. package/dist/cli/commands/debug/test.d.ts +13 -0
  28. package/dist/cli/commands/debug/test.d.ts.map +1 -0
  29. package/dist/cli/commands/debug/test.js +91 -0
  30. package/dist/cli/commands/debug/test.js.map +1 -0
  31. package/dist/cli/commands/debug/tools.d.ts +13 -0
  32. package/dist/cli/commands/debug/tools.d.ts.map +1 -0
  33. package/dist/cli/commands/debug/tools.js +86 -0
  34. package/dist/cli/commands/debug/tools.js.map +1 -0
  35. package/dist/cli/commands/debug/types.d.ts +119 -0
  36. package/dist/cli/commands/debug/types.d.ts.map +1 -0
  37. package/dist/cli/commands/debug/types.js +36 -0
  38. package/dist/cli/commands/debug/types.js.map +1 -0
  39. package/dist/cli/commands/debug/whoami.d.ts +13 -0
  40. package/dist/cli/commands/debug/whoami.d.ts.map +1 -0
  41. package/dist/cli/commands/debug/whoami.js +131 -0
  42. package/dist/cli/commands/debug/whoami.js.map +1 -0
  43. package/dist/cli/commands/doctor.d.ts +16 -0
  44. package/dist/cli/commands/doctor.d.ts.map +1 -0
  45. package/dist/cli/commands/doctor.js +413 -0
  46. package/dist/cli/commands/doctor.js.map +1 -0
  47. package/dist/cli/commands/doctor.types.d.ts +180 -0
  48. package/dist/cli/commands/doctor.types.d.ts.map +1 -0
  49. package/dist/cli/commands/doctor.types.js +140 -0
  50. package/dist/cli/commands/doctor.types.js.map +1 -0
  51. package/dist/cli/commands/feedback.d.ts +27 -0
  52. package/dist/cli/commands/feedback.d.ts.map +1 -0
  53. package/dist/cli/commands/feedback.js +262 -0
  54. package/dist/cli/commands/feedback.js.map +1 -0
  55. package/dist/cli/commands/feedback.types.d.ts +116 -0
  56. package/dist/cli/commands/feedback.types.d.ts.map +1 -0
  57. package/dist/cli/commands/feedback.types.js +65 -0
  58. package/dist/cli/commands/feedback.types.js.map +1 -0
  59. package/dist/cli/commands/index.d.ts +15 -0
  60. package/dist/cli/commands/index.d.ts.map +1 -0
  61. package/dist/cli/commands/index.js +21 -0
  62. package/dist/cli/commands/index.js.map +1 -0
  63. package/dist/cli/commands/init.d.ts +17 -0
  64. package/dist/cli/commands/init.d.ts.map +1 -0
  65. package/dist/cli/commands/init.js +773 -0
  66. package/dist/cli/commands/init.js.map +1 -0
  67. package/dist/cli/commands/init.types.d.ts +193 -0
  68. package/dist/cli/commands/init.types.d.ts.map +1 -0
  69. package/dist/cli/commands/init.types.js +29 -0
  70. package/dist/cli/commands/init.types.js.map +1 -0
  71. package/dist/cli/commands/invitations.d.ts +31 -0
  72. package/dist/cli/commands/invitations.d.ts.map +1 -0
  73. package/dist/cli/commands/invitations.js +250 -0
  74. package/dist/cli/commands/invitations.js.map +1 -0
  75. package/dist/cli/commands/login.d.ts +40 -0
  76. package/dist/cli/commands/login.d.ts.map +1 -0
  77. package/dist/cli/commands/login.js +139 -0
  78. package/dist/cli/commands/login.js.map +1 -0
  79. package/dist/cli/commands/members.d.ts +55 -0
  80. package/dist/cli/commands/members.d.ts.map +1 -0
  81. package/dist/cli/commands/members.js +296 -0
  82. package/dist/cli/commands/members.js.map +1 -0
  83. package/dist/cli/commands/scaffold/agent-types.d.ts +54 -0
  84. package/dist/cli/commands/scaffold/agent-types.d.ts.map +1 -0
  85. package/dist/cli/commands/scaffold/agent-types.js +22 -0
  86. package/dist/cli/commands/scaffold/agent-types.js.map +1 -0
  87. package/dist/cli/commands/scaffold/display.d.ts +56 -0
  88. package/dist/cli/commands/scaffold/display.d.ts.map +1 -0
  89. package/dist/cli/commands/scaffold/display.js +217 -0
  90. package/dist/cli/commands/scaffold/display.js.map +1 -0
  91. package/dist/cli/commands/scaffold/files.d.ts +99 -0
  92. package/dist/cli/commands/scaffold/files.d.ts.map +1 -0
  93. package/dist/cli/commands/scaffold/files.js +122 -0
  94. package/dist/cli/commands/scaffold/files.js.map +1 -0
  95. package/dist/cli/commands/scaffold/index.d.ts +13 -0
  96. package/dist/cli/commands/scaffold/index.d.ts.map +1 -0
  97. package/dist/cli/commands/scaffold/index.js +12 -0
  98. package/dist/cli/commands/scaffold/index.js.map +1 -0
  99. package/dist/cli/commands/scaffold/register.d.ts +11 -0
  100. package/dist/cli/commands/scaffold/register.d.ts.map +1 -0
  101. package/dist/cli/commands/scaffold/register.js +11 -0
  102. package/dist/cli/commands/scaffold/register.js.map +1 -0
  103. package/dist/cli/commands/scaffold/scaffold.d.ts +11 -0
  104. package/dist/cli/commands/scaffold/scaffold.d.ts.map +1 -0
  105. package/dist/cli/commands/scaffold/scaffold.js +728 -0
  106. package/dist/cli/commands/scaffold/scaffold.js.map +1 -0
  107. package/dist/cli/commands/scaffold/targets.d.ts +60 -0
  108. package/dist/cli/commands/scaffold/targets.d.ts.map +1 -0
  109. package/dist/cli/commands/scaffold/targets.js +251 -0
  110. package/dist/cli/commands/scaffold/targets.js.map +1 -0
  111. package/dist/cli/commands/scaffold/types.d.ts +170 -0
  112. package/dist/cli/commands/scaffold/types.d.ts.map +1 -0
  113. package/dist/cli/commands/scaffold/types.js +12 -0
  114. package/dist/cli/commands/scaffold/types.js.map +1 -0
  115. package/dist/cli/commands/scaffold/utils.d.ts +89 -0
  116. package/dist/cli/commands/scaffold/utils.d.ts.map +1 -0
  117. package/dist/cli/commands/scaffold/utils.js +401 -0
  118. package/dist/cli/commands/scaffold/utils.js.map +1 -0
  119. package/dist/cli/commands/status.d.ts +19 -0
  120. package/dist/cli/commands/status.d.ts.map +1 -0
  121. package/dist/cli/commands/status.js +124 -0
  122. package/dist/cli/commands/status.js.map +1 -0
  123. package/dist/cli/commands/status.types.d.ts +37 -0
  124. package/dist/cli/commands/status.types.d.ts.map +1 -0
  125. package/dist/cli/commands/status.types.js +7 -0
  126. package/dist/cli/commands/status.types.js.map +1 -0
  127. package/dist/cli/commands/switch.d.ts +18 -0
  128. package/dist/cli/commands/switch.d.ts.map +1 -0
  129. package/dist/cli/commands/switch.js +134 -0
  130. package/dist/cli/commands/switch.js.map +1 -0
  131. package/dist/cli/commands/switch.types.d.ts +123 -0
  132. package/dist/cli/commands/switch.types.d.ts.map +1 -0
  133. package/dist/cli/commands/switch.types.js +46 -0
  134. package/dist/cli/commands/switch.types.js.map +1 -0
  135. package/dist/cli/config/agent-teams.types.d.ts +120 -0
  136. package/dist/cli/config/agent-teams.types.d.ts.map +1 -0
  137. package/dist/cli/config/agent-teams.types.js +23 -0
  138. package/dist/cli/config/agent-teams.types.js.map +1 -0
  139. package/dist/cli/config/index.d.ts +13 -0
  140. package/dist/cli/config/index.d.ts.map +1 -0
  141. package/dist/cli/config/index.js +7 -0
  142. package/dist/cli/config/index.js.map +1 -0
  143. package/dist/cli/config/loader.d.ts +114 -0
  144. package/dist/cli/config/loader.d.ts.map +1 -0
  145. package/dist/cli/config/loader.js +198 -0
  146. package/dist/cli/config/loader.js.map +1 -0
  147. package/dist/cli/config/paths.d.ts +107 -0
  148. package/dist/cli/config/paths.d.ts.map +1 -0
  149. package/dist/cli/config/paths.js +79 -0
  150. package/dist/cli/config/paths.js.map +1 -0
  151. package/dist/cli/config/planning-sessions-registry.d.ts +52 -0
  152. package/dist/cli/config/planning-sessions-registry.d.ts.map +1 -0
  153. package/dist/cli/config/planning-sessions-registry.js +46 -0
  154. package/dist/cli/config/planning-sessions-registry.js.map +1 -0
  155. package/dist/cli/config/types.d.ts +186 -0
  156. package/dist/cli/config/types.d.ts.map +1 -0
  157. package/dist/cli/config/types.js +27 -0
  158. package/dist/cli/config/types.js.map +1 -0
  159. package/dist/cli/config/validation.d.ts +62 -0
  160. package/dist/cli/config/validation.d.ts.map +1 -0
  161. package/dist/cli/config/validation.js +133 -0
  162. package/dist/cli/config/validation.js.map +1 -0
  163. package/dist/cli/config/writer.d.ts +153 -0
  164. package/dist/cli/config/writer.d.ts.map +1 -0
  165. package/dist/cli/config/writer.js +168 -0
  166. package/dist/cli/config/writer.js.map +1 -0
  167. package/dist/cli/index.d.ts +30 -0
  168. package/dist/cli/index.d.ts.map +1 -0
  169. package/dist/cli/index.js +142 -0
  170. package/dist/cli/index.js.map +1 -0
  171. package/dist/cli/middleware/auth-guard.d.ts +107 -0
  172. package/dist/cli/middleware/auth-guard.d.ts.map +1 -0
  173. package/dist/cli/middleware/auth-guard.js +97 -0
  174. package/dist/cli/middleware/auth-guard.js.map +1 -0
  175. package/dist/cli/middleware/error-handler.d.ts +133 -0
  176. package/dist/cli/middleware/error-handler.d.ts.map +1 -0
  177. package/dist/cli/middleware/error-handler.js +139 -0
  178. package/dist/cli/middleware/error-handler.js.map +1 -0
  179. package/dist/cli/middleware/index.d.ts +6 -0
  180. package/dist/cli/middleware/index.d.ts.map +1 -0
  181. package/dist/cli/middleware/index.js +3 -0
  182. package/dist/cli/middleware/index.js.map +1 -0
  183. package/dist/cli/templates/agents/content/core/sfag-orchestrator.d.ts +9 -0
  184. package/dist/cli/templates/agents/content/core/sfag-orchestrator.d.ts.map +1 -0
  185. package/dist/cli/templates/agents/content/core/sfag-orchestrator.js +160 -0
  186. package/dist/cli/templates/agents/content/core/sfag-orchestrator.js.map +1 -0
  187. package/dist/cli/templates/agents/content/core/sfag-spec-creator.d.ts +9 -0
  188. package/dist/cli/templates/agents/content/core/sfag-spec-creator.d.ts.map +1 -0
  189. package/dist/cli/templates/agents/content/core/sfag-spec-creator.js +343 -0
  190. package/dist/cli/templates/agents/content/core/sfag-spec-creator.js.map +1 -0
  191. package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.d.ts +9 -0
  192. package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.d.ts.map +1 -0
  193. package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.js +254 -0
  194. package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.js.map +1 -0
  195. package/dist/cli/templates/agents/content/research/sfag-package-researcher.d.ts +8 -0
  196. package/dist/cli/templates/agents/content/research/sfag-package-researcher.d.ts.map +1 -0
  197. package/dist/cli/templates/agents/content/research/sfag-package-researcher.js +128 -0
  198. package/dist/cli/templates/agents/content/research/sfag-package-researcher.js.map +1 -0
  199. package/dist/cli/templates/agents/index.d.ts +23 -0
  200. package/dist/cli/templates/agents/index.d.ts.map +1 -0
  201. package/dist/cli/templates/agents/index.js +29 -0
  202. package/dist/cli/templates/agents/index.js.map +1 -0
  203. package/dist/cli/templates/commands.d.ts +17 -0
  204. package/dist/cli/templates/commands.d.ts.map +1 -0
  205. package/dist/cli/templates/commands.js +78 -0
  206. package/dist/cli/templates/commands.js.map +1 -0
  207. package/dist/cli/templates/content/sf-blockers.d.ts +7 -0
  208. package/dist/cli/templates/content/sf-blockers.d.ts.map +1 -0
  209. package/dist/cli/templates/content/sf-blockers.js +69 -0
  210. package/dist/cli/templates/content/sf-blockers.js.map +1 -0
  211. package/dist/cli/templates/content/sf-commit.d.ts +7 -0
  212. package/dist/cli/templates/content/sf-commit.d.ts.map +1 -0
  213. package/dist/cli/templates/content/sf-commit.js +79 -0
  214. package/dist/cli/templates/content/sf-commit.js.map +1 -0
  215. package/dist/cli/templates/content/sf-context.d.ts +7 -0
  216. package/dist/cli/templates/content/sf-context.d.ts.map +1 -0
  217. package/dist/cli/templates/content/sf-context.js +63 -0
  218. package/dist/cli/templates/content/sf-context.js.map +1 -0
  219. package/dist/cli/templates/content/sf-help.d.ts +7 -0
  220. package/dist/cli/templates/content/sf-help.d.ts.map +1 -0
  221. package/dist/cli/templates/content/sf-help.js +42 -0
  222. package/dist/cli/templates/content/sf-help.js.map +1 -0
  223. package/dist/cli/templates/content/sf-init.d.ts +7 -0
  224. package/dist/cli/templates/content/sf-init.d.ts.map +1 -0
  225. package/dist/cli/templates/content/sf-init.js +62 -0
  226. package/dist/cli/templates/content/sf-init.js.map +1 -0
  227. package/dist/cli/templates/content/sf-reset.d.ts +7 -0
  228. package/dist/cli/templates/content/sf-reset.d.ts.map +1 -0
  229. package/dist/cli/templates/content/sf-reset.js +67 -0
  230. package/dist/cli/templates/content/sf-reset.js.map +1 -0
  231. package/dist/cli/templates/content/sf-search.d.ts +7 -0
  232. package/dist/cli/templates/content/sf-search.d.ts.map +1 -0
  233. package/dist/cli/templates/content/sf-search.js +63 -0
  234. package/dist/cli/templates/content/sf-search.js.map +1 -0
  235. package/dist/cli/templates/content/sf-status.d.ts +7 -0
  236. package/dist/cli/templates/content/sf-status.d.ts.map +1 -0
  237. package/dist/cli/templates/content/sf-status.js +62 -0
  238. package/dist/cli/templates/content/sf-status.js.map +1 -0
  239. package/dist/cli/templates/index.d.ts +8 -0
  240. package/dist/cli/templates/index.d.ts.map +1 -0
  241. package/dist/cli/templates/index.js +17 -0
  242. package/dist/cli/templates/index.js.map +1 -0
  243. package/dist/cli/ui/banner.d.ts +38 -0
  244. package/dist/cli/ui/banner.d.ts.map +1 -0
  245. package/dist/cli/ui/banner.js +89 -0
  246. package/dist/cli/ui/banner.js.map +1 -0
  247. package/dist/cli/ui/colors.d.ts +76 -0
  248. package/dist/cli/ui/colors.d.ts.map +1 -0
  249. package/dist/cli/ui/colors.js +67 -0
  250. package/dist/cli/ui/colors.js.map +1 -0
  251. package/dist/cli/ui/index.d.ts +7 -0
  252. package/dist/cli/ui/index.d.ts.map +1 -0
  253. package/dist/cli/ui/index.js +4 -0
  254. package/dist/cli/ui/index.js.map +1 -0
  255. package/dist/cli/ui/output.d.ts +117 -0
  256. package/dist/cli/ui/output.d.ts.map +1 -0
  257. package/dist/cli/ui/output.js +83 -0
  258. package/dist/cli/ui/output.js.map +1 -0
  259. package/dist/cli/ui/spinner.d.ts +120 -0
  260. package/dist/cli/ui/spinner.d.ts.map +1 -0
  261. package/dist/cli/ui/spinner.js +158 -0
  262. package/dist/cli/ui/spinner.js.map +1 -0
  263. package/dist/cli/ui/time.d.ts +54 -0
  264. package/dist/cli/ui/time.d.ts.map +1 -0
  265. package/dist/cli/ui/time.js +119 -0
  266. package/dist/cli/ui/time.js.map +1 -0
  267. package/dist/client/api-client.d.ts +123 -0
  268. package/dist/client/api-client.d.ts.map +1 -0
  269. package/dist/client/api-client.js +105 -0
  270. package/dist/client/api-client.js.map +1 -0
  271. package/dist/client/cli-client.d.ts +26 -0
  272. package/dist/client/cli-client.d.ts.map +1 -0
  273. package/dist/client/cli-client.js +20 -0
  274. package/dist/client/cli-client.js.map +1 -0
  275. package/dist/config/index.d.ts +78 -0
  276. package/dist/config/index.d.ts.map +1 -0
  277. package/dist/config/index.js +79 -0
  278. package/dist/config/index.js.map +1 -0
  279. package/dist/index.d.ts +16 -0
  280. package/dist/index.d.ts.map +1 -0
  281. package/dist/index.js +73 -0
  282. package/dist/index.js.map +1 -0
  283. package/dist/lib/format.d.ts +126 -0
  284. package/dist/lib/format.d.ts.map +1 -0
  285. package/dist/lib/format.js +71 -0
  286. package/dist/lib/format.js.map +1 -0
  287. package/dist/lib/index.d.ts +11 -0
  288. package/dist/lib/index.d.ts.map +1 -0
  289. package/dist/lib/index.js +45 -0
  290. package/dist/lib/index.js.map +1 -0
  291. package/dist/lib/monorepo-detector.d.ts +31 -0
  292. package/dist/lib/monorepo-detector.d.ts.map +1 -0
  293. package/dist/lib/monorepo-detector.js +188 -0
  294. package/dist/lib/monorepo-detector.js.map +1 -0
  295. package/dist/lib/prompt-generator.d.ts +65 -0
  296. package/dist/lib/prompt-generator.d.ts.map +1 -0
  297. package/dist/lib/prompt-generator.js +162 -0
  298. package/dist/lib/prompt-generator.js.map +1 -0
  299. package/dist/lib/strategy-analyzer.d.ts +59 -0
  300. package/dist/lib/strategy-analyzer.d.ts.map +1 -0
  301. package/dist/lib/strategy-analyzer.js +91 -0
  302. package/dist/lib/strategy-analyzer.js.map +1 -0
  303. package/dist/lib/workflow-definitions.d.ts +41 -0
  304. package/dist/lib/workflow-definitions.d.ts.map +1 -0
  305. package/dist/lib/workflow-definitions.js +497 -0
  306. package/dist/lib/workflow-definitions.js.map +1 -0
  307. package/dist/patterns/index.d.ts +9 -0
  308. package/dist/patterns/index.d.ts.map +1 -0
  309. package/dist/patterns/index.js +21 -0
  310. package/dist/patterns/index.js.map +1 -0
  311. package/dist/patterns/inheritance.d.ts +193 -0
  312. package/dist/patterns/inheritance.d.ts.map +1 -0
  313. package/dist/patterns/inheritance.js +125 -0
  314. package/dist/patterns/inheritance.js.map +1 -0
  315. package/dist/server.d.ts +44 -0
  316. package/dist/server.d.ts.map +1 -0
  317. package/dist/server.js +91 -0
  318. package/dist/server.js.map +1 -0
  319. package/dist/tools/core/context-helper.d.ts +50 -0
  320. package/dist/tools/core/context-helper.d.ts.map +1 -0
  321. package/dist/tools/core/context-helper.js +49 -0
  322. package/dist/tools/core/context-helper.js.map +1 -0
  323. package/dist/tools/core/index.d.ts +2 -0
  324. package/dist/tools/core/index.d.ts.map +1 -0
  325. package/dist/tools/core/index.js +2 -0
  326. package/dist/tools/core/index.js.map +1 -0
  327. package/dist/tools/index.d.ts +58 -0
  328. package/dist/tools/index.d.ts.map +1 -0
  329. package/dist/tools/index.js +1499 -0
  330. package/dist/tools/index.js.map +1 -0
  331. package/dist/transport/__tests__/resolve-endpoint.test.d.ts +6 -0
  332. package/dist/transport/__tests__/resolve-endpoint.test.d.ts.map +1 -0
  333. package/dist/transport/index.d.ts +6 -0
  334. package/dist/transport/index.d.ts.map +1 -0
  335. package/dist/transport/index.js +5 -0
  336. package/dist/transport/index.js.map +1 -0
  337. package/dist/transport/interface.d.ts +25 -0
  338. package/dist/transport/interface.d.ts.map +1 -0
  339. package/dist/transport/interface.js +1 -0
  340. package/dist/transport/interface.js.map +1 -0
  341. package/dist/transport/remote.d.ts +38 -0
  342. package/dist/transport/remote.d.ts.map +1 -0
  343. package/dist/transport/remote.js +107 -0
  344. package/dist/transport/remote.js.map +1 -0
  345. package/dist/transport/resolve-endpoint.d.ts +58 -0
  346. package/dist/transport/resolve-endpoint.d.ts.map +1 -0
  347. package/dist/transport/resolve-endpoint.js +146 -0
  348. package/dist/transport/resolve-endpoint.js.map +1 -0
  349. package/dist/types/index.d.ts +212 -0
  350. package/dist/types/index.d.ts.map +1 -0
  351. package/dist/types/index.js +16 -0
  352. package/dist/types/index.js.map +1 -0
  353. package/dist/validation/index.d.ts +86 -0
  354. package/dist/validation/index.d.ts.map +1 -0
  355. package/dist/validation/index.js +392 -0
  356. package/dist/validation/index.js.map +1 -0
  357. package/dist/validation/ticket-validation.d.ts +162 -0
  358. package/dist/validation/ticket-validation.d.ts.map +1 -0
  359. package/dist/validation/ticket-validation.js +188 -0
  360. package/dist/validation/ticket-validation.js.map +1 -0
  361. package/package.json +69 -0
  362. package/src/cli/templates/agents/content/core/sfag-orchestrator.ts +165 -0
  363. package/src/cli/templates/agents/content/core/sfag-spec-creator.ts +348 -0
  364. package/src/cli/templates/agents/content/core/sfag-ticket-implementer.ts +259 -0
  365. package/src/cli/templates/agents/content/research/sfag-package-researcher.ts +132 -0
  366. package/src/cli/templates/agents/index.ts +47 -0
  367. package/src/cli/templates/commands.ts +90 -0
  368. package/src/cli/templates/content/sf-blockers.ts +71 -0
  369. package/src/cli/templates/content/sf-commit.ts +81 -0
  370. package/src/cli/templates/content/sf-context.ts +65 -0
  371. package/src/cli/templates/content/sf-help.ts +44 -0
  372. package/src/cli/templates/content/sf-init.ts +64 -0
  373. package/src/cli/templates/content/sf-reset.ts +69 -0
  374. package/src/cli/templates/content/sf-search.ts +65 -0
  375. package/src/cli/templates/content/sf-status.ts +64 -0
  376. package/src/cli/templates/index.ts +16 -0
  377. package/src/cli/templates/skills/specforge-conventions.md +114 -0
  378. package/src/cli/templates/skills/specforge-orchestrator.md +409 -0
  379. package/src/cli/templates/skills/specforge-validator.md +128 -0
  380. package/src/cli/templates/skills/specforge-worker.md +412 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/cli/config/validation.ts"],"sourcesContent":["/**\n * Config Schema Validation\n *\n * Validates the extended .specforge/config.json configuration schema.\n * Each section is validated independently — absent sections are\n * considered valid (all sections are optional).\n *\n * Uses manual validation (no external libs) to match existing patterns.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A single validation error with field path and expected values.\n */\nexport interface ValidationError {\n /** Dot-separated field path (e.g., \"agentTeams.strategy\") */\n field: string;\n /** Human-readable error message including expected values */\n message: string;\n /** The actual value that failed validation */\n value: unknown;\n}\n\n/**\n * Result of a validation operation.\n */\nexport interface ValidationResult {\n /** Whether all validations passed */\n valid: boolean;\n /** List of validation errors (empty when valid) */\n errors: ValidationError[];\n}\n\n// ---------------------------------------------------------------------------\n// Allowed values\n// ---------------------------------------------------------------------------\n\nconst ALLOWED_STRATEGIES = ['auto', 'single', 'parallel', 'phased'] as const;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction err(field: string, message: string, value: unknown): ValidationError {\n return { field, message, value };\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nfunction oneOf(field: string, value: unknown, allowed: readonly string[]): ValidationError | null {\n if (typeof value !== 'string' || !allowed.includes(value)) {\n return err(field, `Must be one of: ${allowed.join(', ')}`, value);\n }\n return null;\n}\n\nfunction numberInRange(field: string, value: unknown, min: number, max: number): ValidationError | null {\n if (typeof value !== 'number' || !Number.isFinite(value) || value < min || value > max) {\n return err(field, `Must be a number between ${min} and ${max}`, value);\n }\n return null;\n}\n\nfunction mustBeBoolean(field: string, value: unknown): ValidationError | null {\n if (typeof value !== 'boolean') {\n return err(field, 'Must be a boolean', value);\n }\n return null;\n}\n\nfunction mustBeString(field: string, value: unknown): ValidationError | null {\n if (typeof value !== 'string') {\n return err(field, 'Must be a string', value);\n }\n return null;\n}\n\nfunction mustBeNonEmptyString(field: string, value: unknown): ValidationError | null {\n if (typeof value !== 'string' || value.trim().length === 0) {\n return err(field, 'Must be a non-empty string', value);\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Section validators\n// ---------------------------------------------------------------------------\n\n/**\n * Validate the `agentTeams` config section (v2.0.0).\n *\n * Returns valid:true if the section is absent/undefined.\n * Only validates v2.0.0 fields. Deprecated fields are silently ignored.\n */\nexport function validateAgentTeamsConfig(config: unknown): ValidationResult {\n if (config === undefined || config === null) {\n return { valid: true, errors: [] };\n }\n\n const errors: ValidationError[] = [];\n const prefix = 'agentTeams';\n\n if (!isObject(config)) {\n return { valid: false, errors: [err(prefix, 'Must be an object', config)] };\n }\n\n if ('enabled' in config && config.enabled !== undefined) {\n const e = mustBeBoolean(`${prefix}.enabled`, config.enabled);\n if (e) errors.push(e);\n }\n\n if ('strategy' in config && config.strategy !== undefined) {\n const e = oneOf(`${prefix}.strategy`, config.strategy, ALLOWED_STRATEGIES);\n if (e) errors.push(e);\n }\n\n if ('maxTicketsPerTeam' in config && config.maxTicketsPerTeam !== undefined) {\n const e = numberInRange(`${prefix}.maxTicketsPerTeam`, config.maxTicketsPerTeam, 1, 20);\n if (e) errors.push(e);\n }\n\n if ('maxParallelEpics' in config && config.maxParallelEpics !== undefined) {\n const e = numberInRange(`${prefix}.maxParallelEpics`, config.maxParallelEpics, 1, 10);\n if (e) errors.push(e);\n }\n\n if ('branchPrefix' in config && config.branchPrefix !== undefined) {\n const e = mustBeString(`${prefix}.branchPrefix`, config.branchPrefix);\n if (e) errors.push(e);\n }\n\n if ('timeoutMinutes' in config && config.timeoutMinutes !== undefined) {\n const e = numberInRange(`${prefix}.timeoutMinutes`, config.timeoutMinutes, 5, 60);\n if (e) errors.push(e);\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n/**\n * Validate the `activeSpecification` config section.\n *\n * Returns valid:true if the section is absent/undefined.\n * When present, `id`, `title`, and `activatedAt` are required.\n */\nexport function validateActiveSpecification(config: unknown): ValidationResult {\n if (config === undefined || config === null) {\n return { valid: true, errors: [] };\n }\n\n const errors: ValidationError[] = [];\n const prefix = 'activeSpecification';\n\n if (!isObject(config)) {\n return { valid: false, errors: [err(prefix, 'Must be an object', config)] };\n }\n\n // id — required, non-empty string\n if (!('id' in config) || config.id === undefined) {\n errors.push(err(`${prefix}.id`, 'Required field is missing', undefined));\n } else {\n const e = mustBeNonEmptyString(`${prefix}.id`, config.id);\n if (e) errors.push(e);\n }\n\n // title — required, non-empty string, max 200 chars\n if (!('title' in config) || config.title === undefined) {\n errors.push(err(`${prefix}.title`, 'Required field is missing', undefined));\n } else {\n const e = mustBeNonEmptyString(`${prefix}.title`, config.title);\n if (e) {\n errors.push(e);\n } else if (typeof config.title === 'string' && config.title.length > 200) {\n errors.push(err(`${prefix}.title`, 'Must be at most 200 characters', config.title));\n }\n }\n\n // activatedAt — required, valid ISO 8601\n if (!('activatedAt' in config) || config.activatedAt === undefined) {\n errors.push(err(`${prefix}.activatedAt`, 'Required field is missing', undefined));\n } else if (typeof config.activatedAt !== 'string') {\n errors.push(err(`${prefix}.activatedAt`, 'Must be a string', config.activatedAt));\n } else if (isNaN(Date.parse(config.activatedAt))) {\n errors.push(err(`${prefix}.activatedAt`, 'Must be a valid ISO 8601 date string', config.activatedAt));\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n// ---------------------------------------------------------------------------\n// Top-level validator\n// ---------------------------------------------------------------------------\n\n/**\n * Validate the entire .specforge/config.json for Agent Teams sections.\n *\n * Orchestrates sub-validators and aggregates all errors.\n * Only validates the Agent Teams sections — existing ProjectConfig\n * fields are validated elsewhere.\n *\n * @param config - Raw parsed JSON object from .specforge/config.json\n * @returns Combined validation result from all sections\n */\nexport function validateSpecforgeJson(config: unknown): ValidationResult {\n if (config === undefined || config === null) {\n return { valid: true, errors: [] };\n }\n\n if (!isObject(config)) {\n return { valid: false, errors: [err('config', 'Must be an object', config)] };\n }\n\n const results = [\n validateAgentTeamsConfig(config.agentTeams),\n validateActiveSpecification(config.activeSpecification),\n ];\n\n const errors = results.flatMap(r => r.errors);\n return { valid: errors.length === 0, errors };\n}\n\n/**\n * Format validation errors into human-readable lines.\n *\n * @param errors - Array of validation errors\n * @returns Formatted error strings, one per line\n */\nexport function formatValidationErrors(errors: ValidationError[]): string[] {\n return errors.map(e => {\n const valueStr = e.value === undefined ? 'undefined' : JSON.stringify(e.value);\n return ` ${e.field}: ${e.message} (got ${valueStr})`;\n });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Config Writer
3
+ *
4
+ * Provides safe config file writing with atomic operations and proper permissions.
5
+ *
6
+ * Security features:
7
+ * - Global config (contains API key) written with mode 0600 (owner read/write only)
8
+ * - Global directory created with mode 0700 (owner access only)
9
+ * - Project config written with standard permissions (0644)
10
+ * - Path validation to prevent writes outside allowed directories
11
+ */
12
+ import { GlobalConfig, ProjectConfig } from './types.js';
13
+ import type { ActiveSpecification, AgentTeamsConfig } from './agent-teams.types.js';
14
+ /**
15
+ * Ensure global config directory exists with secure permissions
16
+ *
17
+ * Creates ~/.specforge/ if it doesn't exist, with mode 0700 (owner-only access).
18
+ * Idempotent - safe to call multiple times.
19
+ *
20
+ * @throws Error if directory creation fails
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * ensureGlobalDir();
25
+ * // ~/.specforge/ now exists with drwx------ permissions
26
+ * ```
27
+ */
28
+ export declare function ensureGlobalDir(): void;
29
+ /**
30
+ * Save global configuration
31
+ *
32
+ * Merges the provided config with existing global config and writes to
33
+ * ~/.specforge/config.json with secure permissions (0600).
34
+ *
35
+ * Undefined values are removed from the final config.
36
+ *
37
+ * @param config - Partial global config to save
38
+ * @throws Error if write fails
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * // Save API key
43
+ * saveGlobalConfig({ apiKey: 'sf_live_...' });
44
+ *
45
+ * // Update MCP output format, keeping existing apiKey
46
+ * saveGlobalConfig({ mcpOutputFormat: 'json' });
47
+ * ```
48
+ */
49
+ export declare function saveGlobalConfig(config: Partial<GlobalConfig>): void;
50
+ /**
51
+ * Delete a field from global config
52
+ *
53
+ * Loads existing config, removes the specified field, and saves.
54
+ * No-op if the field doesn't exist.
55
+ *
56
+ * @param field - Field name to remove
57
+ * @throws Error if read/write fails
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * deleteGlobalConfigField('apiKey');
62
+ * deleteGlobalConfigField('defaultProjectId');
63
+ * ```
64
+ */
65
+ export declare function deleteGlobalConfigField(field: keyof GlobalConfig): void;
66
+ /**
67
+ * Save project configuration
68
+ *
69
+ * Merges the provided config with existing project config and writes to
70
+ * `.specforge/config.json` in the target directory.
71
+ *
72
+ * Undefined values are removed from the final config.
73
+ *
74
+ * @param config - Partial project config to save
75
+ * @param targetDir - Directory to write the config under (defaults to cwd)
76
+ * @throws Error if write fails
77
+ * @throws ConfigError if a legacy `.specforge.json` is detected
78
+ */
79
+ export declare function saveProjectConfig(config: Partial<ProjectConfig>, targetDir?: string): void;
80
+ /**
81
+ * Set the active specification in .specforge.json
82
+ *
83
+ * Updates only the `activeSpecification` section, preserving all other fields.
84
+ * Pass `null` to remove the active specification entirely.
85
+ *
86
+ * @param spec - ActiveSpecification to set, or null to clear
87
+ * @param targetDir - Directory containing .specforge.json (defaults to cwd)
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * // Set active specification
92
+ * setActiveSpecification({ id: 'spec_123', title: 'My Spec', activatedAt: new Date().toISOString() });
93
+ *
94
+ * // Clear active specification
95
+ * setActiveSpecification(null);
96
+ * ```
97
+ */
98
+ export declare function setActiveSpecification(spec: ActiveSpecification | null, targetDir?: string): void;
99
+ /**
100
+ * Update the Agent Teams configuration in .specforge/config.json (v2.0.0)
101
+ *
102
+ * Claude Code Agent Teams only.
103
+ *
104
+ * Merges the provided partial config with the existing `agentTeams` section,
105
+ * preserving all other fields. Only the keys present in `config` are overwritten.
106
+ * Deprecated pre-v2.0.0 fields are stripped on write.
107
+ *
108
+ * @param config - Partial AgentTeamsConfig to merge
109
+ * @param targetDir - Directory containing config (defaults to cwd)
110
+ */
111
+ export declare function setAgentTeamsConfig(config: Partial<AgentTeamsConfig>, targetDir?: string): void;
112
+ /**
113
+ * Add an entry to .gitignore if it exists
114
+ *
115
+ * Does not create .gitignore if it doesn't exist.
116
+ * Does not duplicate existing entries.
117
+ *
118
+ * @param entry - Line to add to .gitignore
119
+ * @param targetDir - Directory containing .gitignore (defaults to cwd)
120
+ * @returns true if entry was added, false if already exists or no .gitignore
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * const added = addToGitignore('.specforge.json');
125
+ * if (added) {
126
+ * console.log('Added .specforge.json to .gitignore');
127
+ * }
128
+ * ```
129
+ */
130
+ export declare function addToGitignore(entry: string, targetDir?: string): boolean;
131
+ /**
132
+ * Remove API key from global config (logout)
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * clearApiKey();
137
+ * console.log('Logged out successfully');
138
+ * ```
139
+ */
140
+ export declare function clearApiKey(): void;
141
+ /**
142
+ * Check if global config file exists
143
+ *
144
+ * @returns true if ~/.specforge/config.json exists
145
+ */
146
+ export declare function hasGlobalConfig(): boolean;
147
+ /**
148
+ * Check if project config file exists in cwd
149
+ *
150
+ * @returns true if .specforge.json exists
151
+ */
152
+ export declare function hasProjectConfig(): boolean;
153
+ //# sourceMappingURL=writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../../src/cli/config/writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAEL,YAAY,EACZ,aAAa,EACd,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAEjB,MAAM,wBAAwB,CAAC;AAsBhC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAetC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAgCpE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,YAAY,GAAG,IAAI,CAuBvE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,EAC9B,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI,CAgCN;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,mBAAmB,GAAG,IAAI,EAChC,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI,CAaN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,EACjC,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI,CAiBN;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAiCzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
@@ -0,0 +1,168 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ CONFIG_FILES
5
+ } from "./types.js";
6
+ import { DEPRECATED_AGENT_TEAMS_FIELDS } from "./agent-teams.types.js";
7
+ import {
8
+ getGlobalConfigDir,
9
+ getGlobalConfigPath,
10
+ getProjectConfigPath
11
+ } from "./paths.js";
12
+ import { loadGlobalConfig, tryLoadProjectConfig } from "./loader.js";
13
+ const PERMISSIONS = {
14
+ /** Directory accessible only by owner (rwx------) */
15
+ SECURE_DIR: 448,
16
+ /** File readable/writable only by owner (rw-------) */
17
+ SECRET_FILE: 384,
18
+ /** Regular file permissions (rw-r--r--) */
19
+ REGULAR_FILE: 420
20
+ };
21
+ function ensureGlobalDir() {
22
+ const globalDir = getGlobalConfigDir();
23
+ if (fs.existsSync(globalDir)) {
24
+ return;
25
+ }
26
+ try {
27
+ fs.mkdirSync(globalDir, { mode: PERMISSIONS.SECURE_DIR, recursive: true });
28
+ } catch (error) {
29
+ throw new Error(
30
+ `Failed to create global config directory: ${globalDir}
31
+ Error: ${error instanceof Error ? error.message : String(error)}`
32
+ );
33
+ }
34
+ }
35
+ function saveGlobalConfig(config) {
36
+ ensureGlobalDir();
37
+ const configPath = getGlobalConfigPath();
38
+ const existing = loadGlobalConfig();
39
+ const merged = {
40
+ ...existing,
41
+ ...config
42
+ };
43
+ Object.keys(merged).forEach((key) => {
44
+ if (merged[key] === void 0) {
45
+ delete merged[key];
46
+ }
47
+ });
48
+ const content = JSON.stringify(merged, null, 2) + "\n";
49
+ try {
50
+ fs.writeFileSync(configPath, content, { mode: PERMISSIONS.SECRET_FILE });
51
+ } catch (error) {
52
+ throw new Error(
53
+ `Failed to write global config: ${configPath}
54
+ Error: ${error instanceof Error ? error.message : String(error)}`
55
+ );
56
+ }
57
+ }
58
+ function deleteGlobalConfigField(field) {
59
+ const configPath = getGlobalConfigPath();
60
+ if (!fs.existsSync(configPath)) {
61
+ return;
62
+ }
63
+ const existing = loadGlobalConfig();
64
+ delete existing[field];
65
+ const content = JSON.stringify(existing, null, 2) + "\n";
66
+ try {
67
+ fs.writeFileSync(configPath, content, { mode: PERMISSIONS.SECRET_FILE });
68
+ } catch (error) {
69
+ throw new Error(
70
+ `Failed to update global config: ${configPath}
71
+ Error: ${error instanceof Error ? error.message : String(error)}`
72
+ );
73
+ }
74
+ }
75
+ function saveProjectConfig(config, targetDir) {
76
+ const resolvedDir = targetDir ? path.resolve(targetDir) : process.cwd();
77
+ const configPath = path.join(resolvedDir, CONFIG_FILES.PROJECT_DIR, "config.json");
78
+ const existing = tryLoadProjectConfig() || {};
79
+ const merged = {
80
+ ...existing,
81
+ ...config
82
+ };
83
+ Object.keys(merged).forEach((key) => {
84
+ if (merged[key] === void 0) {
85
+ delete merged[key];
86
+ }
87
+ });
88
+ const specforgeDir = path.dirname(configPath);
89
+ if (!fs.existsSync(specforgeDir)) {
90
+ fs.mkdirSync(specforgeDir, { recursive: true });
91
+ }
92
+ const content = JSON.stringify(merged, null, 2) + "\n";
93
+ try {
94
+ fs.writeFileSync(configPath, content, { mode: PERMISSIONS.REGULAR_FILE });
95
+ } catch (error) {
96
+ throw new Error(
97
+ `Failed to write project config: ${configPath}
98
+ Error: ${error instanceof Error ? error.message : String(error)}`
99
+ );
100
+ }
101
+ }
102
+ function setActiveSpecification(spec, targetDir) {
103
+ const existing = tryLoadProjectConfig() || {};
104
+ const updated = structuredClone(existing);
105
+ if (spec === null) {
106
+ updated.activeSpecification = void 0;
107
+ } else {
108
+ updated.activeSpecification = spec;
109
+ }
110
+ saveProjectConfig(updated, targetDir);
111
+ }
112
+ function setAgentTeamsConfig(config, targetDir) {
113
+ const existing = tryLoadProjectConfig() || {};
114
+ const updated = structuredClone(existing);
115
+ const merged = {
116
+ ...updated.agentTeams,
117
+ ...config
118
+ };
119
+ for (const field of DEPRECATED_AGENT_TEAMS_FIELDS) {
120
+ delete merged[field];
121
+ }
122
+ updated.agentTeams = merged;
123
+ saveProjectConfig(updated, targetDir);
124
+ }
125
+ function addToGitignore(entry, targetDir) {
126
+ const gitignorePath = targetDir ? path.join(path.resolve(targetDir), ".gitignore") : path.resolve(".gitignore");
127
+ if (!fs.existsSync(gitignorePath)) {
128
+ return false;
129
+ }
130
+ try {
131
+ const content = fs.readFileSync(gitignorePath, "utf-8");
132
+ const lines = content.split("\n");
133
+ const normalizedEntry = entry.trim();
134
+ if (lines.some((line) => line.trim() === normalizedEntry)) {
135
+ return false;
136
+ }
137
+ const newContent = content.endsWith("\n") ? content + normalizedEntry + "\n" : content + "\n" + normalizedEntry + "\n";
138
+ fs.writeFileSync(gitignorePath, newContent);
139
+ return true;
140
+ } catch (error) {
141
+ console.warn(
142
+ `Warning: Could not update .gitignore: ${error instanceof Error ? error.message : String(error)}`
143
+ );
144
+ return false;
145
+ }
146
+ }
147
+ function clearApiKey() {
148
+ deleteGlobalConfigField("apiKey");
149
+ }
150
+ function hasGlobalConfig() {
151
+ return fs.existsSync(getGlobalConfigPath());
152
+ }
153
+ function hasProjectConfig() {
154
+ return fs.existsSync(getProjectConfigPath());
155
+ }
156
+ export {
157
+ addToGitignore,
158
+ clearApiKey,
159
+ deleteGlobalConfigField,
160
+ ensureGlobalDir,
161
+ hasGlobalConfig,
162
+ hasProjectConfig,
163
+ saveGlobalConfig,
164
+ saveProjectConfig,
165
+ setActiveSpecification,
166
+ setAgentTeamsConfig
167
+ };
168
+ //# sourceMappingURL=writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/cli/config/writer.ts"],"sourcesContent":["/**\n * Config Writer\n *\n * Provides safe config file writing with atomic operations and proper permissions.\n *\n * Security features:\n * - Global config (contains API key) written with mode 0600 (owner read/write only)\n * - Global directory created with mode 0700 (owner access only)\n * - Project config written with standard permissions (0644)\n * - Path validation to prevent writes outside allowed directories\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport {\n CONFIG_FILES,\n GlobalConfig,\n ProjectConfig,\n} from './types.js';\nimport type {\n ActiveSpecification,\n AgentTeamsConfig,\n SpecforgeJsonConfig,\n} from './agent-teams.types.js';\nimport { DEPRECATED_AGENT_TEAMS_FIELDS } from './agent-teams.types.js';\nimport {\n getGlobalConfigDir,\n getGlobalConfigPath,\n getProjectConfigPath,\n isWithinGlobalDir,\n} from './paths.js';\nimport { loadGlobalConfig, tryLoadProjectConfig } from './loader.js';\n\n/**\n * File permission constants\n */\nconst PERMISSIONS = {\n /** Directory accessible only by owner (rwx------) */\n SECURE_DIR: 0o700,\n /** File readable/writable only by owner (rw-------) */\n SECRET_FILE: 0o600,\n /** Regular file permissions (rw-r--r--) */\n REGULAR_FILE: 0o644,\n} as const;\n\n/**\n * Ensure global config directory exists with secure permissions\n *\n * Creates ~/.specforge/ if it doesn't exist, with mode 0700 (owner-only access).\n * Idempotent - safe to call multiple times.\n *\n * @throws Error if directory creation fails\n *\n * @example\n * ```typescript\n * ensureGlobalDir();\n * // ~/.specforge/ now exists with drwx------ permissions\n * ```\n */\nexport function ensureGlobalDir(): void {\n const globalDir = getGlobalConfigDir();\n\n if (fs.existsSync(globalDir)) {\n return; // Already exists\n }\n\n try {\n fs.mkdirSync(globalDir, { mode: PERMISSIONS.SECURE_DIR, recursive: true });\n } catch (error) {\n throw new Error(\n `Failed to create global config directory: ${globalDir}\\n` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\n/**\n * Save global configuration\n *\n * Merges the provided config with existing global config and writes to\n * ~/.specforge/config.json with secure permissions (0600).\n *\n * Undefined values are removed from the final config.\n *\n * @param config - Partial global config to save\n * @throws Error if write fails\n *\n * @example\n * ```typescript\n * // Save API key\n * saveGlobalConfig({ apiKey: 'sf_live_...' });\n *\n * // Update MCP output format, keeping existing apiKey\n * saveGlobalConfig({ mcpOutputFormat: 'json' });\n * ```\n */\nexport function saveGlobalConfig(config: Partial<GlobalConfig>): void {\n ensureGlobalDir();\n\n const configPath = getGlobalConfigPath();\n\n // Load existing config\n const existing = loadGlobalConfig();\n\n // Merge configs\n const merged: GlobalConfig = {\n ...existing,\n ...config,\n };\n\n // Remove undefined values\n Object.keys(merged).forEach(key => {\n if (merged[key as keyof GlobalConfig] === undefined) {\n delete merged[key as keyof GlobalConfig];\n }\n });\n\n // Write with secure permissions\n const content = JSON.stringify(merged, null, 2) + '\\n';\n\n try {\n fs.writeFileSync(configPath, content, { mode: PERMISSIONS.SECRET_FILE });\n } catch (error) {\n throw new Error(\n `Failed to write global config: ${configPath}\\n` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\n/**\n * Delete a field from global config\n *\n * Loads existing config, removes the specified field, and saves.\n * No-op if the field doesn't exist.\n *\n * @param field - Field name to remove\n * @throws Error if read/write fails\n *\n * @example\n * ```typescript\n * deleteGlobalConfigField('apiKey');\n * deleteGlobalConfigField('defaultProjectId');\n * ```\n */\nexport function deleteGlobalConfigField(field: keyof GlobalConfig): void {\n const configPath = getGlobalConfigPath();\n\n if (!fs.existsSync(configPath)) {\n return; // No config to modify\n }\n\n const existing = loadGlobalConfig();\n\n // Remove field\n delete existing[field];\n\n // Save updated config\n const content = JSON.stringify(existing, null, 2) + '\\n';\n\n try {\n fs.writeFileSync(configPath, content, { mode: PERMISSIONS.SECRET_FILE });\n } catch (error) {\n throw new Error(\n `Failed to update global config: ${configPath}\\n` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\n/**\n * Save project configuration\n *\n * Merges the provided config with existing project config and writes to\n * `.specforge/config.json` in the target directory.\n *\n * Undefined values are removed from the final config.\n *\n * @param config - Partial project config to save\n * @param targetDir - Directory to write the config under (defaults to cwd)\n * @throws Error if write fails\n * @throws ConfigError if a legacy `.specforge.json` is detected\n */\nexport function saveProjectConfig(\n config: Partial<ProjectConfig>,\n targetDir?: string\n): void {\n const resolvedDir = targetDir ? path.resolve(targetDir) : process.cwd();\n const configPath = path.join(resolvedDir, CONFIG_FILES.PROJECT_DIR, 'config.json');\n\n const existing = tryLoadProjectConfig() || {};\n\n const merged: ProjectConfig = {\n ...existing,\n ...config,\n };\n\n Object.keys(merged).forEach(key => {\n if (merged[key as keyof ProjectConfig] === undefined) {\n delete merged[key as keyof ProjectConfig];\n }\n });\n\n const specforgeDir = path.dirname(configPath);\n if (!fs.existsSync(specforgeDir)) {\n fs.mkdirSync(specforgeDir, { recursive: true });\n }\n\n const content = JSON.stringify(merged, null, 2) + '\\n';\n\n try {\n fs.writeFileSync(configPath, content, { mode: PERMISSIONS.REGULAR_FILE });\n } catch (error) {\n throw new Error(\n `Failed to write project config: ${configPath}\\n` +\n `Error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n}\n\n/**\n * Set the active specification in .specforge.json\n *\n * Updates only the `activeSpecification` section, preserving all other fields.\n * Pass `null` to remove the active specification entirely.\n *\n * @param spec - ActiveSpecification to set, or null to clear\n * @param targetDir - Directory containing .specforge.json (defaults to cwd)\n *\n * @example\n * ```typescript\n * // Set active specification\n * setActiveSpecification({ id: 'spec_123', title: 'My Spec', activatedAt: new Date().toISOString() });\n *\n * // Clear active specification\n * setActiveSpecification(null);\n * ```\n */\nexport function setActiveSpecification(\n spec: ActiveSpecification | null,\n targetDir?: string\n): void {\n const existing: SpecforgeJsonConfig = tryLoadProjectConfig() || {};\n const updated = structuredClone(existing);\n\n if (spec === null) {\n // Set to undefined (not delete) so the spread in saveProjectConfig\n // overrides the existing value, and the remove-undefined loop cleans it up.\n updated.activeSpecification = undefined;\n } else {\n updated.activeSpecification = spec;\n }\n\n saveProjectConfig(updated as Partial<ProjectConfig>, targetDir);\n}\n\n/**\n * Update the Agent Teams configuration in .specforge/config.json (v2.0.0)\n *\n * Claude Code Agent Teams only.\n *\n * Merges the provided partial config with the existing `agentTeams` section,\n * preserving all other fields. Only the keys present in `config` are overwritten.\n * Deprecated pre-v2.0.0 fields are stripped on write.\n *\n * @param config - Partial AgentTeamsConfig to merge\n * @param targetDir - Directory containing config (defaults to cwd)\n */\nexport function setAgentTeamsConfig(\n config: Partial<AgentTeamsConfig>,\n targetDir?: string\n): void {\n const existing: SpecforgeJsonConfig = tryLoadProjectConfig() || {};\n const updated = structuredClone(existing);\n\n const merged: Record<string, unknown> = {\n ...updated.agentTeams,\n ...config,\n };\n\n // Strip deprecated pre-v2.0.0 fields\n for (const field of DEPRECATED_AGENT_TEAMS_FIELDS) {\n delete merged[field];\n }\n\n updated.agentTeams = merged as AgentTeamsConfig;\n\n saveProjectConfig(updated, targetDir);\n}\n\n/**\n * Add an entry to .gitignore if it exists\n *\n * Does not create .gitignore if it doesn't exist.\n * Does not duplicate existing entries.\n *\n * @param entry - Line to add to .gitignore\n * @param targetDir - Directory containing .gitignore (defaults to cwd)\n * @returns true if entry was added, false if already exists or no .gitignore\n *\n * @example\n * ```typescript\n * const added = addToGitignore('.specforge.json');\n * if (added) {\n * console.log('Added .specforge.json to .gitignore');\n * }\n * ```\n */\nexport function addToGitignore(entry: string, targetDir?: string): boolean {\n const gitignorePath = targetDir\n ? path.join(path.resolve(targetDir), '.gitignore')\n : path.resolve('.gitignore');\n\n if (!fs.existsSync(gitignorePath)) {\n return false;\n }\n\n try {\n const content = fs.readFileSync(gitignorePath, 'utf-8');\n const lines = content.split('\\n');\n\n // Check if entry already exists (exact match, trimmed)\n const normalizedEntry = entry.trim();\n if (lines.some(line => line.trim() === normalizedEntry)) {\n return false; // Already exists\n }\n\n // Add entry with proper newline handling\n const newContent = content.endsWith('\\n')\n ? content + normalizedEntry + '\\n'\n : content + '\\n' + normalizedEntry + '\\n';\n\n fs.writeFileSync(gitignorePath, newContent);\n return true;\n } catch (error) {\n // Log warning but don't fail - this is a nice-to-have\n console.warn(\n `Warning: Could not update .gitignore: ${error instanceof Error ? error.message : String(error)}`\n );\n return false;\n }\n}\n\n/**\n * Remove API key from global config (logout)\n *\n * @example\n * ```typescript\n * clearApiKey();\n * console.log('Logged out successfully');\n * ```\n */\nexport function clearApiKey(): void {\n deleteGlobalConfigField('apiKey');\n}\n\n/**\n * Check if global config file exists\n *\n * @returns true if ~/.specforge/config.json exists\n */\nexport function hasGlobalConfig(): boolean {\n return fs.existsSync(getGlobalConfigPath());\n}\n\n/**\n * Check if project config file exists in cwd\n *\n * @returns true if .specforge.json exists\n */\nexport function hasProjectConfig(): boolean {\n return fs.existsSync(getProjectConfigPath());\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * SpecForge MCP CLI Entry Point
3
+ *
4
+ * Main CLI application using Commander.js.
5
+ * Sets up command routing, middleware, and global options.
6
+ */
7
+ import { Command } from 'commander';
8
+ /**
9
+ * CLI version - read from package.json
10
+ */
11
+ declare const CLI_VERSION: string;
12
+ /**
13
+ * The main CLI program instance
14
+ */
15
+ declare const program: Command;
16
+ /**
17
+ * Run the CLI with given arguments
18
+ *
19
+ * @param argv - Command line arguments (defaults to process.argv)
20
+ */
21
+ export declare function runCLI(argv?: string[]): void;
22
+ /**
23
+ * Export program for testing and extension
24
+ */
25
+ export { program };
26
+ /**
27
+ * Export version for external use
28
+ */
29
+ export { CLI_VERSION };
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0BpC;;GAEG;AACH,QAAA,MAAM,WAAW,EAAE,MAAoB,CAAC;AAqKxC;;GAEG;AACH,QAAA,MAAM,OAAO,SAAkB,CAAC;AAShC;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,IAAI,CAa1D;AAED;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB;;GAEG;AACH,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,142 @@
1
+ import { createRequire } from "node:module";
2
+ import { Command } from "commander";
3
+ import { authGuardHook } from "./middleware/auth-guard.js";
4
+ import { withErrorHandler, debugLog, handleError } from "./middleware/error-handler.js";
5
+ import { resolveConfig } from "./config/loader.js";
6
+ import { colors } from "./ui/index.js";
7
+ import { printBanner } from "./ui/banner.js";
8
+ import { CHANNEL } from "../channel.js";
9
+ const BANNER_SKIP_COMMANDS = /* @__PURE__ */ new Set(["init", "login", "serve"]);
10
+ const require2 = createRequire(import.meta.url);
11
+ const pkg = require2("../../package.json");
12
+ import {
13
+ registerLoginCommand,
14
+ registerInitCommand,
15
+ registerConfigureCommand,
16
+ registerDoctorCommand,
17
+ registerStatusCommand,
18
+ registerSwitchCommand,
19
+ registerFeedbackCommand,
20
+ registerMembersCommand,
21
+ registerInvitationsCommand
22
+ } from "./commands/index.js";
23
+ import { registerDebugCommands } from "./commands/debug/index.js";
24
+ import { registerScaffoldCommand } from "./commands/scaffold/index.js";
25
+ const CLI_VERSION = pkg.version;
26
+ const CLI_NAME = CHANNEL.name;
27
+ function createProgram() {
28
+ const program2 = new Command();
29
+ program2.name(CLI_NAME).description("SpecForge MCP CLI - Configure and interact with SpecForge").version(CLI_VERSION, "-v, --version", "Display version number");
30
+ program2.option("--debug", "Enable debug output").option("--no-color", "Disable colored output").option("--json", "Output in JSON format");
31
+ program2.hook("preAction", (thisCommand, actionCommand) => {
32
+ const commandName = actionCommand.name();
33
+ if (thisCommand.opts().debug) {
34
+ process.env.SPECFORGE_DEBUG = "true";
35
+ }
36
+ if (thisCommand.opts().noColor) {
37
+ process.env.NO_COLOR = "1";
38
+ }
39
+ const jsonOutput = !!thisCommand.opts().json;
40
+ const programState = thisCommand;
41
+ if (!programState._bannerPrinted && process.stdout.isTTY && !jsonOutput && !BANNER_SKIP_COMMANDS.has(commandName)) {
42
+ printBanner("compact");
43
+ programState._bannerPrinted = true;
44
+ }
45
+ debugLog(`Running command: ${commandName}`);
46
+ debugLog("Resolved config:", resolveConfig());
47
+ authGuardHook({ name: () => commandName });
48
+ });
49
+ return program2;
50
+ }
51
+ function registerServeCommand(program2) {
52
+ program2.command("serve", { isDefault: true, hidden: true }).description("Start MCP server (default)").action(withErrorHandler(async () => {
53
+ debugLog("Starting MCP server");
54
+ const { loadConfig, validateConfig } = await import("../config/index.js");
55
+ const { createServer, startServer } = await import("../server.js");
56
+ const config = await loadConfig();
57
+ validateConfig(config);
58
+ config.mcpOutputFormat = resolveConfig().mcpOutputFormat;
59
+ const server = await createServer(config);
60
+ await startServer(server);
61
+ }));
62
+ }
63
+ function registerCommands(program2) {
64
+ registerLoginCommand(program2);
65
+ registerInitCommand(program2);
66
+ registerConfigureCommand(program2);
67
+ registerDoctorCommand(program2);
68
+ registerStatusCommand(program2);
69
+ registerSwitchCommand(program2);
70
+ registerDebugCommands(program2);
71
+ registerScaffoldCommand(program2);
72
+ registerFeedbackCommand(program2);
73
+ registerMembersCommand(program2);
74
+ registerInvitationsCommand(program2);
75
+ }
76
+ function configureHelp(program2) {
77
+ program2.configureHelp({
78
+ sortSubcommands: false,
79
+ subcommandTerm: (cmd) => cmd.name()
80
+ });
81
+ program2.addHelpText("after", `
82
+ ${colors.muted("Command Groups:")}
83
+
84
+ ${colors.bold("Authentication")}
85
+ login Authenticate with SpecForge API
86
+ configure Read/write configuration
87
+
88
+ ${colors.bold("Setup")}
89
+ init Initialize SpecForge in a project
90
+ scaffold Generate skills, agents, and commands
91
+
92
+ ${colors.bold("Diagnostics")}
93
+ doctor Health check
94
+ status Project status and session info
95
+
96
+ ${colors.bold("Context")}
97
+ switch Switch active project or specification
98
+
99
+ ${colors.bold("Collaboration")}
100
+ members Manage project members
101
+ invitations Manage invitations
102
+
103
+ ${colors.bold("Debug")}
104
+ debug call Direct MCP tool dispatch
105
+ debug tools List available MCP tools
106
+ debug test Connection test
107
+ debug whoami Identity and config info
108
+
109
+ ${colors.bold("Info")}
110
+ feedback List and submit feedback
111
+
112
+ ${colors.muted("Examples:")}
113
+ $ ${CLI_NAME} login # Authenticate with API key
114
+ $ ${CLI_NAME} init # Initialize in current directory
115
+ $ ${CLI_NAME} status # Show current status
116
+ $ ${CLI_NAME} switch <id> # Switch active project/spec
117
+
118
+ ${colors.muted("Documentation:")}
119
+ ${colors.primary("https://docs.specforge.com/cli")}
120
+
121
+ ${colors.muted("Report issues:")}
122
+ ${colors.primary("https://github.com/specforge/mcp/issues")}
123
+ `);
124
+ }
125
+ const program = createProgram();
126
+ registerServeCommand(program);
127
+ registerCommands(program);
128
+ configureHelp(program);
129
+ function runCLI(argv = process.argv) {
130
+ try {
131
+ program.parse(argv);
132
+ } catch (error) {
133
+ debugLog("CLI parse error:", error);
134
+ handleError(error);
135
+ }
136
+ }
137
+ export {
138
+ CLI_VERSION,
139
+ program,
140
+ runCLI
141
+ };
142
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["/**\n * SpecForge MCP CLI Entry Point\n *\n * Main CLI application using Commander.js.\n * Sets up command routing, middleware, and global options.\n */\n\nimport { createRequire } from 'node:module';\nimport { Command } from 'commander';\nimport { authGuardHook } from './middleware/auth-guard.js';\nimport { withErrorHandler, debugLog, handleError } from './middleware/error-handler.js';\nimport { resolveConfig } from './config/loader.js';\nimport { colors } from './ui/index.js';\nimport { printBanner } from './ui/banner.js';\nimport { CHANNEL } from '../channel.js';\n\nconst BANNER_SKIP_COMMANDS = new Set(['init', 'login', 'serve']);\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../../package.json');\nimport {\n registerLoginCommand,\n registerInitCommand,\n registerConfigureCommand,\n registerDoctorCommand,\n registerStatusCommand,\n registerSwitchCommand,\n registerFeedbackCommand,\n registerMembersCommand,\n registerInvitationsCommand,\n} from './commands/index.js';\nimport { registerDebugCommands } from './commands/debug/index.js';\nimport { registerScaffoldCommand } from './commands/scaffold/index.js';\n\n/**\n * CLI version - read from package.json\n */\nconst CLI_VERSION: string = pkg.version;\n\n/**\n * CLI name for display — varies per build channel (`specforge` vs.\n * `specforge-canary`). The `SpecForge` brand text in banners stays fixed.\n */\nconst CLI_NAME = CHANNEL.name;\n\n/**\n * Create and configure the CLI program\n */\nfunction createProgram(): Command {\n const program = new Command();\n\n // Basic program info\n program\n .name(CLI_NAME)\n .description('SpecForge MCP CLI - Configure and interact with SpecForge')\n .version(CLI_VERSION, '-v, --version', 'Display version number');\n\n // Global options\n program\n .option('--debug', 'Enable debug output')\n .option('--no-color', 'Disable colored output')\n .option('--json', 'Output in JSON format');\n\n // Pre-action hook for auth guard\n program.hook('preAction', (thisCommand, actionCommand) => {\n const commandName = actionCommand.name();\n\n // Set debug mode from flag\n if (thisCommand.opts().debug) {\n process.env.SPECFORGE_DEBUG = 'true';\n }\n\n // Disable colors if requested\n if (thisCommand.opts().noColor) {\n // chalk.level = 0 is handled in colors module\n process.env.NO_COLOR = '1';\n }\n\n // Compact brand header for human-facing commands\n const jsonOutput = !!thisCommand.opts().json;\n const programState = thisCommand as Command & { _bannerPrinted?: boolean };\n if (\n !programState._bannerPrinted &&\n process.stdout.isTTY &&\n !jsonOutput &&\n !BANNER_SKIP_COMMANDS.has(commandName)\n ) {\n printBanner('compact');\n programState._bannerPrinted = true;\n }\n\n debugLog(`Running command: ${commandName}`);\n debugLog('Resolved config:', resolveConfig());\n\n // Run auth guard\n authGuardHook({ name: () => commandName });\n });\n\n return program;\n}\n\n/**\n * Register the default serve command (MCP server mode)\n *\n * This is the hidden default when no subcommand is provided.\n * Maintains backward compatibility with direct MCP server usage.\n */\nfunction registerServeCommand(program: Command): void {\n program\n .command('serve', { isDefault: true, hidden: true })\n .description('Start MCP server (default)')\n .action(withErrorHandler(async () => {\n debugLog('Starting MCP server');\n\n // Dynamic import to avoid loading server code for CLI commands\n const { loadConfig, validateConfig } = await import('../config/index.js');\n const { createServer, startServer } = await import('../server.js');\n\n // Load and validate config\n const config = await loadConfig();\n validateConfig(config);\n // Honor the resolved MCP output format (env > project > global > default).\n config.mcpOutputFormat = resolveConfig().mcpOutputFormat;\n\n // Create and start server\n const server = await createServer(config);\n await startServer(server);\n }));\n}\n\n/**\n * Register all CLI commands\n */\nfunction registerCommands(program: Command): void {\n registerLoginCommand(program);\n registerInitCommand(program);\n registerConfigureCommand(program);\n registerDoctorCommand(program);\n registerStatusCommand(program);\n registerSwitchCommand(program);\n registerDebugCommands(program);\n registerScaffoldCommand(program);\n registerFeedbackCommand(program);\n registerMembersCommand(program);\n registerInvitationsCommand(program);\n}\n\n/**\n * Configure help output styling\n */\nfunction configureHelp(program: Command): void {\n program.configureHelp({\n sortSubcommands: false,\n subcommandTerm: (cmd) => cmd.name(),\n });\n\n // Custom help with organized command groups\n program.addHelpText('after', `\n${colors.muted('Command Groups:')}\n\n ${colors.bold('Authentication')}\n login Authenticate with SpecForge API\n configure Read/write configuration\n\n ${colors.bold('Setup')}\n init Initialize SpecForge in a project\n scaffold Generate skills, agents, and commands\n\n ${colors.bold('Diagnostics')}\n doctor Health check\n status Project status and session info\n\n ${colors.bold('Context')}\n switch Switch active project or specification\n\n ${colors.bold('Collaboration')}\n members Manage project members\n invitations Manage invitations\n\n ${colors.bold('Debug')}\n debug call Direct MCP tool dispatch\n debug tools List available MCP tools\n debug test Connection test\n debug whoami Identity and config info\n\n ${colors.bold('Info')}\n feedback List and submit feedback\n\n${colors.muted('Examples:')}\n $ ${CLI_NAME} login # Authenticate with API key\n $ ${CLI_NAME} init # Initialize in current directory\n $ ${CLI_NAME} status # Show current status\n $ ${CLI_NAME} switch <id> # Switch active project/spec\n\n${colors.muted('Documentation:')}\n ${colors.primary('https://docs.specforge.com/cli')}\n\n${colors.muted('Report issues:')}\n ${colors.primary('https://github.com/specforge/mcp/issues')}\n`);\n}\n\n/**\n * The main CLI program instance\n */\nconst program = createProgram();\n\n// Register commands\nregisterServeCommand(program);\nregisterCommands(program);\n\n// Configure help\nconfigureHelp(program);\n\n/**\n * Run the CLI with given arguments\n *\n * @param argv - Command line arguments (defaults to process.argv)\n */\nexport function runCLI(argv: string[] = process.argv): void {\n try {\n program.parse(argv);\n } catch (error) {\n // Commander handles action-handler errors via `withErrorHandler`, but\n // anything thrown from a `preAction` hook (e.g. `resolveConfig()`\n // surfacing a `LEGACY_CONFIG_DETECTED` ConfigError for a project dir\n // with a legacy `.specforge.json`) reaches here. Use the shared\n // formatter so the user sees the message + hint instead of a silent\n // exit. Keep `debugLog` for stack-trace-under-debug ergonomics.\n debugLog('CLI parse error:', error);\n handleError(error);\n }\n}\n\n/**\n * Export program for testing and extension\n */\nexport { program };\n\n/**\n * Export version for external use\n */\nexport { CLI_VERSION };\n"],"mappings":"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","names":["require","program"]}