spec-gen-cli 1.2.7 → 1.2.8

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 (174) hide show
  1. package/README.md +135 -41
  2. package/dist/api/generate.d.ts.map +1 -1
  3. package/dist/api/generate.js +1 -0
  4. package/dist/api/generate.js.map +1 -1
  5. package/dist/api/types.d.ts +2 -0
  6. package/dist/api/types.d.ts.map +1 -1
  7. package/dist/cli/commands/analyze.d.ts +3 -0
  8. package/dist/cli/commands/analyze.d.ts.map +1 -1
  9. package/dist/cli/commands/analyze.js +112 -17
  10. package/dist/cli/commands/analyze.js.map +1 -1
  11. package/dist/cli/commands/drift.d.ts.map +1 -1
  12. package/dist/cli/commands/drift.js +8 -10
  13. package/dist/cli/commands/drift.js.map +1 -1
  14. package/dist/cli/commands/generate.d.ts.map +1 -1
  15. package/dist/cli/commands/generate.js +12 -36
  16. package/dist/cli/commands/generate.js.map +1 -1
  17. package/dist/cli/commands/mcp.d.ts +2 -2
  18. package/dist/cli/commands/mcp.d.ts.map +1 -1
  19. package/dist/cli/commands/mcp.js +105 -2
  20. package/dist/cli/commands/mcp.js.map +1 -1
  21. package/dist/cli/commands/run.d.ts.map +1 -1
  22. package/dist/cli/commands/run.js +9 -47
  23. package/dist/cli/commands/run.js.map +1 -1
  24. package/dist/cli/commands/setup.d.ts +17 -0
  25. package/dist/cli/commands/setup.d.ts.map +1 -0
  26. package/dist/cli/commands/setup.js +201 -0
  27. package/dist/cli/commands/setup.js.map +1 -0
  28. package/dist/cli/commands/verify.d.ts.map +1 -1
  29. package/dist/cli/commands/verify.js +7 -8
  30. package/dist/cli/commands/verify.js.map +1 -1
  31. package/dist/cli/index.js +12 -8
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/constants.d.ts +10 -0
  34. package/dist/constants.d.ts.map +1 -1
  35. package/dist/constants.js +10 -0
  36. package/dist/constants.js.map +1 -1
  37. package/dist/core/analyzer/ai-config-generator.d.ts +54 -0
  38. package/dist/core/analyzer/ai-config-generator.d.ts.map +1 -0
  39. package/dist/core/analyzer/ai-config-generator.js +85 -0
  40. package/dist/core/analyzer/ai-config-generator.js.map +1 -0
  41. package/dist/core/analyzer/artifact-generator.d.ts +27 -2
  42. package/dist/core/analyzer/artifact-generator.d.ts.map +1 -1
  43. package/dist/core/analyzer/artifact-generator.js +86 -8
  44. package/dist/core/analyzer/artifact-generator.js.map +1 -1
  45. package/dist/core/analyzer/codebase-digest.d.ts.map +1 -1
  46. package/dist/core/analyzer/codebase-digest.js +12 -11
  47. package/dist/core/analyzer/codebase-digest.js.map +1 -1
  48. package/dist/core/analyzer/env-extractor.d.ts +33 -0
  49. package/dist/core/analyzer/env-extractor.d.ts.map +1 -0
  50. package/dist/core/analyzer/env-extractor.js +196 -0
  51. package/dist/core/analyzer/env-extractor.js.map +1 -0
  52. package/dist/core/analyzer/http-route-parser.d.ts +36 -1
  53. package/dist/core/analyzer/http-route-parser.d.ts.map +1 -1
  54. package/dist/core/analyzer/http-route-parser.js +276 -0
  55. package/dist/core/analyzer/http-route-parser.js.map +1 -1
  56. package/dist/core/analyzer/middleware-extractor.d.ts +29 -0
  57. package/dist/core/analyzer/middleware-extractor.d.ts.map +1 -0
  58. package/dist/core/analyzer/middleware-extractor.js +195 -0
  59. package/dist/core/analyzer/middleware-extractor.js.map +1 -0
  60. package/dist/core/analyzer/schema-extractor.d.ts +41 -0
  61. package/dist/core/analyzer/schema-extractor.d.ts.map +1 -0
  62. package/dist/core/analyzer/schema-extractor.js +229 -0
  63. package/dist/core/analyzer/schema-extractor.js.map +1 -0
  64. package/dist/core/analyzer/ui-component-extractor.d.ts +43 -0
  65. package/dist/core/analyzer/ui-component-extractor.d.ts.map +1 -0
  66. package/dist/core/analyzer/ui-component-extractor.js +245 -0
  67. package/dist/core/analyzer/ui-component-extractor.js.map +1 -0
  68. package/dist/core/generator/openspec-format-generator.d.ts.map +1 -1
  69. package/dist/core/generator/openspec-format-generator.js +8 -0
  70. package/dist/core/generator/openspec-format-generator.js.map +1 -1
  71. package/dist/core/generator/spec-pipeline.d.ts +9 -0
  72. package/dist/core/generator/spec-pipeline.d.ts.map +1 -1
  73. package/dist/core/generator/spec-pipeline.js +94 -2
  74. package/dist/core/generator/spec-pipeline.js.map +1 -1
  75. package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -1
  76. package/dist/core/generator/stages/stage1-survey.js +43 -0
  77. package/dist/core/generator/stages/stage1-survey.js.map +1 -1
  78. package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -1
  79. package/dist/core/generator/stages/stage2-entities.js +6 -2
  80. package/dist/core/generator/stages/stage2-entities.js.map +1 -1
  81. package/dist/core/generator/stages/stage3-services.d.ts.map +1 -1
  82. package/dist/core/generator/stages/stage3-services.js +9 -2
  83. package/dist/core/generator/stages/stage3-services.js.map +1 -1
  84. package/dist/core/generator/stages/stage4-api.d.ts.map +1 -1
  85. package/dist/core/generator/stages/stage4-api.js +6 -2
  86. package/dist/core/generator/stages/stage4-api.js.map +1 -1
  87. package/dist/core/services/llm-service.d.ts +8 -9
  88. package/dist/core/services/llm-service.d.ts.map +1 -1
  89. package/dist/core/services/llm-service.js +59 -12
  90. package/dist/core/services/llm-service.js.map +1 -1
  91. package/dist/core/services/mcp-handlers/analysis.d.ts +27 -1
  92. package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -1
  93. package/dist/core/services/mcp-handlers/analysis.js +165 -2
  94. package/dist/core/services/mcp-handlers/analysis.js.map +1 -1
  95. package/dist/core/verifier/verification-engine.d.ts +67 -6
  96. package/dist/core/verifier/verification-engine.d.ts.map +1 -1
  97. package/dist/core/verifier/verification-engine.js +316 -90
  98. package/dist/core/verifier/verification-engine.js.map +1 -1
  99. package/dist/types/pipeline.d.ts +9 -0
  100. package/dist/types/pipeline.d.ts.map +1 -1
  101. package/dist/utils/command-helpers.d.ts +30 -0
  102. package/dist/utils/command-helpers.d.ts.map +1 -1
  103. package/dist/utils/command-helpers.js +69 -1
  104. package/dist/utils/command-helpers.js.map +1 -1
  105. package/examples/bmad/README.md +113 -0
  106. package/examples/bmad/agents/architect.md +226 -0
  107. package/examples/bmad/agents/dev-brownfield.md +69 -0
  108. package/examples/bmad/setup/architect.customize.yaml +14 -0
  109. package/examples/bmad/tasks/implement-story.md +254 -0
  110. package/examples/bmad/tasks/onboarding.md +169 -0
  111. package/examples/bmad/tasks/refactor.md +178 -0
  112. package/examples/bmad/tasks/sprint-planning.md +168 -0
  113. package/examples/bmad/templates/story.md +108 -0
  114. package/examples/cline-workflows/spec-gen-analyze-codebase.md +100 -0
  115. package/examples/cline-workflows/spec-gen-check-spec-drift.md +102 -0
  116. package/examples/cline-workflows/spec-gen-execute-refactor.md +194 -0
  117. package/examples/cline-workflows/spec-gen-implement-feature.md +238 -0
  118. package/examples/cline-workflows/spec-gen-plan-refactor.md +255 -0
  119. package/examples/cline-workflows/spec-gen-refactor-codebase.md +16 -0
  120. package/examples/drift-demo/openspec/config.yaml +14 -0
  121. package/examples/drift-demo/openspec/specs/architecture/spec.md +30 -0
  122. package/examples/drift-demo/openspec/specs/auth/spec.md +71 -0
  123. package/examples/drift-demo/openspec/specs/database/spec.md +33 -0
  124. package/examples/drift-demo/openspec/specs/overview/spec.md +20 -0
  125. package/examples/drift-demo/openspec/specs/projects/spec.md +55 -0
  126. package/examples/drift-demo/openspec/specs/tasks/spec.md +78 -0
  127. package/examples/drift-demo/package.json +21 -0
  128. package/examples/drift-demo/src/auth/auth-middleware.ts +30 -0
  129. package/examples/drift-demo/src/auth/auth-routes.ts +29 -0
  130. package/examples/drift-demo/src/auth/auth-service.ts +45 -0
  131. package/examples/drift-demo/src/database/connection.ts +27 -0
  132. package/examples/drift-demo/src/index.ts +16 -0
  133. package/examples/drift-demo/src/projects/project-model.ts +15 -0
  134. package/examples/drift-demo/src/projects/project-service.ts +34 -0
  135. package/examples/drift-demo/src/tasks/task-model.ts +37 -0
  136. package/examples/drift-demo/src/tasks/task-routes.ts +53 -0
  137. package/examples/drift-demo/src/tasks/task-service.ts +60 -0
  138. package/examples/drift-demo/src/utils/validation.ts +11 -0
  139. package/examples/drift-demo/tests/auth.test.ts +4 -0
  140. package/examples/drift-demo/tests/tasks.test.ts +4 -0
  141. package/examples/drift-demo/tsconfig.json +10 -0
  142. package/examples/drift-test/run-drift-test.sh +1087 -0
  143. package/examples/gsd/README.md +119 -0
  144. package/examples/gsd/commands/gsd/spec-gen-drift.md +111 -0
  145. package/examples/gsd/commands/gsd/spec-gen-orient.md +191 -0
  146. package/examples/mistral-vibe/README.md +101 -0
  147. package/examples/mistral-vibe/antipatterns-template.md +18 -0
  148. package/examples/mistral-vibe/skills/spec-gen-analyze-codebase/SKILL.md +123 -0
  149. package/examples/mistral-vibe/skills/spec-gen-brainstorm/SKILL.md +379 -0
  150. package/examples/mistral-vibe/skills/spec-gen-debug/SKILL.md +320 -0
  151. package/examples/mistral-vibe/skills/spec-gen-execute-refactor/SKILL.md +210 -0
  152. package/examples/mistral-vibe/skills/spec-gen-generate/SKILL.md +245 -0
  153. package/examples/mistral-vibe/skills/spec-gen-implement-story/SKILL.md +274 -0
  154. package/examples/mistral-vibe/skills/spec-gen-plan-refactor/SKILL.md +251 -0
  155. package/examples/openspec-analysis/README.md +59 -0
  156. package/examples/openspec-analysis/SUMMARY.md +72 -0
  157. package/examples/openspec-analysis/config.json +16 -0
  158. package/examples/openspec-analysis/dependencies.mermaid +35 -0
  159. package/examples/openspec-analysis/dependency-graph.json +12116 -0
  160. package/examples/openspec-analysis/llm-context.json +119 -0
  161. package/examples/openspec-analysis/repo-structure.json +871 -0
  162. package/examples/openspec-cli/README.md +67 -0
  163. package/examples/openspec-cli/openspec/config.yaml +26 -0
  164. package/examples/openspec-cli/openspec/specs/architecture/spec.md +178 -0
  165. package/examples/openspec-cli/openspec/specs/artifact-graph/spec.md +143 -0
  166. package/examples/openspec-cli/openspec/specs/cli/spec.md +138 -0
  167. package/examples/openspec-cli/openspec/specs/overview/spec.md +60 -0
  168. package/examples/openspec-cli/openspec/specs/parsing/spec.md +123 -0
  169. package/examples/openspec-cli/openspec/specs/validation/spec.md +108 -0
  170. package/examples/spec-kit/README.md +104 -0
  171. package/examples/spec-kit/commands/drift.md +87 -0
  172. package/examples/spec-kit/commands/orient.md +138 -0
  173. package/examples/spec-kit/extension.yml +54 -0
  174. package/package.json +3 -6
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Environment Variable Extractor
3
+ *
4
+ * Detects env vars used in a project from two complementary sources:
5
+ * 1. Declaration files — .env.example, .env.local, .env (with optional comments)
6
+ * 2. Source code — process.env.X (JS/TS), os.environ["X"] (Python),
7
+ * os.Getenv("X") (Go), ENV["X"] (Ruby)
8
+ *
9
+ * Variables found in declaration files are marked hasDefault=true when the
10
+ * declaration line has a non-empty value. Variables found only in source code
11
+ * are marked required=true (no known default).
12
+ */
13
+ import { readFile } from 'node:fs/promises';
14
+ import { extname, relative, basename } from 'node:path';
15
+ // ============================================================================
16
+ // ENV FILE PARSER
17
+ // ============================================================================
18
+ function parseEnvFile(content, relPath) {
19
+ const vars = [];
20
+ let pendingComment = '';
21
+ for (const rawLine of content.split('\n')) {
22
+ const line = rawLine.trim();
23
+ // Accumulate comment lines as description
24
+ if (line.startsWith('#')) {
25
+ pendingComment = line.replace(/^#+\s*/, '');
26
+ continue;
27
+ }
28
+ if (!line) {
29
+ pendingComment = '';
30
+ continue;
31
+ }
32
+ const eqIdx = line.indexOf('=');
33
+ if (eqIdx === -1) {
34
+ pendingComment = '';
35
+ continue;
36
+ }
37
+ const name = line.slice(0, eqIdx).trim();
38
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(name)) {
39
+ pendingComment = '';
40
+ continue;
41
+ }
42
+ const rawValue = line.slice(eqIdx + 1).trim();
43
+ // Strip inline comment from value
44
+ const valueWithoutComment = rawValue.replace(/#.*$/, '').trim();
45
+ const inlineComment = rawValue.includes('#')
46
+ ? rawValue.slice(rawValue.indexOf('#') + 1).trim()
47
+ : '';
48
+ const hasDefault = valueWithoutComment.length > 0;
49
+ const description = inlineComment || pendingComment || undefined;
50
+ vars.push({ name, files: [relPath], hasDefault, required: false, description });
51
+ pendingComment = '';
52
+ }
53
+ return vars;
54
+ }
55
+ // ============================================================================
56
+ // SOURCE CODE SCANNERS
57
+ // ============================================================================
58
+ // JS/TS: process.env.VAR_NAME or process.env['VAR_NAME'] or process.env["VAR_NAME"]
59
+ const TS_ENV_RE = /process\.env\.([A-Z_][A-Z0-9_]*)|process\.env\[['"]([A-Z_][A-Z0-9_]*)['"]]/g;
60
+ // Fallback detection: process.env.X ?? 'default' or process.env.X || 'default'
61
+ const TS_HAS_FALLBACK_RE = /process\.env\.(?:[A-Z_][A-Z0-9_]*|\[['"][A-Z_][A-Z0-9_]*['"]])\s*(?:\?\?|(?<!\|)\|\|)/;
62
+ // Python: os.environ["X"], os.environ['X'], os.environ.get("X"), os.getenv("X")
63
+ const PY_ENV_RE = /os\.environ\[['"]([A-Z_][A-Z0-9_]*)['"]|os\.environ\.get\(['"]([A-Z_][A-Z0-9_]*)['"]|os\.getenv\(['"]([A-Z_][A-Z0-9_]*)['"]/g;
64
+ // Go: os.Getenv("X")
65
+ const GO_ENV_RE = /os\.Getenv\("([A-Z_][A-Z0-9_]*)"\)/g;
66
+ // Ruby: ENV["X"], ENV['X'], ENV.fetch("X")
67
+ const RUBY_ENV_RE = /ENV\[['"]([A-Z_][A-Z0-9_]*)['"]|ENV\.fetch\(['"]([A-Z_][A-Z0-9_]*)['"]/g;
68
+ function extractFromSource(source, relPath, ext) {
69
+ const found = [];
70
+ let re;
71
+ let hasFallback = false;
72
+ if (['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext)) {
73
+ re = new RegExp(TS_ENV_RE.source, 'g');
74
+ hasFallback = TS_HAS_FALLBACK_RE.test(source);
75
+ let m;
76
+ while ((m = re.exec(source)) !== null) {
77
+ const name = m[1] ?? m[2];
78
+ if (name)
79
+ found.push({ name, required: !hasFallback });
80
+ }
81
+ }
82
+ else if (['.py', '.pyw'].includes(ext)) {
83
+ re = new RegExp(PY_ENV_RE.source, 'g');
84
+ let m;
85
+ while ((m = re.exec(source)) !== null) {
86
+ const name = m[1] ?? m[2] ?? m[3];
87
+ // os.environ.get and os.getenv have optional defaults → not strictly required
88
+ const isStrict = m[1] !== undefined; // only os.environ["X"] is strict
89
+ if (name)
90
+ found.push({ name, required: isStrict });
91
+ }
92
+ }
93
+ else if (ext === '.go') {
94
+ re = new RegExp(GO_ENV_RE.source, 'g');
95
+ let m;
96
+ while ((m = re.exec(source)) !== null) {
97
+ if (m[1])
98
+ found.push({ name: m[1], required: false }); // Go always uses string return, caller checks
99
+ }
100
+ }
101
+ else if (ext === '.rb') {
102
+ re = new RegExp(RUBY_ENV_RE.source, 'g');
103
+ let m;
104
+ while ((m = re.exec(source)) !== null) {
105
+ const name = m[1] ?? m[2];
106
+ const isStrict = m[1] !== undefined; // ENV.fetch has optional default
107
+ if (name)
108
+ found.push({ name, required: isStrict });
109
+ }
110
+ }
111
+ return found;
112
+ }
113
+ // ============================================================================
114
+ // PUBLIC API
115
+ // ============================================================================
116
+ const ENV_DECLARATION_FILES = new Set(['.env', '.env.example', '.env.local', '.env.test', '.env.production']);
117
+ const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.pyw', '.go', '.rb']);
118
+ const SKIP_DIRS = ['/node_modules/', '/.spec-gen/', '/dist/', '/build/', '/coverage/'];
119
+ /**
120
+ * Extract all environment variables referenced or declared in the project.
121
+ */
122
+ export async function extractEnvVars(filePaths, rootDir) {
123
+ const map = new Map();
124
+ function upsert(name, relPath, patch) {
125
+ const existing = map.get(name);
126
+ if (existing) {
127
+ if (!existing.files.includes(relPath))
128
+ existing.files.push(relPath);
129
+ if (patch.hasDefault)
130
+ existing.hasDefault = true;
131
+ if (patch.required)
132
+ existing.required = true;
133
+ if (patch.description && !existing.description)
134
+ existing.description = patch.description;
135
+ }
136
+ else {
137
+ map.set(name, {
138
+ name,
139
+ files: [relPath],
140
+ hasDefault: patch.hasDefault ?? false,
141
+ required: patch.required ?? false,
142
+ description: patch.description,
143
+ });
144
+ }
145
+ }
146
+ await Promise.all(filePaths.map(async (fp) => {
147
+ if (SKIP_DIRS.some(d => fp.replace(/\\/g, '/').includes(d)))
148
+ return;
149
+ const name = basename(fp);
150
+ const ext = extname(fp).toLowerCase();
151
+ const rel = relative(rootDir, fp);
152
+ let source;
153
+ try {
154
+ source = await readFile(fp, 'utf-8');
155
+ }
156
+ catch {
157
+ return;
158
+ }
159
+ // Env declaration files
160
+ if (ENV_DECLARATION_FILES.has(name)) {
161
+ for (const v of parseEnvFile(source, rel)) {
162
+ upsert(v.name, rel, { hasDefault: v.hasDefault, description: v.description });
163
+ }
164
+ return;
165
+ }
166
+ // Source files
167
+ if (!SOURCE_EXTENSIONS.has(ext))
168
+ return;
169
+ // Skip test files
170
+ if (fp.includes('.test.') || fp.includes('.spec.') || fp.includes('_test.') || fp.includes('_spec.'))
171
+ return;
172
+ for (const { name: varName, required } of extractFromSource(source, rel, ext)) {
173
+ upsert(varName, rel, { required });
174
+ }
175
+ }));
176
+ return [...map.values()].sort((a, b) => a.name.localeCompare(b.name));
177
+ }
178
+ /**
179
+ * Return a compact summary string for LLM prompts.
180
+ */
181
+ export function summarizeEnvVars(vars) {
182
+ if (vars.length === 0)
183
+ return '';
184
+ const lines = vars.map(v => {
185
+ const flags = [];
186
+ if (v.required)
187
+ flags.push('required');
188
+ if (v.hasDefault)
189
+ flags.push('has-default');
190
+ const suffix = flags.length > 0 ? ` [${flags.join(', ')}]` : '';
191
+ const desc = v.description ? ` — ${v.description}` : '';
192
+ return ` ${v.name}${suffix}${desc}`;
193
+ });
194
+ return `Environment variables (${vars.length}):\n${lines.join('\n')}`;
195
+ }
196
+ //# sourceMappingURL=env-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-extractor.js","sourceRoot":"","sources":["../../../src/core/analyzer/env-extractor.ts"],"names":[],"mappings":"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"}
@@ -52,10 +52,16 @@ export interface RouteDefinition {
52
52
  normalizedPath: string;
53
53
  /** Name of the handler function */
54
54
  handlerName: string;
55
- /** fastapi / flask / django / starlette */
55
+ /** fastapi / flask / django / starlette / express / nestjs / nextjs-app etc. */
56
56
  framework: string;
57
57
  /** 1-based source line */
58
58
  line: number;
59
+ /** Request body type extracted from handler signature, e.g. "CreateUserDto" or "z.infer<typeof schema>" */
60
+ requestBodyType?: string;
61
+ /** Response body type extracted from handler return type annotation, e.g. "User[]" or "void" */
62
+ responseType?: string;
63
+ /** How the contract was sourced */
64
+ contractSource: 'annotation' | 'validator' | 'none';
59
65
  }
60
66
  /** A resolved cross-language edge */
61
67
  export interface HttpEdge {
@@ -108,4 +114,33 @@ export declare function extractAllHttpEdges(filePaths: string[]): Promise<{
108
114
  routes: RouteDefinition[];
109
115
  edges: HttpEdge[];
110
116
  }>;
117
+ /**
118
+ * Extract HTTP route definitions from a TypeScript/JavaScript server file.
119
+ * Handles Express-style, NestJS decorators, and Next.js App Router.
120
+ */
121
+ export declare function extractTsRouteDefinitions(filePath: string): Promise<RouteDefinition[]>;
122
+ export interface RouteInventory {
123
+ total: number;
124
+ byMethod: Record<string, number>;
125
+ byFramework: Record<string, number>;
126
+ routes: Array<{
127
+ method: string;
128
+ path: string;
129
+ framework: string;
130
+ file: string;
131
+ handler: string;
132
+ requestBodyType?: string;
133
+ responseType?: string;
134
+ contractSource: 'annotation' | 'validator' | 'none';
135
+ }>;
136
+ }
137
+ /**
138
+ * Build a complete route inventory from all source files.
139
+ * Combines Python routes (extractRouteDefinitions) and TS/JS routes
140
+ * (extractTsRouteDefinitions) into a single summary.
141
+ *
142
+ * @param filePaths - Absolute paths to all source files in the project
143
+ * @param rootDir - Project root for computing relative paths
144
+ */
145
+ export declare function buildRouteInventory(filePaths: string[], rootDir: string): Promise<RouteInventory>;
111
146
  //# sourceMappingURL=http-route-parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-route-parser.d.ts","sourceRoot":"","sources":["../../../src/core/analyzer/http-route-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AASH,gDAAgD;AAChD,MAAM,WAAW,QAAQ;IACvB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qCAAqC;AACrC,MAAM,WAAW,QAAQ;IACvB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,eAAe,CAAC;IACvB,iCAAiC;IACjC,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;CACxC;AAaD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAsBhD;AAuBD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAuH5E;AAMD;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAgI1F;AAMD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,EAAE,eAAe,EAAE,GACxB,QAAQ,EAAE,CAgFZ;AAMD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACtE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC,CAmBD"}
1
+ {"version":3,"file":"http-route-parser.d.ts","sourceRoot":"","sources":["../../../src/core/analyzer/http-route-parser.ts"],"names":[],"mappings":"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"}
@@ -27,6 +27,7 @@
27
27
  */
28
28
  import { readFile } from 'node:fs/promises';
29
29
  import { extname } from 'node:path';
30
+ import { getSkeletonContent, detectLanguage } from './code-shaper.js';
30
31
  // ============================================================================
31
32
  // NORMALISATION HELPERS
32
33
  // ============================================================================
@@ -236,6 +237,7 @@ export async function extractRouteDefinitions(filePath) {
236
237
  handlerName,
237
238
  framework: 'fastapi',
238
239
  line: lineNum,
240
+ contractSource: 'none',
239
241
  });
240
242
  }
241
243
  // @app.api_route("/path", methods=["GET", "POST"])
@@ -255,6 +257,7 @@ export async function extractRouteDefinitions(filePath) {
255
257
  handlerName,
256
258
  framework: 'fastapi',
257
259
  line: lineNum,
260
+ contractSource: 'none',
258
261
  });
259
262
  }
260
263
  }
@@ -278,6 +281,7 @@ export async function extractRouteDefinitions(filePath) {
278
281
  handlerName,
279
282
  framework: 'flask',
280
283
  line: lineNum,
284
+ contractSource: 'none',
281
285
  });
282
286
  }
283
287
  }
@@ -291,6 +295,7 @@ export async function extractRouteDefinitions(filePath) {
291
295
  handlerName,
292
296
  framework: 'flask',
293
297
  line: lineNum,
298
+ contractSource: 'none',
294
299
  });
295
300
  }
296
301
  }
