@codewalla_india/openspec 1.0.1

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 (356) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +225 -0
  3. package/bin/openspec.js +5 -0
  4. package/dist/cli/index.d.ts +10 -0
  5. package/dist/cli/index.js +548 -0
  6. package/dist/commands/change.d.ts +39 -0
  7. package/dist/commands/change.js +279 -0
  8. package/dist/commands/completion.d.ts +72 -0
  9. package/dist/commands/completion.js +264 -0
  10. package/dist/commands/config.d.ts +36 -0
  11. package/dist/commands/config.js +552 -0
  12. package/dist/commands/context.d.ts +3 -0
  13. package/dist/commands/context.js +155 -0
  14. package/dist/commands/doctor.d.ts +8 -0
  15. package/dist/commands/doctor.js +163 -0
  16. package/dist/commands/feedback.d.ts +9 -0
  17. package/dist/commands/feedback.js +183 -0
  18. package/dist/commands/schema.d.ts +6 -0
  19. package/dist/commands/schema.js +869 -0
  20. package/dist/commands/shared-gather.d.ts +14 -0
  21. package/dist/commands/shared-gather.js +31 -0
  22. package/dist/commands/shared-output.d.ts +18 -0
  23. package/dist/commands/shared-output.js +61 -0
  24. package/dist/commands/show.d.ts +19 -0
  25. package/dist/commands/show.js +177 -0
  26. package/dist/commands/spec.d.ts +19 -0
  27. package/dist/commands/spec.js +236 -0
  28. package/dist/commands/store.d.ts +3 -0
  29. package/dist/commands/store.js +547 -0
  30. package/dist/commands/validate.d.ts +26 -0
  31. package/dist/commands/validate.js +330 -0
  32. package/dist/commands/workflow/index.d.ts +17 -0
  33. package/dist/commands/workflow/index.js +12 -0
  34. package/dist/commands/workflow/instructions.d.ts +45 -0
  35. package/dist/commands/workflow/instructions.js +500 -0
  36. package/dist/commands/workflow/new-change.d.ts +20 -0
  37. package/dist/commands/workflow/new-change.js +106 -0
  38. package/dist/commands/workflow/schemas.d.ts +10 -0
  39. package/dist/commands/workflow/schemas.js +34 -0
  40. package/dist/commands/workflow/shared.d.ts +84 -0
  41. package/dist/commands/workflow/shared.js +133 -0
  42. package/dist/commands/workflow/status.d.ts +16 -0
  43. package/dist/commands/workflow/status.js +92 -0
  44. package/dist/commands/workflow/templates.d.ts +16 -0
  45. package/dist/commands/workflow/templates.js +69 -0
  46. package/dist/commands/workset-input.d.ts +19 -0
  47. package/dist/commands/workset-input.js +112 -0
  48. package/dist/commands/workset-prompts.d.ts +12 -0
  49. package/dist/commands/workset-prompts.js +143 -0
  50. package/dist/commands/workset.d.ts +25 -0
  51. package/dist/commands/workset.js +446 -0
  52. package/dist/core/archive.d.ts +22 -0
  53. package/dist/core/archive.js +471 -0
  54. package/dist/core/artifact-graph/graph.d.ts +56 -0
  55. package/dist/core/artifact-graph/graph.js +141 -0
  56. package/dist/core/artifact-graph/index.d.ts +9 -0
  57. package/dist/core/artifact-graph/index.js +14 -0
  58. package/dist/core/artifact-graph/instruction-loader.d.ts +188 -0
  59. package/dist/core/artifact-graph/instruction-loader.js +233 -0
  60. package/dist/core/artifact-graph/outputs.d.ts +14 -0
  61. package/dist/core/artifact-graph/outputs.js +39 -0
  62. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  63. package/dist/core/artifact-graph/resolver.js +257 -0
  64. package/dist/core/artifact-graph/schema.d.ts +13 -0
  65. package/dist/core/artifact-graph/schema.js +108 -0
  66. package/dist/core/artifact-graph/state.d.ts +12 -0
  67. package/dist/core/artifact-graph/state.js +31 -0
  68. package/dist/core/artifact-graph/types.d.ts +40 -0
  69. package/dist/core/artifact-graph/types.js +29 -0
  70. package/dist/core/available-tools.d.ts +17 -0
  71. package/dist/core/available-tools.js +43 -0
  72. package/dist/core/change-metadata/index.d.ts +2 -0
  73. package/dist/core/change-metadata/index.js +2 -0
  74. package/dist/core/change-metadata/schema.d.ts +19 -0
  75. package/dist/core/change-metadata/schema.js +30 -0
  76. package/dist/core/change-status-policy.d.ts +37 -0
  77. package/dist/core/change-status-policy.js +35 -0
  78. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  79. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  80. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  81. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  82. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  83. package/dist/core/command-generation/adapters/auggie.js +27 -0
  84. package/dist/core/command-generation/adapters/bob.d.ts +14 -0
  85. package/dist/core/command-generation/adapters/bob.js +32 -0
  86. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  87. package/dist/core/command-generation/adapters/claude.js +37 -0
  88. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  89. package/dist/core/command-generation/adapters/cline.js +27 -0
  90. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  91. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  92. package/dist/core/command-generation/adapters/codex.d.ts +16 -0
  93. package/dist/core/command-generation/adapters/codex.js +39 -0
  94. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  95. package/dist/core/command-generation/adapters/continue.js +28 -0
  96. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  97. package/dist/core/command-generation/adapters/costrict.js +27 -0
  98. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  99. package/dist/core/command-generation/adapters/crush.js +30 -0
  100. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  101. package/dist/core/command-generation/adapters/cursor.js +31 -0
  102. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  103. package/dist/core/command-generation/adapters/factory.js +27 -0
  104. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  105. package/dist/core/command-generation/adapters/gemini.js +26 -0
  106. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  107. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  108. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  109. package/dist/core/command-generation/adapters/iflow.js +29 -0
  110. package/dist/core/command-generation/adapters/index.d.ts +32 -0
  111. package/dist/core/command-generation/adapters/index.js +32 -0
  112. package/dist/core/command-generation/adapters/junie.d.ts +13 -0
  113. package/dist/core/command-generation/adapters/junie.js +26 -0
  114. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  115. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  116. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  117. package/dist/core/command-generation/adapters/kiro.js +26 -0
  118. package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
  119. package/dist/core/command-generation/adapters/lingma.js +30 -0
  120. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  121. package/dist/core/command-generation/adapters/opencode.js +29 -0
  122. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  123. package/dist/core/command-generation/adapters/pi.js +42 -0
  124. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  125. package/dist/core/command-generation/adapters/qoder.js +30 -0
  126. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  127. package/dist/core/command-generation/adapters/qwen.js +26 -0
  128. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  129. package/dist/core/command-generation/adapters/roocode.js +27 -0
  130. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  131. package/dist/core/command-generation/adapters/windsurf.js +38 -0
  132. package/dist/core/command-generation/generator.d.ts +21 -0
  133. package/dist/core/command-generation/generator.js +27 -0
  134. package/dist/core/command-generation/index.d.ts +22 -0
  135. package/dist/core/command-generation/index.js +24 -0
  136. package/dist/core/command-generation/registry.d.ts +36 -0
  137. package/dist/core/command-generation/registry.js +98 -0
  138. package/dist/core/command-generation/types.d.ts +56 -0
  139. package/dist/core/command-generation/types.js +8 -0
  140. package/dist/core/command-generation/yaml.d.ts +22 -0
  141. package/dist/core/command-generation/yaml.js +38 -0
  142. package/dist/core/completions/command-registry.d.ts +3 -0
  143. package/dist/core/completions/command-registry.js +778 -0
  144. package/dist/core/completions/completion-provider.d.ts +71 -0
  145. package/dist/core/completions/completion-provider.js +129 -0
  146. package/dist/core/completions/factory.d.ts +64 -0
  147. package/dist/core/completions/factory.js +75 -0
  148. package/dist/core/completions/generators/bash-generator.d.ts +35 -0
  149. package/dist/core/completions/generators/bash-generator.js +230 -0
  150. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  151. package/dist/core/completions/generators/fish-generator.js +160 -0
  152. package/dist/core/completions/generators/powershell-generator.d.ts +36 -0
  153. package/dist/core/completions/generators/powershell-generator.js +266 -0
  154. package/dist/core/completions/generators/zsh-generator.d.ts +47 -0
  155. package/dist/core/completions/generators/zsh-generator.js +276 -0
  156. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  157. package/dist/core/completions/installers/bash-installer.js +321 -0
  158. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  159. package/dist/core/completions/installers/fish-installer.js +151 -0
  160. package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
  161. package/dist/core/completions/installers/powershell-installer.js +415 -0
  162. package/dist/core/completions/installers/zsh-installer.d.ts +117 -0
  163. package/dist/core/completions/installers/zsh-installer.js +424 -0
  164. package/dist/core/completions/shared-flags.d.ts +13 -0
  165. package/dist/core/completions/shared-flags.js +33 -0
  166. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  167. package/dist/core/completions/templates/bash-templates.js +30 -0
  168. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  169. package/dist/core/completions/templates/fish-templates.js +45 -0
  170. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  171. package/dist/core/completions/templates/powershell-templates.js +34 -0
  172. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  173. package/dist/core/completions/templates/zsh-templates.js +45 -0
  174. package/dist/core/completions/types.d.ts +101 -0
  175. package/dist/core/completions/types.js +2 -0
  176. package/dist/core/comprehension/config.d.ts +20 -0
  177. package/dist/core/comprehension/config.js +23 -0
  178. package/dist/core/comprehension/fingerprint.d.ts +5 -0
  179. package/dist/core/comprehension/fingerprint.js +25 -0
  180. package/dist/core/comprehension/index.d.ts +49 -0
  181. package/dist/core/comprehension/index.js +78 -0
  182. package/dist/core/comprehension/pass-record.d.ts +29 -0
  183. package/dist/core/comprehension/pass-record.js +64 -0
  184. package/dist/core/comprehension/stats.d.ts +18 -0
  185. package/dist/core/comprehension/stats.js +41 -0
  186. package/dist/core/config-prompts.d.ts +9 -0
  187. package/dist/core/config-prompts.js +34 -0
  188. package/dist/core/config-schema.d.ts +87 -0
  189. package/dist/core/config-schema.js +239 -0
  190. package/dist/core/config.d.ts +18 -0
  191. package/dist/core/config.js +39 -0
  192. package/dist/core/converters/json-converter.d.ts +6 -0
  193. package/dist/core/converters/json-converter.js +51 -0
  194. package/dist/core/file-state.d.ts +36 -0
  195. package/dist/core/file-state.js +112 -0
  196. package/dist/core/global-config.d.ts +51 -0
  197. package/dist/core/global-config.js +124 -0
  198. package/dist/core/id.d.ts +17 -0
  199. package/dist/core/id.js +30 -0
  200. package/dist/core/index.d.ts +6 -0
  201. package/dist/core/index.js +7 -0
  202. package/dist/core/init.d.ts +37 -0
  203. package/dist/core/init.js +613 -0
  204. package/dist/core/legacy-cleanup.d.ts +162 -0
  205. package/dist/core/legacy-cleanup.js +514 -0
  206. package/dist/core/list.d.ts +11 -0
  207. package/dist/core/list.js +185 -0
  208. package/dist/core/migration.d.ts +23 -0
  209. package/dist/core/migration.js +108 -0
  210. package/dist/core/openers.d.ts +77 -0
  211. package/dist/core/openers.js +251 -0
  212. package/dist/core/openspec-root.d.ts +45 -0
  213. package/dist/core/openspec-root.js +192 -0
  214. package/dist/core/parsers/change-parser.d.ts +13 -0
  215. package/dist/core/parsers/change-parser.js +197 -0
  216. package/dist/core/parsers/markdown-parser.d.ts +26 -0
  217. package/dist/core/parsers/markdown-parser.js +227 -0
  218. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  219. package/dist/core/parsers/requirement-blocks.js +201 -0
  220. package/dist/core/parsers/spec-structure.d.ts +9 -0
  221. package/dist/core/parsers/spec-structure.js +88 -0
  222. package/dist/core/planning-home.d.ts +16 -0
  223. package/dist/core/planning-home.js +67 -0
  224. package/dist/core/profile-sync-drift.d.ts +38 -0
  225. package/dist/core/profile-sync-drift.js +200 -0
  226. package/dist/core/profiles.d.ts +26 -0
  227. package/dist/core/profiles.js +40 -0
  228. package/dist/core/project-config.d.ts +120 -0
  229. package/dist/core/project-config.js +406 -0
  230. package/dist/core/references.d.ts +63 -0
  231. package/dist/core/references.js +310 -0
  232. package/dist/core/relationship-health.d.ts +65 -0
  233. package/dist/core/relationship-health.js +64 -0
  234. package/dist/core/root-selection.d.ts +122 -0
  235. package/dist/core/root-selection.js +337 -0
  236. package/dist/core/schemas/base.schema.d.ts +13 -0
  237. package/dist/core/schemas/base.schema.js +13 -0
  238. package/dist/core/schemas/change.schema.d.ts +73 -0
  239. package/dist/core/schemas/change.schema.js +31 -0
  240. package/dist/core/schemas/index.d.ts +4 -0
  241. package/dist/core/schemas/index.js +4 -0
  242. package/dist/core/schemas/spec.schema.d.ts +18 -0
  243. package/dist/core/schemas/spec.schema.js +15 -0
  244. package/dist/core/shared/index.d.ts +8 -0
  245. package/dist/core/shared/index.js +8 -0
  246. package/dist/core/shared/skill-generation.d.ts +49 -0
  247. package/dist/core/shared/skill-generation.js +96 -0
  248. package/dist/core/shared/tool-detection.d.ts +71 -0
  249. package/dist/core/shared/tool-detection.js +158 -0
  250. package/dist/core/specs-apply.d.ts +78 -0
  251. package/dist/core/specs-apply.js +394 -0
  252. package/dist/core/store/errors.d.ts +20 -0
  253. package/dist/core/store/errors.js +22 -0
  254. package/dist/core/store/foundation.d.ts +56 -0
  255. package/dist/core/store/foundation.js +251 -0
  256. package/dist/core/store/git.d.ts +23 -0
  257. package/dist/core/store/git.js +137 -0
  258. package/dist/core/store/index.d.ts +5 -0
  259. package/dist/core/store/index.js +5 -0
  260. package/dist/core/store/operations.d.ts +114 -0
  261. package/dist/core/store/operations.js +783 -0
  262. package/dist/core/store/registry.d.ts +58 -0
  263. package/dist/core/store/registry.js +275 -0
  264. package/dist/core/styles/palette.d.ts +7 -0
  265. package/dist/core/styles/palette.js +8 -0
  266. package/dist/core/templates/index.d.ts +8 -0
  267. package/dist/core/templates/index.js +9 -0
  268. package/dist/core/templates/skill-templates.d.ts +19 -0
  269. package/dist/core/templates/skill-templates.js +18 -0
  270. package/dist/core/templates/types.d.ts +19 -0
  271. package/dist/core/templates/types.js +5 -0
  272. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  273. package/dist/core/templates/workflows/apply-change.js +337 -0
  274. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  275. package/dist/core/templates/workflows/archive-change.js +278 -0
  276. package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
  277. package/dist/core/templates/workflows/bulk-archive-change.js +493 -0
  278. package/dist/core/templates/workflows/comprehension-guidance.d.ts +9 -0
  279. package/dist/core/templates/workflows/comprehension-guidance.js +58 -0
  280. package/dist/core/templates/workflows/continue-change.d.ts +10 -0
  281. package/dist/core/templates/workflows/continue-change.js +239 -0
  282. package/dist/core/templates/workflows/explore.d.ts +10 -0
  283. package/dist/core/templates/workflows/explore.js +464 -0
  284. package/dist/core/templates/workflows/feedback.d.ts +9 -0
  285. package/dist/core/templates/workflows/feedback.js +108 -0
  286. package/dist/core/templates/workflows/ff-change.d.ts +10 -0
  287. package/dist/core/templates/workflows/ff-change.js +205 -0
  288. package/dist/core/templates/workflows/mcp-guidance.d.ts +13 -0
  289. package/dist/core/templates/workflows/mcp-guidance.js +116 -0
  290. package/dist/core/templates/workflows/new-change.d.ts +10 -0
  291. package/dist/core/templates/workflows/new-change.js +148 -0
  292. package/dist/core/templates/workflows/onboard.d.ts +10 -0
  293. package/dist/core/templates/workflows/onboard.js +566 -0
  294. package/dist/core/templates/workflows/propose.d.ts +10 -0
  295. package/dist/core/templates/workflows/propose.js +228 -0
  296. package/dist/core/templates/workflows/store-selection.d.ts +8 -0
  297. package/dist/core/templates/workflows/store-selection.js +8 -0
  298. package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
  299. package/dist/core/templates/workflows/sync-specs.js +291 -0
  300. package/dist/core/templates/workflows/verify-change.d.ts +10 -0
  301. package/dist/core/templates/workflows/verify-change.js +346 -0
  302. package/dist/core/update.d.ts +82 -0
  303. package/dist/core/update.js +557 -0
  304. package/dist/core/validation/constants.d.ts +34 -0
  305. package/dist/core/validation/constants.js +40 -0
  306. package/dist/core/validation/types.d.ts +18 -0
  307. package/dist/core/validation/types.js +2 -0
  308. package/dist/core/validation/validator.d.ts +44 -0
  309. package/dist/core/validation/validator.js +435 -0
  310. package/dist/core/view.d.ts +8 -0
  311. package/dist/core/view.js +168 -0
  312. package/dist/core/working-set.d.ts +47 -0
  313. package/dist/core/working-set.js +43 -0
  314. package/dist/core/worksets.d.ts +75 -0
  315. package/dist/core/worksets.js +245 -0
  316. package/dist/core/zod-issues.d.ts +4 -0
  317. package/dist/core/zod-issues.js +10 -0
  318. package/dist/index.d.ts +3 -0
  319. package/dist/index.js +3 -0
  320. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  321. package/dist/prompts/searchable-multi-select.js +159 -0
  322. package/dist/telemetry/config.d.ts +38 -0
  323. package/dist/telemetry/config.js +136 -0
  324. package/dist/telemetry/index.d.ts +31 -0
  325. package/dist/telemetry/index.js +164 -0
  326. package/dist/ui/ascii-patterns.d.ts +16 -0
  327. package/dist/ui/ascii-patterns.js +133 -0
  328. package/dist/ui/welcome-screen.d.ts +10 -0
  329. package/dist/ui/welcome-screen.js +146 -0
  330. package/dist/utils/change-metadata.d.ts +55 -0
  331. package/dist/utils/change-metadata.js +141 -0
  332. package/dist/utils/change-utils.d.ts +71 -0
  333. package/dist/utils/change-utils.js +138 -0
  334. package/dist/utils/command-references.d.ts +18 -0
  335. package/dist/utils/command-references.js +20 -0
  336. package/dist/utils/file-system.d.ts +41 -0
  337. package/dist/utils/file-system.js +320 -0
  338. package/dist/utils/index.d.ts +6 -0
  339. package/dist/utils/index.js +9 -0
  340. package/dist/utils/interactive.d.ts +18 -0
  341. package/dist/utils/interactive.js +21 -0
  342. package/dist/utils/item-discovery.d.ts +4 -0
  343. package/dist/utils/item-discovery.js +72 -0
  344. package/dist/utils/match.d.ts +3 -0
  345. package/dist/utils/match.js +22 -0
  346. package/dist/utils/shell-detection.d.ts +20 -0
  347. package/dist/utils/shell-detection.js +41 -0
  348. package/dist/utils/task-progress.d.ts +8 -0
  349. package/dist/utils/task-progress.js +36 -0
  350. package/package.json +84 -0
  351. package/schemas/spec-driven/schema.yaml +153 -0
  352. package/schemas/spec-driven/templates/design.md +19 -0
  353. package/schemas/spec-driven/templates/proposal.md +23 -0
  354. package/schemas/spec-driven/templates/spec.md +8 -0
  355. package/schemas/spec-driven/templates/tasks.md +9 -0
  356. package/scripts/postinstall.js +83 -0
