@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,79 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { CONFIG_FILES } from "./types.js";
5
+ function getConfigPaths() {
6
+ const globalDir = getGlobalConfigDir();
7
+ return {
8
+ globalDir,
9
+ globalConfig: path.join(globalDir, CONFIG_FILES.GLOBAL_CONFIG),
10
+ projectConfig: path.resolve(CONFIG_FILES.PROJECT_CONFIG),
11
+ mcpConfig: path.resolve(CONFIG_FILES.MCP_CONFIG)
12
+ };
13
+ }
14
+ function getGlobalConfigDir() {
15
+ return path.join(os.homedir(), CONFIG_FILES.GLOBAL_DIR);
16
+ }
17
+ function getGlobalConfigPath() {
18
+ return path.join(getGlobalConfigDir(), CONFIG_FILES.GLOBAL_CONFIG);
19
+ }
20
+ function getProjectConfigPath() {
21
+ return path.resolve(CONFIG_FILES.PROJECT_CONFIG);
22
+ }
23
+ function getMcpConfigPath() {
24
+ return path.resolve(CONFIG_FILES.MCP_CONFIG);
25
+ }
26
+ function getPlanningSessionsRegistryPath(targetDir) {
27
+ const resolvedDir = targetDir ? path.resolve(targetDir) : process.cwd();
28
+ return path.join(resolvedDir, CONFIG_FILES.PROJECT_DIR, "planning-sessions.json");
29
+ }
30
+ function findProjectConfigPath(startDir) {
31
+ let currentDir = startDir ? path.resolve(startDir) : process.cwd();
32
+ const root = path.parse(currentDir).root;
33
+ while (currentDir !== root) {
34
+ const newConfigPath = path.join(currentDir, CONFIG_FILES.PROJECT_CONFIG);
35
+ if (fs.existsSync(newConfigPath)) {
36
+ return newConfigPath;
37
+ }
38
+ const legacyConfigPath = path.join(currentDir, CONFIG_FILES.LEGACY_PROJECT_CONFIG);
39
+ if (fs.existsSync(legacyConfigPath)) {
40
+ return legacyConfigPath;
41
+ }
42
+ currentDir = path.dirname(currentDir);
43
+ }
44
+ const rootNew = path.join(root, CONFIG_FILES.PROJECT_CONFIG);
45
+ if (fs.existsSync(rootNew)) {
46
+ return rootNew;
47
+ }
48
+ const rootLegacy = path.join(root, CONFIG_FILES.LEGACY_PROJECT_CONFIG);
49
+ if (fs.existsSync(rootLegacy)) {
50
+ return rootLegacy;
51
+ }
52
+ return null;
53
+ }
54
+ function findProjectRoot() {
55
+ const configPath = findProjectConfigPath();
56
+ if (!configPath) return null;
57
+ if (configPath.endsWith(CONFIG_FILES.PROJECT_CONFIG)) {
58
+ return path.dirname(path.dirname(configPath));
59
+ }
60
+ return path.dirname(configPath);
61
+ }
62
+ function isWithinGlobalDir(targetPath) {
63
+ const globalDir = getGlobalConfigDir();
64
+ const resolved = path.resolve(targetPath);
65
+ const relative = path.relative(globalDir, resolved);
66
+ return !relative.startsWith("..") && !path.isAbsolute(relative);
67
+ }
68
+ export {
69
+ findProjectConfigPath,
70
+ findProjectRoot,
71
+ getConfigPaths,
72
+ getGlobalConfigDir,
73
+ getGlobalConfigPath,
74
+ getMcpConfigPath,
75
+ getPlanningSessionsRegistryPath,
76
+ getProjectConfigPath,
77
+ isWithinGlobalDir
78
+ };
79
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/cli/config/paths.ts"],"sourcesContent":["/**\n * Config Paths Resolution\n *\n * Provides functions for resolving configuration file paths.\n * Handles both global (~/.specforge/) and project-level (.specforge.json) paths.\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { ConfigPaths, CONFIG_FILES } from './types.js';\n\n/**\n * Get all configuration file paths\n *\n * @returns ConfigPaths object with all resolved paths\n *\n * @example\n * ```typescript\n * const paths = getConfigPaths();\n * console.log(paths.globalDir); // /home/user/.specforge\n * console.log(paths.globalConfig); // /home/user/.specforge/config.json\n * console.log(paths.projectConfig); // /current/dir/.specforge.json\n * ```\n */\nexport function getConfigPaths(): ConfigPaths {\n const globalDir = getGlobalConfigDir();\n\n return {\n globalDir,\n globalConfig: path.join(globalDir, CONFIG_FILES.GLOBAL_CONFIG),\n projectConfig: path.resolve(CONFIG_FILES.PROJECT_CONFIG),\n mcpConfig: path.resolve(CONFIG_FILES.MCP_CONFIG),\n };\n}\n\n/**\n * Get the global configuration directory path\n *\n * Uses $HOME/.specforge on all platforms.\n * Does not create the directory - that's handled by the writer.\n *\n * @returns Absolute path to global config directory\n *\n * @example\n * ```typescript\n * const dir = getGlobalConfigDir();\n * // Linux/macOS: /home/user/.specforge\n * // Windows: C:\\Users\\user\\.specforge\n * ```\n */\nexport function getGlobalConfigDir(): string {\n return path.join(os.homedir(), CONFIG_FILES.GLOBAL_DIR);\n}\n\n/**\n * Get the global configuration file path\n *\n * @returns Absolute path to ~/.specforge/config.json\n */\nexport function getGlobalConfigPath(): string {\n return path.join(getGlobalConfigDir(), CONFIG_FILES.GLOBAL_CONFIG);\n}\n\n/**\n * Get the project configuration file path\n *\n * Resolves to .specforge.json in the current working directory.\n * Note: This returns the path in CWD only - for searching up the\n * directory tree, use findProjectConfigPath().\n *\n * @returns Absolute path to .specforge.json in current directory\n */\nexport function getProjectConfigPath(): string {\n return path.resolve(CONFIG_FILES.PROJECT_CONFIG);\n}\n\n/**\n * Get the MCP configuration file path\n *\n * @returns Absolute path to .mcp.json in current directory\n */\nexport function getMcpConfigPath(): string {\n return path.resolve(CONFIG_FILES.MCP_CONFIG);\n}\n\n/**\n * Get the planning-sessions registry path (M9.6 §2.6).\n *\n * Lives alongside config.json under the channel-aware project config dir\n * (`.specforge/planning-sessions.json` on stable, `.specforge-canary/...` on\n * canary — `CONFIG_FILES.PROJECT_DIR` flows from `CHANNEL.configDir`).\n *\n * @param targetDir - Project root (defaults to cwd)\n * @returns Absolute path to the planning-sessions registry file\n */\nexport function getPlanningSessionsRegistryPath(targetDir?: string): string {\n const resolvedDir = targetDir ? path.resolve(targetDir) : process.cwd();\n return path.join(resolvedDir, CONFIG_FILES.PROJECT_DIR, 'planning-sessions.json');\n}\n\n/**\n * Find project config by searching up the directory tree\n *\n * Starts from the current directory and walks up to the filesystem root,\n * looking for .specforge.json at each level.\n *\n * @param startDir - Directory to start searching from (defaults to cwd)\n * @returns Absolute path to .specforge.json if found, null otherwise\n *\n * @example\n * ```typescript\n * // Given: /home/user/projects/myapp/src/components\n * // With .specforge.json at /home/user/projects/myapp/.specforge.json\n *\n * const configPath = findProjectConfigPath();\n * // Returns: /home/user/projects/myapp/.specforge.json\n * ```\n */\nexport function findProjectConfigPath(startDir?: string): string | null {\n let currentDir = startDir ? path.resolve(startDir) : process.cwd();\n const root = path.parse(currentDir).root;\n\n while (currentDir !== root) {\n // Check new path first: .specforge/config.json\n const newConfigPath = path.join(currentDir, CONFIG_FILES.PROJECT_CONFIG);\n if (fs.existsSync(newConfigPath)) {\n return newConfigPath;\n }\n\n // Fall back to legacy: .specforge.json\n const legacyConfigPath = path.join(currentDir, CONFIG_FILES.LEGACY_PROJECT_CONFIG);\n if (fs.existsSync(legacyConfigPath)) {\n return legacyConfigPath;\n }\n\n currentDir = path.dirname(currentDir);\n }\n\n // Check root directory as well\n const rootNew = path.join(root, CONFIG_FILES.PROJECT_CONFIG);\n if (fs.existsSync(rootNew)) {\n return rootNew;\n }\n const rootLegacy = path.join(root, CONFIG_FILES.LEGACY_PROJECT_CONFIG);\n if (fs.existsSync(rootLegacy)) {\n return rootLegacy;\n }\n\n return null;\n}\n\n/**\n * Get the directory containing the project config\n *\n * Useful for resolving relative paths from the project root.\n *\n * @returns Directory path if project config found, null otherwise\n */\nexport function findProjectRoot(): string | null {\n const configPath = findProjectConfigPath();\n if (!configPath) return null;\n\n // If found at .specforge/config.json, root is two levels up\n // If found at .specforge.json (legacy), root is one level up\n if (configPath.endsWith(CONFIG_FILES.PROJECT_CONFIG)) {\n return path.dirname(path.dirname(configPath));\n }\n return path.dirname(configPath);\n}\n\n/**\n * Check if a path is within the global config directory\n *\n * Security check to prevent path traversal attacks.\n *\n * @param targetPath - Path to check\n * @returns true if path is within ~/.specforge/\n */\nexport function isWithinGlobalDir(targetPath: string): boolean {\n const globalDir = getGlobalConfigDir();\n const resolved = path.resolve(targetPath);\n const relative = path.relative(globalDir, resolved);\n\n // If the relative path starts with '..' or is absolute, it's outside\n return !relative.startsWith('..') && !path.isAbsolute(relative);\n}\n"],"mappings":"AAOA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,SAAsB,oBAAoB;AAenC,SAAS,iBAA8B;AAC5C,QAAM,YAAY,mBAAmB;AAErC,SAAO;AAAA,IACL;AAAA,IACA,cAAc,KAAK,KAAK,WAAW,aAAa,aAAa;AAAA,IAC7D,eAAe,KAAK,QAAQ,aAAa,cAAc;AAAA,IACvD,WAAW,KAAK,QAAQ,aAAa,UAAU;AAAA,EACjD;AACF;AAiBO,SAAS,qBAA6B;AAC3C,SAAO,KAAK,KAAK,GAAG,QAAQ,GAAG,aAAa,UAAU;AACxD;AAOO,SAAS,sBAA8B;AAC5C,SAAO,KAAK,KAAK,mBAAmB,GAAG,aAAa,aAAa;AACnE;AAWO,SAAS,uBAA+B;AAC7C,SAAO,KAAK,QAAQ,aAAa,cAAc;AACjD;AAOO,SAAS,mBAA2B;AACzC,SAAO,KAAK,QAAQ,aAAa,UAAU;AAC7C;AAYO,SAAS,gCAAgC,WAA4B;AAC1E,QAAM,cAAc,YAAY,KAAK,QAAQ,SAAS,IAAI,QAAQ,IAAI;AACtE,SAAO,KAAK,KAAK,aAAa,aAAa,aAAa,wBAAwB;AAClF;AAoBO,SAAS,sBAAsB,UAAkC;AACtE,MAAI,aAAa,WAAW,KAAK,QAAQ,QAAQ,IAAI,QAAQ,IAAI;AACjE,QAAM,OAAO,KAAK,MAAM,UAAU,EAAE;AAEpC,SAAO,eAAe,MAAM;AAE1B,UAAM,gBAAgB,KAAK,KAAK,YAAY,aAAa,cAAc;AACvE,QAAI,GAAG,WAAW,aAAa,GAAG;AAChC,aAAO;AAAA,IACT;AAGA,UAAM,mBAAmB,KAAK,KAAK,YAAY,aAAa,qBAAqB;AACjF,QAAI,GAAG,WAAW,gBAAgB,GAAG;AACnC,aAAO;AAAA,IACT;AAEA,iBAAa,KAAK,QAAQ,UAAU;AAAA,EACtC;AAGA,QAAM,UAAU,KAAK,KAAK,MAAM,aAAa,cAAc;AAC3D,MAAI,GAAG,WAAW,OAAO,GAAG;AAC1B,WAAO;AAAA,EACT;AACA,QAAM,aAAa,KAAK,KAAK,MAAM,aAAa,qBAAqB;AACrE,MAAI,GAAG,WAAW,UAAU,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AASO,SAAS,kBAAiC;AAC/C,QAAM,aAAa,sBAAsB;AACzC,MAAI,CAAC,WAAY,QAAO;AAIxB,MAAI,WAAW,SAAS,aAAa,cAAc,GAAG;AACpD,WAAO,KAAK,QAAQ,KAAK,QAAQ,UAAU,CAAC;AAAA,EAC9C;AACA,SAAO,KAAK,QAAQ,UAAU;AAChC;AAUO,SAAS,kBAAkB,YAA6B;AAC7D,QAAM,YAAY,mBAAmB;AACrC,QAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,QAAM,WAAW,KAAK,SAAS,WAAW,QAAQ;AAGlD,SAAO,CAAC,SAAS,WAAW,IAAI,KAAK,CAAC,KAAK,WAAW,QAAQ;AAChE;","names":[]}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Planning Sessions Registry (M9.6 §2.6)
3
+ *
4
+ * Append-only record of every planning session the CLI has touched in a
5
+ * project, keyed by `specificationId`. Lives alongside `config.json` under the
6
+ * channel-aware project config dir (`.specforge/planning-sessions.json` on
7
+ * stable, `.specforge-canary/...` on canary).
8
+ *
9
+ * The local MCP server interpose maintains it:
10
+ * - `start_planning_session` success → {@link appendPlanningSessionRegistry}
11
+ * records the new/resumed session id + start timestamp.
12
+ * - a `get_planning_status` response with `planningStatus: 'closed'` →
13
+ * {@link markPlanningSessionRegistryCompleted} stamps `completedAt`.
14
+ *
15
+ * `complete_planning_session` does NOT touch the registry — CPS-success flips
16
+ * the session to `awaiting_human_review`, not closed.
17
+ */
18
+ /** One registry entry, keyed by specificationId in the registry map. */
19
+ export interface PlanningSessionRegistryEntry {
20
+ /** The planning session id returned by start_planning_session. */
21
+ planningSessionId: string;
22
+ /** ISO timestamp captured when the session was opened/resumed. */
23
+ startedAt: string;
24
+ /** ISO timestamp stamped when the session closed (cross_validation approved). */
25
+ completedAt?: string;
26
+ }
27
+ /** The on-disk registry shape: specificationId → entry. */
28
+ export type PlanningSessionsRegistry = Record<string, PlanningSessionRegistryEntry>;
29
+ /**
30
+ * Load the registry. Returns an empty map when the file is absent or
31
+ * unparseable (the registry is advisory bookkeeping — never throw on read).
32
+ */
33
+ export declare function loadPlanningSessionsRegistry(targetDir?: string): PlanningSessionsRegistry;
34
+ /**
35
+ * Record a newly started (or resumed) planning session for a spec.
36
+ * Overwrites any prior open entry for the same spec — there is at most one
37
+ * live planning session per spec (the lifecycle enforces this).
38
+ */
39
+ export declare function appendPlanningSessionRegistry(entry: {
40
+ specificationId: string;
41
+ planningSessionId: string;
42
+ startedAt: string;
43
+ }, targetDir?: string): void;
44
+ /**
45
+ * Stamp `completedAt` on the open entry for a spec. No-op if there is no entry
46
+ * (e.g. registry was cleared or the session predates registry tracking).
47
+ */
48
+ export declare function markPlanningSessionRegistryCompleted(entry: {
49
+ specificationId: string;
50
+ completedAt: string;
51
+ }, targetDir?: string): void;
52
+ //# sourceMappingURL=planning-sessions-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planning-sessions-registry.d.ts","sourceRoot":"","sources":["../../../src/cli/config/planning-sessions-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,wEAAwE;AACxE,MAAM,WAAW,4BAA4B;IAC3C,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,2DAA2D;AAC3D,MAAM,MAAM,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,4BAA4B,CAAC,CAAC;AAEpF;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,wBAAwB,CAazF;AAWD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,EAChF,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI,CAON;AAED;;;GAGG;AACH,wBAAgB,oCAAoC,CAClD,KAAK,EAAE;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,EACvD,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI,CAQN"}
@@ -0,0 +1,46 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { getPlanningSessionsRegistryPath } from "./paths.js";
4
+ function loadPlanningSessionsRegistry(targetDir) {
5
+ const registryPath = getPlanningSessionsRegistryPath(targetDir);
6
+ if (!fs.existsSync(registryPath)) {
7
+ return {};
8
+ }
9
+ try {
10
+ const parsed = JSON.parse(fs.readFileSync(registryPath, "utf-8"));
11
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
12
+ } catch {
13
+ return {};
14
+ }
15
+ }
16
+ function writeRegistry(registry, targetDir) {
17
+ const registryPath = getPlanningSessionsRegistryPath(targetDir);
18
+ const dir = path.dirname(registryPath);
19
+ if (!fs.existsSync(dir)) {
20
+ fs.mkdirSync(dir, { recursive: true });
21
+ }
22
+ fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + "\n");
23
+ }
24
+ function appendPlanningSessionRegistry(entry, targetDir) {
25
+ const registry = loadPlanningSessionsRegistry(targetDir);
26
+ registry[entry.specificationId] = {
27
+ planningSessionId: entry.planningSessionId,
28
+ startedAt: entry.startedAt
29
+ };
30
+ writeRegistry(registry, targetDir);
31
+ }
32
+ function markPlanningSessionRegistryCompleted(entry, targetDir) {
33
+ const registry = loadPlanningSessionsRegistry(targetDir);
34
+ const existing = registry[entry.specificationId];
35
+ if (!existing) {
36
+ return;
37
+ }
38
+ registry[entry.specificationId] = { ...existing, completedAt: entry.completedAt };
39
+ writeRegistry(registry, targetDir);
40
+ }
41
+ export {
42
+ appendPlanningSessionRegistry,
43
+ loadPlanningSessionsRegistry,
44
+ markPlanningSessionRegistryCompleted
45
+ };
46
+ //# sourceMappingURL=planning-sessions-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/cli/config/planning-sessions-registry.ts"],"sourcesContent":["/**\n * Planning Sessions Registry (M9.6 §2.6)\n *\n * Append-only record of every planning session the CLI has touched in a\n * project, keyed by `specificationId`. Lives alongside `config.json` under the\n * channel-aware project config dir (`.specforge/planning-sessions.json` on\n * stable, `.specforge-canary/...` on canary).\n *\n * The local MCP server interpose maintains it:\n * - `start_planning_session` success → {@link appendPlanningSessionRegistry}\n * records the new/resumed session id + start timestamp.\n * - a `get_planning_status` response with `planningStatus: 'closed'` →\n * {@link markPlanningSessionRegistryCompleted} stamps `completedAt`.\n *\n * `complete_planning_session` does NOT touch the registry — CPS-success flips\n * the session to `awaiting_human_review`, not closed.\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { getPlanningSessionsRegistryPath } from './paths.js';\n\n/** One registry entry, keyed by specificationId in the registry map. */\nexport interface PlanningSessionRegistryEntry {\n /** The planning session id returned by start_planning_session. */\n planningSessionId: string;\n /** ISO timestamp captured when the session was opened/resumed. */\n startedAt: string;\n /** ISO timestamp stamped when the session closed (cross_validation approved). */\n completedAt?: string;\n}\n\n/** The on-disk registry shape: specificationId → entry. */\nexport type PlanningSessionsRegistry = Record<string, PlanningSessionRegistryEntry>;\n\n/**\n * Load the registry. Returns an empty map when the file is absent or\n * unparseable (the registry is advisory bookkeeping — never throw on read).\n */\nexport function loadPlanningSessionsRegistry(targetDir?: string): PlanningSessionsRegistry {\n const registryPath = getPlanningSessionsRegistryPath(targetDir);\n if (!fs.existsSync(registryPath)) {\n return {};\n }\n try {\n const parsed = JSON.parse(fs.readFileSync(registryPath, 'utf-8'));\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as PlanningSessionsRegistry)\n : {};\n } catch {\n return {};\n }\n}\n\nfunction writeRegistry(registry: PlanningSessionsRegistry, targetDir?: string): void {\n const registryPath = getPlanningSessionsRegistryPath(targetDir);\n const dir = path.dirname(registryPath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + '\\n');\n}\n\n/**\n * Record a newly started (or resumed) planning session for a spec.\n * Overwrites any prior open entry for the same spec — there is at most one\n * live planning session per spec (the lifecycle enforces this).\n */\nexport function appendPlanningSessionRegistry(\n entry: { specificationId: string; planningSessionId: string; startedAt: string },\n targetDir?: string\n): void {\n const registry = loadPlanningSessionsRegistry(targetDir);\n registry[entry.specificationId] = {\n planningSessionId: entry.planningSessionId,\n startedAt: entry.startedAt,\n };\n writeRegistry(registry, targetDir);\n}\n\n/**\n * Stamp `completedAt` on the open entry for a spec. No-op if there is no entry\n * (e.g. registry was cleared or the session predates registry tracking).\n */\nexport function markPlanningSessionRegistryCompleted(\n entry: { specificationId: string; completedAt: string },\n targetDir?: string\n): void {\n const registry = loadPlanningSessionsRegistry(targetDir);\n const existing = registry[entry.specificationId];\n if (!existing) {\n return;\n }\n registry[entry.specificationId] = { ...existing, completedAt: entry.completedAt };\n writeRegistry(registry, targetDir);\n}\n"],"mappings":"AAkBA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,uCAAuC;AAmBzC,SAAS,6BAA6B,WAA8C;AACzF,QAAM,eAAe,gCAAgC,SAAS;AAC9D,MAAI,CAAC,GAAG,WAAW,YAAY,GAAG;AAChC,WAAO,CAAC;AAAA,EACV;AACA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG,aAAa,cAAc,OAAO,CAAC;AAChE,WAAO,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,IAC/D,SACD,CAAC;AAAA,EACP,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,cAAc,UAAoC,WAA0B;AACnF,QAAM,eAAe,gCAAgC,SAAS;AAC9D,QAAM,MAAM,KAAK,QAAQ,YAAY;AACrC,MAAI,CAAC,GAAG,WAAW,GAAG,GAAG;AACvB,OAAG,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACvC;AACA,KAAG,cAAc,cAAc,KAAK,UAAU,UAAU,MAAM,CAAC,IAAI,IAAI;AACzE;AAOO,SAAS,8BACd,OACA,WACM;AACN,QAAM,WAAW,6BAA6B,SAAS;AACvD,WAAS,MAAM,eAAe,IAAI;AAAA,IAChC,mBAAmB,MAAM;AAAA,IACzB,WAAW,MAAM;AAAA,EACnB;AACA,gBAAc,UAAU,SAAS;AACnC;AAMO,SAAS,qCACd,OACA,WACM;AACN,QAAM,WAAW,6BAA6B,SAAS;AACvD,QAAM,WAAW,SAAS,MAAM,eAAe;AAC/C,MAAI,CAAC,UAAU;AACb;AAAA,EACF;AACA,WAAS,MAAM,eAAe,IAAI,EAAE,GAAG,UAAU,aAAa,MAAM,YAAY;AAChF,gBAAc,UAAU,SAAS;AACnC;","names":[]}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * CLI Configuration Types
3
+ *
4
+ * Defines interfaces for global, project, and resolved configuration
5
+ * used throughout the CLI application.
6
+ */
7
+ /**
8
+ * Output format options for CLI commands
9
+ */
10
+ export type OutputFormat = 'json' | 'toon';
11
+ /**
12
+ * Source tracking for configuration values
13
+ * Used for diagnostics and debugging (e.g., `specforge doctor`)
14
+ */
15
+ export type ConfigSource = 'env' | 'global' | 'project' | 'default' | null;
16
+ /**
17
+ * Global configuration stored in ~/.specforge/config.json
18
+ *
19
+ * This file contains user-level settings that apply across all projects.
20
+ * The API key is stored here for security (not in project files).
21
+ *
22
+ * @example
23
+ * ```json
24
+ * {
25
+ * "apiKey": "sf_live_abc123...",
26
+ * "mcpOutputFormat": "toon",
27
+ * "defaultProjectId": "proj_xyz"
28
+ * }
29
+ * ```
30
+ */
31
+ export interface GlobalConfig {
32
+ /** API key for SpecForge authentication (format: sf_live_*) */
33
+ apiKey?: string;
34
+ /**
35
+ * Wire encoding for the local MCP server's responses to the agent
36
+ * (TOON or JSON). NOT the human CLI output format — CLI commands render via
37
+ * their own per-command `--json`/`--toon` flags.
38
+ */
39
+ mcpOutputFormat?: OutputFormat;
40
+ /** Default project ID when not in a project directory */
41
+ defaultProjectId?: string;
42
+ }
43
+ /**
44
+ * Project configuration stored in .specforge.json
45
+ *
46
+ * This file is created in the project root when running `specforge init`.
47
+ * It links the local directory to a SpecForge project and specification.
48
+ *
49
+ * @example
50
+ * ```json
51
+ * {
52
+ * "projectId": "proj_abc123",
53
+ * "specificationId": "spec_xyz789",
54
+ * "mcpOutputFormat": "json",
55
+ * "autoSetContext": true
56
+ * }
57
+ * ```
58
+ */
59
+ export interface ProjectConfig {
60
+ /** SpecForge project ID this directory is linked to */
61
+ projectId?: string;
62
+ /** Active specification ID for this project */
63
+ specificationId?: string;
64
+ /**
65
+ * Per-project override for the local MCP server's response wire encoding
66
+ * (TOON or JSON) sent to the agent. NOT the human CLI output format.
67
+ */
68
+ mcpOutputFormat?: OutputFormat;
69
+ /** Automatically set working context when CLI runs */
70
+ autoSetContext?: boolean;
71
+ /**
72
+ * Active planning session id for this spec (M9.6).
73
+ *
74
+ * Set after `start_planning_session` returns; auto-injected into
75
+ * `action_planning_session` / `complete_planning_session` by the local MCP
76
+ * server interpose. Cleared only when the session CLOSES (a
77
+ * `get_planning_status` response with `planningStatus: 'closed'`), NOT on
78
+ * `complete_planning_session` — CPS-success flips the session to
79
+ * `awaiting_human_review`, and the agent resumes under the same id across all
80
+ * phase handovers.
81
+ *
82
+ * Named `planningSessionId` (not a generic `sessionId`) because work/review
83
+ * lifecycles will persist their own session ids alongside this one.
84
+ */
85
+ planningSessionId?: string;
86
+ }
87
+ /**
88
+ * Resolved configuration merged from all sources
89
+ *
90
+ * This is the final configuration used by CLI commands after merging:
91
+ * - Environment variables (highest priority)
92
+ * - Project config (.specforge.json)
93
+ * - Global config (~/.specforge/config.json)
94
+ * - Default values (lowest priority)
95
+ */
96
+ export interface ResolvedConfig {
97
+ /** API key for authentication (null if not authenticated) */
98
+ apiKey: string | null;
99
+ /** API endpoint URL */
100
+ apiUrl: string;
101
+ /** Resolved wire encoding for the local MCP server's responses to the agent */
102
+ mcpOutputFormat: OutputFormat;
103
+ /** Current project ID (null if not in project context) */
104
+ projectId: string | null;
105
+ /** Current specification ID (null if not set) */
106
+ specificationId: string | null;
107
+ /** Whether to auto-set working context */
108
+ autoSetContext: boolean;
109
+ /** Debug mode enabled */
110
+ debug: boolean;
111
+ /**
112
+ * Source tracking for each config value
113
+ * Useful for diagnostics and understanding where values come from
114
+ */
115
+ sources: ConfigSources;
116
+ }
117
+ /**
118
+ * Tracks the source of each resolved configuration value
119
+ */
120
+ export interface ConfigSources {
121
+ /** Where the API key was loaded from */
122
+ apiKey: 'env' | 'global' | null;
123
+ /** Where the project ID was loaded from */
124
+ projectId: 'env' | 'project' | 'global' | null;
125
+ /** Where the specification ID was loaded from */
126
+ specificationId: 'project' | null;
127
+ /** Where the MCP output-format setting was loaded from */
128
+ mcpOutputFormat: 'env' | 'project' | 'global' | 'default';
129
+ }
130
+ /**
131
+ * Configuration file paths used by the CLI
132
+ */
133
+ export interface ConfigPaths {
134
+ /** Global config directory (~/.specforge/) */
135
+ globalDir: string;
136
+ /** Global config file (~/.specforge/config.json) */
137
+ globalConfig: string;
138
+ /** Project config file (./.specforge.json) */
139
+ projectConfig: string;
140
+ /** MCP config file for Claude Code (./.mcp.json) */
141
+ mcpConfig: string;
142
+ }
143
+ /**
144
+ * Default configuration values
145
+ *
146
+ * `apiUrl` (MCP) flows from `CHANNEL` — stable channel hits the prod surfaces,
147
+ * canary hits the dev surfaces. The resolver in `transport/resolve-endpoint.ts`
148
+ * (Phase D) layers DNS-class fallback on top. Operators override via
149
+ * `SPECFORGE_API_URL` for smokes / self-hosting. The human CLI's `/cli` route is
150
+ * derived from `apiUrl` (same host) by `createCliClient`.
151
+ */
152
+ export declare const CONFIG_DEFAULTS: {
153
+ readonly apiUrl: string;
154
+ readonly mcpOutputFormat: OutputFormat;
155
+ readonly autoSetContext: true;
156
+ readonly debug: false;
157
+ };
158
+ /**
159
+ * Environment variable names for configuration
160
+ */
161
+ export declare const CONFIG_ENV_VARS: {
162
+ readonly API_KEY: "SPECFORGE_API_KEY";
163
+ readonly API_URL: "SPECFORGE_API_URL";
164
+ readonly MCP_OUTPUT_FORMAT: "SPECFORGE_MCP_OUTPUT_FORMAT";
165
+ readonly DEBUG: "SPECFORGE_DEBUG";
166
+ };
167
+ /**
168
+ * Configuration file names
169
+ *
170
+ * `GLOBAL_DIR`, `PROJECT_DIR`, and `PROJECT_CONFIG` flow from `CHANNEL.configDir`
171
+ * — `.specforge` on stable, `.specforge-canary` on canary. The two binaries
172
+ * coexist on one machine because their config dirs do not collide.
173
+ *
174
+ * `LEGACY_PROJECT_CONFIG` is the historical `.specforge.json` filename and
175
+ * stays fixed (not channel-aware) so the upgrade path keeps surfacing the
176
+ * breaking-change notice on every operation regardless of channel.
177
+ */
178
+ export declare const CONFIG_FILES: {
179
+ readonly GLOBAL_DIR: string;
180
+ readonly GLOBAL_CONFIG: "config.json";
181
+ readonly PROJECT_DIR: string;
182
+ readonly PROJECT_CONFIG: `${string}/config.json`;
183
+ readonly LEGACY_PROJECT_CONFIG: ".specforge.json";
184
+ readonly MCP_CONFIG: ".mcp.json";
185
+ };
186
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/cli/config/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;AAE3C;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,IAAI,CAAC;AAE3E;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,eAAe,CAAC,EAAE,YAAY,CAAC;IAE/B,yDAAyD;IACzD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,+CAA+C;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,eAAe,CAAC,EAAE,YAAY,CAAC;IAE/B,sDAAsD;IACtD,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;;;;;;;;;OAaG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAEtB,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IAEf,+EAA+E;IAC/E,eAAe,EAAE,YAAY,CAAC;IAE9B,0DAA0D;IAC1D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB,iDAAiD;IACjD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAE/B,0CAA0C;IAC1C,cAAc,EAAE,OAAO,CAAC;IAExB,yBAAyB;IACzB,KAAK,EAAE,OAAO,CAAC;IAEf;;;OAGG;IACH,OAAO,EAAE,aAAa,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,IAAI,CAAC;IAEhC,2CAA2C;IAC3C,SAAS,EAAE,KAAK,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;IAE/C,iDAAiD;IACjD,eAAe,EAAE,SAAS,GAAG,IAAI,CAAC;IAElC,0DAA0D;IAC1D,eAAe,EAAE,KAAK,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC3D;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAElB,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;IAErB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC;IAEtB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe;;8BAEC,YAAY;;;CAG/B,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,eAAe;;;;;CAKlB,CAAC;AAEX;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY;;;;;;;CAOf,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { CHANNEL } from "../../channel.js";
2
+ const CONFIG_DEFAULTS = {
3
+ apiUrl: CHANNEL.endpoint.primary,
4
+ mcpOutputFormat: "toon",
5
+ autoSetContext: true,
6
+ debug: false
7
+ };
8
+ const CONFIG_ENV_VARS = {
9
+ API_KEY: "SPECFORGE_API_KEY",
10
+ API_URL: "SPECFORGE_API_URL",
11
+ MCP_OUTPUT_FORMAT: "SPECFORGE_MCP_OUTPUT_FORMAT",
12
+ DEBUG: "SPECFORGE_DEBUG"
13
+ };
14
+ const CONFIG_FILES = {
15
+ GLOBAL_DIR: CHANNEL.configDir,
16
+ GLOBAL_CONFIG: "config.json",
17
+ PROJECT_DIR: CHANNEL.configDir,
18
+ PROJECT_CONFIG: `${CHANNEL.configDir}/config.json`,
19
+ LEGACY_PROJECT_CONFIG: ".specforge.json",
20
+ MCP_CONFIG: ".mcp.json"
21
+ };
22
+ export {
23
+ CONFIG_DEFAULTS,
24
+ CONFIG_ENV_VARS,
25
+ CONFIG_FILES
26
+ };
27
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/cli/config/types.ts"],"sourcesContent":["/**\n * CLI Configuration Types\n *\n * Defines interfaces for global, project, and resolved configuration\n * used throughout the CLI application.\n */\n\nimport { CHANNEL } from '../../channel.js';\n\n/**\n * Output format options for CLI commands\n */\nexport type OutputFormat = 'json' | 'toon';\n\n/**\n * Source tracking for configuration values\n * Used for diagnostics and debugging (e.g., `specforge doctor`)\n */\nexport type ConfigSource = 'env' | 'global' | 'project' | 'default' | null;\n\n/**\n * Global configuration stored in ~/.specforge/config.json\n *\n * This file contains user-level settings that apply across all projects.\n * The API key is stored here for security (not in project files).\n *\n * @example\n * ```json\n * {\n * \"apiKey\": \"sf_live_abc123...\",\n * \"mcpOutputFormat\": \"toon\",\n * \"defaultProjectId\": \"proj_xyz\"\n * }\n * ```\n */\nexport interface GlobalConfig {\n /** API key for SpecForge authentication (format: sf_live_*) */\n apiKey?: string;\n\n /**\n * Wire encoding for the local MCP server's responses to the agent\n * (TOON or JSON). NOT the human CLI output format — CLI commands render via\n * their own per-command `--json`/`--toon` flags.\n */\n mcpOutputFormat?: OutputFormat;\n\n /** Default project ID when not in a project directory */\n defaultProjectId?: string;\n}\n\n/**\n * Project configuration stored in .specforge.json\n *\n * This file is created in the project root when running `specforge init`.\n * It links the local directory to a SpecForge project and specification.\n *\n * @example\n * ```json\n * {\n * \"projectId\": \"proj_abc123\",\n * \"specificationId\": \"spec_xyz789\",\n * \"mcpOutputFormat\": \"json\",\n * \"autoSetContext\": true\n * }\n * ```\n */\nexport interface ProjectConfig {\n /** SpecForge project ID this directory is linked to */\n projectId?: string;\n\n /** Active specification ID for this project */\n specificationId?: string;\n\n /**\n * Per-project override for the local MCP server's response wire encoding\n * (TOON or JSON) sent to the agent. NOT the human CLI output format.\n */\n mcpOutputFormat?: OutputFormat;\n\n /** Automatically set working context when CLI runs */\n autoSetContext?: boolean;\n\n /**\n * Active planning session id for this spec (M9.6).\n *\n * Set after `start_planning_session` returns; auto-injected into\n * `action_planning_session` / `complete_planning_session` by the local MCP\n * server interpose. Cleared only when the session CLOSES (a\n * `get_planning_status` response with `planningStatus: 'closed'`), NOT on\n * `complete_planning_session` — CPS-success flips the session to\n * `awaiting_human_review`, and the agent resumes under the same id across all\n * phase handovers.\n *\n * Named `planningSessionId` (not a generic `sessionId`) because work/review\n * lifecycles will persist their own session ids alongside this one.\n */\n planningSessionId?: string;\n}\n\n/**\n * Resolved configuration merged from all sources\n *\n * This is the final configuration used by CLI commands after merging:\n * - Environment variables (highest priority)\n * - Project config (.specforge.json)\n * - Global config (~/.specforge/config.json)\n * - Default values (lowest priority)\n */\nexport interface ResolvedConfig {\n /** API key for authentication (null if not authenticated) */\n apiKey: string | null;\n\n /** API endpoint URL */\n apiUrl: string;\n\n /** Resolved wire encoding for the local MCP server's responses to the agent */\n mcpOutputFormat: OutputFormat;\n\n /** Current project ID (null if not in project context) */\n projectId: string | null;\n\n /** Current specification ID (null if not set) */\n specificationId: string | null;\n\n /** Whether to auto-set working context */\n autoSetContext: boolean;\n\n /** Debug mode enabled */\n debug: boolean;\n\n /**\n * Source tracking for each config value\n * Useful for diagnostics and understanding where values come from\n */\n sources: ConfigSources;\n}\n\n/**\n * Tracks the source of each resolved configuration value\n */\nexport interface ConfigSources {\n /** Where the API key was loaded from */\n apiKey: 'env' | 'global' | null;\n\n /** Where the project ID was loaded from */\n projectId: 'env' | 'project' | 'global' | null;\n\n /** Where the specification ID was loaded from */\n specificationId: 'project' | null;\n\n /** Where the MCP output-format setting was loaded from */\n mcpOutputFormat: 'env' | 'project' | 'global' | 'default';\n}\n\n/**\n * Configuration file paths used by the CLI\n */\nexport interface ConfigPaths {\n /** Global config directory (~/.specforge/) */\n globalDir: string;\n\n /** Global config file (~/.specforge/config.json) */\n globalConfig: string;\n\n /** Project config file (./.specforge.json) */\n projectConfig: string;\n\n /** MCP config file for Claude Code (./.mcp.json) */\n mcpConfig: string;\n}\n\n/**\n * Default configuration values\n *\n * `apiUrl` (MCP) flows from `CHANNEL` — stable channel hits the prod surfaces,\n * canary hits the dev surfaces. The resolver in `transport/resolve-endpoint.ts`\n * (Phase D) layers DNS-class fallback on top. Operators override via\n * `SPECFORGE_API_URL` for smokes / self-hosting. The human CLI's `/cli` route is\n * derived from `apiUrl` (same host) by `createCliClient`.\n */\nexport const CONFIG_DEFAULTS = {\n apiUrl: CHANNEL.endpoint.primary,\n mcpOutputFormat: 'toon' as OutputFormat,\n autoSetContext: true,\n debug: false,\n} as const;\n\n/**\n * Environment variable names for configuration\n */\nexport const CONFIG_ENV_VARS = {\n API_KEY: 'SPECFORGE_API_KEY',\n API_URL: 'SPECFORGE_API_URL',\n MCP_OUTPUT_FORMAT: 'SPECFORGE_MCP_OUTPUT_FORMAT',\n DEBUG: 'SPECFORGE_DEBUG',\n} as const;\n\n/**\n * Configuration file names\n *\n * `GLOBAL_DIR`, `PROJECT_DIR`, and `PROJECT_CONFIG` flow from `CHANNEL.configDir`\n * — `.specforge` on stable, `.specforge-canary` on canary. The two binaries\n * coexist on one machine because their config dirs do not collide.\n *\n * `LEGACY_PROJECT_CONFIG` is the historical `.specforge.json` filename and\n * stays fixed (not channel-aware) so the upgrade path keeps surfacing the\n * breaking-change notice on every operation regardless of channel.\n */\nexport const CONFIG_FILES = {\n GLOBAL_DIR: CHANNEL.configDir,\n GLOBAL_CONFIG: 'config.json',\n PROJECT_DIR: CHANNEL.configDir,\n PROJECT_CONFIG: `${CHANNEL.configDir}/config.json`,\n LEGACY_PROJECT_CONFIG: '.specforge.json',\n MCP_CONFIG: '.mcp.json',\n} as const;\n"],"mappings":"AAOA,SAAS,eAAe;AA6KjB,MAAM,kBAAkB;AAAA,EAC7B,QAAQ,QAAQ,SAAS;AAAA,EACzB,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,OAAO;AACT;AAKO,MAAM,kBAAkB;AAAA,EAC7B,SAAS;AAAA,EACT,SAAS;AAAA,EACT,mBAAmB;AAAA,EACnB,OAAO;AACT;AAaO,MAAM,eAAe;AAAA,EAC1B,YAAY,QAAQ;AAAA,EACpB,eAAe;AAAA,EACf,aAAa,QAAQ;AAAA,EACrB,gBAAgB,GAAG,QAAQ,SAAS;AAAA,EACpC,uBAAuB;AAAA,EACvB,YAAY;AACd;","names":[]}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Config Schema Validation
3
+ *
4
+ * Validates the extended .specforge/config.json configuration schema.
5
+ * Each section is validated independently — absent sections are
6
+ * considered valid (all sections are optional).
7
+ *
8
+ * Uses manual validation (no external libs) to match existing patterns.
9
+ */
10
+ /**
11
+ * A single validation error with field path and expected values.
12
+ */
13
+ export interface ValidationError {
14
+ /** Dot-separated field path (e.g., "agentTeams.strategy") */
15
+ field: string;
16
+ /** Human-readable error message including expected values */
17
+ message: string;
18
+ /** The actual value that failed validation */
19
+ value: unknown;
20
+ }
21
+ /**
22
+ * Result of a validation operation.
23
+ */
24
+ export interface ValidationResult {
25
+ /** Whether all validations passed */
26
+ valid: boolean;
27
+ /** List of validation errors (empty when valid) */
28
+ errors: ValidationError[];
29
+ }
30
+ /**
31
+ * Validate the `agentTeams` config section (v2.0.0).
32
+ *
33
+ * Returns valid:true if the section is absent/undefined.
34
+ * Only validates v2.0.0 fields. Deprecated fields are silently ignored.
35
+ */
36
+ export declare function validateAgentTeamsConfig(config: unknown): ValidationResult;
37
+ /**
38
+ * Validate the `activeSpecification` config section.
39
+ *
40
+ * Returns valid:true if the section is absent/undefined.
41
+ * When present, `id`, `title`, and `activatedAt` are required.
42
+ */
43
+ export declare function validateActiveSpecification(config: unknown): ValidationResult;
44
+ /**
45
+ * Validate the entire .specforge/config.json for Agent Teams sections.
46
+ *
47
+ * Orchestrates sub-validators and aggregates all errors.
48
+ * Only validates the Agent Teams sections — existing ProjectConfig
49
+ * fields are validated elsewhere.
50
+ *
51
+ * @param config - Raw parsed JSON object from .specforge/config.json
52
+ * @returns Combined validation result from all sections
53
+ */
54
+ export declare function validateSpecforgeJson(config: unknown): ValidationResult;
55
+ /**
56
+ * Format validation errors into human-readable lines.
57
+ *
58
+ * @param errors - Array of validation errors
59
+ * @returns Formatted error strings, one per line
60
+ */
61
+ export declare function formatValidationErrors(errors: ValidationError[]): string[];
62
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/cli/config/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,mDAAmD;IACnD,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AA2DD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,OAAO,GAAG,gBAAgB,CA2C1E;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,OAAO,GAAG,gBAAgB,CA0C7E;AAMD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,gBAAgB,CAgBvE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,EAAE,CAK1E"}
@@ -0,0 +1,133 @@
1
+ const ALLOWED_STRATEGIES = ["auto", "single", "parallel", "phased"];
2
+ function err(field, message, value) {
3
+ return { field, message, value };
4
+ }
5
+ function isObject(v) {
6
+ return typeof v === "object" && v !== null && !Array.isArray(v);
7
+ }
8
+ function oneOf(field, value, allowed) {
9
+ if (typeof value !== "string" || !allowed.includes(value)) {
10
+ return err(field, `Must be one of: ${allowed.join(", ")}`, value);
11
+ }
12
+ return null;
13
+ }
14
+ function numberInRange(field, value, min, max) {
15
+ if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) {
16
+ return err(field, `Must be a number between ${min} and ${max}`, value);
17
+ }
18
+ return null;
19
+ }
20
+ function mustBeBoolean(field, value) {
21
+ if (typeof value !== "boolean") {
22
+ return err(field, "Must be a boolean", value);
23
+ }
24
+ return null;
25
+ }
26
+ function mustBeString(field, value) {
27
+ if (typeof value !== "string") {
28
+ return err(field, "Must be a string", value);
29
+ }
30
+ return null;
31
+ }
32
+ function mustBeNonEmptyString(field, value) {
33
+ if (typeof value !== "string" || value.trim().length === 0) {
34
+ return err(field, "Must be a non-empty string", value);
35
+ }
36
+ return null;
37
+ }
38
+ function validateAgentTeamsConfig(config) {
39
+ if (config === void 0 || config === null) {
40
+ return { valid: true, errors: [] };
41
+ }
42
+ const errors = [];
43
+ const prefix = "agentTeams";
44
+ if (!isObject(config)) {
45
+ return { valid: false, errors: [err(prefix, "Must be an object", config)] };
46
+ }
47
+ if ("enabled" in config && config.enabled !== void 0) {
48
+ const e = mustBeBoolean(`${prefix}.enabled`, config.enabled);
49
+ if (e) errors.push(e);
50
+ }
51
+ if ("strategy" in config && config.strategy !== void 0) {
52
+ const e = oneOf(`${prefix}.strategy`, config.strategy, ALLOWED_STRATEGIES);
53
+ if (e) errors.push(e);
54
+ }
55
+ if ("maxTicketsPerTeam" in config && config.maxTicketsPerTeam !== void 0) {
56
+ const e = numberInRange(`${prefix}.maxTicketsPerTeam`, config.maxTicketsPerTeam, 1, 20);
57
+ if (e) errors.push(e);
58
+ }
59
+ if ("maxParallelEpics" in config && config.maxParallelEpics !== void 0) {
60
+ const e = numberInRange(`${prefix}.maxParallelEpics`, config.maxParallelEpics, 1, 10);
61
+ if (e) errors.push(e);
62
+ }
63
+ if ("branchPrefix" in config && config.branchPrefix !== void 0) {
64
+ const e = mustBeString(`${prefix}.branchPrefix`, config.branchPrefix);
65
+ if (e) errors.push(e);
66
+ }
67
+ if ("timeoutMinutes" in config && config.timeoutMinutes !== void 0) {
68
+ const e = numberInRange(`${prefix}.timeoutMinutes`, config.timeoutMinutes, 5, 60);
69
+ if (e) errors.push(e);
70
+ }
71
+ return { valid: errors.length === 0, errors };
72
+ }
73
+ function validateActiveSpecification(config) {
74
+ if (config === void 0 || config === null) {
75
+ return { valid: true, errors: [] };
76
+ }
77
+ const errors = [];
78
+ const prefix = "activeSpecification";
79
+ if (!isObject(config)) {
80
+ return { valid: false, errors: [err(prefix, "Must be an object", config)] };
81
+ }
82
+ if (!("id" in config) || config.id === void 0) {
83
+ errors.push(err(`${prefix}.id`, "Required field is missing", void 0));
84
+ } else {
85
+ const e = mustBeNonEmptyString(`${prefix}.id`, config.id);
86
+ if (e) errors.push(e);
87
+ }
88
+ if (!("title" in config) || config.title === void 0) {
89
+ errors.push(err(`${prefix}.title`, "Required field is missing", void 0));
90
+ } else {
91
+ const e = mustBeNonEmptyString(`${prefix}.title`, config.title);
92
+ if (e) {
93
+ errors.push(e);
94
+ } else if (typeof config.title === "string" && config.title.length > 200) {
95
+ errors.push(err(`${prefix}.title`, "Must be at most 200 characters", config.title));
96
+ }
97
+ }
98
+ if (!("activatedAt" in config) || config.activatedAt === void 0) {
99
+ errors.push(err(`${prefix}.activatedAt`, "Required field is missing", void 0));
100
+ } else if (typeof config.activatedAt !== "string") {
101
+ errors.push(err(`${prefix}.activatedAt`, "Must be a string", config.activatedAt));
102
+ } else if (isNaN(Date.parse(config.activatedAt))) {
103
+ errors.push(err(`${prefix}.activatedAt`, "Must be a valid ISO 8601 date string", config.activatedAt));
104
+ }
105
+ return { valid: errors.length === 0, errors };
106
+ }
107
+ function validateSpecforgeJson(config) {
108
+ if (config === void 0 || config === null) {
109
+ return { valid: true, errors: [] };
110
+ }
111
+ if (!isObject(config)) {
112
+ return { valid: false, errors: [err("config", "Must be an object", config)] };
113
+ }
114
+ const results = [
115
+ validateAgentTeamsConfig(config.agentTeams),
116
+ validateActiveSpecification(config.activeSpecification)
117
+ ];
118
+ const errors = results.flatMap((r) => r.errors);
119
+ return { valid: errors.length === 0, errors };
120
+ }
121
+ function formatValidationErrors(errors) {
122
+ return errors.map((e) => {
123
+ const valueStr = e.value === void 0 ? "undefined" : JSON.stringify(e.value);
124
+ return ` ${e.field}: ${e.message} (got ${valueStr})`;
125
+ });
126
+ }
127
+ export {
128
+ formatValidationErrors,
129
+ validateActiveSpecification,
130
+ validateAgentTeamsConfig,
131
+ validateSpecforgeJson
132
+ };
133
+ //# sourceMappingURL=validation.js.map