@@ -317,6 +322,7 @@ export async function extractRouteDefinitions(filePath) {
317
322
  handlerName,
318
323
  framework: 'django',
319
324
  line: lineNum,
325
+ contractSource: 'none',
320
326
  });
321
327
  }
322
328
  return routes;
@@ -463,4 +469,274 @@ function extractNextDefName(lines, decoratorLine) {
463
469
  }
464
470
  return 'unknown';
465
471
  }
472
+ // ============================================================================
473
+ // CONTRACT / TYPE EXTRACTION HELPERS
474
+ // ============================================================================
475
+ /**
476
+ * Extract contract information from a handler function body or surrounding context.
477
+ *
478
+ * Strategies:
479
+ * 1. TypeScript Request<P, ResBody, ReqBody, Q> generic → requestBodyType = ReqBody
480
+ * 2. NestJS @Body() dto: Type → requestBodyType = Type
481
+ * 3. Zod .parse( / .parseAsync( → contractSource = 'validator'
482
+ * 4. Promise<ResponseType> return annotation
483
+ */
484
+ function extractContractFromHandler(handlerSource) {
485
+ let requestBodyType;
486
+ let responseType;
487
+ let contractSource = 'none';
488
+ // 1. TypeScript Request<P, ResBody, ReqBody, Q> generic
489
+ // handler(req: Request<Params, ResBody, Body, Query>)
490
+ const reqGenericRe = /:\s*Request\s*<[^,>]+,\s*([^,>]+),\s*([^,>]+)/;
491
+ const reqGenericMatch = reqGenericRe.exec(handlerSource);
492
+ if (reqGenericMatch) {
493
+ const resBodyType = reqGenericMatch[1].trim();
494
+ const reqBodyType = reqGenericMatch[2].trim();
495
+ if (reqBodyType && reqBodyType !== 'unknown' && reqBodyType !== 'any') {
496
+ requestBodyType = reqBodyType;
497
+ contractSource = 'annotation';
498
+ }
499
+ if (resBodyType && resBodyType !== 'unknown' && resBodyType !== 'any') {
500
+ responseType = resBodyType;
501
+ }
502
+ }
503
+ // 2. NestJS @Body() dto: CreateUserDto
504
+ const bodyParamRe = /@Body\s*\(\s*\)\s+\w+\s*:\s*(\w+)/;
505
+ const bodyParamMatch = bodyParamRe.exec(handlerSource);
506
+ if (bodyParamMatch) {
507
+ requestBodyType = bodyParamMatch[1];
508
+ contractSource = 'annotation';
509
+ }
510
+ // 3. Zod validators: schema.parse( / schema.parseAsync( / z.infer<typeof
511
+ const zodRe = /\b\w+\.parse(?:Async)?\s*\(|z\.infer\s*<\s*typeof\s+(\w+)/;
512
+ const zodMatch = zodRe.exec(handlerSource);
513
+ if (zodMatch) {
514
+ contractSource = 'validator';
515
+ if (zodMatch[1]) {
516
+ requestBodyType = `z.infer<typeof ${zodMatch[1]}>`;
517
+ }
518
+ else {
519
+ // Extract schema variable name from .parse( call
520
+ const parseVarRe = /(\w+)\.parse(?:Async)?\s*\(/;
521
+ const parseVarMatch = parseVarRe.exec(handlerSource);
522
+ if (parseVarMatch) {
523
+ requestBodyType = parseVarMatch[1];
524
+ }
525
+ }
526
+ }
527
+ // 4. Promise<ResponseType> return type annotation
528
+ const returnTypeRe = /\):\s*Promise\s*<\s*([^>]+)>/;
529
+ const returnTypeMatch = returnTypeRe.exec(handlerSource);
530
+ if (returnTypeMatch && !responseType) {
531
+ const rType = returnTypeMatch[1].trim();
532
+ if (rType && rType !== 'void' && rType !== 'unknown' && rType !== 'any') {
533
+ responseType = rType;
534
+ }
535
+ }
536
+ return { requestBodyType, responseType, contractSource };
537
+ }
538
+ // ============================================================================
539
+ // TS/JS SERVER ROUTE EXTRACTION
540
+ // ============================================================================
541
+ // Express / Hono / Fastify / Koa / Elysia style:
542
+ // app.get('/path', handler)
543
+ // router.post('/path', ...)
544
+ // app.use('/prefix', router) ← prefix accumulation
545
+ const EXPRESS_ROUTE_RE = /(?:^|[\s;(,])(?:app|router|server|api|r)\.(get|post|put|delete|patch|head|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gm;
546
+ const EXPRESS_USE_RE = /(?:^|[\s;(,])(?:app|router|server|api|r)\.use\s*\(\s*['"`]([^'"`]+)['"`]/gm;
547
+ // NestJS decorator-based:
548
+ // @Controller('prefix') → class methods with @Get / @Post etc.
549
+ const NESTJS_CONTROLLER_RE = /@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/g;
550
+ const NESTJS_METHOD_RE = /@(Get|Post|Put|Delete|Patch|Head|Options|All)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g;
551
+ const NESTJS_HANDLER_RE = /(?:async\s+)?(\w+)\s*\(/;
552
+ // Next.js App Router: export (async) function GET(...) in app/**/route.ts
553
+ const NEXTJS_APP_ROUTER_RE = /^export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(/gm;
554
+ /** Detected framework from a file's content */
555
+ function detectTsFramework(source, filePath) {
556
+ if (/@Controller\s*\(/.test(source) && /@(Get|Post|Put|Delete|Patch)\s*\(/.test(source))
557
+ return 'nestjs';
558
+ if (/app\/.*\/route\.[jt]sx?$/.test(filePath.replace(/\\/g, '/')))
559
+ return 'nextjs-app';
560
+ if (/pages\/api\//.test(filePath.replace(/\\/g, '/')))
561
+ return 'nextjs-pages';
562
+ if (/from\s+['"]hono['"]/.test(source) || /new\s+Hono\s*[(<]/.test(source))
563
+ return 'hono';
564
+ if (/from\s+['"]fastify['"]/.test(source) || /fastify\s*\(/.test(source))
565
+ return 'fastify';
566
+ if (/from\s+['"]express['"]/.test(source) || /require\s*\(\s*['"]express['"]\s*\)/.test(source))
567
+ return 'express';
568
+ if (/from\s+['"]koa['"]/.test(source))
569
+ return 'koa';
570
+ if (/from\s+['"]elysia['"]/.test(source))
571
+ return 'elysia';
572
+ if (new RegExp(EXPRESS_ROUTE_RE.source).test(source))
573
+ return 'express';
574
+ return 'unknown';
575
+ }
576
+ /**
577
+ * Extract HTTP route definitions from a TypeScript/JavaScript server file.
578
+ * Handles Express-style, NestJS decorators, and Next.js App Router.
579
+ */
580
+ export async function extractTsRouteDefinitions(filePath) {
581
+ let source;
582
+ try {
583
+ const { readFile } = await import('node:fs/promises');
584
+ // Use skeleton to strip comments — prevents false positives from comment
585
+ // examples inside parser/extractor files that contain route pattern strings.
586
+ // Line numbers in the result are approximate (skeleton line positions).
587
+ source = getSkeletonContent(await readFile(filePath, 'utf-8'), detectLanguage(filePath));
588
+ }
589
+ catch {
590
+ return [];
591
+ }
592
+ const framework = detectTsFramework(source, filePath);
593
+ const routes = [];
594
+ const lines = source.split('\n');
595
+ function lineOf(index) {
596
+ return source.slice(0, index).split('\n').length;
597
+ }
598
+ // ── Next.js App Router ────────────────────────────────────────────────────
599
+ if (framework === 'nextjs-app') {
600
+ // Derive path from file location: app/users/route.ts → /users
601
+ const rel = filePath.replace(/\\/g, '/');
602
+ const appIdx = rel.lastIndexOf('/app/');
603
+ let routePath = '/';
604
+ if (appIdx >= 0) {
605
+ routePath = rel.slice(appIdx + 4).replace(/\/route\.[jt]sx?$/, '') || '/';
606
+ // Remove dynamic segments brackets for display: [id] → :id
607
+ routePath = routePath.replace(/\[([^\]]+)\]/g, ':$1');
608
+ }
609
+ const re = new RegExp(NEXTJS_APP_ROUTER_RE.source, NEXTJS_APP_ROUTER_RE.flags);
610
+ let m;
611
+ while ((m = re.exec(source)) !== null) {
612
+ // Extract handler body for contract detection (scan next 500 chars)
613
+ const handlerBody = source.slice(m.index, m.index + 500);
614
+ const contract = extractContractFromHandler(handlerBody);
615
+ routes.push({
616
+ file: filePath,
617
+ method: m[1].toUpperCase(),
618
+ path: routePath,
619
+ normalizedPath: normalizeUrl(routePath),
620
+ handlerName: m[1],
621
+ framework: 'nextjs-app',
622
+ line: lineOf(m.index),
623
+ ...contract,
624
+ });
625
+ }
626
+ return routes;
627
+ }
628
+ // ── NestJS ────────────────────────────────────────────────────────────────
629
+ if (framework === 'nestjs') {
630
+ // Collect controller prefixes
631
+ const ctrlRe = new RegExp(NESTJS_CONTROLLER_RE.source, NESTJS_CONTROLLER_RE.flags);
632
+ let ctrlPrefix = '';
633
+ const ctrlMatch = ctrlRe.exec(source);
634
+ if (ctrlMatch) {
635
+ ctrlPrefix = ctrlMatch[1] ? `/${ctrlMatch[1].replace(/^\//, '')}` : '';
636
+ }
637
+ const methodRe = new RegExp(NESTJS_METHOD_RE.source, NESTJS_METHOD_RE.flags);
638
+ let m;
639
+ while ((m = methodRe.exec(source)) !== null) {
640
+ const httpMethod = m[1].toUpperCase();
641
+ const subPath = m[2] ? `/${m[2].replace(/^\//, '')}` : '';
642
+ const fullPath = `${ctrlPrefix}${subPath}` || '/';
643
+ // Find handler function name on subsequent lines
644
+ const afterDecorator = source.slice(m.index + m[0].length);
645
+ const handlerMatch = NESTJS_HANDLER_RE.exec(afterDecorator.slice(0, 200));
646
+ const handlerName = handlerMatch?.[1] ?? 'unknown';
647
+ // Extract contract from decorator + handler context (scan next 400 chars)
648
+ const handlerContext = source.slice(m.index, m.index + 400);
649
+ const contract = extractContractFromHandler(handlerContext);
650
+ routes.push({
651
+ file: filePath,
652
+ method: httpMethod,
653
+ path: fullPath,
654
+ normalizedPath: normalizeUrl(fullPath),
655
+ handlerName,
656
+ framework: 'nestjs',
657
+ line: lineOf(m.index),
658
+ ...contract,
659
+ });
660
+ }
661
+ return routes;
662
+ }
663
+ // ── Express / Hono / Fastify / Koa / Elysia ───────────────────────────────
664
+ // Collect prefix map from .use() calls (best-effort)
665
+ const prefixes = [];
666
+ const useRe = new RegExp(EXPRESS_USE_RE.source, EXPRESS_USE_RE.flags);
667
+ let um;
668
+ while ((um = useRe.exec(source)) !== null) {
669
+ prefixes.push(um[1]);
670
+ }
671
+ const routeRe = new RegExp(EXPRESS_ROUTE_RE.source, EXPRESS_ROUTE_RE.flags);
672
+ let m;
673
+ while ((m = routeRe.exec(source)) !== null) {
674
+ const method = m[1].toUpperCase();
675
+ let path = m[2];
676
+ // Apply a prefix if the route is relative (no leading slash)
677
+ if (!path.startsWith('/') && prefixes.length > 0) {
678
+ path = `${prefixes[0]}/${path}`;
679
+ }
680
+ // Find the handler name from the same line
681
+ const lineText = lines[lineOf(m.index) - 1] ?? '';
682
+ const handlerMatch = lineText.match(/,\s*(?:async\s+)?(?:function\s+)?(\w+)\s*[,)]/);
683
+ const handlerName = handlerMatch?.[1] ?? 'handler';
684
+ // Extract contract from route context (scan next 600 chars)
685
+ const routeContext = source.slice(m.index, m.index + 600);
686
+ const contract = extractContractFromHandler(routeContext);
687
+ routes.push({
688
+ file: filePath,
689
+ method,
690
+ path,
691
+ normalizedPath: normalizeUrl(path),
692
+ handlerName,
693
+ framework,
694
+ line: lineOf(m.index),
695
+ ...contract,
696
+ });
697
+ }
698
+ return routes;
699
+ }
700
+ /**
701
+ * Build a complete route inventory from all source files.
702
+ * Combines Python routes (extractRouteDefinitions) and TS/JS routes
703
+ * (extractTsRouteDefinitions) into a single summary.
704
+ *
705
+ * @param filePaths - Absolute paths to all source files in the project
706
+ * @param rootDir - Project root for computing relative paths
707
+ */
708
+ export async function buildRouteInventory(filePaths, rootDir) {
709
+ const { relative } = await import('node:path');
710
+ const allRoutes = [];
711
+ await Promise.all(filePaths.map(async (fp) => {
712
+ const ext = extname(fp).toLowerCase();
713
+ if (['.py', '.pyw'].includes(ext)) {
714
+ allRoutes.push(...await extractRouteDefinitions(fp));
715
+ }
716
+ else if (['.ts', '.tsx', '.js', '.jsx', '.mjs'].includes(ext)) {
717
+ allRoutes.push(...await extractTsRouteDefinitions(fp));
718
+ }
719
+ }));
720
+ const byMethod = {};
721
+ const byFramework = {};
722
+ for (const r of allRoutes) {
723
+ byMethod[r.method] = (byMethod[r.method] ?? 0) + 1;
724
+ byFramework[r.framework] = (byFramework[r.framework] ?? 0) + 1;
725
+ }
726
+ return {
727
+ total: allRoutes.length,
728
+ byMethod,
729
+ byFramework,
730
+ routes: allRoutes.map(r => ({
731
+ method: r.method,
732
+ path: r.path,
733
+ framework: r.framework,
734
+ file: relative(rootDir, r.file),
735
+ handler: r.handlerName,
736
+ requestBodyType: r.requestBodyType,
737
+ responseType: r.responseType,
738
+ contractSource: r.contractSource,
739
+ })),
740
+ };
741
+ }
466
742
  //# sourceMappingURL=http-route-parser.js.map