@@ -0,0 +1,239 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Zod schema for global OpenSpec configuration.
4
+ * Uses passthrough() to preserve unknown fields for forward compatibility.
5
+ */
6
+ export const GlobalConfigSchema = z
7
+ .object({
8
+ featureFlags: z
9
+ .record(z.string(), z.boolean())
10
+ .optional()
11
+ .default({}),
12
+ profile: z
13
+ .enum(['core', 'custom'])
14
+ .optional()
15
+ .default('core'),
16
+ delivery: z
17
+ .enum(['both', 'skills', 'commands'])
18
+ .optional()
19
+ .default('both'),
20
+ workflows: z
21
+ .array(z.string())
22
+ .optional(),
23
+ })
24
+ .passthrough();
25
+ /**
26
+ * Default configuration values.
27
+ */
28
+ export const DEFAULT_CONFIG = {
29
+ featureFlags: {},
30
+ profile: 'core',
31
+ delivery: 'both',
32
+ };
33
+ const KNOWN_TOP_LEVEL_KEYS = new Set([...Object.keys(DEFAULT_CONFIG), 'workflows']);
34
+ /**
35
+ * Validate a config key path for CLI set operations.
36
+ * Unknown top-level keys are rejected unless explicitly allowed by the caller.
37
+ */
38
+ export function validateConfigKeyPath(path) {
39
+ const rawKeys = path.split('.');
40
+ if (rawKeys.length === 0 || rawKeys.some((key) => key.trim() === '')) {
41
+ return { valid: false, reason: 'Key path must not be empty' };
42
+ }
43
+ const rootKey = rawKeys[0];
44
+ if (!KNOWN_TOP_LEVEL_KEYS.has(rootKey)) {
45
+ return { valid: false, reason: `Unknown top-level key "${rootKey}"` };
46
+ }
47
+ if (rootKey === 'featureFlags') {
48
+ if (rawKeys.length > 2) {
49
+ return { valid: false, reason: 'featureFlags values are booleans and do not support nested keys' };
50
+ }
51
+ return { valid: true };
52
+ }
53
+ if (rawKeys.length > 1) {
54
+ return { valid: false, reason: `"${rootKey}" does not support nested keys` };
55
+ }
56
+ return { valid: true };
57
+ }
58
+ /**
59
+ * Get a nested value from an object using dot notation.
60
+ *
61
+ * @param obj - The object to access
62
+ * @param path - Dot-separated path (e.g., "featureFlags.someFlag")
63
+ * @returns The value at the path, or undefined if not found
64
+ */
65
+ export function getNestedValue(obj, path) {
66
+ const keys = path.split('.');
67
+ let current = obj;
68
+ for (const key of keys) {
69
+ if (current === null || current === undefined) {
70
+ return undefined;
71
+ }
72
+ if (typeof current !== 'object') {
73
+ return undefined;
74
+ }
75
+ current = current[key];
76
+ }
77
+ return current;
78
+ }
79
+ /**
80
+ * Set a nested value in an object using dot notation.
81
+ * Creates intermediate objects as needed.
82
+ *
83
+ * @param obj - The object to modify (mutated in place)
84
+ * @param path - Dot-separated path (e.g., "featureFlags.someFlag")
85
+ * @param value - The value to set
86
+ */
87
+ export function setNestedValue(obj, path, value) {
88
+ const keys = path.split('.');
89
+ let current = obj;
90
+ for (let i = 0; i < keys.length - 1; i++) {
91
+ const key = keys[i];
92
+ if (current[key] === undefined || current[key] === null || typeof current[key] !== 'object') {
93
+ current[key] = {};
94
+ }
95
+ current = current[key];
96
+ }
97
+ const lastKey = keys[keys.length - 1];
98
+ current[lastKey] = value;
99
+ }
100
+ /**
101
+ * Delete a nested value from an object using dot notation.
102
+ *
103
+ * @param obj - The object to modify (mutated in place)
104
+ * @param path - Dot-separated path (e.g., "featureFlags.someFlag")
105
+ * @returns true if the key existed and was deleted, false otherwise
106
+ */
107
+ export function deleteNestedValue(obj, path) {
108
+ const keys = path.split('.');
109
+ let current = obj;
110
+ for (let i = 0; i < keys.length - 1; i++) {
111
+ const key = keys[i];
112
+ if (current[key] === undefined || current[key] === null || typeof current[key] !== 'object') {
113
+ return false;
114
+ }
115
+ current = current[key];
116
+ }
117
+ const lastKey = keys[keys.length - 1];
118
+ if (lastKey in current) {
119
+ delete current[lastKey];
120
+ return true;
121
+ }
122
+ return false;
123
+ }
124
+ /**
125
+ * Coerce a string value to its appropriate type.
126
+ * - "true" / "false" -> boolean
127
+ * - Numeric strings -> number
128
+ * - JSON arrays/objects -> parsed containers
129
+ * - Everything else -> string
130
+ *
131
+ * @param value - The string value to coerce
132
+ * @param forceString - If true, always return the value as a string
133
+ * @returns The coerced value
134
+ */
135
+ export function coerceValue(value, forceString = false) {
136
+ if (forceString) {
137
+ return value;
138
+ }
139
+ // Boolean coercion
140
+ if (value === 'true') {
141
+ return true;
142
+ }
143
+ if (value === 'false') {
144
+ return false;
145
+ }
146
+ // Number coercion - must be a valid finite number
147
+ const num = Number(value);
148
+ if (!isNaN(num) && isFinite(num) && value.trim() !== '') {
149
+ return num;
150
+ }
151
+ const jsonContainer = parseJsonContainer(value);
152
+ if (jsonContainer !== undefined) {
153
+ return jsonContainer;
154
+ }
155
+ return value;
156
+ }
157
+ function parseJsonContainer(value) {
158
+ const trimmed = value.trim();
159
+ const looksLikeContainer = (trimmed.startsWith('[') && trimmed.endsWith(']')) ||
160
+ (trimmed.startsWith('{') && trimmed.endsWith('}'));
161
+ if (!looksLikeContainer) {
162
+ return undefined;
163
+ }
164
+ try {
165
+ const parsed = JSON.parse(trimmed);
166
+ if (Array.isArray(parsed)) {
167
+ return parsed;
168
+ }
169
+ if (parsed !== null && typeof parsed === 'object') {
170
+ return parsed;
171
+ }
172
+ }
173
+ catch {
174
+ return undefined;
175
+ }
176
+ return undefined;
177
+ }
178
+ /**
179
+ * Format a value for YAML-like display.
180
+ *
181
+ * @param value - The value to format
182
+ * @param indent - Current indentation level
183
+ * @returns Formatted string
184
+ */
185
+ export function formatValueYaml(value, indent = 0) {
186
+ const indentStr = ' '.repeat(indent);
187
+ if (value === null || value === undefined) {
188
+ return 'null';
189
+ }
190
+ if (typeof value === 'boolean' || typeof value === 'number') {
191
+ return String(value);
192
+ }
193
+ if (typeof value === 'string') {
194
+ return value;
195
+ }
196
+ if (Array.isArray(value)) {
197
+ if (value.length === 0) {
198
+ return '[]';
199
+ }
200
+ return value.map((item) => `${indentStr}- ${formatValueYaml(item, indent + 1)}`).join('\n');
201
+ }
202
+ if (typeof value === 'object') {
203
+ const entries = Object.entries(value);
204
+ if (entries.length === 0) {
205
+ return '{}';
206
+ }
207
+ return entries
208
+ .map(([key, val]) => {
209
+ const formattedVal = formatValueYaml(val, indent + 1);
210
+ if (typeof val === 'object' && val !== null && Object.keys(val).length > 0) {
211
+ return `${indentStr}${key}:\n${formattedVal}`;
212
+ }
213
+ return `${indentStr}${key}: ${formattedVal}`;
214
+ })
215
+ .join('\n');
216
+ }
217
+ return String(value);
218
+ }
219
+ /**
220
+ * Validate a configuration object against the schema.
221
+ *
222
+ * @param config - The configuration to validate
223
+ * @returns Validation result with success status and optional error message
224
+ */
225
+ export function validateConfig(config) {
226
+ try {
227
+ GlobalConfigSchema.parse(config);
228
+ return { success: true };
229
+ }
230
+ catch (error) {
231
+ if (error instanceof z.ZodError) {
232
+ const zodError = error;
233
+ const messages = zodError.issues.map((e) => `${e.path.join('.')}: ${e.message}`);
234
+ return { success: false, error: messages.join('; ') };
235
+ }
236
+ return { success: false, error: 'Unknown validation error' };
237
+ }
238
+ }
239
+ //# sourceMappingURL=config-schema.js.map
@@ -0,0 +1,18 @@
1
+ export declare const OPENSPEC_DIR_NAME = "openspec";
2
+ export declare const OPENSPEC_MARKERS: {
3
+ start: string;
4
+ end: string;
5
+ };
6
+ export interface OpenSpecConfig {
7
+ aiTools: string[];
8
+ }
9
+ export interface AIToolOption {
10
+ name: string;
11
+ value: string;
12
+ available: boolean;
13
+ successLabel?: string;
14
+ skillsDir?: string;
15
+ detectionPaths?: string[];
16
+ }
17
+ export declare const AI_TOOLS: AIToolOption[];
18
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,39 @@
1
+ export const OPENSPEC_DIR_NAME = 'openspec';
2
+ export const OPENSPEC_MARKERS = {
3
+ start: '<!-- OPENSPEC:START -->',
4
+ end: '<!-- OPENSPEC:END -->'
5
+ };
6
+ export const AI_TOOLS = [
7
+ { name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer', skillsDir: '.amazonq' },
8
+ { name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity', skillsDir: '.agent' },
9
+ { name: 'Auggie (Augment CLI)', value: 'auggie', available: true, successLabel: 'Auggie', skillsDir: '.augment' },
10
+ { name: 'Bob Shell', value: 'bob', available: true, successLabel: 'Bob Shell', skillsDir: '.bob' },
11
+ { name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code', skillsDir: '.claude' },
12
+ { name: 'Cline', value: 'cline', available: true, successLabel: 'Cline', skillsDir: '.cline' },
13
+ { name: 'Codex', value: 'codex', available: true, successLabel: 'Codex', skillsDir: '.codex' },
14
+ { name: 'ForgeCode', value: 'forgecode', available: true, successLabel: 'ForgeCode', skillsDir: '.forge' },
15
+ { name: 'CodeBuddy Code (CLI)', value: 'codebuddy', available: true, successLabel: 'CodeBuddy Code', skillsDir: '.codebuddy' },
16
+ { name: 'Continue', value: 'continue', available: true, successLabel: 'Continue (VS Code / JetBrains / Cli)', skillsDir: '.continue' },
17
+ { name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict', skillsDir: '.cospec' },
18
+ { name: 'Crush', value: 'crush', available: true, successLabel: 'Crush', skillsDir: '.crush' },
19
+ { name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor', skillsDir: '.cursor' },
20
+ { name: 'Factory Droid', value: 'factory', available: true, successLabel: 'Factory Droid', skillsDir: '.factory' },
21
+ { name: 'Gemini CLI', value: 'gemini', available: true, successLabel: 'Gemini CLI', skillsDir: '.gemini' },
22
+ { name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot', skillsDir: '.github', detectionPaths: ['.github/copilot-instructions.md', '.github/instructions', '.github/workflows/copilot-setup-steps.yml', '.github/prompts', '.github/agents', '.github/skills', '.github/.mcp.json'] },
23
+ { name: 'iFlow', value: 'iflow', available: true, successLabel: 'iFlow', skillsDir: '.iflow' },
24
+ { name: 'Junie', value: 'junie', available: true, successLabel: 'Junie', skillsDir: '.junie' },
25
+ { name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code', skillsDir: '.kilocode' },
26
+ { name: 'Kimi CLI', value: 'kimi', available: true, successLabel: 'Kimi CLI', skillsDir: '.kimi' },
27
+ { name: 'Kiro', value: 'kiro', available: true, successLabel: 'Kiro', skillsDir: '.kiro' },
28
+ { name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma', skillsDir: '.lingma' },
29
+ { name: 'Mistral Vibe', value: 'vibe', available: true, successLabel: 'Mistral Vibe', skillsDir: '.vibe' },
30
+ { name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode', skillsDir: '.opencode' },
31
+ { name: 'Pi', value: 'pi', available: true, successLabel: 'Pi', skillsDir: '.pi' },
32
+ { name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder' },
33
+ { name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code', skillsDir: '.qwen' },
34
+ { name: 'RooCode', value: 'roocode', available: true, successLabel: 'RooCode', skillsDir: '.roo' },
35
+ { name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae' },
36
+ { name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf', skillsDir: '.windsurf' },
37
+ { name: 'AGENTS.md (works with Amp, VS Code, …)', value: 'agents', available: false, successLabel: 'your AGENTS.md-compatible assistant' }
38
+ ];
39
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,6 @@
1
+ export declare class JsonConverter {
2
+ convertSpecToJson(filePath: string): string;
3
+ convertChangeToJson(filePath: string): Promise<string>;
4
+ private extractNameFromPath;
5
+ }
6
+ //# sourceMappingURL=json-converter.d.ts.map
@@ -0,0 +1,51 @@
1
+ import { readFileSync } from 'fs';
2
+ import path from 'path';
3
+ import { MarkdownParser } from '../parsers/markdown-parser.js';
4
+ import { ChangeParser } from '../parsers/change-parser.js';
5
+ import { FileSystemUtils } from '../../utils/file-system.js';
6
+ export class JsonConverter {
7
+ convertSpecToJson(filePath) {
8
+ const content = readFileSync(filePath, 'utf-8');
9
+ const parser = new MarkdownParser(content);
10
+ const specName = this.extractNameFromPath(filePath);
11
+ const spec = parser.parseSpec(specName);
12
+ const jsonSpec = {
13
+ ...spec,
14
+ metadata: {
15
+ ...spec.metadata,
16
+ sourcePath: filePath,
17
+ },
18
+ };
19
+ return JSON.stringify(jsonSpec, null, 2);
20
+ }
21
+ async convertChangeToJson(filePath) {
22
+ const content = readFileSync(filePath, 'utf-8');
23
+ const changeName = this.extractNameFromPath(filePath);
24
+ const changeDir = path.dirname(filePath);
25
+ const parser = new ChangeParser(content, changeDir);
26
+ const change = await parser.parseChangeWithDeltas(changeName);
27
+ const jsonChange = {
28
+ ...change,
29
+ metadata: {
30
+ ...change.metadata,
31
+ sourcePath: filePath,
32
+ },
33
+ };
34
+ return JSON.stringify(jsonChange, null, 2);
35
+ }
36
+ extractNameFromPath(filePath) {
37
+ const normalizedPath = FileSystemUtils.toPosixPath(filePath);
38
+ const parts = normalizedPath.split('/');
39
+ for (let i = parts.length - 1; i >= 0; i--) {
40
+ if (parts[i] === 'specs' || parts[i] === 'changes') {
41
+ if (i < parts.length - 1) {
42
+ return parts[i + 1];
43
+ }
44
+ }
45
+ }
46
+ const fileName = parts[parts.length - 1] ?? '';
47
+ const dotIndex = fileName.lastIndexOf('.');
48
+ return dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName;
49
+ }
50
+ }
51
+ //# sourceMappingURL=json-converter.js.map
@@ -0,0 +1,36 @@
1
+ import * as nodeFs from 'node:fs';
2
+ import { StoreError } from './store/errors.js';
3
+ /**
4
+ * Shared machine-local state-file mechanics (extracted from the store
5
+ * registry in slice 7.1, its second consumer). Callers own the
6
+ * diagnostic data (code, target, wording); the factory owns the
7
+ * shared mechanics - the fix strings describe the lock's own
8
+ * behavior (stale-steal, creation), so their templates live here.
9
+ */
10
+ export type FileLockErrorKind = 'create-failed' | 'timeout';
11
+ export interface FileLockErrorInfo {
12
+ lockPath: string;
13
+ /** The original errno error for 'create-failed'. */
14
+ cause?: unknown;
15
+ }
16
+ export interface FileLockOptions {
17
+ lockPath: string;
18
+ errorFor: (kind: FileLockErrorKind, info: FileLockErrorInfo) => Error;
19
+ }
20
+ export interface LockErrorData {
21
+ /** Noun phrase for the create-failed message, e.g. "the registry lock file". */
22
+ createSubject: string;
23
+ /** The full timeout message, e.g. "Store registry is busy." */
24
+ busyMessage: string;
25
+ code: string;
26
+ target: string;
27
+ }
28
+ /** One template for lock diagnostics; callers supply the data. */
29
+ export declare function makeLockErrorFactory(data: LockErrorData): (kind: FileLockErrorKind, info: FileLockErrorInfo) => StoreError;
30
+ export declare function isNodeErrorCode(error: unknown, code: string): boolean;
31
+ export declare function pathIsFile(filePath: string): Promise<boolean>;
32
+ export declare function pathIsDirectory(dirPath: string): Promise<boolean>;
33
+ export declare function writeFileAtomically(filePath: string, content: string): Promise<void>;
34
+ export declare function acquireFileLock(options: FileLockOptions): Promise<nodeFs.promises.FileHandle>;
35
+ export declare function releaseFileLock(lock: nodeFs.promises.FileHandle, lockPath: string): Promise<void>;
36
+ //# sourceMappingURL=file-state.d.ts.map
@@ -0,0 +1,112 @@
1
+ import * as nodeFs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { FileSystemUtils } from '../utils/file-system.js';
4
+ import { StoreError } from './store/errors.js';
5
+ const fs = nodeFs.promises;
6
+ /** One template for lock diagnostics; callers supply the data. */
7
+ export function makeLockErrorFactory(data) {
8
+ return (kind, info) => {
9
+ if (kind === 'create-failed') {
10
+ // A permission or filesystem problem, not contention - say so.
11
+ return new StoreError(`Cannot create ${data.createSubject} ${info.lockPath} (${info.cause?.code ?? info.cause}).`, data.code, {
12
+ target: data.target,
13
+ fix: `Check permissions on ${path.dirname(info.lockPath)}.`,
14
+ });
15
+ }
16
+ return new StoreError(data.busyMessage, data.code, {
17
+ target: data.target,
18
+ fix: `Retry shortly; if this persists, delete the stale lock file ${info.lockPath}.`,
19
+ });
20
+ };
21
+ }
22
+ const STALE_LOCK_THRESHOLD_MS = 30_000;
23
+ const LOCK_DEADLINE_MS = 5000;
24
+ const LOCK_POLL_MS = 25;
25
+ export function isNodeErrorCode(error, code) {
26
+ return (typeof error === 'object' &&
27
+ error !== null &&
28
+ 'code' in error &&
29
+ error.code === code);
30
+ }
31
+ export async function pathIsFile(filePath) {
32
+ try {
33
+ return (await fs.stat(filePath)).isFile();
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ // Deliberately not FileSystemUtils.directoryExists: that variant
40
+ // debug-logs non-ENOENT failures, which is noise inside prompt
41
+ // validators, and pathIsFile has no FileSystemUtils equivalent - the
42
+ // silent symmetric pair lives here.
43
+ export async function pathIsDirectory(dirPath) {
44
+ try {
45
+ return (await fs.stat(dirPath)).isDirectory();
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ }
51
+ async function sleep(milliseconds) {
52
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
53
+ }
54
+ export async function writeFileAtomically(filePath, content) {
55
+ const dirPath = path.dirname(filePath);
56
+ await FileSystemUtils.createDirectory(dirPath);
57
+ const tempPath = path.join(dirPath, `.${path.basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
58
+ try {
59
+ await fs.writeFile(tempPath, content, 'utf-8');
60
+ await fs.rename(tempPath, filePath);
61
+ }
62
+ catch (error) {
63
+ await fs.rm(tempPath, { force: true }).catch(() => undefined);
64
+ throw error;
65
+ }
66
+ }
67
+ export async function acquireFileLock(options) {
68
+ const { lockPath, errorFor } = options;
69
+ const lockDir = path.dirname(lockPath);
70
+ await FileSystemUtils.createDirectory(lockDir);
71
+ if (!(await FileSystemUtils.canWriteFile(lockDir))) {
72
+ throw errorFor('create-failed', { lockPath, cause: 'EACCES' });
73
+ }
74
+ const deadline = Date.now() + LOCK_DEADLINE_MS;
75
+ while (true) {
76
+ try {
77
+ return await fs.open(lockPath, 'wx');
78
+ }
79
+ catch (error) {
80
+ if (!isNodeErrorCode(error, 'EEXIST')) {
81
+ // A permission or filesystem problem, not contention - say so.
82
+ throw errorFor('create-failed', { lockPath, cause: error });
83
+ }
84
+ // A crashed process leaves the lock behind forever; state-file
85
+ // writes are sub-second, so an old lock is an orphan - steal it.
86
+ let staleStolen = false;
87
+ try {
88
+ const lockStat = await fs.stat(lockPath);
89
+ if (Date.now() - lockStat.mtimeMs > STALE_LOCK_THRESHOLD_MS) {
90
+ await fs.rm(lockPath, { force: true });
91
+ staleStolen = true;
92
+ }
93
+ }
94
+ catch {
95
+ // The holder released between open and stat - retry, but stay
96
+ // bounded: a persistently failing stat (EPERM, delete-pending)
97
+ // must hit the deadline instead of spinning forever.
98
+ }
99
+ if (!staleStolen) {
100
+ if (Date.now() >= deadline) {
101
+ throw errorFor('timeout', { lockPath });
102
+ }
103
+ await sleep(LOCK_POLL_MS);
104
+ }
105
+ }
106
+ }
107
+ }
108
+ export async function releaseFileLock(lock, lockPath) {
109
+ await lock.close().catch(() => undefined);
110
+ await fs.rm(lockPath, { force: true }).catch(() => undefined);
111
+ }
112
+ //# sourceMappingURL=file-state.js.map
@@ -0,0 +1,51 @@
1
+ export declare const GLOBAL_CONFIG_DIR_NAME = "openspec";
2
+ export declare const GLOBAL_CONFIG_FILE_NAME = "config.json";
3
+ export declare const GLOBAL_DATA_DIR_NAME = "openspec";
4
+ export type Profile = 'core' | 'custom';
5
+ export type Delivery = 'both' | 'skills' | 'commands';
6
+ export interface GlobalConfig {
7
+ featureFlags?: Record<string, boolean>;
8
+ profile?: Profile;
9
+ delivery?: Delivery;
10
+ workflows?: string[];
11
+ /** Workset opener rows (slice 7.1); hand-edited, validated on use. */
12
+ openers?: unknown;
13
+ }
14
+ /**
15
+ * Gets the global configuration directory path following XDG Base Directory Specification.
16
+ *
17
+ * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
18
+ * - Unix/macOS fallback: ~/.config/openspec/
19
+ * - Windows fallback: %APPDATA%/openspec/
20
+ */
21
+ export declare function getGlobalConfigDir(): string;
22
+ /**
23
+ * Gets the global data directory path following XDG Base Directory Specification.
24
+ * Used for user data like schema overrides.
25
+ *
26
+ * - All platforms: $XDG_DATA_HOME/openspec/ if XDG_DATA_HOME is set
27
+ * - Unix/macOS fallback: ~/.local/share/openspec/
28
+ * - Windows fallback: %LOCALAPPDATA%/openspec/
29
+ */
30
+ export interface GlobalDataDirOptions {
31
+ env?: NodeJS.ProcessEnv;
32
+ platform?: NodeJS.Platform;
33
+ homedir?: string;
34
+ }
35
+ export declare function getGlobalDataDir(options?: GlobalDataDirOptions): string;
36
+ /**
37
+ * Gets the path to the global config file.
38
+ */
39
+ export declare function getGlobalConfigPath(): string;
40
+ /**
41
+ * Loads the global configuration from disk.
42
+ * Returns default configuration if file doesn't exist or is invalid.
43
+ * Merges loaded config with defaults to ensure new fields are available.
44
+ */
45
+ export declare function getGlobalConfig(): GlobalConfig;
46
+ /**
47
+ * Saves the global configuration to disk.
48
+ * Creates the config directory if it doesn't exist.
49
+ */
50
+ export declare function saveGlobalConfig(config: GlobalConfig): void;
51
+ //# sourceMappingURL=global-config.d.ts.map
@@ -0,0 +1,124 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ // Constants
5
+ export const GLOBAL_CONFIG_DIR_NAME = 'openspec';
6
+ export const GLOBAL_CONFIG_FILE_NAME = 'config.json';
7
+ export const GLOBAL_DATA_DIR_NAME = 'openspec';
8
+ const DEFAULT_CONFIG = {
9
+ featureFlags: {},
10
+ profile: 'core',
11
+ delivery: 'both',
12
+ };
13
+ /**
14
+ * Gets the global configuration directory path following XDG Base Directory Specification.
15
+ *
16
+ * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
17
+ * - Unix/macOS fallback: ~/.config/openspec/
18
+ * - Windows fallback: %APPDATA%/openspec/
19
+ */
20
+ export function getGlobalConfigDir() {
21
+ // XDG_CONFIG_HOME takes precedence on all platforms when explicitly set
22
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME;
23
+ if (xdgConfigHome) {
24
+ return path.join(xdgConfigHome, GLOBAL_CONFIG_DIR_NAME);
25
+ }
26
+ const platform = os.platform();
27
+ if (platform === 'win32') {
28
+ // Windows: use %APPDATA%
29
+ const appData = process.env.APPDATA;
30
+ if (appData) {
31
+ return path.join(appData, GLOBAL_CONFIG_DIR_NAME);
32
+ }
33
+ // Fallback for Windows if APPDATA is not set
34
+ return path.join(os.homedir(), 'AppData', 'Roaming', GLOBAL_CONFIG_DIR_NAME);
35
+ }
36
+ // Unix/macOS fallback: ~/.config
37
+ return path.join(os.homedir(), '.config', GLOBAL_CONFIG_DIR_NAME);
38
+ }
39
+ function joinGlobalDataPath(platform, ...segments) {
40
+ return platform === 'win32'
41
+ ? path.win32.join(...segments)
42
+ : path.posix.join(...segments);
43
+ }
44
+ export function getGlobalDataDir(options = {}) {
45
+ const env = options.env ?? process.env;
46
+ const platform = options.platform ?? os.platform();
47
+ // XDG_DATA_HOME takes precedence on all platforms when explicitly set
48
+ const xdgDataHome = env.XDG_DATA_HOME;
49
+ if (xdgDataHome) {
50
+ return joinGlobalDataPath(platform, xdgDataHome, GLOBAL_DATA_DIR_NAME);
51
+ }
52
+ const homedir = options.homedir ?? os.homedir();
53
+ if (platform === 'win32') {
54
+ // Windows: use %LOCALAPPDATA%
55
+ const localAppData = env.LOCALAPPDATA;
56
+ if (localAppData) {
57
+ return joinGlobalDataPath(platform, localAppData, GLOBAL_DATA_DIR_NAME);
58
+ }
59
+ // Fallback for Windows if LOCALAPPDATA is not set
60
+ return joinGlobalDataPath(platform, homedir, 'AppData', 'Local', GLOBAL_DATA_DIR_NAME);
61
+ }
62
+ // Unix/macOS fallback: ~/.local/share
63
+ return joinGlobalDataPath(platform, homedir, '.local', 'share', GLOBAL_DATA_DIR_NAME);
64
+ }
65
+ /**
66
+ * Gets the path to the global config file.
67
+ */
68
+ export function getGlobalConfigPath() {
69
+ return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE_NAME);
70
+ }
71
+ /**
72
+ * Loads the global configuration from disk.
73
+ * Returns default configuration if file doesn't exist or is invalid.
74
+ * Merges loaded config with defaults to ensure new fields are available.
75
+ */
76
+ export function getGlobalConfig() {
77
+ const configPath = getGlobalConfigPath();
78
+ try {
79
+ if (!fs.existsSync(configPath)) {
80
+ return { ...DEFAULT_CONFIG };
81
+ }
82
+ const content = fs.readFileSync(configPath, 'utf-8');
83
+ const parsed = JSON.parse(content);
84
+ // Merge with defaults (loaded values take precedence)
85
+ const merged = {
86
+ ...DEFAULT_CONFIG,
87
+ ...parsed,
88
+ // Deep merge featureFlags
89
+ featureFlags: {
90
+ ...DEFAULT_CONFIG.featureFlags,
91
+ ...(parsed.featureFlags || {})
92
+ }
93
+ };
94
+ // Schema evolution: apply defaults for new fields if not present in loaded config
95
+ if (parsed.profile === undefined) {
96
+ merged.profile = DEFAULT_CONFIG.profile;
97
+ }
98
+ if (parsed.delivery === undefined) {
99
+ merged.delivery = DEFAULT_CONFIG.delivery;
100
+ }
101
+ return merged;
102
+ }
103
+ catch (error) {
104
+ // Log warning for parse errors, but not for missing files
105
+ if (error instanceof SyntaxError) {
106
+ console.error(`Warning: Invalid JSON in ${configPath}, using defaults`);
107
+ }
108
+ return { ...DEFAULT_CONFIG };
109
+ }
110
+ }
111
+ /**
112
+ * Saves the global configuration to disk.
113
+ * Creates the config directory if it doesn't exist.
114
+ */
115
+ export function saveGlobalConfig(config) {
116
+ const configDir = getGlobalConfigDir();
117
+ const configPath = getGlobalConfigPath();
118
+ // Create directory if it doesn't exist
119
+ if (!fs.existsSync(configDir)) {
120
+ fs.mkdirSync(configDir, { recursive: true });
121
+ }
122
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
123
+ }
124
+ //# sourceMappingURL=global-config.js.map