@seanmars/tospec 0.19.0-beta.0 → 0.19.0-beta.13

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 (431) hide show
  1. package/CHANGELOG.md +578 -0
  2. package/README.md +71 -77
  3. package/assets/dashboard/app.js +14 -3
  4. package/assets/dashboard/style.css +7 -0
  5. package/assets/metrics/app.js +22 -0
  6. package/assets/metrics/style.css +8 -0
  7. package/assets/rules/tospec/decision.md +3 -0
  8. package/assets/rules/tospec/single-source-of-truth.md +19 -0
  9. package/dist/cli/index.d.ts.map +1 -1
  10. package/dist/cli/index.js +266 -82
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/commands/config.d.ts +9 -17
  13. package/dist/commands/config.d.ts.map +1 -1
  14. package/dist/commands/config.js +374 -95
  15. package/dist/commands/config.js.map +1 -1
  16. package/dist/commands/dashboard.d.ts +64 -81
  17. package/dist/commands/dashboard.d.ts.map +1 -1
  18. package/dist/commands/dashboard.js +350 -218
  19. package/dist/commands/dashboard.js.map +1 -1
  20. package/dist/commands/decision.d.ts +41 -19
  21. package/dist/commands/decision.d.ts.map +1 -1
  22. package/dist/commands/decision.js +400 -70
  23. package/dist/commands/decision.js.map +1 -1
  24. package/dist/commands/metrics.d.ts +34 -48
  25. package/dist/commands/metrics.d.ts.map +1 -1
  26. package/dist/commands/metrics.js +74 -84
  27. package/dist/commands/metrics.js.map +1 -1
  28. package/dist/commands/shared-output.d.ts +24 -10
  29. package/dist/commands/shared-output.d.ts.map +1 -1
  30. package/dist/commands/shared-output.js +62 -11
  31. package/dist/commands/shared-output.js.map +1 -1
  32. package/dist/commands/show.d.ts +7 -0
  33. package/dist/commands/show.d.ts.map +1 -1
  34. package/dist/commands/show.js +39 -8
  35. package/dist/commands/show.js.map +1 -1
  36. package/dist/commands/validate.d.ts +47 -30
  37. package/dist/commands/validate.d.ts.map +1 -1
  38. package/dist/commands/validate.js +282 -108
  39. package/dist/commands/validate.js.map +1 -1
  40. package/dist/commands/workflow/index.d.ts +6 -10
  41. package/dist/commands/workflow/index.d.ts.map +1 -1
  42. package/dist/commands/workflow/index.js +6 -10
  43. package/dist/commands/workflow/index.js.map +1 -1
  44. package/dist/commands/workflow/instructions.d.ts +21 -8
  45. package/dist/commands/workflow/instructions.d.ts.map +1 -1
  46. package/dist/commands/workflow/instructions.js +254 -95
  47. package/dist/commands/workflow/instructions.js.map +1 -1
  48. package/dist/commands/workflow/new-change.d.ts +4 -5
  49. package/dist/commands/workflow/new-change.d.ts.map +1 -1
  50. package/dist/commands/workflow/new-change.js +90 -25
  51. package/dist/commands/workflow/new-change.js.map +1 -1
  52. package/dist/commands/workflow/schemas.d.ts +3 -5
  53. package/dist/commands/workflow/schemas.d.ts.map +1 -1
  54. package/dist/commands/workflow/schemas.js +37 -11
  55. package/dist/commands/workflow/schemas.js.map +1 -1
  56. package/dist/commands/workflow/shared.d.ts +48 -21
  57. package/dist/commands/workflow/shared.d.ts.map +1 -1
  58. package/dist/commands/workflow/shared.js +36 -33
  59. package/dist/commands/workflow/shared.js.map +1 -1
  60. package/dist/commands/workflow/status.d.ts +10 -6
  61. package/dist/commands/workflow/status.d.ts.map +1 -1
  62. package/dist/commands/workflow/status.js +84 -42
  63. package/dist/commands/workflow/status.js.map +1 -1
  64. package/dist/commands/workflow/templates.d.ts +10 -3
  65. package/dist/commands/workflow/templates.d.ts.map +1 -1
  66. package/dist/commands/workflow/templates.js +39 -40
  67. package/dist/commands/workflow/templates.js.map +1 -1
  68. package/dist/core/archive.d.ts +26 -21
  69. package/dist/core/archive.d.ts.map +1 -1
  70. package/dist/core/archive.js +415 -201
  71. package/dist/core/archive.js.map +1 -1
  72. package/dist/core/artifact-graph/graph.d.ts +29 -36
  73. package/dist/core/artifact-graph/graph.d.ts.map +1 -1
  74. package/dist/core/artifact-graph/graph.js +50 -58
  75. package/dist/core/artifact-graph/graph.js.map +1 -1
  76. package/dist/core/artifact-graph/index.d.ts +2 -2
  77. package/dist/core/artifact-graph/index.d.ts.map +1 -1
  78. package/dist/core/artifact-graph/index.js +2 -2
  79. package/dist/core/artifact-graph/index.js.map +1 -1
  80. package/dist/core/artifact-graph/instruction-loader.d.ts +105 -100
  81. package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
  82. package/dist/core/artifact-graph/instruction-loader.js +176 -110
  83. package/dist/core/artifact-graph/instruction-loader.js.map +1 -1
  84. package/dist/core/artifact-graph/outputs.d.ts +13 -6
  85. package/dist/core/artifact-graph/outputs.d.ts.map +1 -1
  86. package/dist/core/artifact-graph/outputs.js +146 -8
  87. package/dist/core/artifact-graph/outputs.js.map +1 -1
  88. package/dist/core/artifact-graph/resolver.d.ts +44 -63
  89. package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
  90. package/dist/core/artifact-graph/resolver.js +85 -86
  91. package/dist/core/artifact-graph/resolver.js.map +1 -1
  92. package/dist/core/artifact-graph/schema.d.ts +0 -6
  93. package/dist/core/artifact-graph/schema.d.ts.map +1 -1
  94. package/dist/core/artifact-graph/schema.js +7 -32
  95. package/dist/core/artifact-graph/schema.js.map +1 -1
  96. package/dist/core/artifact-graph/state.d.ts +1 -8
  97. package/dist/core/artifact-graph/state.d.ts.map +1 -1
  98. package/dist/core/artifact-graph/state.js +2 -17
  99. package/dist/core/artifact-graph/state.js.map +1 -1
  100. package/dist/core/artifact-graph/stub-detection.d.ts +12 -0
  101. package/dist/core/artifact-graph/stub-detection.d.ts.map +1 -0
  102. package/dist/core/artifact-graph/stub-detection.js +39 -0
  103. package/dist/core/artifact-graph/stub-detection.js.map +1 -0
  104. package/dist/core/artifact-graph/types.d.ts +4 -0
  105. package/dist/core/artifact-graph/types.d.ts.map +1 -1
  106. package/dist/core/artifact-graph/types.js +30 -10
  107. package/dist/core/artifact-graph/types.js.map +1 -1
  108. package/dist/core/available-tools.d.ts +3 -12
  109. package/dist/core/available-tools.d.ts.map +1 -1
  110. package/dist/core/available-tools.js +4 -13
  111. package/dist/core/available-tools.js.map +1 -1
  112. package/dist/core/change-metadata/schema.d.ts +1 -1
  113. package/dist/core/change-metadata/schema.d.ts.map +1 -1
  114. package/dist/core/change-metadata/schema.js +10 -7
  115. package/dist/core/change-metadata/schema.js.map +1 -1
  116. package/dist/core/change-presenter.d.ts +23 -18
  117. package/dist/core/change-presenter.d.ts.map +1 -1
  118. package/dist/core/change-presenter.js +102 -43
  119. package/dist/core/change-presenter.js.map +1 -1
  120. package/dist/core/change-status-policy.d.ts +8 -1
  121. package/dist/core/change-status-policy.d.ts.map +1 -1
  122. package/dist/core/change-status-policy.js +25 -1
  123. package/dist/core/change-status-policy.js.map +1 -1
  124. package/dist/core/codex-metrics.d.ts +25 -45
  125. package/dist/core/codex-metrics.d.ts.map +1 -1
  126. package/dist/core/codex-metrics.js +44 -88
  127. package/dist/core/codex-metrics.js.map +1 -1
  128. package/dist/core/codex-residue.d.ts +22 -0
  129. package/dist/core/codex-residue.d.ts.map +1 -0
  130. package/dist/core/codex-residue.js +61 -0
  131. package/dist/core/codex-residue.js.map +1 -0
  132. package/dist/core/command-generation/adapters/claude.d.ts +2 -9
  133. package/dist/core/command-generation/adapters/claude.d.ts.map +1 -1
  134. package/dist/core/command-generation/adapters/claude.js +2 -12
  135. package/dist/core/command-generation/adapters/claude.js.map +1 -1
  136. package/dist/core/command-generation/adapters/index.d.ts +1 -9
  137. package/dist/core/command-generation/adapters/index.d.ts.map +1 -1
  138. package/dist/core/command-generation/adapters/index.js +1 -9
  139. package/dist/core/command-generation/adapters/index.js.map +1 -1
  140. package/dist/core/command-generation/generator.d.ts +0 -17
  141. package/dist/core/command-generation/generator.d.ts.map +1 -1
  142. package/dist/core/command-generation/generator.js +0 -17
  143. package/dist/core/command-generation/generator.js.map +1 -1
  144. package/dist/core/command-generation/index.d.ts +2 -5
  145. package/dist/core/command-generation/index.d.ts.map +1 -1
  146. package/dist/core/command-generation/index.js +0 -9
  147. package/dist/core/command-generation/index.js.map +1 -1
  148. package/dist/core/command-generation/types.d.ts +10 -36
  149. package/dist/core/command-generation/types.d.ts.map +1 -1
  150. package/dist/core/command-generation/types.js +0 -6
  151. package/dist/core/command-generation/types.js.map +1 -1
  152. package/dist/core/command-generation/yaml.d.ts +3 -18
  153. package/dist/core/command-generation/yaml.d.ts.map +1 -1
  154. package/dist/core/command-generation/yaml.js +5 -23
  155. package/dist/core/command-generation/yaml.js.map +1 -1
  156. package/dist/core/config-prompts.d.ts +2 -4
  157. package/dist/core/config-prompts.d.ts.map +1 -1
  158. package/dist/core/config-prompts.js +2 -7
  159. package/dist/core/config-prompts.js.map +1 -1
  160. package/dist/core/config-schema.d.ts +8 -53
  161. package/dist/core/config-schema.d.ts.map +1 -1
  162. package/dist/core/config-schema.js +49 -62
  163. package/dist/core/config-schema.js.map +1 -1
  164. package/dist/core/config.d.ts +56 -0
  165. package/dist/core/config.d.ts.map +1 -1
  166. package/dist/core/config.js +73 -2
  167. package/dist/core/config.js.map +1 -1
  168. package/dist/core/converters/json-converter.d.ts.map +1 -1
  169. package/dist/core/dashboard-activity.d.ts +7 -9
  170. package/dist/core/dashboard-activity.d.ts.map +1 -1
  171. package/dist/core/dashboard-activity.js +26 -24
  172. package/dist/core/dashboard-activity.js.map +1 -1
  173. package/dist/core/dashboard-data.d.ts +40 -22
  174. package/dist/core/dashboard-data.d.ts.map +1 -1
  175. package/dist/core/dashboard-data.js +84 -68
  176. package/dist/core/dashboard-data.js.map +1 -1
  177. package/dist/core/global-config.d.ts +24 -53
  178. package/dist/core/global-config.d.ts.map +1 -1
  179. package/dist/core/global-config.js +43 -62
  180. package/dist/core/global-config.js.map +1 -1
  181. package/dist/core/init.d.ts +29 -11
  182. package/dist/core/init.d.ts.map +1 -1
  183. package/dist/core/init.js +232 -164
  184. package/dist/core/init.js.map +1 -1
  185. package/dist/core/list.d.ts +1 -1
  186. package/dist/core/list.d.ts.map +1 -1
  187. package/dist/core/list.js +121 -28
  188. package/dist/core/list.js.map +1 -1
  189. package/dist/core/local-server.d.ts +63 -39
  190. package/dist/core/local-server.d.ts.map +1 -1
  191. package/dist/core/local-server.js +99 -53
  192. package/dist/core/local-server.js.map +1 -1
  193. package/dist/core/markdown-render.d.ts +25 -0
  194. package/dist/core/markdown-render.d.ts.map +1 -0
  195. package/dist/core/markdown-render.js +94 -0
  196. package/dist/core/markdown-render.js.map +1 -0
  197. package/dist/core/migrate.d.ts +32 -15
  198. package/dist/core/migrate.d.ts.map +1 -1
  199. package/dist/core/migrate.js +220 -108
  200. package/dist/core/migrate.js.map +1 -1
  201. package/dist/core/parsers/change-parser.d.ts +7 -10
  202. package/dist/core/parsers/change-parser.d.ts.map +1 -1
  203. package/dist/core/parsers/change-parser.js +48 -56
  204. package/dist/core/parsers/change-parser.js.map +1 -1
  205. package/dist/core/parsers/markdown-parser.d.ts +8 -9
  206. package/dist/core/parsers/markdown-parser.d.ts.map +1 -1
  207. package/dist/core/parsers/markdown-parser.js +31 -22
  208. package/dist/core/parsers/markdown-parser.js.map +1 -1
  209. package/dist/core/parsers/requirement-blocks.d.ts +53 -11
  210. package/dist/core/parsers/requirement-blocks.d.ts.map +1 -1
  211. package/dist/core/parsers/requirement-blocks.js +200 -60
  212. package/dist/core/parsers/requirement-blocks.js.map +1 -1
  213. package/dist/core/parsers/requirement-text.d.ts +73 -79
  214. package/dist/core/parsers/requirement-text.d.ts.map +1 -1
  215. package/dist/core/parsers/requirement-text.js +137 -79
  216. package/dist/core/parsers/requirement-text.js.map +1 -1
  217. package/dist/core/parsers/spec-structure.d.ts +1 -1
  218. package/dist/core/parsers/spec-structure.d.ts.map +1 -1
  219. package/dist/core/parsers/spec-structure.js +30 -3
  220. package/dist/core/parsers/spec-structure.js.map +1 -1
  221. package/dist/core/planning-home.js.map +1 -1
  222. package/dist/core/profiles.d.ts +3 -10
  223. package/dist/core/profiles.d.ts.map +1 -1
  224. package/dist/core/profiles.js +5 -12
  225. package/dist/core/profiles.js.map +1 -1
  226. package/dist/core/project-config.d.ts +43 -44
  227. package/dist/core/project-config.d.ts.map +1 -1
  228. package/dist/core/project-config.js +107 -82
  229. package/dist/core/project-config.js.map +1 -1
  230. package/dist/core/project-layout.d.ts +10 -18
  231. package/dist/core/project-layout.d.ts.map +1 -1
  232. package/dist/core/project-layout.js +16 -26
  233. package/dist/core/project-layout.js.map +1 -1
  234. package/dist/core/root-selection.d.ts +11 -7
  235. package/dist/core/root-selection.d.ts.map +1 -1
  236. package/dist/core/root-selection.js +7 -8
  237. package/dist/core/root-selection.js.map +1 -1
  238. package/dist/core/rules.d.ts +10 -0
  239. package/dist/core/rules.d.ts.map +1 -0
  240. package/dist/core/rules.js +43 -0
  241. package/dist/core/rules.js.map +1 -0
  242. package/dist/core/schema-names.d.ts +16 -0
  243. package/dist/core/schema-names.d.ts.map +1 -0
  244. package/dist/core/schema-names.js +16 -0
  245. package/dist/core/schema-names.js.map +1 -0
  246. package/dist/core/schemas/base.schema.d.ts +3 -0
  247. package/dist/core/schemas/base.schema.d.ts.map +1 -1
  248. package/dist/core/schemas/base.schema.js +22 -6
  249. package/dist/core/schemas/base.schema.js.map +1 -1
  250. package/dist/core/schemas/change.schema.d.ts +16 -0
  251. package/dist/core/schemas/change.schema.d.ts.map +1 -1
  252. package/dist/core/schemas/change.schema.js +41 -10
  253. package/dist/core/schemas/change.schema.js.map +1 -1
  254. package/dist/core/schemas/spec.schema.d.ts +2 -0
  255. package/dist/core/schemas/spec.schema.d.ts.map +1 -1
  256. package/dist/core/shared/index.d.ts +3 -8
  257. package/dist/core/shared/index.d.ts.map +1 -1
  258. package/dist/core/shared/index.js +3 -8
  259. package/dist/core/shared/index.js.map +1 -1
  260. package/dist/core/shared/rules-generation.d.ts +27 -8
  261. package/dist/core/shared/rules-generation.d.ts.map +1 -1
  262. package/dist/core/shared/rules-generation.js +151 -16
  263. package/dist/core/shared/rules-generation.js.map +1 -1
  264. package/dist/core/shared/skill-generation.d.ts +38 -53
  265. package/dist/core/shared/skill-generation.d.ts.map +1 -1
  266. package/dist/core/shared/skill-generation.js +82 -51
  267. package/dist/core/shared/skill-generation.js.map +1 -1
  268. package/dist/core/shared/tool-detection.d.ts +40 -62
  269. package/dist/core/shared/tool-detection.d.ts.map +1 -1
  270. package/dist/core/shared/tool-detection.js +88 -80
  271. package/dist/core/shared/tool-detection.js.map +1 -1
  272. package/dist/core/skill-metrics.d.ts +36 -63
  273. package/dist/core/skill-metrics.d.ts.map +1 -1
  274. package/dist/core/skill-metrics.js +34 -73
  275. package/dist/core/skill-metrics.js.map +1 -1
  276. package/dist/core/spec-presenter.d.ts.map +1 -1
  277. package/dist/core/spec-presenter.js +6 -6
  278. package/dist/core/spec-presenter.js.map +1 -1
  279. package/dist/core/specs-apply.d.ts +24 -23
  280. package/dist/core/specs-apply.d.ts.map +1 -1
  281. package/dist/core/specs-apply.js +188 -173
  282. package/dist/core/specs-apply.js.map +1 -1
  283. package/dist/core/templates/fragments/interview.d.ts +2 -6
  284. package/dist/core/templates/fragments/interview.d.ts.map +1 -1
  285. package/dist/core/templates/fragments/interview.js +2 -6
  286. package/dist/core/templates/fragments/interview.js.map +1 -1
  287. package/dist/core/templates/fragments/next-step.d.ts +4 -8
  288. package/dist/core/templates/fragments/next-step.d.ts.map +1 -1
  289. package/dist/core/templates/fragments/next-step.js +4 -8
  290. package/dist/core/templates/fragments/next-step.js.map +1 -1
  291. package/dist/core/templates/fragments/verify.d.ts +9 -12
  292. package/dist/core/templates/fragments/verify.d.ts.map +1 -1
  293. package/dist/core/templates/fragments/verify.js +9 -12
  294. package/dist/core/templates/fragments/verify.js.map +1 -1
  295. package/dist/core/templates/index.d.ts +0 -6
  296. package/dist/core/templates/index.d.ts.map +1 -1
  297. package/dist/core/templates/index.js +0 -7
  298. package/dist/core/templates/index.js.map +1 -1
  299. package/dist/core/templates/skill-templates.d.ts +1 -5
  300. package/dist/core/templates/skill-templates.d.ts.map +1 -1
  301. package/dist/core/templates/skill-templates.js +0 -5
  302. package/dist/core/templates/skill-templates.js.map +1 -1
  303. package/dist/core/templates/types.d.ts +3 -7
  304. package/dist/core/templates/types.d.ts.map +1 -1
  305. package/dist/core/templates/types.js +0 -3
  306. package/dist/core/templates/types.js.map +1 -1
  307. package/dist/core/templates/workflows/apply.d.ts +3 -9
  308. package/dist/core/templates/workflows/apply.d.ts.map +1 -1
  309. package/dist/core/templates/workflows/apply.js +9 -12
  310. package/dist/core/templates/workflows/apply.js.map +1 -1
  311. package/dist/core/templates/workflows/archive.d.ts +0 -6
  312. package/dist/core/templates/workflows/archive.d.ts.map +1 -1
  313. package/dist/core/templates/workflows/archive.js +7 -5
  314. package/dist/core/templates/workflows/archive.js.map +1 -1
  315. package/dist/core/templates/workflows/decision.js +4 -4
  316. package/dist/core/templates/workflows/decision.js.map +1 -1
  317. package/dist/core/templates/workflows/explore.js +1 -1
  318. package/dist/core/templates/workflows/grill.d.ts.map +1 -1
  319. package/dist/core/templates/workflows/grill.js +0 -2
  320. package/dist/core/templates/workflows/grill.js.map +1 -1
  321. package/dist/core/templates/workflows/issue.d.ts +0 -6
  322. package/dist/core/templates/workflows/issue.d.ts.map +1 -1
  323. package/dist/core/templates/workflows/issue.js +3 -0
  324. package/dist/core/templates/workflows/issue.js.map +1 -1
  325. package/dist/core/templates/workflows/propose.d.ts +0 -6
  326. package/dist/core/templates/workflows/propose.d.ts.map +1 -1
  327. package/dist/core/templates/workflows/propose.js +0 -1
  328. package/dist/core/templates/workflows/propose.js.map +1 -1
  329. package/dist/core/templates/workflows/sync.d.ts +2 -8
  330. package/dist/core/templates/workflows/sync.d.ts.map +1 -1
  331. package/dist/core/templates/workflows/sync.js +2 -2
  332. package/dist/core/templates/workflows/sync.js.map +1 -1
  333. package/dist/core/templates/workflows/update.d.ts +0 -6
  334. package/dist/core/templates/workflows/update.d.ts.map +1 -1
  335. package/dist/core/templates/workflows/update.js.map +1 -1
  336. package/dist/core/update.d.ts +26 -21
  337. package/dist/core/update.d.ts.map +1 -1
  338. package/dist/core/update.js +165 -116
  339. package/dist/core/update.js.map +1 -1
  340. package/dist/core/user-state-migration.d.ts +13 -15
  341. package/dist/core/user-state-migration.d.ts.map +1 -1
  342. package/dist/core/user-state-migration.js +16 -20
  343. package/dist/core/user-state-migration.js.map +1 -1
  344. package/dist/core/validation/constants.d.ts +19 -25
  345. package/dist/core/validation/constants.d.ts.map +1 -1
  346. package/dist/core/validation/constants.js +25 -20
  347. package/dist/core/validation/constants.js.map +1 -1
  348. package/dist/core/validation/prose-length.d.ts +15 -0
  349. package/dist/core/validation/prose-length.d.ts.map +1 -0
  350. package/dist/core/validation/prose-length.js +29 -0
  351. package/dist/core/validation/prose-length.js.map +1 -0
  352. package/dist/core/validation/purpose-placeholder.d.ts +9 -16
  353. package/dist/core/validation/purpose-placeholder.d.ts.map +1 -1
  354. package/dist/core/validation/purpose-placeholder.js +30 -44
  355. package/dist/core/validation/purpose-placeholder.js.map +1 -1
  356. package/dist/core/validation/section-validator.d.ts +4 -4
  357. package/dist/core/validation/section-validator.d.ts.map +1 -1
  358. package/dist/core/validation/section-validator.js +43 -7
  359. package/dist/core/validation/section-validator.js.map +1 -1
  360. package/dist/core/validation/task-numbering.d.ts +6 -3
  361. package/dist/core/validation/task-numbering.d.ts.map +1 -1
  362. package/dist/core/validation/task-numbering.js +23 -11
  363. package/dist/core/validation/task-numbering.js.map +1 -1
  364. package/dist/core/validation/types.d.ts +18 -0
  365. package/dist/core/validation/types.d.ts.map +1 -1
  366. package/dist/core/validation/types.js +12 -1
  367. package/dist/core/validation/types.js.map +1 -1
  368. package/dist/core/validation/validator.d.ts +50 -51
  369. package/dist/core/validation/validator.d.ts.map +1 -1
  370. package/dist/core/validation/validator.js +486 -263
  371. package/dist/core/validation/validator.js.map +1 -1
  372. package/dist/prompts/searchable-multi-select.d.ts +3 -8
  373. package/dist/prompts/searchable-multi-select.d.ts.map +1 -1
  374. package/dist/prompts/searchable-multi-select.js +16 -39
  375. package/dist/prompts/searchable-multi-select.js.map +1 -1
  376. package/dist/utils/change-metadata.d.ts +11 -50
  377. package/dist/utils/change-metadata.d.ts.map +1 -1
  378. package/dist/utils/change-metadata.js +48 -67
  379. package/dist/utils/change-metadata.js.map +1 -1
  380. package/dist/utils/change-utils.d.ts +31 -54
  381. package/dist/utils/change-utils.d.ts.map +1 -1
  382. package/dist/utils/change-utils.js +143 -100
  383. package/dist/utils/change-utils.js.map +1 -1
  384. package/dist/utils/file-lock.d.ts +39 -0
  385. package/dist/utils/file-lock.d.ts.map +1 -0
  386. package/dist/utils/file-lock.js +149 -0
  387. package/dist/utils/file-lock.js.map +1 -0
  388. package/dist/utils/file-system.d.ts +12 -32
  389. package/dist/utils/file-system.d.ts.map +1 -1
  390. package/dist/utils/file-system.js +16 -40
  391. package/dist/utils/file-system.js.map +1 -1
  392. package/dist/utils/frontmatter.d.ts +7 -11
  393. package/dist/utils/frontmatter.d.ts.map +1 -1
  394. package/dist/utils/frontmatter.js +11 -11
  395. package/dist/utils/frontmatter.js.map +1 -1
  396. package/dist/utils/interactive.d.ts +4 -9
  397. package/dist/utils/interactive.d.ts.map +1 -1
  398. package/dist/utils/interactive.js +2 -4
  399. package/dist/utils/interactive.js.map +1 -1
  400. package/dist/utils/item-discovery.d.ts +15 -10
  401. package/dist/utils/item-discovery.d.ts.map +1 -1
  402. package/dist/utils/item-discovery.js +42 -47
  403. package/dist/utils/item-discovery.js.map +1 -1
  404. package/dist/utils/link.d.ts +13 -4
  405. package/dist/utils/link.d.ts.map +1 -1
  406. package/dist/utils/link.js +13 -4
  407. package/dist/utils/link.js.map +1 -1
  408. package/dist/utils/match.js.map +1 -1
  409. package/dist/utils/requirement-diff.d.ts +13 -23
  410. package/dist/utils/requirement-diff.d.ts.map +1 -1
  411. package/dist/utils/requirement-diff.js +13 -23
  412. package/dist/utils/requirement-diff.js.map +1 -1
  413. package/dist/utils/spec-files.d.ts +10 -11
  414. package/dist/utils/spec-files.d.ts.map +1 -1
  415. package/dist/utils/spec-files.js +31 -22
  416. package/dist/utils/spec-files.js.map +1 -1
  417. package/dist/utils/task-progress.d.ts +11 -9
  418. package/dist/utils/task-progress.d.ts.map +1 -1
  419. package/dist/utils/task-progress.js +53 -32
  420. package/dist/utils/task-progress.js.map +1 -1
  421. package/dist/utils/timestamp.d.ts +5 -8
  422. package/dist/utils/timestamp.d.ts.map +1 -1
  423. package/dist/utils/timestamp.js +5 -8
  424. package/dist/utils/timestamp.js.map +1 -1
  425. package/package.json +9 -10
  426. package/schemas/decision/templates/decision.md +3 -1
  427. package/schemas/decision/templates/index.md +2 -2
  428. package/schemas/issue/schema.yaml +11 -2
  429. package/schemas/issue/templates/spec.md +37 -3
  430. package/schemas/sdd/schema.yaml +24 -1
  431. package/schemas/sdd/templates/spec.md +37 -3
