spec-gen-cli 1.0.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 (303) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1078 -0
  3. package/dist/api/analyze.d.ts +17 -0
  4. package/dist/api/analyze.d.ts.map +1 -0
  5. package/dist/api/analyze.js +109 -0
  6. package/dist/api/analyze.js.map +1 -0
  7. package/dist/api/drift.d.ts +21 -0
  8. package/dist/api/drift.d.ts.map +1 -0
  9. package/dist/api/drift.js +145 -0
  10. package/dist/api/drift.js.map +1 -0
  11. package/dist/api/generate.d.ts +18 -0
  12. package/dist/api/generate.d.ts.map +1 -0
  13. package/dist/api/generate.js +251 -0
  14. package/dist/api/generate.js.map +1 -0
  15. package/dist/api/index.d.ts +39 -0
  16. package/dist/api/index.d.ts.map +1 -0
  17. package/dist/api/index.js +32 -0
  18. package/dist/api/index.js.map +1 -0
  19. package/dist/api/init.d.ts +18 -0
  20. package/dist/api/init.d.ts.map +1 -0
  21. package/dist/api/init.js +82 -0
  22. package/dist/api/init.js.map +1 -0
  23. package/dist/api/run.d.ts +19 -0
  24. package/dist/api/run.d.ts.map +1 -0
  25. package/dist/api/run.js +291 -0
  26. package/dist/api/run.js.map +1 -0
  27. package/dist/api/specs.d.ts +49 -0
  28. package/dist/api/specs.d.ts.map +1 -0
  29. package/dist/api/specs.js +136 -0
  30. package/dist/api/specs.js.map +1 -0
  31. package/dist/api/types.d.ts +176 -0
  32. package/dist/api/types.d.ts.map +1 -0
  33. package/dist/api/types.js +9 -0
  34. package/dist/api/types.js.map +1 -0
  35. package/dist/api/verify.d.ts +20 -0
  36. package/dist/api/verify.d.ts.map +1 -0
  37. package/dist/api/verify.js +117 -0
  38. package/dist/api/verify.js.map +1 -0
  39. package/dist/cli/commands/analyze.d.ts +27 -0
  40. package/dist/cli/commands/analyze.d.ts.map +1 -0
  41. package/dist/cli/commands/analyze.js +485 -0
  42. package/dist/cli/commands/analyze.js.map +1 -0
  43. package/dist/cli/commands/drift.d.ts +9 -0
  44. package/dist/cli/commands/drift.d.ts.map +1 -0
  45. package/dist/cli/commands/drift.js +540 -0
  46. package/dist/cli/commands/drift.js.map +1 -0
  47. package/dist/cli/commands/generate.d.ts +9 -0
  48. package/dist/cli/commands/generate.d.ts.map +1 -0
  49. package/dist/cli/commands/generate.js +633 -0
  50. package/dist/cli/commands/generate.js.map +1 -0
  51. package/dist/cli/commands/init.d.ts +9 -0
  52. package/dist/cli/commands/init.d.ts.map +1 -0
  53. package/dist/cli/commands/init.js +171 -0
  54. package/dist/cli/commands/init.js.map +1 -0
  55. package/dist/cli/commands/mcp.d.ts +638 -0
  56. package/dist/cli/commands/mcp.d.ts.map +1 -0
  57. package/dist/cli/commands/mcp.js +574 -0
  58. package/dist/cli/commands/mcp.js.map +1 -0
  59. package/dist/cli/commands/run.d.ts +24 -0
  60. package/dist/cli/commands/run.d.ts.map +1 -0
  61. package/dist/cli/commands/run.js +546 -0
  62. package/dist/cli/commands/run.js.map +1 -0
  63. package/dist/cli/commands/verify.d.ts +9 -0
  64. package/dist/cli/commands/verify.d.ts.map +1 -0
  65. package/dist/cli/commands/verify.js +417 -0
  66. package/dist/cli/commands/verify.js.map +1 -0
  67. package/dist/cli/commands/view.d.ts +9 -0
  68. package/dist/cli/commands/view.d.ts.map +1 -0
  69. package/dist/cli/commands/view.js +511 -0
  70. package/dist/cli/commands/view.js.map +1 -0
  71. package/dist/cli/index.d.ts +9 -0
  72. package/dist/cli/index.d.ts.map +1 -0
  73. package/dist/cli/index.js +83 -0
  74. package/dist/cli/index.js.map +1 -0
  75. package/dist/core/analyzer/architecture-writer.d.ts +67 -0
  76. package/dist/core/analyzer/architecture-writer.d.ts.map +1 -0
  77. package/dist/core/analyzer/architecture-writer.js +209 -0
  78. package/dist/core/analyzer/architecture-writer.js.map +1 -0
  79. package/dist/core/analyzer/artifact-generator.d.ts +222 -0
  80. package/dist/core/analyzer/artifact-generator.d.ts.map +1 -0
  81. package/dist/core/analyzer/artifact-generator.js +726 -0
  82. package/dist/core/analyzer/artifact-generator.js.map +1 -0
  83. package/dist/core/analyzer/call-graph.d.ts +83 -0
  84. package/dist/core/analyzer/call-graph.d.ts.map +1 -0
  85. package/dist/core/analyzer/call-graph.js +827 -0
  86. package/dist/core/analyzer/call-graph.js.map +1 -0
  87. package/dist/core/analyzer/code-shaper.d.ts +33 -0
  88. package/dist/core/analyzer/code-shaper.d.ts.map +1 -0
  89. package/dist/core/analyzer/code-shaper.js +149 -0
  90. package/dist/core/analyzer/code-shaper.js.map +1 -0
  91. package/dist/core/analyzer/dependency-graph.d.ts +179 -0
  92. package/dist/core/analyzer/dependency-graph.d.ts.map +1 -0
  93. package/dist/core/analyzer/dependency-graph.js +574 -0
  94. package/dist/core/analyzer/dependency-graph.js.map +1 -0
  95. package/dist/core/analyzer/duplicate-detector.d.ts +52 -0
  96. package/dist/core/analyzer/duplicate-detector.d.ts.map +1 -0
  97. package/dist/core/analyzer/duplicate-detector.js +279 -0
  98. package/dist/core/analyzer/duplicate-detector.js.map +1 -0
  99. package/dist/core/analyzer/embedding-service.d.ts +50 -0
  100. package/dist/core/analyzer/embedding-service.d.ts.map +1 -0
  101. package/dist/core/analyzer/embedding-service.js +104 -0
  102. package/dist/core/analyzer/embedding-service.js.map +1 -0
  103. package/dist/core/analyzer/file-walker.d.ts +78 -0
  104. package/dist/core/analyzer/file-walker.d.ts.map +1 -0
  105. package/dist/core/analyzer/file-walker.js +531 -0
  106. package/dist/core/analyzer/file-walker.js.map +1 -0
  107. package/dist/core/analyzer/import-parser.d.ts +91 -0
  108. package/dist/core/analyzer/import-parser.d.ts.map +1 -0
  109. package/dist/core/analyzer/import-parser.js +720 -0
  110. package/dist/core/analyzer/import-parser.js.map +1 -0
  111. package/dist/core/analyzer/index.d.ts +10 -0
  112. package/dist/core/analyzer/index.d.ts.map +1 -0
  113. package/dist/core/analyzer/index.js +10 -0
  114. package/dist/core/analyzer/index.js.map +1 -0
  115. package/dist/core/analyzer/refactor-analyzer.d.ts +80 -0
  116. package/dist/core/analyzer/refactor-analyzer.d.ts.map +1 -0
  117. package/dist/core/analyzer/refactor-analyzer.js +339 -0
  118. package/dist/core/analyzer/refactor-analyzer.js.map +1 -0
  119. package/dist/core/analyzer/repository-mapper.d.ts +150 -0
  120. package/dist/core/analyzer/repository-mapper.d.ts.map +1 -0
  121. package/dist/core/analyzer/repository-mapper.js +731 -0
  122. package/dist/core/analyzer/repository-mapper.js.map +1 -0
  123. package/dist/core/analyzer/signature-extractor.d.ts +31 -0
  124. package/dist/core/analyzer/signature-extractor.d.ts.map +1 -0
  125. package/dist/core/analyzer/signature-extractor.js +387 -0
  126. package/dist/core/analyzer/signature-extractor.js.map +1 -0
  127. package/dist/core/analyzer/significance-scorer.d.ts +79 -0
  128. package/dist/core/analyzer/significance-scorer.d.ts.map +1 -0
  129. package/dist/core/analyzer/significance-scorer.js +407 -0
  130. package/dist/core/analyzer/significance-scorer.js.map +1 -0
  131. package/dist/core/analyzer/subgraph-extractor.d.ts +43 -0
  132. package/dist/core/analyzer/subgraph-extractor.d.ts.map +1 -0
  133. package/dist/core/analyzer/subgraph-extractor.js +129 -0
  134. package/dist/core/analyzer/subgraph-extractor.js.map +1 -0
  135. package/dist/core/analyzer/vector-index.d.ts +63 -0
  136. package/dist/core/analyzer/vector-index.d.ts.map +1 -0
  137. package/dist/core/analyzer/vector-index.js +169 -0
  138. package/dist/core/analyzer/vector-index.js.map +1 -0
  139. package/dist/core/drift/drift-detector.d.ts +102 -0
  140. package/dist/core/drift/drift-detector.d.ts.map +1 -0
  141. package/dist/core/drift/drift-detector.js +597 -0
  142. package/dist/core/drift/drift-detector.js.map +1 -0
  143. package/dist/core/drift/git-diff.d.ts +55 -0
  144. package/dist/core/drift/git-diff.d.ts.map +1 -0
  145. package/dist/core/drift/git-diff.js +356 -0
  146. package/dist/core/drift/git-diff.js.map +1 -0
  147. package/dist/core/drift/index.d.ts +12 -0
  148. package/dist/core/drift/index.d.ts.map +1 -0
  149. package/dist/core/drift/index.js +9 -0
  150. package/dist/core/drift/index.js.map +1 -0
  151. package/dist/core/drift/spec-mapper.d.ts +73 -0
  152. package/dist/core/drift/spec-mapper.d.ts.map +1 -0
  153. package/dist/core/drift/spec-mapper.js +353 -0
  154. package/dist/core/drift/spec-mapper.js.map +1 -0
  155. package/dist/core/generator/adr-generator.d.ts +32 -0
  156. package/dist/core/generator/adr-generator.d.ts.map +1 -0
  157. package/dist/core/generator/adr-generator.js +192 -0
  158. package/dist/core/generator/adr-generator.js.map +1 -0
  159. package/dist/core/generator/index.d.ts +9 -0
  160. package/dist/core/generator/index.d.ts.map +1 -0
  161. package/dist/core/generator/index.js +12 -0
  162. package/dist/core/generator/index.js.map +1 -0
  163. package/dist/core/generator/mapping-generator.d.ts +54 -0
  164. package/dist/core/generator/mapping-generator.d.ts.map +1 -0
  165. package/dist/core/generator/mapping-generator.js +239 -0
  166. package/dist/core/generator/mapping-generator.js.map +1 -0
  167. package/dist/core/generator/openspec-compat.d.ts +160 -0
  168. package/dist/core/generator/openspec-compat.d.ts.map +1 -0
  169. package/dist/core/generator/openspec-compat.js +523 -0
  170. package/dist/core/generator/openspec-compat.js.map +1 -0
  171. package/dist/core/generator/openspec-format-generator.d.ts +111 -0
  172. package/dist/core/generator/openspec-format-generator.d.ts.map +1 -0
  173. package/dist/core/generator/openspec-format-generator.js +817 -0
  174. package/dist/core/generator/openspec-format-generator.js.map +1 -0
  175. package/dist/core/generator/openspec-writer.d.ts +131 -0
  176. package/dist/core/generator/openspec-writer.d.ts.map +1 -0
  177. package/dist/core/generator/openspec-writer.js +379 -0
  178. package/dist/core/generator/openspec-writer.js.map +1 -0
  179. package/dist/core/generator/prompts.d.ts +35 -0
  180. package/dist/core/generator/prompts.d.ts.map +1 -0
  181. package/dist/core/generator/prompts.js +212 -0
  182. package/dist/core/generator/prompts.js.map +1 -0
  183. package/dist/core/generator/spec-pipeline.d.ts +94 -0
  184. package/dist/core/generator/spec-pipeline.d.ts.map +1 -0
  185. package/dist/core/generator/spec-pipeline.js +474 -0
  186. package/dist/core/generator/spec-pipeline.js.map +1 -0
  187. package/dist/core/generator/stages/stage1-survey.d.ts +19 -0
  188. package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -0
  189. package/dist/core/generator/stages/stage1-survey.js +105 -0
  190. package/dist/core/generator/stages/stage1-survey.js.map +1 -0
  191. package/dist/core/generator/stages/stage2-entities.d.ts +11 -0
  192. package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -0
  193. package/dist/core/generator/stages/stage2-entities.js +67 -0
  194. package/dist/core/generator/stages/stage2-entities.js.map +1 -0
  195. package/dist/core/generator/stages/stage3-services.d.ts +11 -0
  196. package/dist/core/generator/stages/stage3-services.d.ts.map +1 -0
  197. package/dist/core/generator/stages/stage3-services.js +75 -0
  198. package/dist/core/generator/stages/stage3-services.js.map +1 -0
  199. package/dist/core/generator/stages/stage4-api.d.ts +11 -0
  200. package/dist/core/generator/stages/stage4-api.d.ts.map +1 -0
  201. package/dist/core/generator/stages/stage4-api.js +65 -0
  202. package/dist/core/generator/stages/stage4-api.js.map +1 -0
  203. package/dist/core/generator/stages/stage5-architecture.d.ts +10 -0
  204. package/dist/core/generator/stages/stage5-architecture.d.ts.map +1 -0
  205. package/dist/core/generator/stages/stage5-architecture.js +62 -0
  206. package/dist/core/generator/stages/stage5-architecture.js.map +1 -0
  207. package/dist/core/generator/stages/stage6-adr.d.ts +8 -0
  208. package/dist/core/generator/stages/stage6-adr.d.ts.map +1 -0
  209. package/dist/core/generator/stages/stage6-adr.js +41 -0
  210. package/dist/core/generator/stages/stage6-adr.js.map +1 -0
  211. package/dist/core/services/chat-agent.d.ts +45 -0
  212. package/dist/core/services/chat-agent.d.ts.map +1 -0
  213. package/dist/core/services/chat-agent.js +310 -0
  214. package/dist/core/services/chat-agent.js.map +1 -0
  215. package/dist/core/services/chat-tools.d.ts +32 -0
  216. package/dist/core/services/chat-tools.d.ts.map +1 -0
  217. package/dist/core/services/chat-tools.js +270 -0
  218. package/dist/core/services/chat-tools.js.map +1 -0
  219. package/dist/core/services/config-manager.d.ts +61 -0
  220. package/dist/core/services/config-manager.d.ts.map +1 -0
  221. package/dist/core/services/config-manager.js +143 -0
  222. package/dist/core/services/config-manager.js.map +1 -0
  223. package/dist/core/services/gitignore-manager.d.ts +29 -0
  224. package/dist/core/services/gitignore-manager.d.ts.map +1 -0
  225. package/dist/core/services/gitignore-manager.js +106 -0
  226. package/dist/core/services/gitignore-manager.js.map +1 -0
  227. package/dist/core/services/index.d.ts +8 -0
  228. package/dist/core/services/index.d.ts.map +1 -0
  229. package/dist/core/services/index.js +8 -0
  230. package/dist/core/services/index.js.map +1 -0
  231. package/dist/core/services/llm-service.d.ts +336 -0
  232. package/dist/core/services/llm-service.d.ts.map +1 -0
  233. package/dist/core/services/llm-service.js +1155 -0
  234. package/dist/core/services/llm-service.js.map +1 -0
  235. package/dist/core/services/mcp-handlers/analysis.d.ts +42 -0
  236. package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -0
  237. package/dist/core/services/mcp-handlers/analysis.js +300 -0
  238. package/dist/core/services/mcp-handlers/analysis.js.map +1 -0
  239. package/dist/core/services/mcp-handlers/graph.d.ts +65 -0
  240. package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -0
  241. package/dist/core/services/mcp-handlers/graph.js +509 -0
  242. package/dist/core/services/mcp-handlers/graph.js.map +1 -0
  243. package/dist/core/services/mcp-handlers/semantic.d.ts +38 -0
  244. package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -0
  245. package/dist/core/services/mcp-handlers/semantic.js +172 -0
  246. package/dist/core/services/mcp-handlers/semantic.js.map +1 -0
  247. package/dist/core/services/mcp-handlers/utils.d.ts +21 -0
  248. package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -0
  249. package/dist/core/services/mcp-handlers/utils.js +62 -0
  250. package/dist/core/services/mcp-handlers/utils.js.map +1 -0
  251. package/dist/core/services/project-detector.d.ts +32 -0
  252. package/dist/core/services/project-detector.d.ts.map +1 -0
  253. package/dist/core/services/project-detector.js +111 -0
  254. package/dist/core/services/project-detector.js.map +1 -0
  255. package/dist/core/verifier/index.d.ts +5 -0
  256. package/dist/core/verifier/index.d.ts.map +1 -0
  257. package/dist/core/verifier/index.js +5 -0
  258. package/dist/core/verifier/index.js.map +1 -0
  259. package/dist/core/verifier/verification-engine.d.ts +226 -0
  260. package/dist/core/verifier/verification-engine.d.ts.map +1 -0
  261. package/dist/core/verifier/verification-engine.js +681 -0
  262. package/dist/core/verifier/verification-engine.js.map +1 -0
  263. package/dist/types/index.d.ts +252 -0
  264. package/dist/types/index.d.ts.map +1 -0
  265. package/dist/types/index.js +5 -0
  266. package/dist/types/index.js.map +1 -0
  267. package/dist/types/pipeline.d.ts +148 -0
  268. package/dist/types/pipeline.d.ts.map +1 -0
  269. package/dist/types/pipeline.js +5 -0
  270. package/dist/types/pipeline.js.map +1 -0
  271. package/dist/utils/errors.d.ts +51 -0
  272. package/dist/utils/errors.d.ts.map +1 -0
  273. package/dist/utils/errors.js +128 -0
  274. package/dist/utils/errors.js.map +1 -0
  275. package/dist/utils/logger.d.ts +149 -0
  276. package/dist/utils/logger.d.ts.map +1 -0
  277. package/dist/utils/logger.js +331 -0
  278. package/dist/utils/logger.js.map +1 -0
  279. package/dist/utils/progress.d.ts +142 -0
  280. package/dist/utils/progress.d.ts.map +1 -0
  281. package/dist/utils/progress.js +280 -0
  282. package/dist/utils/progress.js.map +1 -0
  283. package/dist/utils/prompts.d.ts +53 -0
  284. package/dist/utils/prompts.d.ts.map +1 -0
  285. package/dist/utils/prompts.js +199 -0
  286. package/dist/utils/prompts.js.map +1 -0
  287. package/dist/utils/shutdown.d.ts +89 -0
  288. package/dist/utils/shutdown.d.ts.map +1 -0
  289. package/dist/utils/shutdown.js +237 -0
  290. package/dist/utils/shutdown.js.map +1 -0
  291. package/package.json +114 -0
  292. package/src/viewer/InteractiveGraphViewer.jsx +1486 -0
  293. package/src/viewer/app/index.html +17 -0
  294. package/src/viewer/app/main.jsx +13 -0
  295. package/src/viewer/components/ArchitectureView.jsx +177 -0
  296. package/src/viewer/components/ChatPanel.jsx +448 -0
  297. package/src/viewer/components/ClusterGraph.jsx +441 -0
  298. package/src/viewer/components/FilterBar.jsx +179 -0
  299. package/src/viewer/components/FlatGraph.jsx +275 -0
  300. package/src/viewer/components/MicroComponents.jsx +83 -0
  301. package/src/viewer/hooks/usePanZoom.js +79 -0
  302. package/src/viewer/utils/constants.js +47 -0
  303. package/src/viewer/utils/graph-helpers.js +291 -0
