@kbdavis07/openspec 1.5.0-nov.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 (352) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +239 -0
  3. package/bin/openspec.js +5 -0
  4. package/dist/cli/index.d.ts +10 -0
  5. package/dist/cli/index.js +544 -0
  6. package/dist/commands/change.d.ts +38 -0
  7. package/dist/commands/change.js +245 -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 +34 -0
  31. package/dist/commands/validate.js +342 -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 +39 -0
  35. package/dist/commands/workflow/instructions.js +393 -0
  36. package/dist/commands/workflow/new-change.d.ts +20 -0
  37. package/dist/commands/workflow/new-change.js +109 -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 +74 -0
  41. package/dist/commands/workflow/shared.js +170 -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 +496 -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 +39 -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 +34 -0
  111. package/dist/core/command-generation/adapters/index.js +34 -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/oh-my-pi.d.ts +21 -0
  121. package/dist/core/command-generation/adapters/oh-my-pi.js +44 -0
  122. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  123. package/dist/core/command-generation/adapters/opencode.js +29 -0
  124. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  125. package/dist/core/command-generation/adapters/pi.js +42 -0
  126. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  127. package/dist/core/command-generation/adapters/qoder.js +30 -0
  128. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  129. package/dist/core/command-generation/adapters/qwen.js +26 -0
  130. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  131. package/dist/core/command-generation/adapters/roocode.js +27 -0
  132. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  133. package/dist/core/command-generation/adapters/trae.js +48 -0
  134. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  135. package/dist/core/command-generation/adapters/windsurf.js +38 -0
  136. package/dist/core/command-generation/generator.d.ts +21 -0
  137. package/dist/core/command-generation/generator.js +27 -0
  138. package/dist/core/command-generation/index.d.ts +22 -0
  139. package/dist/core/command-generation/index.js +24 -0
  140. package/dist/core/command-generation/registry.d.ts +36 -0
  141. package/dist/core/command-generation/registry.js +102 -0
  142. package/dist/core/command-generation/types.d.ts +56 -0
  143. package/dist/core/command-generation/types.js +8 -0
  144. package/dist/core/command-generation/yaml.d.ts +22 -0
  145. package/dist/core/command-generation/yaml.js +38 -0
  146. package/dist/core/completions/command-registry.d.ts +3 -0
  147. package/dist/core/completions/command-registry.js +759 -0
  148. package/dist/core/completions/completion-provider.d.ts +71 -0
  149. package/dist/core/completions/completion-provider.js +129 -0
  150. package/dist/core/completions/factory.d.ts +64 -0
  151. package/dist/core/completions/factory.js +75 -0
  152. package/dist/core/completions/generators/bash-generator.d.ts +35 -0
  153. package/dist/core/completions/generators/bash-generator.js +230 -0
  154. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  155. package/dist/core/completions/generators/fish-generator.js +160 -0
  156. package/dist/core/completions/generators/powershell-generator.d.ts +36 -0
  157. package/dist/core/completions/generators/powershell-generator.js +266 -0
  158. package/dist/core/completions/generators/zsh-generator.d.ts +47 -0
  159. package/dist/core/completions/generators/zsh-generator.js +276 -0
  160. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  161. package/dist/core/completions/installers/bash-installer.js +321 -0
  162. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  163. package/dist/core/completions/installers/fish-installer.js +151 -0
  164. package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
  165. package/dist/core/completions/installers/powershell-installer.js +415 -0
  166. package/dist/core/completions/installers/zsh-installer.d.ts +117 -0
  167. package/dist/core/completions/installers/zsh-installer.js +424 -0
  168. package/dist/core/completions/shared-flags.d.ts +13 -0
  169. package/dist/core/completions/shared-flags.js +33 -0
  170. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  171. package/dist/core/completions/templates/bash-templates.js +30 -0
  172. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  173. package/dist/core/completions/templates/fish-templates.js +45 -0
  174. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  175. package/dist/core/completions/templates/powershell-templates.js +34 -0
  176. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  177. package/dist/core/completions/templates/zsh-templates.js +45 -0
  178. package/dist/core/completions/types.d.ts +101 -0
  179. package/dist/core/completions/types.js +2 -0
  180. package/dist/core/config-prompts.d.ts +9 -0
  181. package/dist/core/config-prompts.js +34 -0
  182. package/dist/core/config-schema.d.ts +87 -0
  183. package/dist/core/config-schema.js +239 -0
  184. package/dist/core/config.d.ts +18 -0
  185. package/dist/core/config.js +40 -0
  186. package/dist/core/converters/json-converter.d.ts +6 -0
  187. package/dist/core/converters/json-converter.js +51 -0
  188. package/dist/core/file-state.d.ts +36 -0
  189. package/dist/core/file-state.js +112 -0
  190. package/dist/core/global-config.d.ts +51 -0
  191. package/dist/core/global-config.js +124 -0
  192. package/dist/core/id.d.ts +17 -0
  193. package/dist/core/id.js +30 -0
  194. package/dist/core/index.d.ts +6 -0
  195. package/dist/core/index.js +7 -0
  196. package/dist/core/init.d.ts +37 -0
  197. package/dist/core/init.js +614 -0
  198. package/dist/core/legacy-cleanup.d.ts +162 -0
  199. package/dist/core/legacy-cleanup.js +514 -0
  200. package/dist/core/list.d.ts +11 -0
  201. package/dist/core/list.js +214 -0
  202. package/dist/core/migration.d.ts +23 -0
  203. package/dist/core/migration.js +108 -0
  204. package/dist/core/openers.d.ts +77 -0
  205. package/dist/core/openers.js +251 -0
  206. package/dist/core/openspec-root.d.ts +45 -0
  207. package/dist/core/openspec-root.js +195 -0
  208. package/dist/core/parsers/change-parser.d.ts +13 -0
  209. package/dist/core/parsers/change-parser.js +198 -0
  210. package/dist/core/parsers/markdown-parser.d.ts +23 -0
  211. package/dist/core/parsers/markdown-parser.js +173 -0
  212. package/dist/core/parsers/requirement-blocks.d.ts +48 -0
  213. package/dist/core/parsers/requirement-blocks.js +235 -0
  214. package/dist/core/parsers/requirement-text.d.ts +55 -0
  215. package/dist/core/parsers/requirement-text.js +136 -0
  216. package/dist/core/parsers/spec-structure.d.ts +9 -0
  217. package/dist/core/parsers/spec-structure.js +88 -0
  218. package/dist/core/planning-home.d.ts +16 -0
  219. package/dist/core/planning-home.js +67 -0
  220. package/dist/core/profile-sync-drift.d.ts +38 -0
  221. package/dist/core/profile-sync-drift.js +201 -0
  222. package/dist/core/profiles.d.ts +26 -0
  223. package/dist/core/profiles.js +41 -0
  224. package/dist/core/project-config.d.ts +107 -0
  225. package/dist/core/project-config.js +373 -0
  226. package/dist/core/references.d.ts +63 -0
  227. package/dist/core/references.js +310 -0
  228. package/dist/core/relationship-health.d.ts +65 -0
  229. package/dist/core/relationship-health.js +64 -0
  230. package/dist/core/root-selection.d.ts +122 -0
  231. package/dist/core/root-selection.js +337 -0
  232. package/dist/core/schemas/base.schema.d.ts +13 -0
  233. package/dist/core/schemas/base.schema.js +18 -0
  234. package/dist/core/schemas/change.schema.d.ts +73 -0
  235. package/dist/core/schemas/change.schema.js +31 -0
  236. package/dist/core/schemas/index.d.ts +4 -0
  237. package/dist/core/schemas/index.js +4 -0
  238. package/dist/core/schemas/spec.schema.d.ts +18 -0
  239. package/dist/core/schemas/spec.schema.js +15 -0
  240. package/dist/core/shared/allowed-tools.d.ts +12 -0
  241. package/dist/core/shared/allowed-tools.js +12 -0
  242. package/dist/core/shared/index.d.ts +8 -0
  243. package/dist/core/shared/index.js +8 -0
  244. package/dist/core/shared/skill-generation.d.ts +49 -0
  245. package/dist/core/shared/skill-generation.js +100 -0
  246. package/dist/core/shared/tool-detection.d.ts +71 -0
  247. package/dist/core/shared/tool-detection.js +160 -0
  248. package/dist/core/specs-apply.d.ts +78 -0
  249. package/dist/core/specs-apply.js +428 -0
  250. package/dist/core/store/errors.d.ts +20 -0
  251. package/dist/core/store/errors.js +22 -0
  252. package/dist/core/store/foundation.d.ts +56 -0
  253. package/dist/core/store/foundation.js +251 -0
  254. package/dist/core/store/git.d.ts +23 -0
  255. package/dist/core/store/git.js +137 -0
  256. package/dist/core/store/index.d.ts +5 -0
  257. package/dist/core/store/index.js +5 -0
  258. package/dist/core/store/operations.d.ts +114 -0
  259. package/dist/core/store/operations.js +803 -0
  260. package/dist/core/store/registry.d.ts +58 -0
  261. package/dist/core/store/registry.js +275 -0
  262. package/dist/core/styles/palette.d.ts +7 -0
  263. package/dist/core/styles/palette.js +8 -0
  264. package/dist/core/templates/index.d.ts +8 -0
  265. package/dist/core/templates/index.js +9 -0
  266. package/dist/core/templates/skill-templates.d.ts +20 -0
  267. package/dist/core/templates/skill-templates.js +19 -0
  268. package/dist/core/templates/types.d.ts +19 -0
  269. package/dist/core/templates/types.js +5 -0
  270. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  271. package/dist/core/templates/workflows/apply-change.js +315 -0
  272. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  273. package/dist/core/templates/workflows/archive-change.js +278 -0
  274. package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
  275. package/dist/core/templates/workflows/bulk-archive-change.js +493 -0
  276. package/dist/core/templates/workflows/continue-change.d.ts +10 -0
  277. package/dist/core/templates/workflows/continue-change.js +239 -0
  278. package/dist/core/templates/workflows/explore.d.ts +10 -0
  279. package/dist/core/templates/workflows/explore.js +464 -0
  280. package/dist/core/templates/workflows/feedback.d.ts +9 -0
  281. package/dist/core/templates/workflows/feedback.js +108 -0
  282. package/dist/core/templates/workflows/ff-change.d.ts +10 -0
  283. package/dist/core/templates/workflows/ff-change.js +205 -0
  284. package/dist/core/templates/workflows/new-change.d.ts +10 -0
  285. package/dist/core/templates/workflows/new-change.js +148 -0
  286. package/dist/core/templates/workflows/onboard.d.ts +10 -0
  287. package/dist/core/templates/workflows/onboard.js +566 -0
  288. package/dist/core/templates/workflows/propose.d.ts +10 -0
  289. package/dist/core/templates/workflows/propose.js +223 -0
  290. package/dist/core/templates/workflows/store-selection.d.ts +8 -0
  291. package/dist/core/templates/workflows/store-selection.js +8 -0
  292. package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
  293. package/dist/core/templates/workflows/sync-specs.js +291 -0
  294. package/dist/core/templates/workflows/update-change.d.ts +10 -0
  295. package/dist/core/templates/workflows/update-change.js +167 -0
  296. package/dist/core/templates/workflows/verify-change.d.ts +10 -0
  297. package/dist/core/templates/workflows/verify-change.js +339 -0
  298. package/dist/core/update.d.ts +82 -0
  299. package/dist/core/update.js +557 -0
  300. package/dist/core/validation/constants.d.ts +34 -0
  301. package/dist/core/validation/constants.js +40 -0
  302. package/dist/core/validation/types.d.ts +18 -0
  303. package/dist/core/validation/types.js +2 -0
  304. package/dist/core/validation/validator.d.ts +51 -0
  305. package/dist/core/validation/validator.js +498 -0
  306. package/dist/core/view.d.ts +8 -0
  307. package/dist/core/view.js +168 -0
  308. package/dist/core/working-set.d.ts +47 -0
  309. package/dist/core/working-set.js +43 -0
  310. package/dist/core/worksets.d.ts +75 -0
  311. package/dist/core/worksets.js +245 -0
  312. package/dist/core/zod-issues.d.ts +4 -0
  313. package/dist/core/zod-issues.js +10 -0
  314. package/dist/index.d.ts +3 -0
  315. package/dist/index.js +3 -0
  316. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  317. package/dist/prompts/searchable-multi-select.js +159 -0
  318. package/dist/telemetry/config.d.ts +38 -0
  319. package/dist/telemetry/config.js +136 -0
  320. package/dist/telemetry/index.d.ts +31 -0
  321. package/dist/telemetry/index.js +164 -0
  322. package/dist/ui/ascii-patterns.d.ts +16 -0
  323. package/dist/ui/ascii-patterns.js +133 -0
  324. package/dist/ui/welcome-screen.d.ts +10 -0
  325. package/dist/ui/welcome-screen.js +146 -0
  326. package/dist/utils/change-metadata.d.ts +55 -0
  327. package/dist/utils/change-metadata.js +141 -0
  328. package/dist/utils/change-utils.d.ts +85 -0
  329. package/dist/utils/change-utils.js +173 -0
  330. package/dist/utils/command-references.d.ts +18 -0
  331. package/dist/utils/command-references.js +20 -0
  332. package/dist/utils/file-system.d.ts +41 -0
  333. package/dist/utils/file-system.js +320 -0
  334. package/dist/utils/index.d.ts +6 -0
  335. package/dist/utils/index.js +9 -0
  336. package/dist/utils/interactive.d.ts +18 -0
  337. package/dist/utils/interactive.js +21 -0
  338. package/dist/utils/item-discovery.d.ts +4 -0
  339. package/dist/utils/item-discovery.js +72 -0
  340. package/dist/utils/match.d.ts +3 -0
  341. package/dist/utils/match.js +22 -0
  342. package/dist/utils/shell-detection.d.ts +20 -0
  343. package/dist/utils/shell-detection.js +41 -0
  344. package/dist/utils/task-progress.d.ts +17 -0
  345. package/dist/utils/task-progress.js +100 -0
  346. package/package.json +85 -0
  347. package/schemas/spec-driven/schema.yaml +153 -0
  348. package/schemas/spec-driven/templates/design.md +19 -0
  349. package/schemas/spec-driven/templates/proposal.md +23 -0
  350. package/schemas/spec-driven/templates/spec.md +8 -0
  351. package/schemas/spec-driven/templates/tasks.md +9 -0
  352. package/scripts/postinstall.js +83 -0