package/dist/core/init.js CHANGED
@@ -1,90 +1,171 @@
1
- /**
2
- * Init Command
3
- *
4
- * Sets up tospec Agent Skills for Claude Code and Codex.
5
- */
1
+ /** Sets up tospec Agent Skills for Claude Code and Codex. */
6
2
  import path from 'path';
7
3
  import * as fs from 'fs';
8
4
  import { createRequire } from 'module';
9
5
  import { FileSystemUtils } from '../utils/file-system.js';
10
6
  import { ensureDirectoryAnchor, tospecLayoutDirs } from './project-layout.js';
11
- import { AI_TOOLS, TOSPEC_DIR_NAME, } from './config.js';
7
+ import { AI_TOOLS, TOSPEC_DIR_NAME, fallbackToolIds, resolveToolSelection, } from './config.js';
12
8
  import { isInteractive } from '../utils/interactive.js';
13
9
  import { serializeConfig } from './config-prompts.js';
14
10
  import { copyDir } from '../utils/link.js';
15
- import { removeLegacySkillDirs, removeLegacyToolSkillLayout, getToolsWithSkillsDir, getToolStates, getSkillTemplates, writeSkillDir, writeToolCommands, writeToolRules, } from './shared/index.js';
11
+ import { removeLegacySkillDirs, removeUnselectedSkillDirs, removeLegacyToolSkillLayout, getToolsWithSkillsDir, getToolStates, getSkillTemplates, writeSkillDir, writeToolCommands, findWorkflowReferenceGaps, describeWorkflowReferenceGaps, } from './shared/index.js';
16
12
  import { getGlobalConfig } from './global-config.js';