@@ -0,0 +1,817 @@
1
+ /**
2
+ * OpenSpec Format Generator
3
+ *
4
+ * Takes structured LLM outputs and formats them into clean OpenSpec-compatible
5
+ * specification files.
6
+ */
7
+ // ============================================================================
8
+ // CONSTANTS
9
+ // ============================================================================
10
+ const DEFAULT_OPTIONS = {
11
+ version: '1.0.0',
12
+ style: 'detailed',
13
+ includeConfidence: true,
14
+ includeTechnicalNotes: true,
15
+ maxLineWidth: 100,
16
+ };
17
+ // ============================================================================
18
+ // OPENSPEC FORMAT GENERATOR
19
+ // ============================================================================
20
+ /**
21
+ * OpenSpec Format Generator
22
+ */
23
+ export class OpenSpecFormatGenerator {
24
+ options;
25
+ constructor(options = {}) {
26
+ this.options = { ...DEFAULT_OPTIONS, ...options };
27
+ }
28
+ /**
29
+ * Generate all spec files from pipeline result
30
+ */
31
+ generateSpecs(result) {
32
+ const specs = [];
33
+ const domains = this.groupByDomain(result);
34
+ // 1. Overview spec
35
+ specs.push(this.generateOverviewSpec(result.survey, domains, result.architecture));
36
+ // 2. Domain specs
37
+ for (const domain of domains) {
38
+ specs.push(this.generateDomainSpec(domain, result.survey));
39
+ }
40
+ // 3. Architecture spec
41
+ specs.push(this.generateArchitectureSpec(result.architecture, result.survey, domains));
42
+ // 4. API spec (if endpoints exist)
43
+ if (result.endpoints.length > 0) {
44
+ specs.push(this.generateApiSpec(result.endpoints, result.survey));
45
+ }
46
+ return specs;
47
+ }
48
+ /**
49
+ * Group entities, services, and endpoints by domain
50
+ */
51
+ groupByDomain(result) {
52
+ const domainMap = new Map();
53
+ // Initialize domains from survey suggestions
54
+ for (const domainName of result.survey.suggestedDomains) {
55
+ domainMap.set(domainName.toLowerCase(), {
56
+ name: domainName,
57
+ description: '',
58
+ entities: [],
59
+ services: [],
60
+ endpoints: [],
61
+ files: [],
62
+ });
63
+ }
64
+ // Add entities to domains
65
+ for (const entity of result.entities) {
66
+ const domainName = this.inferDomain(entity.name, entity.location, result.survey.suggestedDomains);
67
+ let domain = domainMap.get(domainName.toLowerCase());
68
+ if (!domain) {
69
+ domain = {
70
+ name: domainName,
71
+ description: '',
72
+ entities: [],
73
+ services: [],
74
+ endpoints: [],
75
+ files: [],
76
+ };
77
+ domainMap.set(domainName.toLowerCase(), domain);
78
+ }
79
+ domain.entities.push(entity);
80
+ if (entity.location && !domain.files.includes(entity.location)) {
81
+ domain.files.push(entity.location);
82
+ }
83
+ }
84
+ // Add services to domains
85
+ for (const service of result.services) {
86
+ const domainName = service.domain || this.inferDomain(service.name, '', result.survey.suggestedDomains);
87
+ let domain = domainMap.get(domainName.toLowerCase());
88
+ if (!domain) {
89
+ domain = {
90
+ name: domainName,
91
+ description: '',
92
+ entities: [],
93
+ services: [],
94
+ endpoints: [],
95
+ files: [],
96
+ };
97
+ domainMap.set(domainName.toLowerCase(), domain);
98
+ }
99
+ domain.services.push(service);
100
+ }
101
+ // Add endpoints to domains
102
+ for (const endpoint of result.endpoints) {
103
+ const domainName = endpoint.relatedEntity
104
+ ? this.inferDomain(endpoint.relatedEntity, endpoint.path, result.survey.suggestedDomains)
105
+ : 'api';
106
+ let domain = domainMap.get(domainName.toLowerCase());
107
+ if (!domain) {
108
+ domain = {
109
+ name: domainName,
110
+ description: '',
111
+ entities: [],
112
+ services: [],
113
+ endpoints: [],
114
+ files: [],
115
+ };
116
+ domainMap.set(domainName.toLowerCase(), domain);
117
+ }
118
+ domain.endpoints.push(endpoint);
119
+ }
120
+ // Set descriptions based on content — prefer service purpose (descriptive) over entity list
121
+ for (const domain of domainMap.values()) {
122
+ if (domain.services.length > 0) {
123
+ const representative = domain.services.find(s => s.name.toLowerCase().includes(domain.name.toLowerCase())) ?? domain.services[0];
124
+ domain.description = representative.purpose;
125
+ }
126
+ else if (domain.entities.length > 0) {
127
+ const preview = domain.entities.slice(0, 3).map(e => e.name).join(', ');
128
+ const extra = domain.entities.length > 3 ? ` and ${domain.entities.length - 3} more` : '';
129
+ domain.description = `Defines core data models: ${preview}${extra}.`;
130
+ }
131
+ else if (domain.endpoints.length > 0) {
132
+ const firstPurpose = domain.endpoints[0]?.purpose;
133
+ domain.description = firstPurpose
134
+ ? firstPurpose
135
+ : `Provides ${domain.endpoints.length} API endpoint${domain.endpoints.length > 1 ? 's' : ''}`;
136
+ }
137
+ }
138
+ // Filter out empty domains
139
+ return Array.from(domainMap.values()).filter(d => d.entities.length > 0 || d.services.length > 0 || d.endpoints.length > 0);
140
+ }
141
+ /**
142
+ * Infer domain from name and location
143
+ */
144
+ inferDomain(name, location, suggestedDomains) {
145
+ const nameLower = (name ?? '').toLowerCase();
146
+ const locationLower = (location ?? '').toLowerCase();
147
+ // Check suggested domains first
148
+ for (const domain of suggestedDomains) {
149
+ if (nameLower.includes(domain.toLowerCase()) || locationLower.includes(domain.toLowerCase())) {
150
+ return domain;
151
+ }
152
+ }
153
+ // Fall back to first suggested domain rather than inventing one from the name prefix
154
+ return suggestedDomains[0] ?? 'core';
155
+ }
156
+ /**
157
+ * Generate the overview spec
158
+ */
159
+ generateOverviewSpec(survey, domains, architecture) {
160
+ const lines = [];
161
+ const now = new Date();
162
+ const date = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
163
+ // Header
164
+ lines.push('# System Overview');
165
+ lines.push('');
166
+ lines.push(`> Generated by spec-gen v${this.options.version} on ${date}`);
167
+ if (this.options.includeConfidence) {
168
+ lines.push(`> Confidence: ${Math.round(survey.confidence * 100)}%`);
169
+ }
170
+ lines.push('');
171
+ // Purpose
172
+ lines.push('## Purpose');
173
+ lines.push('');
174
+ lines.push(this.wrapText(architecture.systemPurpose));
175
+ lines.push('');
176
+ // Domains
177
+ lines.push('## Domains');
178
+ lines.push('');
179
+ lines.push('This system is organized into the following domains:');
180
+ lines.push('');
181
+ lines.push('| Domain | Description | Spec |');
182
+ lines.push('|--------|-------------|------|');
183
+ for (const domain of domains) {
184
+ const specPath = `../${domain.name.toLowerCase()}/spec.md`;
185
+ lines.push(`| ${this.capitalize(domain.name)} | ${domain.description || 'No description'} | [spec.md](${specPath}) |`);
186
+ }
187
+ lines.push('');
188
+ // Technical Stack
189
+ lines.push('## Technical Stack');
190
+ lines.push('');
191
+ lines.push(`- **Type**: ${this.formatCategory(survey.projectCategory)}`);
192
+ lines.push(`- **Primary Language**: ${survey.primaryLanguage}`);
193
+ lines.push(`- **Key Frameworks**: ${survey.frameworks.join(', ') || 'None detected'}`);
194
+ lines.push(`- **Architecture**: ${this.formatArchitecture(survey.architecturePattern)}`);
195
+ lines.push('');
196
+ // Requirements
197
+ lines.push('## Requirements');
198
+ lines.push('');
199
+ // Generate capabilities from architecture
200
+ if (architecture.keyDecisions.length > 0) {
201
+ lines.push('### Requirement: SystemCapabilities');
202
+ lines.push('');
203
+ lines.push('The system SHALL provide the following capabilities:');
204
+ for (const decision of architecture.keyDecisions) {
205
+ lines.push(`- ${decision}`);
206
+ }
207
+ lines.push('');
208
+ lines.push('#### Scenario: CapabilitiesProvided');
209
+ lines.push('- **GIVEN** the system is operational');
210
+ lines.push('- **WHEN** a user interacts with the system');
211
+ lines.push('- **THEN** the system provides the documented capabilities');
212
+ lines.push('');
213
+ }
214
+ // Data flow as a scenario
215
+ if (architecture.dataFlow && architecture.dataFlow !== 'Unknown') {
216
+ lines.push('### Requirement: DataFlow');
217
+ lines.push('');
218
+ lines.push('The system SHALL process data through defined layers:');
219
+ lines.push('');
220
+ lines.push('#### Scenario: StandardDataFlow');
221
+ lines.push('- **GIVEN** an incoming request');
222
+ lines.push(`- **WHEN** the request is processed`);
223
+ lines.push(`- **THEN** data flows through: ${architecture.dataFlow}`);
224
+ lines.push('');
225
+ }
226
+ // Technical notes
227
+ if (this.options.includeTechnicalNotes) {
228
+ lines.push('## Technical Notes');
229
+ lines.push('');
230
+ const archStyleNote = typeof architecture.architectureStyle === 'string'
231
+ ? architecture.architectureStyle
232
+ : architecture.architectureStyle?.pattern ?? architecture.architectureStyle?.name ?? JSON.stringify(architecture.architectureStyle);
233
+ lines.push(`- **Architecture Style**: ${archStyleNote}`);
234
+ if (architecture.securityModel && architecture.securityModel !== 'Unknown') {
235
+ lines.push(`- **Security Model**: ${architecture.securityModel}`);
236
+ }
237
+ if (architecture.integrations.length > 0) {
238
+ const integrationNames = architecture.integrations.map(i => typeof i === 'string' ? i : i.name ?? JSON.stringify(i));
239
+ lines.push(`- **External Integrations**: ${integrationNames.join(', ')}`);
240
+ }
241
+ lines.push('');
242
+ }
243
+ return {
244
+ path: 'openspec/specs/overview/spec.md',
245
+ content: lines.join('\n'),
246
+ domain: 'overview',
247
+ type: 'overview',
248
+ };
249
+ }
250
+ /**
251
+ * Generate a domain spec
252
+ */
253
+ generateDomainSpec(domain, _survey) {
254
+ const lines = [];
255
+ const now = new Date();
256
+ const date = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
257
+ // Header
258
+ lines.push(`# ${this.capitalize(domain.name)} Specification`);
259
+ lines.push('');
260
+ lines.push(`> Generated by spec-gen v${this.options.version} on ${date}`);
261
+ if (domain.files.length > 0) {
262
+ lines.push(`> Source files: ${domain.files.join(', ')}`);
263
+ }
264
+ lines.push('');
265
+ // Purpose
266
+ lines.push('## Purpose');
267
+ lines.push('');
268
+ lines.push(this.wrapText(domain.description || `The ${domain.name} domain manages core business logic.`));
269
+ lines.push('');
270
+ // Entities section
271
+ if (domain.entities.length > 0) {
272
+ lines.push('## Entities');
273
+ lines.push('');
274
+ for (const entity of domain.entities) {
275
+ lines.push(`### ${entity.name}`);
276
+ lines.push('');
277
+ lines.push(this.wrapText(entity.description));
278
+ lines.push('');
279
+ // Properties table
280
+ if ((entity.properties ?? []).length > 0) {
281
+ lines.push('**Properties:**');
282
+ lines.push('');
283
+ lines.push('| Name | Type | Description |');
284
+ lines.push('|------|------|-------------|');
285
+ for (const prop of (entity.properties ?? [])) {
286
+ const desc = prop.description || (prop.required ? 'Required' : 'Optional');
287
+ lines.push(`| ${prop.name} | ${prop.type} | ${desc} |`);
288
+ }
289
+ lines.push('');
290
+ }
291
+ // Relationships
292
+ if ((entity.relationships ?? []).length > 0) {
293
+ lines.push('**Relationships:**');
294
+ lines.push('');
295
+ for (const rel of (entity.relationships ?? [])) {
296
+ lines.push(`- ${this.formatRelationship(rel)}`);
297
+ }
298
+ lines.push('');
299
+ }
300
+ }
301
+ }
302
+ // Requirements section
303
+ lines.push('## Requirements');
304
+ lines.push('');
305
+ // Entity validation requirements
306
+ for (const entity of domain.entities) {
307
+ if ((entity.validations ?? []).length > 0) {
308
+ lines.push(`### Requirement: ${entity.name}Validation`);
309
+ lines.push('');
310
+ lines.push(`The system SHALL validate ${entity.name} according to these rules:`);
311
+ for (const rule of (entity.validations ?? [])) {
312
+ lines.push(`- ${rule}`);
313
+ }
314
+ lines.push('');
315
+ // Scenarios from entity
316
+ const entityScenarios = entity.scenarios ?? [];
317
+ if (entityScenarios.length > 0) {
318
+ for (const scenario of entityScenarios) {
319
+ this.addScenario(lines, scenario);
320
+ }
321
+ }
322
+ else {
323
+ // Validator requires at least one scenario per requirement
324
+ lines.push(`#### Scenario: Valid${entity.name}Accepted`);
325
+ lines.push(`- **GIVEN** A valid ${entity.name} object with all required fields`);
326
+ lines.push(`- **WHEN** The object is validated`);
327
+ lines.push(`- **THEN** Validation passes with no errors`);
328
+ lines.push('');
329
+ }
330
+ }
331
+ }
332
+ // Service operation requirements
333
+ for (const service of domain.services) {
334
+ for (const operation of (service.operations ?? [])) {
335
+ lines.push(`### Requirement: ${this.formatRequirementName(operation.name)}`);
336
+ lines.push('');
337
+ const opDesc = (operation.description ?? '').replace(/^\s*(shall|must|should|may)\s+/i, '');
338
+ lines.push(`The system SHALL ${opDesc.toLowerCase()}`);
339
+ lines.push('');
340
+ // Operation scenarios
341
+ for (const scenario of (operation.scenarios ?? [])) {
342
+ this.addScenario(lines, scenario);
343
+ }
344
+ }
345
+ // Sub-components for orchestrator services (god functions)
346
+ if (service.subSpecs && service.subSpecs.length > 0) {
347
+ lines.push('');
348
+ lines.push('## Sub-components');
349
+ lines.push('');
350
+ lines.push(`> \`${service.name}\` is an orchestrator. Each sub-component below implements one logical block.`);
351
+ lines.push('');
352
+ for (const sub of service.subSpecs) {
353
+ lines.push(`### Sub-component: ${this.formatRequirementName(sub.name)}`);
354
+ lines.push('');
355
+ lines.push(`> Implements: \`${sub.callee}\``);
356
+ lines.push('');
357
+ lines.push(sub.purpose);
358
+ lines.push('');
359
+ for (const op of (sub.operations ?? [])) {
360
+ lines.push(`#### Requirement: ${this.formatRequirementName(op.name)}`);
361
+ lines.push('');
362
+ const opDesc = (op.description ?? '').replace(/^\s*(shall|must|should|may)\s+/i, '');
363
+ lines.push(`The system SHALL ${opDesc.toLowerCase()}`);
364
+ lines.push('');
365
+ for (const scenario of (op.scenarios ?? [])) {
366
+ this.addScenario(lines, scenario);
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+ // Fallback: if no requirements were generated, add a placeholder
373
+ const hasRequirements = domain.entities.some(e => (e.validations ?? []).length > 0) ||
374
+ domain.services.some(s => (s.operations ?? []).length > 0);
375
+ if (!hasRequirements) {
376
+ if (domain.endpoints.length > 0) {
377
+ for (const endpoint of domain.endpoints) {
378
+ const reqName = this.formatRequirementName(endpoint.purpose || `${endpoint.method}${endpoint.path}`);
379
+ lines.push(`### Requirement: ${reqName}`);
380
+ lines.push('');
381
+ const epPurpose = (endpoint.purpose ?? 'handle this endpoint').replace(/^\s*(shall|must|should|may)\s+/i, '');
382
+ lines.push(`The system SHALL ${epPurpose.toLowerCase()}`);
383
+ lines.push('');
384
+ lines.push(`#### Scenario: ${reqName}Success`);
385
+ lines.push(`- **GIVEN** the system is operational`);
386
+ lines.push(`- **WHEN** ${endpoint.method} ${endpoint.path} is called`);
387
+ lines.push(`- **THEN** the request is processed successfully`);
388
+ lines.push('');
389
+ }
390
+ }
391
+ else {
392
+ const reqName = this.formatRequirementName(`${domain.name}Overview`);
393
+ lines.push(`### Requirement: ${reqName}`);
394
+ lines.push('');
395
+ lines.push(`The ${domain.name} domain SHALL provide its documented functionality.`);
396
+ lines.push('');
397
+ lines.push(`#### Scenario: ${reqName}Works`);
398
+ lines.push('- **GIVEN** the system is operational');
399
+ lines.push('- **WHEN** the domain functionality is invoked');
400
+ lines.push('- **THEN** the expected outcome is produced');
401
+ lines.push('');
402
+ }
403
+ }
404
+ // Technical notes
405
+ if (this.options.includeTechnicalNotes && domain.services.length > 0) {
406
+ lines.push('## Technical Notes');
407
+ lines.push('');
408
+ const allFiles = new Set(domain.files);
409
+ const allDeps = new Set();
410
+ for (const service of domain.services) {
411
+ for (const dep of (service.dependencies ?? [])) {
412
+ allDeps.add(dep);
413
+ }
414
+ }
415
+ if (allFiles.size > 0) {
416
+ lines.push(`- **Implementation**: \`${Array.from(allFiles).join(', ')}\``);
417
+ }
418
+ if (allDeps.size > 0) {
419
+ lines.push(`- **Dependencies**: ${Array.from(allDeps).join(', ')}`);
420
+ }
421
+ lines.push('');
422
+ }
423
+ return {
424
+ path: `openspec/specs/${domain.name.toLowerCase()}/spec.md`,
425
+ content: lines.join('\n'),
426
+ domain: domain.name.toLowerCase(),
427
+ type: 'domain',
428
+ };
429
+ }
430
+ /**
431
+ * Generate the architecture spec
432
+ */
433
+ generateArchitectureSpec(architecture, _survey, _domains) {
434
+ const lines = [];
435
+ const now = new Date();
436
+ const date = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
437
+ // Header
438
+ lines.push('# Architecture Specification');
439
+ lines.push('');
440
+ lines.push(`> Generated by spec-gen v${this.options.version} on ${date}`);
441
+ lines.push('');
442
+ // Purpose
443
+ lines.push('## Purpose');
444
+ lines.push('');
445
+ lines.push('This document describes the architectural patterns and structure of the system.');
446
+ lines.push('');
447
+ // Architecture Style
448
+ lines.push('## Architecture Style');
449
+ lines.push('');
450
+ const archStyle = architecture.architectureStyle;
451
+ const archStyleStr = typeof archStyle === 'string'
452
+ ? archStyle
453
+ : archStyle?.pattern ?? archStyle?.name ?? JSON.stringify(archStyle);
454
+ const archJustification = typeof archStyle === 'object' && archStyle !== null
455
+ ? archStyle?.justification
456
+ : undefined;
457
+ lines.push(this.wrapText(archStyleStr));
458
+ if (archJustification) {
459
+ lines.push('');
460
+ lines.push(`*${archJustification}*`);
461
+ }
462
+ lines.push('');
463
+ // Requirements
464
+ lines.push('## Requirements');
465
+ lines.push('');
466
+ // Layered architecture requirement
467
+ if (architecture.layerMap.length > 0) {
468
+ lines.push('### Requirement: LayeredArchitecture');
469
+ lines.push('');
470
+ lines.push('The system SHALL maintain separation between:');
471
+ for (const layer of architecture.layerMap) {
472
+ lines.push(`- ${layer.name} (${layer.purpose})`);
473
+ }
474
+ lines.push('');
475
+ lines.push('#### Scenario: LayerSeparation');
476
+ lines.push('- **GIVEN** a request from the presentation layer');
477
+ lines.push('- **WHEN** business logic is needed');
478
+ lines.push('- **THEN** the presentation layer delegates to the business layer');
479
+ lines.push('- **AND** direct database access from presentation is prohibited');
480
+ lines.push('');
481
+ }
482
+ // Security requirement
483
+ if (architecture.securityModel && architecture.securityModel !== 'Unknown') {
484
+ lines.push('### Requirement: SecurityModel');
485
+ lines.push('');
486
+ lines.push(`The system SHALL implement security via: ${architecture.securityModel}`);
487
+ lines.push('');
488
+ lines.push('#### Scenario: AuthenticatedAccess');
489
+ lines.push('- **GIVEN** an unauthenticated request');
490
+ lines.push('- **WHEN** accessing protected resources');
491
+ lines.push('- **THEN** access is denied');
492
+ lines.push('');
493
+ }
494
+ // System Diagram (Mermaid)
495
+ lines.push('## System Diagram');
496
+ lines.push('');
497
+ lines.push('```mermaid');
498
+ lines.push('graph TB');
499
+ // Generate layer diagram
500
+ for (let i = 0; i < architecture.layerMap.length; i++) {
501
+ const layer = architecture.layerMap[i];
502
+ const layerId = layer.name.replace(/\s+/g, '');
503
+ lines.push(` ${layerId}[${layer.name}]`);
504
+ if (i < architecture.layerMap.length - 1) {
505
+ const nextLayerId = architecture.layerMap[i + 1].name.replace(/\s+/g, '');
506
+ lines.push(` ${layerId} --> ${nextLayerId}`);
507
+ }
508
+ }
509
+ lines.push('```');
510
+ lines.push('');
511
+ // Layer Structure
512
+ lines.push('## Layer Structure');
513
+ lines.push('');
514
+ for (const layer of architecture.layerMap) {
515
+ lines.push(`### ${layer.name}`);
516
+ lines.push('');
517
+ lines.push(`**Purpose**: ${layer.purpose}`);
518
+ if (layer.components.length > 0) {
519
+ lines.push(`**Location**: \`${layer.components.join(', ')}\``);
520
+ }
521
+ lines.push('');
522
+ }
523
+ // Data Flow
524
+ lines.push('## Data Flow');
525
+ lines.push('');
526
+ if (architecture.dataFlow && architecture.dataFlow !== 'Unknown') {
527
+ lines.push(this.wrapText(architecture.dataFlow));
528
+ }
529
+ else {
530
+ lines.push('Data flows through the defined layers in sequence.');
531
+ }
532
+ lines.push('');
533
+ // External Integrations
534
+ if (architecture.integrations.length > 0) {
535
+ lines.push('## External Integrations');
536
+ lines.push('');
537
+ lines.push('| System | Purpose |');
538
+ lines.push('|--------|---------|');
539
+ for (const integration of architecture.integrations) {
540
+ const name = typeof integration === 'string' ? integration : integration.name ?? String(integration);
541
+ const purpose = typeof integration === 'object' && integration !== null
542
+ ? (integration.purpose ?? 'External integration')
543
+ : 'External integration';
544
+ lines.push(`| ${name} | ${purpose} |`);
545
+ }
546
+ lines.push('');
547
+ }
548
+ return {
549
+ path: 'openspec/specs/architecture/spec.md',
550
+ content: lines.join('\n'),
551
+ domain: 'architecture',
552
+ type: 'architecture',
553
+ };
554
+ }
555
+ /**
556
+ * Generate the API spec
557
+ */
558
+ generateApiSpec(endpoints, _survey) {
559
+ const lines = [];
560
+ const now = new Date();
561
+ const date = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
562
+ // Header
563
+ lines.push('# API Specification');
564
+ lines.push('');
565
+ lines.push(`> Generated by spec-gen v${this.options.version} on ${date}`);
566
+ lines.push('');
567
+ // Purpose
568
+ lines.push('## Purpose');
569
+ lines.push('');
570
+ lines.push('This document specifies the HTTP API exposed by the system.');
571
+ lines.push('');
572
+ // Requirements section (always present)
573
+ lines.push('## Requirements');
574
+ lines.push('');
575
+ // Authentication requirement
576
+ const authMethods = new Set(endpoints.map(e => e.authentication).filter(Boolean));
577
+ if (authMethods.size > 0) {
578
+ lines.push('### Requirement: APIAuthentication');
579
+ lines.push('');
580
+ lines.push(`The API SHALL require authentication via: ${Array.from(authMethods).join(', ')}`);
581
+ lines.push('');
582
+ lines.push('#### Scenario: AuthenticatedRequest');
583
+ lines.push('- **GIVEN** a request with valid authentication credentials');
584
+ lines.push('- **WHEN** the request is processed');
585
+ lines.push('- **THEN** the request is authenticated successfully');
586
+ lines.push('');
587
+ lines.push('#### Scenario: UnauthenticatedRequest');
588
+ lines.push('- **GIVEN** a request without authentication');
589
+ lines.push('- **WHEN** accessing a protected endpoint');
590
+ lines.push('- **THEN** the response status is 401 Unauthorized');
591
+ lines.push('');
592
+ }
593
+ // Group endpoints by related entity
594
+ const endpointsByResource = new Map();
595
+ for (const endpoint of endpoints) {
596
+ const resource = endpoint.relatedEntity || 'General';
597
+ const existing = endpointsByResource.get(resource) || [];
598
+ existing.push(endpoint);
599
+ endpointsByResource.set(resource, existing);
600
+ }
601
+ // Endpoint requirements (under ## Requirements, no separate ## Endpoints section)
602
+ for (const [resource, resourceEndpoints] of endpointsByResource) {
603
+ for (const endpoint of resourceEndpoints) {
604
+ const reqName = this.formatRequirementName(`${endpoint.method}${resource}`);
605
+ lines.push(`### Requirement: ${reqName}`);
606
+ lines.push('');
607
+ lines.push(`The API SHALL support \`${endpoint.method} ${endpoint.path}\` to ${(endpoint.purpose ?? '').toLowerCase()}`);
608
+ lines.push('');
609
+ // Request schema
610
+ if (endpoint.requestSchema && Object.keys(endpoint.requestSchema).length > 0) {
611
+ lines.push('**Request:**');
612
+ lines.push('');
613
+ lines.push('```json');
614
+ lines.push(JSON.stringify(endpoint.requestSchema, null, 2));
615
+ lines.push('```');
616
+ lines.push('');
617
+ }
618
+ // Response schema
619
+ if (endpoint.responseSchema && Object.keys(endpoint.responseSchema).length > 0) {
620
+ lines.push('**Response:**');
621
+ lines.push('');
622
+ lines.push('```json');
623
+ lines.push(JSON.stringify(endpoint.responseSchema, null, 2));
624
+ lines.push('```');
625
+ lines.push('');
626
+ }
627
+ // Scenarios
628
+ for (const scenario of (endpoint.scenarios ?? [])) {
629
+ this.addScenario(lines, scenario);
630
+ }
631
+ // Default success scenario if none provided
632
+ if ((endpoint.scenarios ?? []).length === 0) {
633
+ lines.push(`#### Scenario: ${reqName}Success`);
634
+ lines.push('- **GIVEN** an authenticated user');
635
+ lines.push(`- **WHEN** \`${endpoint.method} ${endpoint.path}\` is called with valid data`);
636
+ lines.push('- **THEN** the response status is 200 OK');
637
+ lines.push('');
638
+ }
639
+ }
640
+ }
641
+ return {
642
+ path: 'openspec/specs/api/spec.md',
643
+ content: lines.join('\n'),
644
+ domain: 'api',
645
+ type: 'api',
646
+ };
647
+ }
648
+ /**
649
+ * Add a scenario to the lines array
650
+ */
651
+ addScenario(lines, scenario) {
652
+ lines.push(`#### Scenario: ${this.formatRequirementName(scenario.name)}`);
653
+ lines.push(`- **GIVEN** ${this.wrapText(scenario.given ?? 'the system is in a valid state')}`);
654
+ lines.push(`- **WHEN** ${this.wrapText(scenario.when ?? 'the operation is invoked')}`);
655
+ lines.push(`- **THEN** ${this.wrapText(scenario.then ?? 'the expected outcome occurs')}`);
656
+ if (scenario.and && scenario.and.length > 0) {
657
+ const andClauses = Array.isArray(scenario.and) ? scenario.and : [scenario.and];
658
+ for (const andClause of andClauses) {
659
+ lines.push(`- **AND** ${this.wrapText(andClause)}`);
660
+ }
661
+ }
662
+ lines.push('');
663
+ }
664
+ /**
665
+ * Format a requirement name (PascalCase, no spaces)
666
+ */
667
+ formatRequirementName(name) {
668
+ if (!name)
669
+ return 'Unnamed';
670
+ return name
671
+ .split(/[\s_-]+/)
672
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
673
+ .join('');
674
+ }
675
+ /**
676
+ * Format a relationship for display
677
+ */
678
+ formatRelationship(rel) {
679
+ const typeLabel = {
680
+ 'one-to-one': 'has one',
681
+ 'one-to-many': 'has many',
682
+ 'many-to-many': 'has many',
683
+ 'belongs-to': 'belongs to',
684
+ }[rel.type] || rel.type;
685
+ return `${typeLabel} ${rel.targetEntity}${rel.description ? ` (${rel.description})` : ''}`;
686
+ }
687
+ /**
688
+ * Format project category for display
689
+ */
690
+ formatCategory(category) {
691
+ const labels = {
692
+ 'web-frontend': 'Web Frontend Application',
693
+ 'web-backend': 'Web Backend Service',
694
+ 'api-service': 'API Service',
695
+ 'cli-tool': 'Command Line Tool',
696
+ library: 'Library/Package',
697
+ 'mobile-app': 'Mobile Application',
698
+ 'desktop-app': 'Desktop Application',
699
+ 'data-pipeline': 'Data Pipeline',
700
+ 'ml-service': 'Machine Learning Service',
701
+ monorepo: 'Monorepo',
702
+ other: 'Other',
703
+ };
704
+ return labels[category] || category;
705
+ }
706
+ /**
707
+ * Format architecture pattern for display
708
+ */
709
+ formatArchitecture(pattern) {
710
+ const labels = {
711
+ layered: 'Layered Architecture',
712
+ hexagonal: 'Hexagonal Architecture (Ports & Adapters)',
713
+ microservices: 'Microservices',
714
+ monolith: 'Monolithic',
715
+ serverless: 'Serverless',
716
+ 'event-driven': 'Event-Driven Architecture',
717
+ mvc: 'Model-View-Controller (MVC)',
718
+ other: 'Custom Architecture',
719
+ };
720
+ return labels[pattern] || pattern;
721
+ }
722
+ /**
723
+ * Capitalize first letter
724
+ */
725
+ capitalize(str) {
726
+ return str.charAt(0).toUpperCase() + str.slice(1);
727
+ }
728
+ /**
729
+ * Wrap text at max line width
730
+ */
731
+ wrapText(text) {
732
+ if (!text)
733
+ return '';
734
+ const str = typeof text === 'string' ? text : JSON.stringify(text);
735
+ const words = str.split(/\s+/);
736
+ const lines = [];
737
+ let currentLine = '';
738
+ for (const word of words) {
739
+ if (currentLine.length + word.length + 1 > this.options.maxLineWidth) {
740
+ lines.push(currentLine);
741
+ currentLine = word;
742
+ }
743
+ else {
744
+ currentLine = currentLine ? `${currentLine} ${word}` : word;
745
+ }
746
+ }
747
+ if (currentLine) {
748
+ lines.push(currentLine);
749
+ }
750
+ return lines.join('\n');
751
+ }
752
+ }
753
+ /**
754
+ * Validate a generated spec against OpenSpec conventions
755
+ */
756
+ export function validateSpec(content) {
757
+ const errors = [];
758
+ const warnings = [];
759
+ // Check for title
760
+ if (!content.match(/^#\s+.+/m)) {
761
+ errors.push('Missing title (# heading)');
762
+ }
763
+ // Check for Purpose section
764
+ if (!content.includes('## Purpose')) {
765
+ warnings.push('Missing Purpose section');
766
+ }
767
+ // Check for Requirements section (except overview)
768
+ if (!content.includes('## Requirements') && !content.includes('## Domains')) {
769
+ warnings.push('Missing Requirements section');
770
+ }
771
+ // Check requirement format (RFC 2119 keywords)
772
+ const requirements = content.match(/###\s+Requirement:\s+.+/g) || [];
773
+ for (const req of requirements) {
774
+ const reqSection = content.substring(content.indexOf(req));
775
+ const nextSection = reqSection.indexOf('\n### ');
776
+ const reqContent = nextSection > 0 ? reqSection.substring(0, nextSection) : reqSection;
777
+ if (!reqContent.match(/\b(SHALL|MUST|SHOULD|MAY)\b/)) {
778
+ warnings.push(`Requirement missing RFC 2119 keyword: ${req}`);
779
+ }
780
+ }
781
+ // Check scenario format
782
+ const scenarios = content.match(/####\s+Scenario:\s+.+/g) || [];
783
+ for (const scenario of scenarios) {
784
+ const scenarioSection = content.substring(content.indexOf(scenario));
785
+ const nextScenario = scenarioSection.indexOf('\n#### ');
786
+ const scenarioContent = nextScenario > 0 ? scenarioSection.substring(0, nextScenario) : scenarioSection;
787
+ if (!scenarioContent.includes('**GIVEN**')) {
788
+ errors.push(`Scenario missing GIVEN: ${scenario}`);
789
+ }
790
+ if (!scenarioContent.includes('**WHEN**')) {
791
+ errors.push(`Scenario missing WHEN: ${scenario}`);
792
+ }
793
+ if (!scenarioContent.includes('**THEN**')) {
794
+ errors.push(`Scenario missing THEN: ${scenario}`);
795
+ }
796
+ }
797
+ // Check for delta markers (should not be in generated specs)
798
+ if (content.match(/\[ADDED\]|\[MODIFIED\]|\[REMOVED\]/)) {
799
+ errors.push('Generated specs should not contain delta markers');
800
+ }
801
+ return {
802
+ valid: errors.length === 0,
803
+ errors,
804
+ warnings,
805
+ };
806
+ }
807
+ // ============================================================================
808
+ // CONVENIENCE FUNCTIONS
809
+ // ============================================================================
810
+ /**
811
+ * Generate OpenSpec files from pipeline result
812
+ */
813
+ export function generateOpenSpecs(result, options) {
814
+ const generator = new OpenSpecFormatGenerator(options);
815
+ return generator.generateSpecs(result);
816
+ }
817
+ //# sourceMappingURL=openspec-format-generator.js.map