@niujiajie/freespec 0.0.0-stage → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +58 -2
  3. package/bin/freespec.js +5 -0
  4. package/dist/cli/index.d.ts +25 -0
  5. package/dist/cli/index.js +359 -0
  6. package/dist/commands/shared-output.d.ts +18 -0
  7. package/dist/commands/shared-output.js +61 -0
  8. package/dist/commands/workflow/default-schema.d.ts +2 -0
  9. package/dist/commands/workflow/default-schema.js +4 -0
  10. package/dist/commands/workflow/index.d.ts +13 -0
  11. package/dist/commands/workflow/index.js +10 -0
  12. package/dist/commands/workflow/instructions.d.ts +39 -0
  13. package/dist/commands/workflow/instructions.js +565 -0
  14. package/dist/commands/workflow/new-change.d.ts +19 -0
  15. package/dist/commands/workflow/new-change.js +135 -0
  16. package/dist/commands/workflow/shared.d.ts +94 -0
  17. package/dist/commands/workflow/shared.js +169 -0
  18. package/dist/commands/workflow/status.d.ts +18 -0
  19. package/dist/commands/workflow/status.js +201 -0
  20. package/dist/core/artifact-graph/graph.d.ts +72 -0
  21. package/dist/core/artifact-graph/graph.js +165 -0
  22. package/dist/core/artifact-graph/index.d.ts +9 -0
  23. package/dist/core/artifact-graph/index.js +14 -0
  24. package/dist/core/artifact-graph/instruction-loader.d.ts +208 -0
  25. package/dist/core/artifact-graph/instruction-loader.js +268 -0
  26. package/dist/core/artifact-graph/outputs.d.ts +19 -0
  27. package/dist/core/artifact-graph/outputs.js +351 -0
  28. package/dist/core/artifact-graph/resolver.d.ts +89 -0
  29. package/dist/core/artifact-graph/resolver.js +342 -0
  30. package/dist/core/artifact-graph/schema.d.ts +27 -0
  31. package/dist/core/artifact-graph/schema.js +160 -0
  32. package/dist/core/artifact-graph/state.d.ts +12 -0
  33. package/dist/core/artifact-graph/state.js +31 -0
  34. package/dist/core/artifact-graph/types.d.ts +40 -0
  35. package/dist/core/artifact-graph/types.js +59 -0
  36. package/dist/core/available-tools.d.ts +18 -0
  37. package/dist/core/available-tools.js +49 -0
  38. package/dist/core/change-layout.d.ts +17 -0
  39. package/dist/core/change-layout.js +30 -0
  40. package/dist/core/change-metadata/index.d.ts +2 -0
  41. package/dist/core/change-metadata/index.js +2 -0
  42. package/dist/core/change-metadata/schema.d.ts +23 -0
  43. package/dist/core/change-metadata/schema.js +46 -0
  44. package/dist/core/change-status-policy.d.ts +49 -0
  45. package/dist/core/change-status-policy.js +50 -0
  46. package/dist/core/command-generation/adapters/amazon-q.d.ts +17 -0
  47. package/dist/core/command-generation/adapters/amazon-q.js +32 -0
  48. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  49. package/dist/core/command-generation/adapters/antigravity.js +27 -0
  50. package/dist/core/command-generation/adapters/atomcode.d.ts +24 -0
  51. package/dist/core/command-generation/adapters/atomcode.js +46 -0
  52. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  53. package/dist/core/command-generation/adapters/auggie.js +28 -0
  54. package/dist/core/command-generation/adapters/bob.d.ts +18 -0
  55. package/dist/core/command-generation/adapters/bob.js +33 -0
  56. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  57. package/dist/core/command-generation/adapters/claude.js +32 -0
  58. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  59. package/dist/core/command-generation/adapters/cline.js +27 -0
  60. package/dist/core/command-generation/adapters/codeassistant.d.ts +15 -0
  61. package/dist/core/command-generation/adapters/codeassistant.js +29 -0
  62. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  63. package/dist/core/command-generation/adapters/codebuddy.js +29 -0
  64. package/dist/core/command-generation/adapters/codestudio.d.ts +13 -0
  65. package/dist/core/command-generation/adapters/codestudio.js +27 -0
  66. package/dist/core/command-generation/adapters/command-code.d.ts +19 -0
  67. package/dist/core/command-generation/adapters/command-code.js +34 -0
  68. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  69. package/dist/core/command-generation/adapters/continue.js +33 -0
  70. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  71. package/dist/core/command-generation/adapters/costrict.js +28 -0
  72. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  73. package/dist/core/command-generation/adapters/crush.js +30 -0
  74. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  75. package/dist/core/command-generation/adapters/cursor.js +31 -0
  76. package/dist/core/command-generation/adapters/devin.d.ts +19 -0
  77. package/dist/core/command-generation/adapters/devin.js +36 -0
  78. package/dist/core/command-generation/adapters/easycode.d.ts +17 -0
  79. package/dist/core/command-generation/adapters/easycode.js +28 -0
  80. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  81. package/dist/core/command-generation/adapters/factory.js +28 -0
  82. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  83. package/dist/core/command-generation/adapters/gemini.js +27 -0
  84. package/dist/core/command-generation/adapters/gigacode.d.ts +17 -0
  85. package/dist/core/command-generation/adapters/gigacode.js +31 -0
  86. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  87. package/dist/core/command-generation/adapters/github-copilot.js +27 -0
  88. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  89. package/dist/core/command-generation/adapters/iflow.js +30 -0
  90. package/dist/core/command-generation/adapters/index.d.ts +40 -0
  91. package/dist/core/command-generation/adapters/index.js +40 -0
  92. package/dist/core/command-generation/adapters/junie.d.ts +13 -0
  93. package/dist/core/command-generation/adapters/junie.js +27 -0
  94. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  95. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  96. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  97. package/dist/core/command-generation/adapters/kiro.js +27 -0
  98. package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
  99. package/dist/core/command-generation/adapters/lingma.js +30 -0
  100. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
  101. package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
  102. package/dist/core/command-generation/adapters/opencode.d.ts +14 -0
  103. package/dist/core/command-generation/adapters/opencode.js +38 -0
  104. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  105. package/dist/core/command-generation/adapters/pi.js +39 -0
  106. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  107. package/dist/core/command-generation/adapters/qoder.js +30 -0
  108. package/dist/core/command-generation/adapters/qwen.d.ts +17 -0
  109. package/dist/core/command-generation/adapters/qwen.js +31 -0
  110. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  111. package/dist/core/command-generation/adapters/roocode.js +27 -0
  112. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  113. package/dist/core/command-generation/adapters/trae.js +28 -0
  114. package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
  115. package/dist/core/command-generation/adapters/zcode.js +33 -0
  116. package/dist/core/command-generation/generator.d.ts +29 -0
  117. package/dist/core/command-generation/generator.js +43 -0
  118. package/dist/core/command-generation/index.d.ts +22 -0
  119. package/dist/core/command-generation/index.js +24 -0
  120. package/dist/core/command-generation/invocation.d.ts +73 -0
  121. package/dist/core/command-generation/invocation.js +77 -0
  122. package/dist/core/command-generation/registry.d.ts +36 -0
  123. package/dist/core/command-generation/registry.js +114 -0
  124. package/dist/core/command-generation/toml.d.ts +19 -0
  125. package/dist/core/command-generation/toml.js +39 -0
  126. package/dist/core/command-generation/types.d.ts +63 -0
  127. package/dist/core/command-generation/types.js +8 -0
  128. package/dist/core/command-generation/yaml.d.ts +36 -0
  129. package/dist/core/command-generation/yaml.js +48 -0
  130. package/dist/core/command-surface.d.ts +15 -0
  131. package/dist/core/command-surface.js +33 -0
  132. package/dist/core/config-prompts.d.ts +9 -0
  133. package/dist/core/config-prompts.js +56 -0
  134. package/dist/core/config-schema.d.ts +98 -0
  135. package/dist/core/config-schema.js +308 -0
  136. package/dist/core/config.d.ts +50 -0
  137. package/dist/core/config.js +122 -0
  138. package/dist/core/file-state.d.ts +36 -0
  139. package/dist/core/file-state.js +144 -0
  140. package/dist/core/file-view.d.ts +13 -0
  141. package/dist/core/file-view.js +14 -0
  142. package/dist/core/global-config.d.ts +101 -0
  143. package/dist/core/global-config.js +182 -0
  144. package/dist/core/id.d.ts +17 -0
  145. package/dist/core/id.js +30 -0
  146. package/dist/core/index.d.ts +6 -0
  147. package/dist/core/index.js +7 -0
  148. package/dist/core/init.d.ts +57 -0
  149. package/dist/core/init.js +836 -0
  150. package/dist/core/list.d.ts +11 -0
  151. package/dist/core/list.js +197 -0
  152. package/dist/core/migration.d.ts +121 -0
  153. package/dist/core/migration.js +540 -0
  154. package/dist/core/onboarding-commands.d.ts +30 -0
  155. package/dist/core/onboarding-commands.js +38 -0
  156. package/dist/core/openspec-root.d.ts +47 -0
  157. package/dist/core/openspec-root.js +196 -0
  158. package/dist/core/planning-home.d.ts +16 -0
  159. package/dist/core/planning-home.js +67 -0
  160. package/dist/core/profile-sync-drift.d.ts +31 -0
  161. package/dist/core/profile-sync-drift.js +201 -0
  162. package/dist/core/profiles.d.ts +19 -0
  163. package/dist/core/profiles.js +33 -0
  164. package/dist/core/project-config.d.ts +132 -0
  165. package/dist/core/project-config.js +519 -0
  166. package/dist/core/references.d.ts +10 -0
  167. package/dist/core/references.js +46 -0
  168. package/dist/core/root-selection.d.ts +59 -0
  169. package/dist/core/root-selection.js +132 -0
  170. package/dist/core/shared/allowed-tools.d.ts +12 -0
  171. package/dist/core/shared/allowed-tools.js +12 -0
  172. package/dist/core/shared/ide-restart.d.ts +26 -0
  173. package/dist/core/shared/ide-restart.js +42 -0
  174. package/dist/core/shared/index.d.ts +11 -0
  175. package/dist/core/shared/index.js +11 -0
  176. package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
  177. package/dist/core/shared/skill-content-equivalence.js +45 -0
  178. package/dist/core/shared/skill-files.d.ts +7 -0
  179. package/dist/core/shared/skill-files.js +43 -0
  180. package/dist/core/shared/skill-generation.d.ts +49 -0
  181. package/dist/core/shared/skill-generation.js +125 -0
  182. package/dist/core/shared/skill-paths.d.ts +13 -0
  183. package/dist/core/shared/skill-paths.js +23 -0
  184. package/dist/core/shared/tool-detection.d.ts +99 -0
  185. package/dist/core/shared/tool-detection.js +318 -0
  186. package/dist/core/shared-skill-target.d.ts +51 -0
  187. package/dist/core/shared-skill-target.js +239 -0
  188. package/dist/core/store/errors.d.ts +20 -0
  189. package/dist/core/store/errors.js +22 -0
  190. package/dist/core/store/index.d.ts +2 -0
  191. package/dist/core/store/index.js +2 -0
  192. package/dist/core/styles/palette.d.ts +7 -0
  193. package/dist/core/styles/palette.js +8 -0
  194. package/dist/core/templates/optional-workflow.d.ts +69 -0
  195. package/dist/core/templates/optional-workflow.js +140 -0
  196. package/dist/core/templates/types.d.ts +19 -0
  197. package/dist/core/templates/types.js +5 -0
  198. package/dist/core/templates/workflows/personal.d.ts +22 -0
  199. package/dist/core/templates/workflows/personal.js +225 -0
  200. package/dist/core/update-plan.d.ts +127 -0
  201. package/dist/core/update-plan.js +390 -0
  202. package/dist/core/update.d.ts +154 -0
  203. package/dist/core/update.js +723 -0
  204. package/dist/core/version-check.d.ts +188 -0
  205. package/dist/core/version-check.js +906 -0
  206. package/dist/core/zod-issues.d.ts +4 -0
  207. package/dist/core/zod-issues.js +10 -0
  208. package/dist/index.d.ts +3 -0
  209. package/dist/index.js +3 -0
  210. package/dist/prompts/searchable-multi-select.d.ts +35 -0
  211. package/dist/prompts/searchable-multi-select.js +188 -0
  212. package/dist/telemetry/config.d.ts +37 -0
  213. package/dist/telemetry/config.js +148 -0
  214. package/dist/telemetry/index.d.ts +39 -0
  215. package/dist/telemetry/index.js +217 -0
  216. package/dist/telemetry/opt-out.d.ts +17 -0
  217. package/dist/telemetry/opt-out.js +27 -0
  218. package/dist/ui/ascii-patterns.d.ts +16 -0
  219. package/dist/ui/ascii-patterns.js +133 -0
  220. package/dist/ui/welcome-screen.d.ts +18 -0
  221. package/dist/ui/welcome-screen.js +199 -0
  222. package/dist/utils/change-metadata.d.ts +116 -0
  223. package/dist/utils/change-metadata.js +324 -0
  224. package/dist/utils/change-utils.d.ts +79 -0
  225. package/dist/utils/change-utils.js +121 -0
  226. package/dist/utils/ci.d.ts +12 -0
  227. package/dist/utils/ci.js +16 -0
  228. package/dist/utils/command-references.d.ts +104 -0
  229. package/dist/utils/command-references.js +190 -0
  230. package/dist/utils/date.d.ts +8 -0
  231. package/dist/utils/date.js +13 -0
  232. package/dist/utils/executable-path.d.ts +5 -0
  233. package/dist/utils/executable-path.js +44 -0
  234. package/dist/utils/file-system.d.ts +58 -0
  235. package/dist/utils/file-system.js +432 -0
  236. package/dist/utils/index.d.ts +6 -0
  237. package/dist/utils/index.js +9 -0
  238. package/dist/utils/interactive.d.ts +85 -0
  239. package/dist/utils/interactive.js +174 -0
  240. package/dist/utils/item-discovery.d.ts +15 -0
  241. package/dist/utils/item-discovery.js +33 -0
  242. package/dist/utils/line-endings.d.ts +34 -0
  243. package/dist/utils/line-endings.js +46 -0
  244. package/dist/utils/match.d.ts +3 -0
  245. package/dist/utils/match.js +22 -0
  246. package/dist/utils/nested-change.d.ts +35 -0
  247. package/dist/utils/nested-change.js +185 -0
  248. package/dist/utils/shell-detection.d.ts +20 -0
  249. package/dist/utils/shell-detection.js +94 -0
  250. package/dist/utils/task-progress.d.ts +67 -0
  251. package/dist/utils/task-progress.js +184 -0
  252. package/package.json +74 -4
  253. package/schemas//351/273/230/350/256/244/schema.yaml +68 -0
  254. package/schemas//351/273/230/350/256/244/templates//344/273/273/345/212/241/346/270/205/345/215/225.md +25 -0
  255. package/schemas//351/273/230/350/256/244/templates//346/265/213/350/257/225/347/224/250/344/276/213.md +34 -0
  256. package/schemas//351/273/230/350/256/244/templates//350/256/276/350/256/241/346/226/207/346/241/243.md +172 -0
  257. package/schemas//351/273/230/350/256/244/templates//351/234/200/346/261/202/346/226/207/346/241/243.md +19 -0
  258. package/skill-files//344/273/273/345/212/241/references//346/212/244/346/240/217.md +43 -0
  259. package/skill-files//344/273/273/345/212/241/references//347/262/222/345/272/246.md +23 -0
  260. package/skill-files//344/273/273/345/212/241/references//347/272/246/346/235/237.md +8 -0
  261. package/skill-files//344/273/273/345/212/241/scripts/check-tasks.mjs +83 -0
  262. package/skill-files//345/206/231Java/references/Java/350/247/204/350/214/203.md +25 -0
  263. package/skill-files//345/206/231Java/references//345/215/261/351/231/251/345/206/231/346/263/225.md +27 -0
  264. package/skill-files//345/206/231Java/references//345/256/214/346/210/220/345/257/271/347/205/247.md +15 -0
  265. package/skill-files//345/206/231Java/references//346/212/244/346/240/217.md +35 -0
  266. package/skill-files//345/206/231Java/references//347/272/246/346/235/237.md +8 -0
  267. package/skill-files//345/206/231Java/references//350/270/251/345/235/221.md +17 -0
  268. package/skill-files//345/256/241Java/references//346/212/244/346/240/217.md +19 -0
  269. package/skill-files//345/256/241Java/references//347/244/272/344/276/213.md +23 -0
  270. package/skill-files//345/256/241Java/references//347/272/246/346/235/237.md +7 -0
  271. package/skill-files//345/256/241Java/references//350/247/204/345/210/231.md +23 -0
  272. package/skill-files//345/256/241Java/references//351/242/206/345/237/237/346/243/200/351/241/271.md +58 -0
  273. package/skill-files//345/256/241/346/226/207/346/241/243/references//346/212/244/346/240/217.md +18 -0
  274. package/skill-files//345/256/241/346/226/207/346/241/243/references//347/272/246/346/235/237.md +8 -0
  275. package/skill-files//345/256/241/346/226/207/346/241/243/references//347/273/264/345/272/246.md +17 -0
  276. package/skill-files//345/256/241/346/226/207/346/241/243/references//350/270/251/345/235/221.md +11 -0
  277. package/skill-files//346/226/260/345/273/272/references//344/272/244/346/216/245.md +11 -0
  278. package/skill-files//346/226/260/345/273/272/references//346/212/244/346/240/217.md +14 -0
  279. package/skill-files//346/226/260/345/273/272/references//347/272/246/346/235/237.md +7 -0
  280. package/skill-files//346/267/261/346/200/235/config/prompts.yaml +114 -0
  281. package/skill-files//346/267/261/346/200/235/references//344/270/211/346/226/271/346/240/270/345/257/271.md +72 -0
  282. package/skill-files//346/267/261/346/200/235/references//344/276/213/345/255/220.md +39 -0
  283. package/skill-files//346/267/261/346/200/235/references//346/200/216/344/271/210/346/203/263.md +61 -0
  284. package/skill-files//346/267/261/346/200/235/references//346/212/244/346/240/217.md +26 -0
  285. package/skill-files//346/267/261/346/200/235/references//347/202/271/351/200/211/346/217/220/351/227/256.md +66 -0
  286. package/skill-files//346/267/261/346/200/235/references//347/231/273/350/256/260.md +45 -0
  287. package/skill-files//346/267/261/346/200/235/references//347/272/246/346/235/237.md +9 -0
  288. package/skill-files//347/224/250/344/276/213/references//346/200/216/344/271/210/345/206/231.md +13 -0
  289. package/skill-files//347/224/250/344/276/213/references//346/212/244/346/240/217.md +42 -0
  290. package/skill-files//347/224/250/344/276/213/references//347/272/246/346/235/237.md +5 -0
  291. package/skill-files//347/224/250/344/276/213/scripts/check-cases.mjs +70 -0
  292. package/skill-files//350/256/276/350/256/241/references//345/207/206/345/244/207.md +25 -0
  293. package/skill-files//350/256/276/350/256/241/references//346/212/244/346/240/217.md +134 -0
  294. package/skill-files//350/256/276/350/256/241/references//346/226/207/351/243/216.md +17 -0
  295. package/skill-files//350/256/276/350/256/241/references//347/225/214/351/235/242.md +18 -0
  296. package/skill-files//350/256/276/350/256/241/references//347/261/273/345/236/213.md +19 -0
  297. package/skill-files//350/256/276/350/256/241/references//347/272/246/346/235/237.md +25 -0
  298. package/skill-files//350/256/276/350/256/241/scripts/_smoke.mjs +72 -0
  299. package/skill-files//350/256/276/350/256/241/scripts/check-design.mjs +331 -0
  300. package/skill-files//351/234/200/346/261/202/references//346/212/244/346/240/217.md +26 -0
  301. package/skill-files//351/234/200/346/261/202/references//346/226/207/351/243/216.md +23 -0
  302. package/skill-files//351/234/200/346/261/202/references//346/267/261/346/200/235/346/221/204/345/205/245.md +21 -0
  303. package/skill-files//351/234/200/346/261/202/references//347/272/246/346/235/237.md +8 -0
  304. package/skill-files//351/234/200/346/261/202/references//351/234/200/346/261/202/345/276/205/345/256/232.md +13 -0
  305. package/skill-files//351/234/200/346/261/202/scripts/check-requirements.mjs +60 -0
  306. package/skill-files//351/252/214/346/224/266/references//346/212/244/346/240/217.md +27 -0
  307. package/skill-files//351/252/214/346/224/266/references//346/240/270/345/257/271.md +17 -0
  308. package/skill-files//351/252/214/346/224/266/references//347/272/246/346/235/237.md +7 -0