17
13
  import { runLegacyUserStateMigration } from './user-state-migration.js';
18
14
  import { getProfileWorkflows, ALL_WORKFLOWS } from './profiles.js';
19
15
  import { getAvailableTools } from './available-tools.js';
20
16
  import { resolveConfigFileInDir } from './project-config.js';
17
+ import { planWorkflowRules, writeWorkflowRules } from './shared/rules-generation.js';
18
+ import { hasResidualCodexOutput, RESIDUAL_CODEX_NOTICE, RESIDUAL_CODEX_FIX } from './codex-residue.js';
19
+ import { emitSuccess } from '../commands/shared-output.js';
21
20
  const require = createRequire(import.meta.url);
22
21
  const { version: TOSPEC_VERSION } = require('../../package.json');
23
- // -----------------------------------------------------------------------------
24
- // Constants
25
- // -----------------------------------------------------------------------------
26
22
  const DEFAULT_SCHEMA = 'sdd';
27
- // -----------------------------------------------------------------------------
28
- // Init Command Class
29
- // -----------------------------------------------------------------------------
23
+ /** The canonical skills directory every dependent target is copied from. */
24
+ function agentsSkillsDirFor(projectPath) {
25
+ return path.join(projectPath, '.agents', 'skills');
26
+ }
27
+ /** The `--json` null-shape; mirrors the success payload with `init` nulled. */
28
+ export const INIT_FAILURE_PAYLOAD = { init: null, root: null };
30
29
  export class InitCommand {
31
30
  toolsArg;
32
31
  force;
33
32
  interactiveOption;
34
33
  profileOverride;
34
+ json;
35
35
  constructor(options = {}) {
36
36
  this.toolsArg = options.tools;
37
37
  this.force = options.force ?? false;
38
38
  this.interactiveOption = options.interactive;
39
39
  this.profileOverride = options.profile;
40
+ this.json = options.json ?? false;
40
41
  }
42
+ /**
43
+ * Progress prose, suppressed under `--json`: the envelope contract allows only
44
+ * one document on stdout.
45
+ */
46
+ say(...args) {
47
+ if (!this.json)
48
+ console.log(...args);
49
+ }
50
+ /**
51
+ * Unlike `say`, a warning has no other carrier — dropping it under `--json`
52
+ * would leave an agent caller less informed than a human.
53
+ */
54
+ warn(code, message, fix) {
55
+ this.jsonWarnings.push({ severity: 'warning', code, message, ...(fix ? { fix } : {}) });
56
+ if (!this.json) {
57
+ console.log(`Warning: ${message}`);
58
+ if (fix)
59
+ console.log(fix);
60
+ }
61
+ }
62
+ /** Emitted alongside the success payload; empty means the key is omitted. */
63
+ jsonWarnings = [];
41
64
  async execute(targetPath) {
42
- // Above every getGlobalConfig() below, so this run reads the migrated file
43
- // rather than the legacy fallback.
44
- await runLegacyUserStateMigration();
45
65
  const projectPath = path.resolve(targetPath);
46
66
  const tospecDir = TOSPEC_DIR_NAME;
47
67
  const tospecPath = path.join(projectPath, tospecDir);
48
- // Validation happens silently in the background
49
- const extendMode = await this.validate(projectPath, tospecPath);
50
- // Detect available tools in the project (task 7.1)
68
+ // Detect without writing: even the permission probe must follow rule preflight.
69
+ const extendMode = await FileSystemUtils.directoryExists(tospecPath);
51
70
  const detectedTools = getAvailableTools(projectPath);
52
- // Show animated welcome screen (interactive mode only)
53
71
  const canPrompt = this.canPromptInteractively();
54
72
  if (canPrompt) {
55
73
  const { showWelcomeScreen } = await import('../ui/welcome-screen.js');
56
74
  await showWelcomeScreen();
57
75
  }
58
- // Validate profile override early so invalid values fail before tool setup.
59
- // The resolved value is consumed later when generation reads effective config.
76
+ // Validated early so an invalid value fails before tool setup; the resolved
77
+ // value is read again later, when generation needs the effective config.
60
78
  this.resolveProfileOverride();
61
- // Get tool states before processing
62
79
  const toolStates = getToolStates(projectPath);
63
- // Get tool selection (pass detected tools for pre-selection)
64
80
  const selectedToolIds = await this.getSelectedTools(toolStates, extendMode, detectedTools, projectPath);
65
- // Validate selected tools
66
81
  const validatedTools = this.validateTools(selectedToolIds, toolStates);
67
- // The decision rule is always installed (only skipped when no agent dir exists).
68
- const installRules = validatedTools.length > 0;
69
- // Create directory structure and config
82
+ const rulePlan = await planWorkflowRules(projectPath, validatedTools.map((tool) => tool.value));
83
+ // Migration remains before every getGlobalConfig(), but after conflict checks.
84
+ await runLegacyUserStateMigration();
85
+ await this.validate(projectPath);
70
86
  await this.createDirectoryStructure(tospecPath, extendMode);
71
- // Create config.yaml if needed — must run before skill/command generation.
87
+ // Must run before skill/command generation.
72
88
  const configStatus = await this.createConfig(tospecPath, extendMode);
73
- // Generate skills and commands for each tool
74
- const results = await this.generateSkillsAndCommands(projectPath, validatedTools, installRules);
75
- // Display success message
89
+ // A profile left at `custom` with nothing picked resolves to zero workflows,
90
+ // so init would report both tools `created` with no skill on disk. Raised
91
+ // before the write, and only when a tool was asked for — "no tools selected"
92
+ // has its own line and is not a profile problem.
93
+ const initConfig = getGlobalConfig();
94
+ const initProfile = this.profileOverride ?? initConfig.profile ?? 'core';
95
+ if (validatedTools.length > 0 &&
96
+ getProfileWorkflows(initProfile, initConfig.workflows).length === 0) {
97
+ this.warn('init_profile_selects_no_workflows', `Profile '${initProfile}' selects no workflows, so this init writes no tospec skills.`, 'Run "tospec config profile core" then "tospec init" to install them.');
98
+ }
99
+ // The same check `update` runs, for the same reason: the skills about to be
100
+ // written name their siblings in fixed prose, so a partial selection
101
+ // installs instructions that point at workflows this project will not have.
102
+ if (validatedTools.length > 0) {
103
+ const referenceGaps = findWorkflowReferenceGaps(getProfileWorkflows(initProfile, initConfig.workflows));
104
+ if (referenceGaps.length > 0) {
105
+ const missing = [...new Set(referenceGaps.map((gap) => gap.missing))].sort();
106
+ this.warn('init_workflow_reference_missing', `Installed skills point at workflows this profile leaves out: ${describeWorkflowReferenceGaps(referenceGaps)}.`, `Add them with "tospec config set workflows" (missing: ${missing.join(', ')}), or expect agents to hit instructions they cannot follow.`);
107
+ }
108
+ }
109
+ const results = await this.generateSkillsAndCommands(projectPath, validatedTools);
110
+ const successfulIds = new Set([...results.createdTools, ...results.refreshedTools].map((tool) => tool.value));
111
+ await writeWorkflowRules(rulePlan.filter((entry) => successfulIds.has(entry.toolId)), {
112
+ silent: this.json,
113
+ });
76
114
  this.displaySuccessMessage(projectPath, validatedTools, results, configStatus);
115
+ // After every write, so a run that dies in rule preflight never leaves
116
+ // advice about a state it did not reach; before the JSON envelope, or the
117
+ // warning is recorded into a document already on stdout.
118
+ if (hasResidualCodexOutput(projectPath)) {
119
+ this.warn('codex_residue', RESIDUAL_CODEX_NOTICE, RESIDUAL_CODEX_FIX);
120
+ }
121
+ if (this.json) {
122
+ const globalCfg = getGlobalConfig();
123
+ const activeProfile = this.profileOverride ?? globalCfg.profile ?? 'core';
124
+ const activeWorkflows = [...getProfileWorkflows(activeProfile, globalCfg.workflows)];
125
+ emitSuccess({
126
+ init: {
127
+ profile: activeProfile,
128
+ workflows: activeWorkflows,
129
+ created: results.createdTools.map((t) => ({ tool: t.value, dir: t.skillsDir })),
130
+ refreshed: results.refreshedTools.map((t) => ({ tool: t.value, dir: t.skillsDir })),
131
+ failed: results.failedTools.map((f) => ({ name: f.name, error: f.error.message })),
132
+ // `created` only records that a tool was configured, so this is the
133
+ // only field saying what was installed, and it must count what
134
+ // reached a tool directory: `--tools none` writes nothing anywhere.
135
+ skills: results.createdTools.length + results.refreshedTools.length > 0
136
+ ? getSkillTemplates(activeWorkflows).length
137
+ : 0,
138
+ config: configStatus,
139
+ },
140
+ ...(this.jsonWarnings.length ? { status: this.jsonWarnings } : {}),
141
+ }, { path: projectPath, source: 'explicit' });
142
+ }
77
143
  }
78
- // ═══════════════════════════════════════════════════════════
79
- // VALIDATION & SETUP
80
- // ═══════════════════════════════════════════════════════════
81
- async validate(projectPath, tospecPath) {
82
- const extendMode = await FileSystemUtils.directoryExists(tospecPath);
83
- // Check write permissions
144
+ async validate(projectPath) {
84
145
  if (!(await FileSystemUtils.ensureWritePermissions(projectPath))) {
85
146
  throw new Error(`Insufficient permissions to write to ${projectPath}`);
86
147
  }
87
- return extendMode;
148
+ }
149
+ /**
150
+ * A row can arrive uninvited by being required by another, which has to be
151
+ * said *before* the prompt to inform the answer. Derived from the table so a
152
+ * future dependency documents itself.
153
+ *
154
+ * Prompt path only: a `--tools` run has no answer to inform.
155
+ */
156
+ discloseToolDependencies() {
157
+ for (const tool of AI_TOOLS) {
158
+ if (!tool.requires?.length)
159
+ continue;
160
+ const required = tool.requires
161
+ .map((id) => AI_TOOLS.find((candidate) => candidate.value === id))
162
+ .filter((candidate) => Boolean(candidate));
163
+ if (required.length === 0)
164
+ continue;
165
+ const names = required.map((candidate) => candidate.name).join(', ');
166
+ const dirs = required.map((candidate) => `${candidate.skillsDir}/skills/`).join(', ');
167
+ this.say(`${tool.name} copies its skills from ${dirs}, so selecting it also selects ${names}.`);
168
+ }
88
169
  }
89
170
  canPromptInteractively() {
90
171
  if (this.interactiveOption === false)
@@ -102,11 +183,7 @@ export class InitCommand {
102
183
  }
103
184
  throw new Error(`Invalid profile "${this.profileOverride}". Available profiles: core, custom`);
104
185
  }
105
- // ═══════════════════════════════════════════════════════════
106
- // TOOL SELECTION
107
- // ═══════════════════════════════════════════════════════════
108
186
  async getSelectedTools(toolStates, extendMode, detectedTools, projectPath) {
109
- // Check for --tools flag first
110
187
  const nonInteractiveSelection = this.resolveToolsArg();
111
188
  if (nonInteractiveSelection !== null) {
112
189
  return nonInteractiveSelection;
@@ -118,20 +195,22 @@ export class InitCommand {
118
195
  .map(([toolId]) => toolId));
119
196
  const shouldPreselectDetected = !extendMode && configuredToolIds.size === 0;
120
197
  const canPrompt = this.canPromptInteractively();
121
- // Non-interactive mode: use detected tools as fallback (task 7.8)
198
+ // Detecting nothing is an absence of information, not a decision to write
199
+ // nothing, so it falls back to the base target. (A cleared menu *is* a
200
+ // decision.)
122
201
  if (!canPrompt) {
123
- if (detectedToolIds.size > 0) {
124
- return [...detectedToolIds];
125
- }
126
- throw new Error(`No tools detected and no --tools flag provided. Valid tools:\n ${validTools.join('\n ')}\n\nUse --tools all, --tools none, or --tools claude,codex.`);
202
+ const detected = [...detectedToolIds];
203
+ return resolveToolSelection(detected.length > 0 ? detected : fallbackToolIds());
127
204
  }
128
205
  if (validTools.length === 0) {
129
206
  throw new Error(`No tools available for skill generation.`);
130
207
  }
131
- // Interactive mode: show searchable multi-select
132
208
  const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
133
- // Build choices: pre-select configured tools; keep detected tools visible but unselected.
134
- const sortedChoices = validTools
209
+ // Every target with an output directory is offered, including the one others
210
+ // copy from: what the list shows is what gets written, and a row pulled in
211
+ // by another comes from a disclosed expansion, not a locked checkbox.
212
+ const offeredTools = getToolsWithSkillsDir();
213
+ const sortedChoices = offeredTools
135
214
  .map((toolId) => {
136
215
  const tool = AI_TOOLS.find((t) => t.value === toolId);
137
216
  const status = toolStates.get(toolId);
@@ -146,7 +225,7 @@ export class InitCommand {
146
225
  };
147
226
  })
148
227
  .sort((a, b) => {
149
- // Configured tools first, then detected (not configured), then everything else.
228
+ // Configured first, then detected-but-unconfigured, then the rest.
150
229
  if (a.configured && !b.configured)
151
230
  return -1;
152
231
  if (!a.configured && b.configured)
@@ -157,31 +236,32 @@ export class InitCommand {
157
236
  return 1;
158
237
  return 0;
159
238
  });
160
- const configuredNames = validTools
239
+ const configuredNames = offeredTools
161
240
  .filter((toolId) => configuredToolIds.has(toolId))
162
241
  .map((toolId) => AI_TOOLS.find((t) => t.value === toolId)?.name || toolId);
163
242
  if (configuredNames.length > 0) {
164
- console.log(`tospec configured: ${configuredNames.join(', ')} (pre-selected)`);
243
+ this.say(`tospec configured: ${configuredNames.join(', ')} (pre-selected)`);
165
244
  }
166
245
  const detectedOnlyNames = detectedTools
167
- .filter((tool) => !configuredToolIds.has(tool.value))
246
+ .filter((tool) => !configuredToolIds.has(tool.value) && offeredTools.includes(tool.value))
168
247
  .map((tool) => tool.name);
169
248
  if (detectedOnlyNames.length > 0) {
170
249
  const detectionLabel = shouldPreselectDetected
171
250
  ? 'pre-selected for first-time setup'
172
251
  : 'not pre-selected';
173
- console.log(`Detected tool directories: ${detectedOnlyNames.join(', ')} (${detectionLabel})`);
252
+ this.say(`Detected tool directories: ${detectedOnlyNames.join(', ')} (${detectionLabel})`);
174
253
  }
254
+ // Last thing before the question, so the dependency is on screen while it
255
+ // can still change the answer.
256
+ this.discloseToolDependencies();
257
+ // No minimum: clearing every checkbox means "write no agent content", as
258
+ // `--tools none` does.
175
259
  const selectedTools = await searchableMultiSelect({
176
- message: `Select tools to set up (${validTools.length} available)`,
260
+ message: `Select tools to set up (${offeredTools.length} available)`,
177
261
  pageSize: 15,
178
262
  choices: sortedChoices,
179
- validate: (selected) => selected.length > 0 || 'Select at least one tool',
180
263
  });
181
- if (selectedTools.length === 0) {
182
- throw new Error('At least one tool must be selected');
183
- }
184
- return selectedTools;
264
+ return resolveToolSelection(selectedTools);
185
265
  }
186
266
  resolveToolsArg() {
187
267
  if (typeof this.toolsArg === 'undefined') {
@@ -196,7 +276,7 @@ export class InitCommand {
196
276
  const availableList = ['all', 'none', ...availableTools].join(', ');
197
277
  const lowerRaw = raw.toLowerCase();
198
278
  if (lowerRaw === 'all') {
199
- return availableTools;
279
+ return resolveToolSelection(availableTools);
200
280
  }
201
281
  if (lowerRaw === 'none') {
202
282
  return [];
@@ -216,14 +296,9 @@ export class InitCommand {
216
296
  if (invalidTokens.length > 0) {
217
297
  throw new Error(`Invalid tool(s): ${invalidTokens.join(', ')}. Available values: ${availableList}`);
218
298
  }
219
- // Deduplicate while preserving order
220
- const deduped = [];
221
- for (const token of normalizedTokens) {
222
- if (!deduped.includes(token)) {
223
- deduped.push(token);
224
- }
225
- }
226
- return deduped;
299
+ // Expansion deduplicates and orders dependencies first, so naming a tool
300
+ // whose skills copy from another never produces a copy with no source.
301
+ return resolveToolSelection(normalizedTokens);
227
302
  }
228
303
  validateTools(toolIds, toolStates) {
229
304
  const validatedTools = [];
@@ -247,20 +322,14 @@ export class InitCommand {
247
322
  }
248
323
  return validatedTools;
249
324
  }
250
- // ═══════════════════════════════════════════════════════════
251
- // DIRECTORY STRUCTURE
252
- // ═══════════════════════════════════════════════════════════
253
325
  /**
254
- * Anchoring runs on every init, not only the first: a project that predates
255
- * the rule reaches the same state by re-running the command it already runs
256
- * to refresh generated files, with no separate migration step.
257
- *
258
- * extendMode differs only in staying quiet — the work itself is identical, so
259
- * it is a pair of guards rather than a second copy of the loop.
326
+ * Anchoring runs on every init, not only the first, so a project predating the
327
+ * rule reaches the same state without a migration step. `extendMode` differs
328
+ * only in staying quiet.
260
329
  */
261
330
  async createDirectoryStructure(tospecPath, extendMode) {
262
331
  if (!extendMode)
263
- console.log('Creating tospec structure...');
332
+ this.say('Creating tospec structure...');
264
333
  const layout = tospecLayoutDirs(tospecPath);
265
334
  for (const dir of [tospecPath, ...layout]) {
266
335
  await FileSystemUtils.createDirectory(dir);
@@ -269,58 +338,56 @@ export class InitCommand {
269
338
  await ensureDirectoryAnchor(dir);
270
339
  }
271
340
  if (!extendMode)
272
- console.log('Created tospec structure');
341
+ this.say('Created tospec structure');
273
342
  }
274
- // ═══════════════════════════════════════════════════════════
275
- // SKILL GENERATION
276
- // ═══════════════════════════════════════════════════════════
277
- async generateSkillsAndCommands(projectPath, tools, installRules) {
343
+ async generateSkillsAndCommands(projectPath, tools) {
278
344
  const createdTools = [];
279
345
  const refreshedTools = [];
280
346
  const failedTools = [];
281
347
  let removedCommandCount = 0;
282
- // Read global config for profile settings (use --profile override if set)
283
348
  const globalConfig = getGlobalConfig();
284
349
  const profile = this.resolveProfileOverride() ?? globalConfig.profile ?? 'core';
285
350
  const workflows = getProfileWorkflows(profile, globalConfig.workflows);
286
351
  const skillTemplates = getSkillTemplates(workflows);
287
- // Write canonical files once to .agents/ — the single source of truth
288
- // for every tool. Tool directories below get a link into these where a
289
- // tool's on-disk shape allows it, or a real generated copy where it
290
- // can't (see the per-tool loop's comments).
291
- const agentsSkillsDir = path.join(projectPath, '.agents', 'skills');
292
- for (const { template, dirName } of skillTemplates) {
293
- await writeSkillDir(agentsSkillsDir, dirName, template, TOSPEC_VERSION);
352
+ // Canonical files, written once to .agents/. Gated on "any target at all" so
353
+ // `--tools none` writes nothing; dependency expansion guarantees .agents is
354
+ // present whenever anything else is.
355
+ const agentsSkillsDir = agentsSkillsDirFor(projectPath);
356
+ if (tools.length > 0) {
357
+ for (const { template, dirName } of skillTemplates) {
358
+ await writeSkillDir(agentsSkillsDir, dirName, template, TOSPEC_VERSION);
359
+ }
294
360
  }
361
+ // Both sweeps sit outside that gate: retiring a workflow is not conditional
362
+ // on this run writing anything. Gated, `--tools none` would leave a retired
363
+ // skill dir loadable by every agent reading .agents/skills/, and a dropped
364
+ // workflow would come back on the next copy from the canonical directory.
295
365
  await removeLegacySkillDirs(agentsSkillsDir);
366
+ await removeUnselectedSkillDirs(agentsSkillsDir, workflows);
296
367
  removedCommandCount += await this.removeLegacyCommands(projectPath);
297
- // Process each tool
298
368
  for (const tool of tools) {
299
- console.log(`Setting up ${tool.name}...`);
369
+ this.say(`Setting up ${tool.name}...`);
300
370
  try {
301
- // Link (or copy) each skill dir from .agents/skills/ into the tool's
302
- // standard `skills/` dir. Project-scope skills can't live in a nested
303
- // sub-folder, so skills stay flat and prefixed:
304
- // `<tool>/skills/tospec-<workflow>/SKILL.md`. Clean up the intermediate
305
- // grouped/bare layouts (and the retired verify skill) from older builds first.
371
+ // Project-scope skills cannot live in a nested sub-folder, so they stay
372
+ // flat and prefixed: `<tool>/skills/tospec-<workflow>/SKILL.md`.
306
373
  const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
307
374
  await removeLegacyToolSkillLayout(path.join(projectPath, tool.skillsDir));
308
375
  await removeLegacySkillDirs(skillsDir);
309
- // Every tool gets a plain copy of each skill dir; copies don't track
310
- // .agents/ edits — `tospec update` regenerates them.
311
- for (const { dirName } of skillTemplates) {
312
- const src = path.join(agentsSkillsDir, dirName);
313
- const dest = path.join(skillsDir, dirName);
314
- await copyDir(src, dest);
376
+ // Independent copies, not links: see the header of utils/link.ts.
377
+ // copyDir clears its dest, so copying .agents/skills onto itself would
378
+ // delete the source.
379
+ if (path.resolve(skillsDir) !== path.resolve(agentsSkillsDir)) {
380
+ for (const { dirName } of skillTemplates) {
381
+ await copyDir(path.join(agentsSkillsDir, dirName), path.join(skillsDir, dirName));
382
+ }
315
383
  }
316
- // Commands: for tools with a registered adapter (Claude Code), write
317
- // `.claude/commands/tosx/<id>.md` so they can be invoked as `/tosx:<id>`.
384
+ // Per-tool prune. The canonical sweep above cannot reach a copy that a
385
+ // previous, wider profile left in this tool's directory.
386
+ await removeUnselectedSkillDirs(skillsDir, workflows);
387
+ // Tools with a registered adapter (Claude Code) get
388
+ // `.claude/commands/tosx/<id>.md`, invokable as `/tosx:<id>`.
318
389
  await writeToolCommands(projectPath, tool.value, workflows);
319
- // Rules: opt-in static guidance under `<tool>/rules/tospec/`.
320
- if (installRules) {
321
- await writeToolRules(projectPath, tool.skillsDir);
322
- }
323
- console.log(`Setup complete for ${tool.name}`);
390
+ this.say(`Setup complete for ${tool.name}`);
324
391
  if (tool.wasConfigured) {
325
392
  refreshedTools.push(tool);
326
393
  }
@@ -329,7 +396,7 @@ export class InitCommand {
329
396
  }
330
397
  }
331
398
  catch (error) {
332
- console.log(`Failed for ${tool.name}`);
399
+ this.say(`Failed for ${tool.name}`);
333
400
  failedTools.push({ name: tool.name, error: error });
334
401
  }
335
402
  }
@@ -340,9 +407,6 @@ export class InitCommand {
340
407
  removedCommandCount,
341
408
  };
342
409
  }
343
- // ═══════════════════════════════════════════════════════════
344
- // CONFIG FILE
345
- // ═══════════════════════════════════════════════════════════
346
410
  async createConfig(tospecPath, extendMode) {
347
411
  const configPath = path.join(tospecPath, 'config.yaml');
348
412
  if (resolveConfigFileInDir(tospecPath)) {
@@ -357,82 +421,86 @@ export class InitCommand {
357
421
  return 'skipped';
358
422
  }
359
423
  }
360
- // ═══════════════════════════════════════════════════════════
361
- // UI & OUTPUT
362
- // ═══════════════════════════════════════════════════════════
363
424
  displaySuccessMessage(projectPath, tools, results, configStatus) {
364
- console.log();
365
- console.log('tospec Setup Complete');
366
- console.log();
367
- // Show created vs refreshed tools
425
+ const globalConfig = getGlobalConfig();
426
+ const activeProfile = this.profileOverride ?? globalConfig.profile ?? 'core';
427
+ const activeWorkflows = [...getProfileWorkflows(activeProfile, globalConfig.workflows)];
428
+ this.say();
429
+ this.say('tospec Setup Complete');
430
+ this.say();
368
431
  if (results.createdTools.length > 0) {
369
- console.log(`Created: ${results.createdTools.map((t) => t.name).join(', ')}`);
432
+ this.say(`Created: ${results.createdTools.map((t) => t.name).join(', ')}`);
370
433
  }
371
434
  if (results.refreshedTools.length > 0) {
372
- console.log(`Refreshed: ${results.refreshedTools.map((t) => t.name).join(', ')}`);
435
+ this.say(`Refreshed: ${results.refreshedTools.map((t) => t.name).join(', ')}`);
436
+ }
437
+ // Every block below is keyed off a non-empty selection, so an empty one
438
+ // needs its own line saying no skills exist.
439
+ if (tools.length === 0) {
440
+ this.say('No tools selected: no skills, commands or rules were written.');
373
441
  }
374
- // Show counts (respecting profile filter)
375
442
  const successfulTools = [...results.createdTools, ...results.refreshedTools];
376
443
  if (successfulTools.length > 0) {
377
- const globalConfig = getGlobalConfig();
378
- const profile = this.profileOverride ?? globalConfig.profile ?? 'core';
379
- const workflows = getProfileWorkflows(profile, globalConfig.workflows);
380
- const toolDirs = [...new Set(successfulTools.map((t) => t.skillsDir))].join(', ');
381
- const skillCount = getSkillTemplates(workflows).length;
382
- console.log(`${skillCount} skills in ${toolDirs}/`);
383
- }
384
- // Show failures
444
+ // Trailing slash per directory, not once at the end: with more than one
445
+ // target ".agents, .claude/" reads as a single odd path.
446
+ const toolDirs = [...new Set(successfulTools.map((t) => `${t.skillsDir}/`))].join(', ');
447
+ const skillCount = getSkillTemplates(activeWorkflows).length;
448
+ this.say(`${skillCount} skills in ${toolDirs}`);
449
+ // Dependent targets hold independent copies, so an edit to the canonical
450
+ // skill does not reach them. Only when something was copied: under an
451
+ // empty profile this would claim a copy from a directory holding none.
452
+ const dependents = successfulTools.filter((t) => path.resolve(projectPath, t.skillsDir, 'skills') !== path.resolve(agentsSkillsDirFor(projectPath)));
453
+ if (skillCount > 0 && dependents.length > 0) {
454
+ this.say(`Copied from .agents/skills/: ${dependents.map((t) => t.name).join(', ')} — rerun 'tospec update' after editing the canonical skills`);
455
+ }
456
+ }
385
457
  if (results.failedTools.length > 0) {
386
- console.log(`Failed: ${results.failedTools.map((f) => `${f.name} (${f.error.message})`).join(', ')}`);
458
+ this.say(`Failed: ${results.failedTools.map((f) => `${f.name} (${f.error.message})`).join(', ')}`);
387
459
  }
388
460
  if (results.removedCommandCount > 0) {
389
- console.log(`Removed: ${results.removedCommandCount} legacy command entries`);
461
+ this.say(`Removed: ${results.removedCommandCount} legacy command entries`);
390
462
  }
391
- // Config status
392
463
  if (configStatus === 'created') {
393
- console.log(`Config: tospec/config.yaml (schema: ${DEFAULT_SCHEMA})`);
464
+ this.say(`Config: tospec/config.yaml (schema: ${DEFAULT_SCHEMA})`);
394
465
  }
395
466
  else if (configStatus === 'exists') {
396
- // Show actual filename (config.yaml or config.yml)
467
+ // Name the file that is actually there: config.yaml or config.yml.
397
468
  const configFile = resolveConfigFileInDir(path.join(projectPath, TOSPEC_DIR_NAME));
398
469
  const configName = configFile ? path.basename(configFile) : 'config.yaml';
399
- console.log(`Config: tospec/${configName} (exists)`);
470
+ this.say(`Config: tospec/${configName} (exists)`);
400
471
  }
401
472
  else {
402
- console.log(`Config: skipped (non-interactive mode)`);
473
+ this.say(`Config: skipped (non-interactive mode)`);
474
+ }
475
+ this.say();
476
+ if (tools.length === 0) {
477
+ // `tospec-propose` is a skill and this run wrote none, so advising it
478
+ // would name something not on disk.
479
+ this.say('Getting started:');
480
+ this.say(" Add agent skills: tospec init --tools <tool> (or rerun 'tospec init')");
403
481
  }
404
- // Getting started (task 7.6: show propose if in profile)
405
- const globalCfg = getGlobalConfig();
406
- const activeProfile = this.profileOverride ?? globalCfg.profile ?? 'core';
407
- const activeWorkflows = [...getProfileWorkflows(activeProfile, globalCfg.workflows)];
408
- console.log();
409
- if (activeWorkflows.includes('propose')) {
410
- console.log('Getting started:');
411
- console.log(' Start your first change: tospec-propose "your idea"');
482
+ else if (activeWorkflows.includes('propose')) {
483
+ this.say('Getting started:');
484
+ this.say(' Start your first change: tospec-propose "your idea"');
412
485
  }
413
486
  else {
414
- console.log("Done. Run 'tospec config profile' to configure your workflows.");
487
+ this.say("Done. Run 'tospec config profile' to configure your workflows.");
415
488
  }
416
- // Links
417
- console.log();
418
- console.log(`Documentation: README.md`);
419
- // Restart instruction if any tools were configured
420
489
  if (results.createdTools.length > 0 || results.refreshedTools.length > 0) {
421
- console.log();
422
- console.log('Restart your agent for skills to take effect.');
490
+ this.say();
491
+ this.say('Restart your agent for skills to take effect.');
423
492
  }
424
- console.log();
493
+ this.say();
425
494
  }
426
495
  async removeLegacyCommands(projectPath) {
427
496
  let removed = 0;
428
- // Commands now live under `.claude/commands/tosx/` (managed by
429
- // writeToolCommands). These are unused/legacy command locations — including
430
- // the old `.claude/commands/tospec/` namespace this replaced.
497
+ // Commands live under `.claude/commands/tosx/` now (writeToolCommands);
498
+ // these are the locations it replaced.
431
499
  const legacyPaths = [
432
500
  path.join(projectPath, '.agents', 'commands', 'tospec'),
433
501
  path.join(projectPath, '.claude', 'commands', 'tospec'),
434
- // Cursor support was removed; this cleanup stays so existing users'
435
- // stale .cursor command files still get swept away on init/update.
502
+ // Cursor support was removed; this stays so stale .cursor command files
503
+ // are still swept away.
436
504
  ...ALL_WORKFLOWS.map((workflow) => path.join(projectPath, '.cursor', 'commands', `tospec-${workflow}.md`)),
437
505
  ];
438
506
  for (const legacyPath of legacyPaths) {