@@ -0,0 +1,373 @@
1
+ import { existsSync, readFileSync, statSync } from 'fs';
2
+ import path from 'path';
3
+ import { parse as parseYaml } from 'yaml';
4
+ import { z } from 'zod';
5
+ /**
6
+ * Zod schema for project configuration.
7
+ *
8
+ * Purpose:
9
+ * 1. Documentation - clearly defines the config file structure
10
+ * 2. Type safety - TypeScript infers ProjectConfig type from schema
11
+ * 3. Runtime validation - uses safeParse() for resilient field-by-field validation
12
+ *
13
+ * Why Zod over manual validation:
14
+ * - Helps understand OpenSpec's data interfaces at a glance
15
+ * - Single source of truth for type and validation
16
+ * - Consistent with other OpenSpec schemas
17
+ */
18
+ export const ProjectConfigSchema = z.object({
19
+ // Required: which schema to use (e.g., "spec-driven", or project-local schema name)
20
+ schema: z
21
+ .string()
22
+ .min(1)
23
+ .describe('The workflow schema to use (e.g., "spec-driven")'),
24
+ // Optional: project context (injected into all artifact instructions)
25
+ // Max size: 50KB (enforced during parsing)
26
+ context: z
27
+ .string()
28
+ .optional()
29
+ .describe('Project context injected into all artifact instructions'),
30
+ // Optional: per-artifact rules (additive to schema's built-in guidance)
31
+ rules: z
32
+ .record(z.string(), // artifact ID
33
+ z.array(z.string()) // list of rules
34
+ )
35
+ .optional()
36
+ .describe('Per-artifact rules, keyed by artifact ID'),
37
+ // Note: the `references` field (id strings or {id, remote} maps) is
38
+ // deliberately absent here — readProjectConfig parses and normalizes
39
+ // it by hand (see DeclarationEntry below); a schema entry nothing
40
+ // parses would only drift from the real behavior.
41
+ // Optional: the declared default store. Only consulted by root
42
+ // resolution when this openspec/ directory is config-only (no specs/
43
+ // or changes/); a fallback, never an override.
44
+ store: z
45
+ .string()
46
+ .optional()
47
+ .describe('Store id used as the OpenSpec root when no local planning shape exists'),
48
+ });
49
+ /**
50
+ * Parser for `references:` declarations: string entries or
51
+ * {id, remote} maps, normalized to DeclarationEntry[]. Dedup keys on
52
+ * id and keeps the first position; the first entry carrying a remote
53
+ * supplies it (a later duplicate fills a missing remote, never
54
+ * overrides). Invalid entries drop with a warning like other resilient
55
+ * fields; returns undefined when the field is absent or normalizes to
56
+ * empty.
57
+ */
58
+ function parseDeclarationList(raw) {
59
+ const fieldName = 'references';
60
+ if (raw === undefined) {
61
+ return undefined;
62
+ }
63
+ if (!Array.isArray(raw)) {
64
+ console.warn(`Invalid '${fieldName}' field in config (must be an array of store ids)`);
65
+ return undefined;
66
+ }
67
+ const byId = new Map();
68
+ let droppedEntries = false;
69
+ let droppedRemotes = false;
70
+ for (const entry of raw) {
71
+ let declaration = null;
72
+ if (typeof entry === 'string') {
73
+ declaration = { id: entry };
74
+ }
75
+ else if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
76
+ const candidate = entry;
77
+ if (typeof candidate.id === 'string') {
78
+ declaration = { id: candidate.id };
79
+ if (typeof candidate.remote === 'string' && candidate.remote.length > 0) {
80
+ declaration.remote = candidate.remote;
81
+ }
82
+ else if (candidate.remote !== undefined) {
83
+ droppedRemotes = true; // remote dropped, id kept
84
+ }
85
+ }
86
+ }
87
+ if (!declaration) {
88
+ droppedEntries = true;
89
+ continue;
90
+ }
91
+ const existing = byId.get(declaration.id);
92
+ if (!existing) {
93
+ byId.set(declaration.id, declaration);
94
+ }
95
+ else if (existing.remote === undefined && declaration.remote !== undefined) {
96
+ existing.remote = declaration.remote;
97
+ }
98
+ }
99
+ if (droppedEntries) {
100
+ console.warn(`Some '${fieldName}' entries are invalid, ignoring them`);
101
+ }
102
+ if (droppedRemotes) {
103
+ console.warn(`Some '${fieldName}' remotes are not non-empty strings; the ids are kept without a clone source`);
104
+ }
105
+ return byId.size > 0 ? [...byId.values()] : undefined;
106
+ }
107
+ export const MAX_CONTEXT_SIZE = 50 * 1024; // 50KB hard limit, shared with the references index
108
+ /**
109
+ * Read and parse openspec/config.yaml from project root.
110
+ * Uses resilient parsing - validates each field independently using Zod safeParse.
111
+ * Returns null if file doesn't exist.
112
+ * Returns partial config if some fields are invalid (with warnings).
113
+ *
114
+ * Performance note (Jan 2025):
115
+ * Benchmarks showed direct file reads are fast enough without caching:
116
+ * - Typical config (1KB): ~0.5ms per read
117
+ * - Large config (50KB): ~1.6ms per read
118
+ * - Missing config: ~0.01ms per read
119
+ * Config is read 1-2 times per command (schema resolution + instruction loading),
120
+ * adding ~1-3ms total overhead. Caching would add complexity (mtime checks,
121
+ * invalidation logic) for negligible benefit. Direct reads also ensure config
122
+ * changes are reflected immediately without stale cache issues.
123
+ *
124
+ * @param projectRoot - The root directory of the project (where `openspec/` lives)
125
+ * @returns Parsed config or null if file doesn't exist
126
+ */
127
+ export function readProjectConfig(projectRoot) {
128
+ const configPath = resolveConfigFilePath(projectRoot);
129
+ if (configPath === null) {
130
+ return null; // No config is OK
131
+ }
132
+ try {
133
+ const content = readFileSync(configPath, 'utf-8');
134
+ const raw = parseYaml(content);
135
+ if (!raw || typeof raw !== 'object') {
136
+ console.warn(`openspec/config.yaml is not a valid YAML object`);
137
+ return null;
138
+ }
139
+ const config = {};
140
+ // Parse schema field using Zod
141
+ const schemaField = z.string().min(1);
142
+ const schemaResult = schemaField.safeParse(raw.schema);
143
+ if (schemaResult.success) {
144
+ config.schema = schemaResult.data;
145
+ }
146
+ else if (raw.schema !== undefined) {
147
+ console.warn(`Invalid 'schema' field in config (must be non-empty string)`);
148
+ }
149
+ // Parse context field with size limit
150
+ if (raw.context !== undefined) {
151
+ const contextField = z.string();
152
+ const contextResult = contextField.safeParse(raw.context);
153
+ if (contextResult.success) {
154
+ const contextSize = Buffer.byteLength(contextResult.data, 'utf-8');
155
+ if (contextSize > MAX_CONTEXT_SIZE) {
156
+ console.warn(`Context too large (${(contextSize / 1024).toFixed(1)}KB, limit: ${MAX_CONTEXT_SIZE / 1024}KB)`);
157
+ console.warn(`Ignoring context field`);
158
+ }
159
+ else {
160
+ config.context = contextResult.data;
161
+ }
162
+ }
163
+ else {
164
+ console.warn(`Invalid 'context' field in config (must be string)`);
165
+ }
166
+ }
167
+ // Parse rules field using Zod
168
+ if (raw.rules !== undefined) {
169
+ const rulesField = z.record(z.string(), z.array(z.string()));
170
+ // First check if it's an object structure (guard against null since typeof null === 'object')
171
+ if (typeof raw.rules === 'object' && raw.rules !== null && !Array.isArray(raw.rules)) {
172
+ const parsedRules = {};
173
+ let hasValidRules = false;
174
+ for (const [artifactId, rules] of Object.entries(raw.rules)) {
175
+ const rulesArrayResult = z.array(z.string()).safeParse(rules);
176
+ if (rulesArrayResult.success) {
177
+ // Filter out empty strings
178
+ const validRules = rulesArrayResult.data.filter((r) => r.length > 0);
179
+ if (validRules.length > 0) {
180
+ parsedRules[artifactId] = validRules;
181
+ hasValidRules = true;
182
+ }
183
+ if (validRules.length < rulesArrayResult.data.length) {
184
+ console.warn(`Some rules for '${artifactId}' are empty strings, ignoring them`);
185
+ }
186
+ }
187
+ else {
188
+ console.warn(`Rules for '${artifactId}' must be an array of strings, ignoring this artifact's rules`);
189
+ }
190
+ }
191
+ if (hasValidRules) {
192
+ config.rules = parsedRules;
193
+ }
194
+ }
195
+ else {
196
+ console.warn(`Invalid 'rules' field in config (must be object)`);
197
+ }
198
+ }
199
+ const references = parseDeclarationList(raw.references);
200
+ if (references) {
201
+ config.references = references;
202
+ }
203
+ // Parse store pointer field: a string, or dropped with a warning.
204
+ // (Root resolution does NOT use this parse — it uses readStorePointer
205
+ // below, which errors on malformed pointers instead of dropping.)
206
+ if (raw.store !== undefined) {
207
+ if (typeof raw.store === 'string') {
208
+ config.store = raw.store;
209
+ }
210
+ else {
211
+ console.warn(`Warning: ignoring invalid store: field in ${configPathForWarnings(projectRoot)} (must be a single store id string).`);
212
+ }
213
+ }
214
+ // Return partial config even if some fields failed
215
+ return Object.keys(config).length > 0 ? config : null;
216
+ }
217
+ catch (error) {
218
+ console.warn(`Warning: could not parse ${configPathForWarnings(projectRoot)} (${error instanceof Error ? error.message.split('\n')[0] : String(error)}); ignoring it.`);
219
+ return null;
220
+ }
221
+ }
222
+ function configPathForWarnings(projectRoot) {
223
+ return resolveConfigFilePath(projectRoot) ?? path.join(projectRoot, 'openspec', 'config.yaml');
224
+ }
225
+ /**
226
+ * Validate artifact IDs in rules against a schema's artifacts.
227
+ * Called during instruction loading (when schema is known).
228
+ * Returns warnings for unknown artifact IDs.
229
+ *
230
+ * @param rules - The rules object from config
231
+ * @param validArtifactIds - Set of valid artifact IDs from the schema
232
+ * @param schemaName - Name of the schema for error messages
233
+ * @returns Array of warning messages for unknown artifact IDs
234
+ */
235
+ export function validateConfigRules(rules, validArtifactIds, schemaName) {
236
+ const warnings = [];
237
+ for (const artifactId of Object.keys(rules)) {
238
+ if (!validArtifactIds.has(artifactId)) {
239
+ const validIds = Array.from(validArtifactIds).sort().join(', ');
240
+ warnings.push(`Unknown artifact ID in rules: "${artifactId}". ` +
241
+ `Valid IDs for schema "${schemaName}": ${validIds}`);
242
+ }
243
+ }
244
+ return warnings;
245
+ }
246
+ /**
247
+ * Suggest valid schema names when user provides invalid schema.
248
+ * Uses fuzzy matching to find similar names.
249
+ *
250
+ * @param invalidSchemaName - The invalid schema name from config
251
+ * @param availableSchemas - List of available schemas with their type (built-in or project-local)
252
+ * @returns Error message with suggestions and available schemas
253
+ */
254
+ export function suggestSchemas(invalidSchemaName, availableSchemas) {
255
+ // Simple fuzzy match: Levenshtein distance
256
+ function levenshtein(a, b) {
257
+ const matrix = [];
258
+ for (let i = 0; i <= b.length; i++) {
259
+ matrix[i] = [i];
260
+ }
261
+ for (let j = 0; j <= a.length; j++) {
262
+ matrix[0][j] = j;
263
+ }
264
+ for (let i = 1; i <= b.length; i++) {
265
+ for (let j = 1; j <= a.length; j++) {
266
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
267
+ matrix[i][j] = matrix[i - 1][j - 1];
268
+ }
269
+ else {
270
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
271
+ }
272
+ }
273
+ }
274
+ return matrix[b.length][a.length];
275
+ }
276
+ // Find closest matches (distance <= 3)
277
+ const suggestions = availableSchemas
278
+ .map((s) => ({ ...s, distance: levenshtein(invalidSchemaName, s.name) }))
279
+ .filter((s) => s.distance <= 3)
280
+ .sort((a, b) => a.distance - b.distance)
281
+ .slice(0, 3);
282
+ const builtIn = availableSchemas.filter((s) => s.isBuiltIn).map((s) => s.name);
283
+ const projectLocal = availableSchemas.filter((s) => !s.isBuiltIn).map((s) => s.name);
284
+ let message = `Schema '${invalidSchemaName}' not found in openspec/config.yaml\n\n`;
285
+ if (suggestions.length > 0) {
286
+ message += `Did you mean one of these?\n`;
287
+ suggestions.forEach((s) => {
288
+ const type = s.isBuiltIn ? 'built-in' : 'project-local';
289
+ message += ` - ${s.name} (${type})\n`;
290
+ });
291
+ message += '\n';
292
+ }
293
+ message += `Available schemas:\n`;
294
+ if (builtIn.length > 0) {
295
+ message += ` Built-in: ${builtIn.join(', ')}\n`;
296
+ }
297
+ if (projectLocal.length > 0) {
298
+ message += ` Project-local: ${projectLocal.join(', ')}\n`;
299
+ }
300
+ else {
301
+ message += ` Project-local: (none found)\n`;
302
+ }
303
+ message += `\nFix: Edit openspec/config.yaml and change 'schema: ${invalidSchemaName}' to a valid schema name`;
304
+ return message;
305
+ }
306
+ /**
307
+ * Warning-silent targeted read of the `store:` pointer. Used by root
308
+ * resolution (which must not re-emit the resilient parser's field
309
+ * warnings) and by `openspec init`'s pointer guard. Unlike
310
+ * `readProjectConfig`, a malformed value is REPORTED, not dropped —
311
+ * a dropped pointer would silently flip where work lands.
312
+ */
313
+ export function readStorePointer(projectRoot) {
314
+ const configPath = resolveConfigFilePath(projectRoot);
315
+ if (configPath === null) {
316
+ return { filePath: null };
317
+ }
318
+ try {
319
+ const raw = parseYaml(readFileSync(configPath, 'utf-8'));
320
+ // Empty, comments-only, or non-mapping configs carry no pointer;
321
+ // they are imperfect, not malformed (readProjectConfig owns the
322
+ // field warnings for those).
323
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
324
+ return { filePath: configPath };
325
+ }
326
+ const value = raw.store;
327
+ if (value === undefined) {
328
+ return { filePath: configPath };
329
+ }
330
+ if (typeof value === 'string') {
331
+ return { value, filePath: configPath };
332
+ }
333
+ return { malformed: 'non_string', filePath: configPath };
334
+ }
335
+ catch {
336
+ return { malformed: 'unparseable', filePath: configPath };
337
+ }
338
+ }
339
+ /** Shared .yaml/.yml probe used by readProjectConfig and readStorePointer. */
340
+ export function resolveConfigFilePath(projectRoot) {
341
+ const yamlPath = path.join(projectRoot, 'openspec', 'config.yaml');
342
+ if (existsSync(yamlPath)) {
343
+ return yamlPath;
344
+ }
345
+ const ymlPath = path.join(projectRoot, 'openspec', 'config.yml');
346
+ return existsSync(ymlPath) ? ymlPath : null;
347
+ }
348
+ /** Human rendering of a malformed pointer reason, shared by every surface. */
349
+ export function storePointerProblem(reason) {
350
+ return reason === 'unparseable'
351
+ ? 'the config file could not be read as YAML'
352
+ : 'the store key must be a single store id string';
353
+ }
354
+ /**
355
+ * One classification for "real root vs config-only pointer dir", shared
356
+ * by root resolution and the init pointer guard so they can never
357
+ * disagree (slice 3.2).
358
+ */
359
+ export function classifyOpenSpecDir(projectRoot) {
360
+ const openspecDir = path.join(projectRoot, 'openspec');
361
+ const hasPlanningShape = isDirectorySync(path.join(openspecDir, 'specs')) ||
362
+ isDirectorySync(path.join(openspecDir, 'changes'));
363
+ return { hasPlanningShape, pointer: readStorePointer(projectRoot) };
364
+ }
365
+ function isDirectorySync(candidatePath) {
366
+ try {
367
+ return statSync(candidatePath).isDirectory();
368
+ }
369
+ catch {
370
+ return false;
371
+ }
372
+ }
373
+ //# sourceMappingURL=project-config.js.map
@@ -0,0 +1,63 @@
1
+ import { type StoreDiagnostic } from './store/errors.js';
2
+ import { listStoreRegistryEntries } from './store/foundation.js';
3
+ import { type ResolvedOpenSpecRoot } from './root-selection.js';
4
+ import { type DeclarationEntry } from './project-config.js';
5
+ export interface ReferenceSpecEntry {
6
+ id: string;
7
+ summary: string;
8
+ }
9
+ export interface ReferenceIndexEntry {
10
+ store_id: string;
11
+ root?: string;
12
+ specs?: ReferenceSpecEntry[];
13
+ fetch?: string;
14
+ status: StoreDiagnostic[];
15
+ }
16
+ /**
17
+ * Tolerant first-Purpose-line extraction. parseSpec() throws on specs
18
+ * without Purpose/Requirements sections; the index must never fail on an
19
+ * imperfect upstream spec, so this scans for the heading directly —
20
+ * fence-aware, so `## Purpose` inside a code block never matches, and
21
+ * tolerant of CommonMark closing hashes (`## Purpose ##`).
22
+ */
23
+ export declare function extractFirstPurposeLine(markdown: string): string;
24
+ export declare function fetchRecipe(storeId: string): string;
25
+ /**
26
+ * Pure renderer for the artifact-instructions XML block. Also the byte
27
+ * budget's measuring stick (it is the larger rendering).
28
+ */
29
+ export declare function renderReferencedStoresBlock(entries: ReferenceIndexEntry[]): string;
30
+ /** Pure renderer for the apply-instructions markdown section. */
31
+ export declare function renderReferencedStoresSection(entries: ReferenceIndexEntry[]): string;
32
+ /**
33
+ * Strings rendered into agent guidance can come from cloned content
34
+ * (spec directory names, Purpose lines, config-declared remotes). One
35
+ * line in, one line out: control characters and newlines must never
36
+ * let hostile content forge instruction lines (slice 6.1 hardening).
37
+ */
38
+ export declare function sanitizeInline(value: string, maxLength?: number): string;
39
+ export interface AssembleReferenceIndexInput {
40
+ references: DeclarationEntry[];
41
+ resolvedRoot: ResolvedOpenSpecRoot;
42
+ globalDataDir?: string;
43
+ /**
44
+ * Health mode (3.6): false skips the spec-file reads AND the byte
45
+ * budget — entries carry no `specs`/`fetch` keys at all, and the
46
+ * content-only truncation diagnostic can never appear.
47
+ */
48
+ includeSpecs?: boolean;
49
+ /**
50
+ * Pre-read registry entries (3.6): `[]` = registry empty or absent,
51
+ * `null` = unreadable, undefined = read internally as before.
52
+ * (Mirrors the internal post-read variable — never inject a raw
53
+ * read result: a healthy-absent registry reads as null.)
54
+ */
55
+ registryEntries?: ReturnType<typeof listStoreRegistryEntries> | null;
56
+ }
57
+ /**
58
+ * Builds the referenced-store index. One registry read per call; one
59
+ * level deep (a referenced store's own references are never followed);
60
+ * self-references omitted; every failure degrades to a warning entry.
61
+ */
62
+ export declare function assembleReferenceIndex(input: AssembleReferenceIndexInput): Promise<ReferenceIndexEntry[]>;
63
+ //# sourceMappingURL=references.d.ts.map