@@ -0,0 +1,132 @@
1
+ import { z } from 'zod';
2
+ export declare const OPERATION_IDS: readonly ["apply", "archive"];
3
+ export type OperationId = (typeof OPERATION_IDS)[number];
4
+ export interface OperationConfig {
5
+ guidance?: string[];
6
+ }
7
+ export type OperationsConfig = Partial<Record<OperationId, OperationConfig>>;
8
+ /**
9
+ * Zod schema for project configuration.
10
+ *
11
+ * Purpose:
12
+ * 1. Documentation - clearly defines the config file structure
13
+ * 2. Type safety - TypeScript infers ProjectConfig type from schema
14
+ * 3. Runtime validation - uses safeParse() for resilient field-by-field validation
15
+ *
16
+ * Why Zod over manual validation:
17
+ * - Helps understand FreeSpec's data interfaces at a glance
18
+ * - Single source of truth for type and validation
19
+ * - Consistent with other FreeSpec schemas
20
+ */
21
+ export declare const ProjectConfigSchema: z.ZodObject<{
22
+ schema: z.ZodString;
23
+ context: z.ZodOptional<z.ZodString>;
24
+ rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
25
+ operations: z.ZodOptional<z.ZodObject<{
26
+ apply: z.ZodOptional<z.ZodObject<{
27
+ guidance: z.ZodOptional<z.ZodArray<z.ZodString>>;
28
+ }, z.core.$strip>>;
29
+ archive: z.ZodOptional<z.ZodObject<{
30
+ guidance: z.ZodOptional<z.ZodArray<z.ZodString>>;
31
+ }, z.core.$strip>>;
32
+ }, z.core.$strip>>;
33
+ store: z.ZodOptional<z.ZodString>;
34
+ githubCopilot: z.ZodOptional<z.ZodObject<{
35
+ cloudAgent: z.ZodOptional<z.ZodBoolean>;
36
+ }, z.core.$strip>>;
37
+ }, z.core.$strip>;
38
+ /** Normalized in-memory shape of a referenced store declaration. */
39
+ export interface DeclarationEntry {
40
+ id: string;
41
+ /** Clone source rendered into onboarding fixes. */
42
+ remote?: string;
43
+ }
44
+ export type ProjectConfig = z.infer<typeof ProjectConfigSchema> & {
45
+ references?: DeclarationEntry[];
46
+ };
47
+ export interface OperationInputs {
48
+ context?: string;
49
+ operationGuidance?: string[];
50
+ }
51
+ export declare function loadOperationInputs(projectConfig: ProjectConfig | null, operationId: OperationId): OperationInputs;
52
+ export declare const MAX_CONTEXT_SIZE: number;
53
+ /**
54
+ * Read and parse freespec/config.yaml from project root.
55
+ * Uses resilient parsing - validates each field independently using Zod safeParse.
56
+ * Returns null if file doesn't exist.
57
+ * Returns partial config if some fields are invalid (with warnings).
58
+ *
59
+ * Performance note (Jan 2025):
60
+ * Benchmarks showed direct file reads are fast enough without caching:
61
+ * - Typical config (1KB): ~0.5ms per read
62
+ * - Large config (50KB): ~1.6ms per read
63
+ * - Missing config: ~0.01ms per read
64
+ * Config is read 1-2 times per command (schema resolution + instruction loading),
65
+ * adding ~1-3ms total overhead. Caching would add complexity (mtime checks,
66
+ * invalidation logic) for negligible benefit. Direct reads also ensure config
67
+ * changes are reflected immediately without stale cache issues.
68
+ *
69
+ * @param projectRoot - The root directory of the project (where `freespec/` lives)
70
+ * @returns Parsed config or null if file doesn't exist
71
+ */
72
+ export declare function readProjectConfig(projectRoot: string): ProjectConfig | null;
73
+ /**
74
+ * Validate artifact IDs in rules against the artifacts of every available
75
+ * schema. The `rules:` map is global, but each change can use a different
76
+ * schema, so a key is only unknown when it matches no artifact in ANY schema.
77
+ * Returns warnings for keys that are unknown everywhere.
78
+ *
79
+ * @param rules - The rules object from config
80
+ * @param validArtifactIds - Set of valid artifact IDs across all schemas
81
+ * @returns Array of warning messages for unknown artifact IDs
82
+ */
83
+ export declare function validateConfigRules(rules: Record<string, string[]>, validArtifactIds: Set<string>): string[];
84
+ /**
85
+ * Suggest valid schema names when user provides invalid schema.
86
+ * Uses fuzzy matching to find similar names.
87
+ *
88
+ * @param invalidSchemaName - The invalid schema name from config
89
+ * @param availableSchemas - List of available schemas with their type (built-in or project-local)
90
+ * @returns Error message with suggestions and available schemas
91
+ */
92
+ export declare function suggestSchemas(invalidSchemaName: string, availableSchemas: {
93
+ name: string;
94
+ isBuiltIn: boolean;
95
+ }[]): string;
96
+ export interface StorePointerRead {
97
+ /** The declared store id, when present and a string. */
98
+ value?: string;
99
+ /** Set when the pointer cannot be trusted: the config file could not be
100
+ * read as YAML, or the store key is present but not a string. An empty
101
+ * or comments-only config is NOT malformed - it simply has no pointer. */
102
+ malformed?: 'unparseable' | 'non_string';
103
+ /** Absolute path of the config file actually read, or null when none exists. */
104
+ filePath: string | null;
105
+ }
106
+ /**
107
+ * Warning-silent targeted read of the `store:` pointer. Used by root
108
+ * resolution (which must not re-emit the resilient parser's field
109
+ * warnings) and by `freespec init`'s pointer guard. Unlike
110
+ * `readProjectConfig`, a malformed value is REPORTED, not dropped —
111
+ * a dropped pointer would silently flip where work lands.
112
+ */
113
+ export declare function readStorePointer(projectRoot: string): StorePointerRead;
114
+ /** Shared .yaml/.yml probe used by readProjectConfig and readStorePointer. */
115
+ export declare function resolveConfigFilePath(projectRoot: string): string | null;
116
+ /** Human rendering of a malformed pointer reason, shared by every surface. */
117
+ export declare function storePointerProblem(reason: 'unparseable' | 'non_string'): string;
118
+ export interface FreeSpecDirClassification {
119
+ /** True when freespec/ has a `<名称>变更` directory. */
120
+ hasPlanningShape: boolean;
121
+ pointer: StorePointerRead;
122
+ }
123
+ /**
124
+ * One classification for "real root vs config-only pointer dir", shared
125
+ * by root resolution and the init pointer guard so they can never
126
+ * disagree (slice 3.2).
127
+ *
128
+ * A bare `freespec/` directory is not a planning root. The qualifying walk
129
+ * also accepts a config file, so a fresh init is still a root.
130
+ */
131
+ export declare function classifyFreeSpecDir(projectRoot: string): FreeSpecDirClassification;
132
+ //# sourceMappingURL=project-config.d.ts.map
@@ -0,0 +1,519 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'fs';
2
+ import path from 'path';
3
+ import { parse as parseYaml } from 'yaml';
4
+ import { z } from 'zod';
5
+ import { isChangeDirectoryName } from './change-layout.js';
6
+ export const OPERATION_IDS = ['apply', 'archive'];
7
+ const OperationConfigSchema = z.object({
8
+ guidance: z.array(z.string()).optional(),
9
+ });
10
+ /**
11
+ * Zod schema for project configuration.
12
+ *
13
+ * Purpose:
14
+ * 1. Documentation - clearly defines the config file structure
15
+ * 2. Type safety - TypeScript infers ProjectConfig type from schema
16
+ * 3. Runtime validation - uses safeParse() for resilient field-by-field validation
17
+ *
18
+ * Why Zod over manual validation:
19
+ * - Helps understand FreeSpec's data interfaces at a glance
20
+ * - Single source of truth for type and validation
21
+ * - Consistent with other FreeSpec schemas
22
+ */
23
+ export const ProjectConfigSchema = z.object({
24
+ // Required: which schema to use (e.g., "spec-driven", or project-local schema name)
25
+ schema: z
26
+ .string()
27
+ .min(1)
28
+ .describe('The workflow schema to use (e.g., "默认")'),
29
+ // Optional: project context (injected into all artifact instructions)
30
+ // Max size: 50KB (enforced during parsing)
31
+ context: z
32
+ .string()
33
+ .optional()
34
+ .describe('Project context injected into all artifact instructions'),
35
+ // Optional: per-artifact rules (additive to schema's built-in guidance)
36
+ rules: z
37
+ .record(z.string(), // artifact ID
38
+ z.array(z.string()) // list of rules
39
+ )
40
+ .optional()
41
+ .describe('Per-artifact rules, keyed by artifact ID'),
42
+ // Optional: per-operation advisory guidance, kept separate from artifact rules.
43
+ operations: z
44
+ .object({
45
+ apply: OperationConfigSchema.optional(),
46
+ archive: OperationConfigSchema.optional(),
47
+ })
48
+ .optional()
49
+ .describe('Per-operation advisory guidance'),
50
+ // Note: the `references` field (id strings or {id, remote} maps) is
51
+ // deliberately absent here — readProjectConfig parses and normalizes
52
+ // it by hand (see DeclarationEntry below); a schema entry nothing
53
+ // parses would only drift from the real behavior.
54
+ // Optional: the declared default store. Only consulted by root
55
+ // resolution when this freespec/ directory is config-only (no specs/
56
+ // directory and no `<名称>变更` folder); a fallback, never an override.
57
+ store: z
58
+ .string()
59
+ .optional()
60
+ .describe('Store id used as the FreeSpec root when no local planning shape exists'),
61
+ // Optional: GitHub Copilot integration preferences. `cloudAgent` is the
62
+ // opt-in for generating the Copilot cloud coding-agent files (a GitHub
63
+ // Actions workflow + agent file); absent means "not yet decided".
64
+ githubCopilot: z
65
+ .object({
66
+ cloudAgent: z.boolean().optional(),
67
+ })
68
+ .optional()
69
+ .describe('GitHub Copilot integration preferences'),
70
+ });
71
+ export function loadOperationInputs(projectConfig, operationId) {
72
+ const context = projectConfig?.context !== undefined && projectConfig.context.trim().length > 0
73
+ ? projectConfig.context
74
+ : undefined;
75
+ const guidance = projectConfig?.operations?.[operationId]?.guidance;
76
+ const operationGuidance = guidance && guidance.length > 0 ? guidance : undefined;
77
+ return {
78
+ ...(context !== undefined ? { context } : {}),
79
+ ...(operationGuidance !== undefined ? { operationGuidance } : {}),
80
+ };
81
+ }
82
+ function parseOperations(raw) {
83
+ if (raw === undefined) {
84
+ return undefined;
85
+ }
86
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
87
+ console.warn(`Invalid 'operations' field in config (must be object)`);
88
+ return undefined;
89
+ }
90
+ const supported = new Set(OPERATION_IDS);
91
+ const operations = {};
92
+ for (const [operationId, value] of Object.entries(raw)) {
93
+ if (!supported.has(operationId)) {
94
+ console.warn(`Unknown operation ID '${operationId}' in config. Supported operation IDs: ${OPERATION_IDS.join(', ')}`);
95
+ continue;
96
+ }
97
+ const typedOperationId = operationId;
98
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
99
+ console.warn(`Invalid 'operations.${operationId}' field in config (must be object), ignoring this operation`);
100
+ continue;
101
+ }
102
+ const operation = value;
103
+ const unknownFields = Object.keys(operation).filter((field) => field !== 'guidance');
104
+ if (unknownFields.length > 0) {
105
+ console.warn(`Unknown field(s) in 'operations.${operationId}': ${unknownFields.join(', ')}. Supported fields: guidance`);
106
+ }
107
+ if (operation.guidance === undefined) {
108
+ continue;
109
+ }
110
+ const guidanceResult = z.array(z.string()).safeParse(operation.guidance);
111
+ if (!guidanceResult.success) {
112
+ console.warn(`Guidance for operation '${operationId}' must be an array of strings, ignoring this operation's guidance`);
113
+ continue;
114
+ }
115
+ const guidance = guidanceResult.data.filter((entry) => entry.length > 0);
116
+ if (guidance.length < guidanceResult.data.length) {
117
+ console.warn(`Some guidance for operation '${operationId}' are empty strings, ignoring them`);
118
+ }
119
+ if (guidance.length > 0) {
120
+ operations[typedOperationId] = { guidance };
121
+ }
122
+ }
123
+ return Object.keys(operations).length > 0 ? operations : undefined;
124
+ }
125
+ /**
126
+ * Parser for `references:` declarations: string entries or
127
+ * {id, remote} maps, normalized to DeclarationEntry[]. Dedup keys on
128
+ * id and keeps the first position; the first entry carrying a remote
129
+ * supplies it (a later duplicate fills a missing remote, never
130
+ * overrides). Invalid entries drop with a warning like other resilient
131
+ * fields; returns undefined when the field is absent or normalizes to
132
+ * empty.
133
+ */
134
+ function parseDeclarationList(raw) {
135
+ const fieldName = 'references';
136
+ if (raw === undefined) {
137
+ return undefined;
138
+ }
139
+ if (!Array.isArray(raw)) {
140
+ console.warn(`Invalid '${fieldName}' field in config (must be an array of store ids)`);
141
+ return undefined;
142
+ }
143
+ const byId = new Map();
144
+ let droppedEntries = false;
145
+ let droppedRemotes = false;
146
+ for (const entry of raw) {
147
+ let declaration = null;
148
+ if (typeof entry === 'string') {
149
+ declaration = { id: entry };
150
+ }
151
+ else if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
152
+ const candidate = entry;
153
+ if (typeof candidate.id === 'string') {
154
+ declaration = { id: candidate.id };
155
+ if (typeof candidate.remote === 'string' && candidate.remote.length > 0) {
156
+ declaration.remote = candidate.remote;
157
+ }
158
+ else if (candidate.remote !== undefined) {
159
+ droppedRemotes = true; // remote dropped, id kept
160
+ }
161
+ }
162
+ }
163
+ if (!declaration) {
164
+ droppedEntries = true;
165
+ continue;
166
+ }
167
+ const existing = byId.get(declaration.id);
168
+ if (!existing) {
169
+ byId.set(declaration.id, declaration);
170
+ }
171
+ else if (existing.remote === undefined && declaration.remote !== undefined) {
172
+ existing.remote = declaration.remote;
173
+ }
174
+ }
175
+ if (droppedEntries) {
176
+ console.warn(`Some '${fieldName}' entries are invalid, ignoring them`);
177
+ }
178
+ if (droppedRemotes) {
179
+ console.warn(`Some '${fieldName}' remotes are not non-empty strings; the ids are kept without a clone source`);
180
+ }
181
+ return byId.size > 0 ? [...byId.values()] : undefined;
182
+ }
183
+ export const MAX_CONTEXT_SIZE = 50 * 1024; // 50KB hard limit, shared with the references index
184
+ /**
185
+ * Build the warning for an artifact whose `rules:` list is not an array of
186
+ * strings. Names the offending index and what YAML actually produced there, so
187
+ * a config that silently loses an entire rule set can be fixed without
188
+ * bisecting the list by hand. A bare `-` item containing an unquoted ": " is the
189
+ * common cause: YAML reads it as a mapping, so the hint points at quoting.
190
+ */
191
+ function describeRulesShapeError(artifactId, rules) {
192
+ const base = `Rules for '${artifactId}' must be an array of strings, ignoring this artifact's rules`;
193
+ if (!Array.isArray(rules)) {
194
+ return `${base}. rules.${artifactId} is ${describeYamlType(rules)}`;
195
+ }
196
+ const bad = rules
197
+ .map((rule, index) => ({ rule, index }))
198
+ .filter(({ rule }) => typeof rule !== 'string');
199
+ if (bad.length === 0) {
200
+ return base;
201
+ }
202
+ // Name every offending index with its own shape, so a mixed list does not
203
+ // have to be re-bisected one item at a time.
204
+ const details = bad
205
+ .map(({ rule, index }) => `rules.${artifactId}[${index}] is ${describeYamlType(rule)}`)
206
+ .join('; ');
207
+ // A bare `-` item with an unquoted ": " is the common cause: YAML reads it as
208
+ // a mapping. Say so rather than leaving the reader to work out the quoting.
209
+ const hasMapping = bad.some(({ rule }) => rule !== null && typeof rule === 'object' && !Array.isArray(rule));
210
+ const hint = hasMapping
211
+ ? ' — an unquoted ": " makes YAML read the item as a key/value pair; quote the whole scalar to keep it a string.'
212
+ : '';
213
+ return `${base}. ${details}${hint}`;
214
+ }
215
+ /** Name a YAML value's shape in a warning, e.g. "a mapping" or "a number". */
216
+ function describeYamlType(value) {
217
+ if (value === null)
218
+ return 'null';
219
+ if (Array.isArray(value))
220
+ return 'a nested list';
221
+ const type = typeof value;
222
+ if (type === 'object')
223
+ return 'a mapping';
224
+ if (type === 'number' || type === 'boolean')
225
+ return `a ${type}`;
226
+ return `a ${type}`;
227
+ }
228
+ /**
229
+ * Read and parse freespec/config.yaml from project root.
230
+ * Uses resilient parsing - validates each field independently using Zod safeParse.
231
+ * Returns null if file doesn't exist.
232
+ * Returns partial config if some fields are invalid (with warnings).
233
+ *
234
+ * Performance note (Jan 2025):
235
+ * Benchmarks showed direct file reads are fast enough without caching:
236
+ * - Typical config (1KB): ~0.5ms per read
237
+ * - Large config (50KB): ~1.6ms per read
238
+ * - Missing config: ~0.01ms per read
239
+ * Config is read 1-2 times per command (schema resolution + instruction loading),
240
+ * adding ~1-3ms total overhead. Caching would add complexity (mtime checks,
241
+ * invalidation logic) for negligible benefit. Direct reads also ensure config
242
+ * changes are reflected immediately without stale cache issues.
243
+ *
244
+ * @param projectRoot - The root directory of the project (where `freespec/` lives)
245
+ * @returns Parsed config or null if file doesn't exist
246
+ */
247
+ export function readProjectConfig(projectRoot) {
248
+ const configPath = resolveConfigFilePath(projectRoot);
249
+ if (configPath === null) {
250
+ return null; // No config is OK
251
+ }
252
+ try {
253
+ const content = readFileSync(configPath, 'utf-8');
254
+ const raw = parseYaml(content);
255
+ if (!raw || typeof raw !== 'object') {
256
+ console.warn(`freespec/config.yaml is not a valid YAML object`);
257
+ return null;
258
+ }
259
+ const config = {};
260
+ // Parse schema field using Zod
261
+ const schemaField = z.string().min(1);
262
+ const schemaResult = schemaField.safeParse(raw.schema);
263
+ if (schemaResult.success) {
264
+ config.schema = schemaResult.data;
265
+ }
266
+ else if (raw.schema !== undefined) {
267
+ console.warn(`Invalid 'schema' field in config (must be non-empty string)`);
268
+ }
269
+ // Parse context field with size limit
270
+ if (raw.context !== undefined) {
271
+ const contextField = z.string();
272
+ const contextResult = contextField.safeParse(raw.context);
273
+ if (contextResult.success) {
274
+ const contextSize = Buffer.byteLength(contextResult.data, 'utf-8');
275
+ if (contextSize > MAX_CONTEXT_SIZE) {
276
+ console.warn(`Context too large (${(contextSize / 1024).toFixed(1)}KB, limit: ${MAX_CONTEXT_SIZE / 1024}KB)`);
277
+ console.warn(`Ignoring context field`);
278
+ }
279
+ else {
280
+ config.context = contextResult.data;
281
+ }
282
+ }
283
+ else {
284
+ console.warn(`Invalid 'context' field in config (must be string)`);
285
+ }
286
+ }
287
+ // Parse rules field using Zod
288
+ if (raw.rules !== undefined) {
289
+ const rulesField = z.record(z.string(), z.array(z.string()));
290
+ // First check if it's an object structure (guard against null since typeof null === 'object')
291
+ if (typeof raw.rules === 'object' && raw.rules !== null && !Array.isArray(raw.rules)) {
292
+ // Artifact ids are intentionally not restricted to the built-in naming
293
+ // convention, so keys such as "constructor" remain valid for custom
294
+ // schemas. A null-prototype map preserves those keys as data without
295
+ // letting "__proto__" mutate the lookup object's prototype.
296
+ const parsedRules = Object.create(null);
297
+ let hasValidRules = false;
298
+ for (const [artifactId, rules] of Object.entries(raw.rules)) {
299
+ const rulesArrayResult = z.array(z.string()).safeParse(rules);
300
+ if (rulesArrayResult.success) {
301
+ // Filter out empty strings
302
+ const validRules = rulesArrayResult.data.filter((r) => r.length > 0);
303
+ if (validRules.length > 0) {
304
+ parsedRules[artifactId] = validRules;
305
+ hasValidRules = true;
306
+ }
307
+ if (validRules.length < rulesArrayResult.data.length) {
308
+ console.warn(`Some rules for '${artifactId}' are empty strings, ignoring them`);
309
+ }
310
+ }
311
+ else {
312
+ console.warn(describeRulesShapeError(artifactId, rules));
313
+ }
314
+ }
315
+ if (hasValidRules) {
316
+ config.rules = parsedRules;
317
+ }
318
+ }
319
+ else {
320
+ console.warn(`Invalid 'rules' field in config (must be object)`);
321
+ }
322
+ }
323
+ const operations = parseOperations(raw.operations);
324
+ if (operations) {
325
+ config.operations = operations;
326
+ }
327
+ const references = parseDeclarationList(raw.references);
328
+ if (references) {
329
+ config.references = references;
330
+ }
331
+ // Parse store pointer field: a string, or dropped with a warning.
332
+ // (Root resolution does NOT use this parse — it uses readStorePointer
333
+ // below, which errors on malformed pointers instead of dropping.)
334
+ if (raw.store !== undefined) {
335
+ if (typeof raw.store === 'string') {
336
+ config.store = raw.store;
337
+ }
338
+ else {
339
+ console.warn(`Warning: ignoring invalid store: field in ${configPathForWarnings(projectRoot)} (must be a single store id string).`);
340
+ }
341
+ }
342
+ // Parse githubCopilot preferences (only cloudAgent is recognized today).
343
+ if (raw.githubCopilot !== undefined) {
344
+ if (typeof raw.githubCopilot === 'object' &&
345
+ raw.githubCopilot !== null &&
346
+ !Array.isArray(raw.githubCopilot)) {
347
+ const cloudAgent = raw.githubCopilot.cloudAgent;
348
+ if (typeof cloudAgent === 'boolean') {
349
+ config.githubCopilot = { cloudAgent };
350
+ }
351
+ else if (cloudAgent !== undefined) {
352
+ console.warn(`Invalid 'githubCopilot.cloudAgent' field in config (must be a boolean)`);
353
+ }
354
+ }
355
+ else {
356
+ console.warn(`Invalid 'githubCopilot' field in config (must be an object)`);
357
+ }
358
+ }
359
+ // Return partial config even if some fields failed
360
+ return Object.keys(config).length > 0 ? config : null;
361
+ }
362
+ catch (error) {
363
+ console.warn(`Warning: could not parse ${configPathForWarnings(projectRoot)} (${error instanceof Error ? error.message.split('\n')[0] : String(error)}); ignoring it.`);
364
+ return null;
365
+ }
366
+ }
367
+ function configPathForWarnings(projectRoot) {
368
+ return resolveConfigFilePath(projectRoot) ?? path.join(projectRoot, 'freespec', 'config.yaml');
369
+ }
370
+ /**
371
+ * Validate artifact IDs in rules against the artifacts of every available
372
+ * schema. The `rules:` map is global, but each change can use a different
373
+ * schema, so a key is only unknown when it matches no artifact in ANY schema.
374
+ * Returns warnings for keys that are unknown everywhere.
375
+ *
376
+ * @param rules - The rules object from config
377
+ * @param validArtifactIds - Set of valid artifact IDs across all schemas
378
+ * @returns Array of warning messages for unknown artifact IDs
379
+ */
380
+ export function validateConfigRules(rules, validArtifactIds) {
381
+ const warnings = [];
382
+ for (const artifactId of Object.keys(rules)) {
383
+ if (!validArtifactIds.has(artifactId)) {
384
+ const validIds = Array.from(validArtifactIds).sort().join(', ');
385
+ warnings.push(`Unknown artifact ID in rules: "${artifactId}". ` +
386
+ `It matches no artifact in any available schema. Known artifact IDs: ${validIds}`);
387
+ }
388
+ }
389
+ return warnings;
390
+ }
391
+ /**
392
+ * Suggest valid schema names when user provides invalid schema.
393
+ * Uses fuzzy matching to find similar names.
394
+ *
395
+ * @param invalidSchemaName - The invalid schema name from config
396
+ * @param availableSchemas - List of available schemas with their type (built-in or project-local)
397
+ * @returns Error message with suggestions and available schemas
398
+ */
399
+ export function suggestSchemas(invalidSchemaName, availableSchemas) {
400
+ // Simple fuzzy match: Levenshtein distance
401
+ function levenshtein(a, b) {
402
+ const matrix = [];
403
+ for (let i = 0; i <= b.length; i++) {
404
+ matrix[i] = [i];
405
+ }
406
+ for (let j = 0; j <= a.length; j++) {
407
+ matrix[0][j] = j;
408
+ }
409
+ for (let i = 1; i <= b.length; i++) {
410
+ for (let j = 1; j <= a.length; j++) {
411
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
412
+ matrix[i][j] = matrix[i - 1][j - 1];
413
+ }
414
+ else {
415
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
416
+ }
417
+ }
418
+ }
419
+ return matrix[b.length][a.length];
420
+ }
421
+ // Find closest matches (distance <= 3)
422
+ const suggestions = availableSchemas
423
+ .map((s) => ({ ...s, distance: levenshtein(invalidSchemaName, s.name) }))
424
+ .filter((s) => s.distance <= 3)
425
+ .sort((a, b) => a.distance - b.distance)
426
+ .slice(0, 3);
427
+ const builtIn = availableSchemas.filter((s) => s.isBuiltIn).map((s) => s.name);
428
+ const projectLocal = availableSchemas.filter((s) => !s.isBuiltIn).map((s) => s.name);
429
+ let message = `Schema '${invalidSchemaName}' not found in freespec/config.yaml\n\n`;
430
+ if (suggestions.length > 0) {
431
+ message += `Did you mean one of these?\n`;
432
+ suggestions.forEach((s) => {
433
+ const type = s.isBuiltIn ? 'built-in' : 'project-local';
434
+ message += ` - ${s.name} (${type})\n`;
435
+ });
436
+ message += '\n';
437
+ }
438
+ message += `Available schemas:\n`;
439
+ if (builtIn.length > 0) {
440
+ message += ` Built-in: ${builtIn.join(', ')}\n`;
441
+ }
442
+ if (projectLocal.length > 0) {
443
+ message += ` Project-local: ${projectLocal.join(', ')}\n`;
444
+ }
445
+ else {
446
+ message += ` Project-local: (none found)\n`;
447
+ }
448
+ message += `\nFix: Edit freespec/config.yaml and change 'schema: ${invalidSchemaName}' to a valid schema name`;
449
+ return message;
450
+ }
451
+ /**
452
+ * Warning-silent targeted read of the `store:` pointer. Used by root
453
+ * resolution (which must not re-emit the resilient parser's field
454
+ * warnings) and by `freespec init`'s pointer guard. Unlike
455
+ * `readProjectConfig`, a malformed value is REPORTED, not dropped —
456
+ * a dropped pointer would silently flip where work lands.
457
+ */
458
+ export function readStorePointer(projectRoot) {
459
+ const configPath = resolveConfigFilePath(projectRoot);
460
+ if (configPath === null) {
461
+ return { filePath: null };
462
+ }
463
+ try {
464
+ const raw = parseYaml(readFileSync(configPath, 'utf-8'));
465
+ // Empty, comments-only, or non-mapping configs carry no pointer;
466
+ // they are imperfect, not malformed (readProjectConfig owns the
467
+ // field warnings for those).
468
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
469
+ return { filePath: configPath };
470
+ }
471
+ const value = raw.store;
472
+ if (value === undefined) {
473
+ return { filePath: configPath };
474
+ }
475
+ if (typeof value === 'string') {
476
+ return { value, filePath: configPath };
477
+ }
478
+ return { malformed: 'non_string', filePath: configPath };
479
+ }
480
+ catch {
481
+ return { malformed: 'unparseable', filePath: configPath };
482
+ }
483
+ }
484
+ /** Shared .yaml/.yml probe used by readProjectConfig and readStorePointer. */
485
+ export function resolveConfigFilePath(projectRoot) {
486
+ const yamlPath = path.join(projectRoot, 'freespec', 'config.yaml');
487
+ if (existsSync(yamlPath)) {
488
+ return yamlPath;
489
+ }
490
+ const ymlPath = path.join(projectRoot, 'freespec', 'config.yml');
491
+ return existsSync(ymlPath) ? ymlPath : null;
492
+ }
493
+ /** Human rendering of a malformed pointer reason, shared by every surface. */
494
+ export function storePointerProblem(reason) {
495
+ return reason === 'unparseable'
496
+ ? 'the config file could not be read as YAML'
497
+ : 'the store key must be a single store id string';
498
+ }
499
+ /**
500
+ * One classification for "real root vs config-only pointer dir", shared
501
+ * by root resolution and the init pointer guard so they can never
502
+ * disagree (slice 3.2).
503
+ *
504
+ * A bare `freespec/` directory is not a planning root. The qualifying walk
505
+ * also accepts a config file, so a fresh init is still a root.
506
+ */
507
+ export function classifyFreeSpecDir(projectRoot) {
508
+ const freespecDir = path.join(projectRoot, 'freespec');
509
+ return { hasPlanningShape: hasDirectChangeFolder(freespecDir), pointer: readStorePointer(projectRoot) };
510
+ }
511
+ function hasDirectChangeFolder(freespecDir) {
512
+ try {
513
+ return readdirSync(freespecDir, { withFileTypes: true }).some((entry) => entry.isDirectory() && isChangeDirectoryName(entry.name));
514
+ }
515
+ catch {
516
+ return false;
517
+ }
518
+ }
519
+ //# sourceMappingURL=project-config.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Strings rendered into agent guidance. One line in, one line out: control
3
+ * characters and newlines must never let hostile content forge instruction lines.
4
+ */
5
+ export declare function sanitizeInline(value: string, maxLength?: number): string;
6
+ /** Neutralize the envelope's own tags in repo-supplied text. */
7
+ export declare function escapeEnvelopeTags(value: string): string;
8
+ /** Escape attribute metacharacters so a quote cannot close the attribute. */
9
+ export declare function escapeEnvelopeAttribute(value: string): string;
10
+ //# sourceMappingURL=references.d.ts.map