@rasensio/aidlc 0.1.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 (187) hide show
  1. package/README.md +251 -0
  2. package/capabilities/claude-code.yaml +9 -0
  3. package/capabilities/codex.yaml +8 -0
  4. package/capabilities/cursor.yaml +8 -0
  5. package/capabilities/kiro.yaml +9 -0
  6. package/capabilities/windsurf.yaml +8 -0
  7. package/ci/aidlc-gate.yml +100 -0
  8. package/dist/cli.d.ts +11 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +39 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands/add-action.d.ts +83 -0
  13. package/dist/commands/add-action.d.ts.map +1 -0
  14. package/dist/commands/add-action.js +437 -0
  15. package/dist/commands/add-action.js.map +1 -0
  16. package/dist/commands/continue.d.ts +23 -0
  17. package/dist/commands/continue.d.ts.map +1 -0
  18. package/dist/commands/continue.js +238 -0
  19. package/dist/commands/continue.js.map +1 -0
  20. package/dist/commands/discover.d.ts +19 -0
  21. package/dist/commands/discover.d.ts.map +1 -0
  22. package/dist/commands/discover.js +85 -0
  23. package/dist/commands/discover.js.map +1 -0
  24. package/dist/commands/docs.d.ts +25 -0
  25. package/dist/commands/docs.d.ts.map +1 -0
  26. package/dist/commands/docs.js +282 -0
  27. package/dist/commands/docs.js.map +1 -0
  28. package/dist/commands/gate.d.ts +25 -0
  29. package/dist/commands/gate.d.ts.map +1 -0
  30. package/dist/commands/gate.js +168 -0
  31. package/dist/commands/gate.js.map +1 -0
  32. package/dist/commands/init.d.ts +24 -0
  33. package/dist/commands/init.d.ts.map +1 -0
  34. package/dist/commands/init.js +187 -0
  35. package/dist/commands/init.js.map +1 -0
  36. package/dist/commands/review.d.ts +60 -0
  37. package/dist/commands/review.d.ts.map +1 -0
  38. package/dist/commands/review.js +264 -0
  39. package/dist/commands/review.js.map +1 -0
  40. package/dist/commands/setup.d.ts +24 -0
  41. package/dist/commands/setup.d.ts.map +1 -0
  42. package/dist/commands/setup.js +206 -0
  43. package/dist/commands/setup.js.map +1 -0
  44. package/dist/commands/start.d.ts +27 -0
  45. package/dist/commands/start.d.ts.map +1 -0
  46. package/dist/commands/start.js +243 -0
  47. package/dist/commands/start.js.map +1 -0
  48. package/dist/commands/status.d.ts +25 -0
  49. package/dist/commands/status.d.ts.map +1 -0
  50. package/dist/commands/status.js +263 -0
  51. package/dist/commands/status.js.map +1 -0
  52. package/dist/commands/transition.d.ts +30 -0
  53. package/dist/commands/transition.d.ts.map +1 -0
  54. package/dist/commands/transition.js +353 -0
  55. package/dist/commands/transition.js.map +1 -0
  56. package/dist/compile/adapters/claude-code.d.ts +24 -0
  57. package/dist/compile/adapters/claude-code.d.ts.map +1 -0
  58. package/dist/compile/adapters/claude-code.js +271 -0
  59. package/dist/compile/adapters/claude-code.js.map +1 -0
  60. package/dist/compile/adapters/codex.d.ts +19 -0
  61. package/dist/compile/adapters/codex.d.ts.map +1 -0
  62. package/dist/compile/adapters/codex.js +157 -0
  63. package/dist/compile/adapters/codex.js.map +1 -0
  64. package/dist/compile/adapters/cursor.d.ts +21 -0
  65. package/dist/compile/adapters/cursor.d.ts.map +1 -0
  66. package/dist/compile/adapters/cursor.js +181 -0
  67. package/dist/compile/adapters/cursor.js.map +1 -0
  68. package/dist/compile/adapters/index.d.ts +16 -0
  69. package/dist/compile/adapters/index.d.ts.map +1 -0
  70. package/dist/compile/adapters/index.js +26 -0
  71. package/dist/compile/adapters/index.js.map +1 -0
  72. package/dist/compile/adapters/kiro.d.ts +24 -0
  73. package/dist/compile/adapters/kiro.d.ts.map +1 -0
  74. package/dist/compile/adapters/kiro.js +222 -0
  75. package/dist/compile/adapters/kiro.js.map +1 -0
  76. package/dist/compile/adapters/windsurf.d.ts +25 -0
  77. package/dist/compile/adapters/windsurf.d.ts.map +1 -0
  78. package/dist/compile/adapters/windsurf.js +242 -0
  79. package/dist/compile/adapters/windsurf.js.map +1 -0
  80. package/dist/compile/install-plan.d.ts +49 -0
  81. package/dist/compile/install-plan.d.ts.map +1 -0
  82. package/dist/compile/install-plan.js +112 -0
  83. package/dist/compile/install-plan.js.map +1 -0
  84. package/dist/compile/loaders.d.ts +81 -0
  85. package/dist/compile/loaders.d.ts.map +1 -0
  86. package/dist/compile/loaders.js +293 -0
  87. package/dist/compile/loaders.js.map +1 -0
  88. package/dist/compile/merger.d.ts +32 -0
  89. package/dist/compile/merger.d.ts.map +1 -0
  90. package/dist/compile/merger.js +60 -0
  91. package/dist/compile/merger.js.map +1 -0
  92. package/dist/core/gate.d.ts +36 -0
  93. package/dist/core/gate.d.ts.map +1 -0
  94. package/dist/core/gate.js +97 -0
  95. package/dist/core/gate.js.map +1 -0
  96. package/dist/core/index.d.ts +7 -0
  97. package/dist/core/index.d.ts.map +1 -0
  98. package/dist/core/index.js +7 -0
  99. package/dist/core/index.js.map +1 -0
  100. package/dist/core/lifecycle.d.ts +89 -0
  101. package/dist/core/lifecycle.d.ts.map +1 -0
  102. package/dist/core/lifecycle.js +356 -0
  103. package/dist/core/lifecycle.js.map +1 -0
  104. package/dist/core/template-resolver.d.ts +56 -0
  105. package/dist/core/template-resolver.d.ts.map +1 -0
  106. package/dist/core/template-resolver.js +261 -0
  107. package/dist/core/template-resolver.js.map +1 -0
  108. package/dist/core/types.d.ts +527 -0
  109. package/dist/core/types.d.ts.map +1 -0
  110. package/dist/core/types.js +8 -0
  111. package/dist/core/types.js.map +1 -0
  112. package/dist/discover/scanner.d.ts +58 -0
  113. package/dist/discover/scanner.d.ts.map +1 -0
  114. package/dist/discover/scanner.js +772 -0
  115. package/dist/discover/scanner.js.map +1 -0
  116. package/dist/events/event-bus.d.ts +53 -0
  117. package/dist/events/event-bus.d.ts.map +1 -0
  118. package/dist/events/event-bus.js +263 -0
  119. package/dist/events/event-bus.js.map +1 -0
  120. package/dist/events/plugin-hooks.d.ts +44 -0
  121. package/dist/events/plugin-hooks.d.ts.map +1 -0
  122. package/dist/events/plugin-hooks.js +225 -0
  123. package/dist/events/plugin-hooks.js.map +1 -0
  124. package/dist/orchestrator/budget-packer.d.ts +57 -0
  125. package/dist/orchestrator/budget-packer.d.ts.map +1 -0
  126. package/dist/orchestrator/budget-packer.js +70 -0
  127. package/dist/orchestrator/budget-packer.js.map +1 -0
  128. package/dist/orchestrator/config.d.ts +43 -0
  129. package/dist/orchestrator/config.d.ts.map +1 -0
  130. package/dist/orchestrator/config.js +109 -0
  131. package/dist/orchestrator/config.js.map +1 -0
  132. package/dist/orchestrator/endpoint-clients.d.ts +23 -0
  133. package/dist/orchestrator/endpoint-clients.d.ts.map +1 -0
  134. package/dist/orchestrator/endpoint-clients.js +129 -0
  135. package/dist/orchestrator/endpoint-clients.js.map +1 -0
  136. package/dist/orchestrator/orchestrator-loop.d.ts +93 -0
  137. package/dist/orchestrator/orchestrator-loop.d.ts.map +1 -0
  138. package/dist/orchestrator/orchestrator-loop.js +185 -0
  139. package/dist/orchestrator/orchestrator-loop.js.map +1 -0
  140. package/dist/security/confirmation.d.ts +90 -0
  141. package/dist/security/confirmation.d.ts.map +1 -0
  142. package/dist/security/confirmation.js +140 -0
  143. package/dist/security/confirmation.js.map +1 -0
  144. package/dist/setup/setup-compute.d.ts +54 -0
  145. package/dist/setup/setup-compute.d.ts.map +1 -0
  146. package/dist/setup/setup-compute.js +66 -0
  147. package/dist/setup/setup-compute.js.map +1 -0
  148. package/dist/state/snapshot-store.d.ts +79 -0
  149. package/dist/state/snapshot-store.d.ts.map +1 -0
  150. package/dist/state/snapshot-store.js +164 -0
  151. package/dist/state/snapshot-store.js.map +1 -0
  152. package/dist/state/state-recovery.d.ts +87 -0
  153. package/dist/state/state-recovery.d.ts.map +1 -0
  154. package/dist/state/state-recovery.js +274 -0
  155. package/dist/state/state-recovery.js.map +1 -0
  156. package/dist/state/transition-log.d.ts +40 -0
  157. package/dist/state/transition-log.d.ts.map +1 -0
  158. package/dist/state/transition-log.js +83 -0
  159. package/dist/state/transition-log.js.map +1 -0
  160. package/dist/state/yaml-helpers.d.ts +53 -0
  161. package/dist/state/yaml-helpers.d.ts.map +1 -0
  162. package/dist/state/yaml-helpers.js +110 -0
  163. package/dist/state/yaml-helpers.js.map +1 -0
  164. package/examples/.gitkeep +0 -0
  165. package/guidance/accessibility.md +67 -0
  166. package/guidance/api-conventions.md +124 -0
  167. package/guidance/index.yaml +7 -0
  168. package/guidance/secure-defaults.md +71 -0
  169. package/package.json +52 -0
  170. package/phases/.gitkeep +0 -0
  171. package/skills/00-overview.md +74 -0
  172. package/skills/10-ideation.md +45 -0
  173. package/skills/20-requirements.md +54 -0
  174. package/skills/30-design.md +57 -0
  175. package/skills/40-implementation.md +64 -0
  176. package/skills/50-testing.md +60 -0
  177. package/skills/60-deployment.md +63 -0
  178. package/skills/70-maintenance.md +56 -0
  179. package/skills/80-review.md +85 -0
  180. package/skills/81-continue.md +57 -0
  181. package/skills/82-add-action.md +71 -0
  182. package/templates/.gitkeep +0 -0
  183. package/templates/bugfix.yaml +14 -0
  184. package/templates/full-feature.yaml +44 -0
  185. package/templates/micro-task.yaml +11 -0
  186. package/templates/quick-feature.yaml +20 -0
  187. package/templates/spike.yaml +15 -0
@@ -0,0 +1,772 @@
1
+ /**
2
+ * Codebase scanner for the AIDLC Discovery Engine.
3
+ *
4
+ * Static analysis only — no build, no execution (Req 15.7).
5
+ * Detects languages, frameworks, conventions and architecture.
6
+ * Writes Context_Docs to `.aidlc/context/`.
7
+ *
8
+ * Requirements: 15.1, 15.2, 15.5
9
+ */
10
+ import { readFileSync, readdirSync, statSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
11
+ import { join, extname, relative, basename } from 'node:path';
12
+ // ---------------------------------------------------------------------------
13
+ // Constants
14
+ // ---------------------------------------------------------------------------
15
+ /** Extension-to-language mapping for detection. */
16
+ const EXTENSION_MAP = {
17
+ '.ts': 'TypeScript',
18
+ '.tsx': 'TypeScript',
19
+ '.js': 'JavaScript',
20
+ '.jsx': 'JavaScript',
21
+ '.py': 'Python',
22
+ '.go': 'Go',
23
+ '.rs': 'Rust',
24
+ '.java': 'Java',
25
+ '.kt': 'Kotlin',
26
+ '.rb': 'Ruby',
27
+ '.php': 'PHP',
28
+ '.cs': 'C#',
29
+ '.cpp': 'C++',
30
+ '.cc': 'C++',
31
+ '.c': 'C',
32
+ '.h': 'C',
33
+ '.swift': 'Swift',
34
+ '.scala': 'Scala',
35
+ '.zig': 'Zig',
36
+ '.lua': 'Lua',
37
+ '.ex': 'Elixir',
38
+ '.exs': 'Elixir',
39
+ '.erl': 'Erlang',
40
+ '.hs': 'Haskell',
41
+ '.ml': 'OCaml',
42
+ '.dart': 'Dart',
43
+ '.r': 'R',
44
+ '.R': 'R',
45
+ };
46
+ /** Directories to always skip during scanning. */
47
+ const SKIP_DIRS = new Set([
48
+ 'node_modules',
49
+ '.git',
50
+ '.hg',
51
+ '.svn',
52
+ 'dist',
53
+ 'build',
54
+ 'out',
55
+ 'target',
56
+ '.next',
57
+ '.nuxt',
58
+ '__pycache__',
59
+ '.venv',
60
+ 'venv',
61
+ 'vendor',
62
+ '.aidlc',
63
+ '.kiro',
64
+ '.claude',
65
+ '.cursor',
66
+ '.windsurf',
67
+ 'coverage',
68
+ ]);
69
+ /** Maximum files to walk (prevents runaway on monorepos). */
70
+ const MAX_FILES = 10_000;
71
+ /** Number of source files to sample for style inference. */
72
+ const STYLE_SAMPLE_SIZE = 10;
73
+ // ---------------------------------------------------------------------------
74
+ // File walker (respects .gitignore patterns via SKIP_DIRS)
75
+ // ---------------------------------------------------------------------------
76
+ /**
77
+ * Walk a directory tree collecting file paths.
78
+ * Respects .gitignore by skipping common ignored directories.
79
+ * Static analysis only — no execution (Req 15.7).
80
+ */
81
+ function walkFiles(root, gitignorePatterns) {
82
+ const files = [];
83
+ function walk(dir) {
84
+ if (files.length >= MAX_FILES)
85
+ return;
86
+ let entries;
87
+ try {
88
+ entries = readdirSync(dir);
89
+ }
90
+ catch {
91
+ return;
92
+ }
93
+ for (const entry of entries) {
94
+ if (files.length >= MAX_FILES)
95
+ return;
96
+ const fullPath = join(dir, entry);
97
+ const rel = relative(root, fullPath);
98
+ // Skip hidden files/dirs (except known config files)
99
+ if (entry.startsWith('.') && !entry.startsWith('.env'))
100
+ continue;
101
+ let stat;
102
+ try {
103
+ stat = statSync(fullPath);
104
+ }
105
+ catch {
106
+ continue;
107
+ }
108
+ if (stat.isDirectory()) {
109
+ if (SKIP_DIRS.has(entry))
110
+ continue;
111
+ if (matchesGitignore(rel, gitignorePatterns))
112
+ continue;
113
+ walk(fullPath);
114
+ }
115
+ else if (stat.isFile()) {
116
+ if (matchesGitignore(rel, gitignorePatterns))
117
+ continue;
118
+ files.push(fullPath);
119
+ }
120
+ }
121
+ }
122
+ walk(root);
123
+ return files;
124
+ }
125
+ /**
126
+ * Simple gitignore pattern matching (supports basic glob patterns).
127
+ * Returns true if the relative path matches any pattern.
128
+ */
129
+ function matchesGitignore(relPath, patterns) {
130
+ for (const pattern of patterns) {
131
+ if (!pattern || pattern.startsWith('#'))
132
+ continue;
133
+ const trimmed = pattern.trim();
134
+ if (!trimmed)
135
+ continue;
136
+ // Simple prefix/suffix matching for common gitignore patterns
137
+ const normalized = trimmed.replace(/\/$/, '');
138
+ if (relPath === normalized)
139
+ return true;
140
+ if (relPath.startsWith(normalized + '/'))
141
+ return true;
142
+ // Handle wildcard patterns like *.log
143
+ if (normalized.startsWith('*')) {
144
+ const suffix = normalized.slice(1);
145
+ if (relPath.endsWith(suffix))
146
+ return true;
147
+ if (basename(relPath).endsWith(suffix))
148
+ return true;
149
+ }
150
+ }
151
+ return false;
152
+ }
153
+ /**
154
+ * Load .gitignore patterns from a project root.
155
+ */
156
+ function loadGitignore(root) {
157
+ const gitignorePath = join(root, '.gitignore');
158
+ if (!existsSync(gitignorePath))
159
+ return [];
160
+ try {
161
+ return readFileSync(gitignorePath, 'utf8').split('\n');
162
+ }
163
+ catch {
164
+ return [];
165
+ }
166
+ }
167
+ // ---------------------------------------------------------------------------
168
+ // Language detection
169
+ // ---------------------------------------------------------------------------
170
+ /**
171
+ * Count files by extension and map to language names.
172
+ * Returns sorted by file count descending.
173
+ */
174
+ function detectLanguages(files) {
175
+ const counts = new Map();
176
+ for (const file of files) {
177
+ const ext = extname(file).toLowerCase();
178
+ const lang = EXTENSION_MAP[ext];
179
+ if (lang) {
180
+ counts.set(lang, (counts.get(lang) ?? 0) + 1);
181
+ }
182
+ }
183
+ const totalSource = Array.from(counts.values()).reduce((a, b) => a + b, 0);
184
+ if (totalSource === 0)
185
+ return [];
186
+ return Array.from(counts.entries())
187
+ .map(([name, fileCount]) => ({
188
+ name,
189
+ fileCount,
190
+ percentage: Math.round((fileCount / totalSource) * 100),
191
+ }))
192
+ .sort((a, b) => b.fileCount - a.fileCount);
193
+ }
194
+ const FRAMEWORK_DETECTORS = [
195
+ {
196
+ manifest: 'package.json',
197
+ detect: (content) => {
198
+ const found = [];
199
+ try {
200
+ const pkg = JSON.parse(content);
201
+ const allDeps = {
202
+ ...pkg.dependencies,
203
+ ...pkg.devDependencies,
204
+ };
205
+ found.push('Node.js');
206
+ const checks = [
207
+ ['react', 'React'],
208
+ ['next', 'Next.js'],
209
+ ['express', 'Express'],
210
+ ['fastify', 'Fastify'],
211
+ ['vue', 'Vue'],
212
+ ['@angular/core', 'Angular'],
213
+ ['svelte', 'Svelte'],
214
+ ['nuxt', 'Nuxt'],
215
+ ['nestjs', 'NestJS'],
216
+ ['@nestjs/core', 'NestJS'],
217
+ ['hono', 'Hono'],
218
+ ['astro', 'Astro'],
219
+ ['remix', 'Remix'],
220
+ ['@remix-run/node', 'Remix'],
221
+ ['vite', 'Vite'],
222
+ ['webpack', 'Webpack'],
223
+ ['esbuild', 'esbuild'],
224
+ ['tailwindcss', 'Tailwind CSS'],
225
+ ['prisma', 'Prisma'],
226
+ ['@prisma/client', 'Prisma'],
227
+ ['drizzle-orm', 'Drizzle'],
228
+ ['vitest', 'Vitest'],
229
+ ['jest', 'Jest'],
230
+ ['mocha', 'Mocha'],
231
+ ];
232
+ for (const [dep, name] of checks) {
233
+ if (dep in allDeps)
234
+ found.push(name);
235
+ }
236
+ }
237
+ catch {
238
+ // Invalid JSON — skip
239
+ }
240
+ return found;
241
+ },
242
+ },
243
+ {
244
+ manifest: 'pyproject.toml',
245
+ detect: (content) => {
246
+ const found = ['Python'];
247
+ const lower = content.toLowerCase();
248
+ if (lower.includes('django'))
249
+ found.push('Django');
250
+ if (lower.includes('flask'))
251
+ found.push('Flask');
252
+ if (lower.includes('fastapi'))
253
+ found.push('FastAPI');
254
+ if (lower.includes('pytest'))
255
+ found.push('pytest');
256
+ if (lower.includes('poetry'))
257
+ found.push('Poetry');
258
+ return found;
259
+ },
260
+ },
261
+ {
262
+ manifest: 'requirements.txt',
263
+ detect: (content) => {
264
+ const found = ['Python'];
265
+ const lower = content.toLowerCase();
266
+ if (lower.includes('django'))
267
+ found.push('Django');
268
+ if (lower.includes('flask'))
269
+ found.push('Flask');
270
+ if (lower.includes('fastapi'))
271
+ found.push('FastAPI');
272
+ return found;
273
+ },
274
+ },
275
+ {
276
+ manifest: 'go.mod',
277
+ detect: (_content) => ['Go'],
278
+ },
279
+ {
280
+ manifest: 'Cargo.toml',
281
+ detect: (content) => {
282
+ const found = ['Rust'];
283
+ const lower = content.toLowerCase();
284
+ if (lower.includes('actix'))
285
+ found.push('Actix');
286
+ if (lower.includes('tokio'))
287
+ found.push('Tokio');
288
+ if (lower.includes('axum'))
289
+ found.push('Axum');
290
+ return found;
291
+ },
292
+ },
293
+ {
294
+ manifest: 'pom.xml',
295
+ detect: (content) => {
296
+ const found = ['Java', 'Maven'];
297
+ const lower = content.toLowerCase();
298
+ if (lower.includes('spring'))
299
+ found.push('Spring');
300
+ return found;
301
+ },
302
+ },
303
+ {
304
+ manifest: 'build.gradle',
305
+ detect: (content) => {
306
+ const found = ['Gradle'];
307
+ const lower = content.toLowerCase();
308
+ if (lower.includes('kotlin'))
309
+ found.push('Kotlin');
310
+ else
311
+ found.push('Java');
312
+ if (lower.includes('spring'))
313
+ found.push('Spring');
314
+ return found;
315
+ },
316
+ },
317
+ ];
318
+ /**
319
+ * Detect frameworks by checking for manifest files at the project root.
320
+ */
321
+ function detectFrameworks(root) {
322
+ const found = new Set();
323
+ for (const detector of FRAMEWORK_DETECTORS) {
324
+ const manifestPath = join(root, detector.manifest);
325
+ if (!existsSync(manifestPath))
326
+ continue;
327
+ try {
328
+ const content = readFileSync(manifestPath, 'utf8');
329
+ for (const fw of detector.detect(content)) {
330
+ found.add(fw);
331
+ }
332
+ }
333
+ catch {
334
+ // Skip unreadable manifests
335
+ }
336
+ }
337
+ return Array.from(found).sort();
338
+ }
339
+ // ---------------------------------------------------------------------------
340
+ // Style inference (sampled)
341
+ // ---------------------------------------------------------------------------
342
+ /** Source file extensions to consider for style analysis. */
343
+ const SOURCE_EXTENSIONS = new Set([
344
+ '.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs', '.java', '.kt', '.rb',
345
+ ]);
346
+ /**
347
+ * Infer code style conventions from a sample of source files.
348
+ * Analyzes indentation, naming, and import patterns.
349
+ */
350
+ function inferConventions(files, root) {
351
+ // Select first N source files for sampling
352
+ const sourceFiles = files
353
+ .filter((f) => SOURCE_EXTENSIONS.has(extname(f).toLowerCase()))
354
+ .slice(0, STYLE_SAMPLE_SIZE);
355
+ if (sourceFiles.length === 0) {
356
+ return { indentation: 'unknown', naming: 'unknown', importStyle: 'unknown' };
357
+ }
358
+ const indentCounts = { twoSpace: 0, fourSpace: 0, tab: 0 };
359
+ const namingCounts = { camelCase: 0, snake_case: 0, PascalCase: 0 };
360
+ let groupedImports = 0;
361
+ let ungroupedImports = 0;
362
+ for (const filePath of sourceFiles) {
363
+ let content;
364
+ try {
365
+ content = readFileSync(filePath, 'utf8');
366
+ }
367
+ catch {
368
+ continue;
369
+ }
370
+ const lines = content.split('\n');
371
+ analyzeIndentation(lines, indentCounts);
372
+ analyzeNaming(content, namingCounts);
373
+ analyzeImports(lines, filePath, (grouped) => {
374
+ if (grouped)
375
+ groupedImports++;
376
+ else
377
+ ungroupedImports++;
378
+ });
379
+ }
380
+ return {
381
+ indentation: resolveIndentation(indentCounts),
382
+ naming: resolveNaming(namingCounts),
383
+ importStyle: groupedImports > ungroupedImports ? 'grouped' : 'ungrouped',
384
+ };
385
+ }
386
+ function analyzeIndentation(lines, counts) {
387
+ for (const line of lines) {
388
+ if (!line || line.trim() === '')
389
+ continue;
390
+ const match = line.match(/^(\s+)/);
391
+ if (!match)
392
+ continue;
393
+ const indent = match[1];
394
+ if (indent.includes('\t')) {
395
+ counts.tab++;
396
+ }
397
+ else if (indent.length % 4 === 0) {
398
+ counts.fourSpace++;
399
+ }
400
+ else if (indent.length % 2 === 0) {
401
+ counts.twoSpace++;
402
+ }
403
+ }
404
+ }
405
+ function analyzeNaming(content, counts) {
406
+ // Look for variable/function declarations
407
+ const camelRe = /\b(?:const|let|var|function)\s+([a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*)\b/g;
408
+ const snakeRe = /\b(?:const|let|var|function|def)\s+([a-z][a-z0-9]*_[a-z0-9_]*)\b/g;
409
+ const pascalRe = /\b(?:class|interface|type|struct|enum)\s+([A-Z][a-zA-Z0-9]+)\b/g;
410
+ let match;
411
+ while ((match = camelRe.exec(content)) !== null) {
412
+ if (match[1])
413
+ counts.camelCase++;
414
+ }
415
+ while ((match = snakeRe.exec(content)) !== null) {
416
+ if (match[1])
417
+ counts.snake_case++;
418
+ }
419
+ while ((match = pascalRe.exec(content)) !== null) {
420
+ if (match[1])
421
+ counts.PascalCase++;
422
+ }
423
+ }
424
+ function analyzeImports(lines, filePath, report) {
425
+ const ext = extname(filePath).toLowerCase();
426
+ const importLines = [];
427
+ for (let i = 0; i < lines.length; i++) {
428
+ const line = lines[i];
429
+ if (line.startsWith('import ') ||
430
+ line.startsWith('from ') ||
431
+ (ext === '.py' && line.match(/^(?:import|from)\s/))) {
432
+ importLines.push(i);
433
+ }
434
+ }
435
+ if (importLines.length < 3)
436
+ return;
437
+ // Check for blank lines between import groups (indicates grouping)
438
+ let hasGap = false;
439
+ for (let i = 1; i < importLines.length; i++) {
440
+ const diff = importLines[i] - importLines[i - 1];
441
+ if (diff > 1) {
442
+ // Check if the gap contains a blank line
443
+ for (let j = importLines[i - 1] + 1; j < importLines[i]; j++) {
444
+ if (lines[j]?.trim() === '') {
445
+ hasGap = true;
446
+ break;
447
+ }
448
+ }
449
+ }
450
+ if (hasGap)
451
+ break;
452
+ }
453
+ report(hasGap);
454
+ }
455
+ function resolveIndentation(counts) {
456
+ const max = Math.max(counts.twoSpace, counts.fourSpace, counts.tab);
457
+ if (max === 0)
458
+ return 'unknown';
459
+ if (counts.tab === max)
460
+ return 'tabs';
461
+ if (counts.fourSpace === max)
462
+ return '4-space';
463
+ return '2-space';
464
+ }
465
+ function resolveNaming(counts) {
466
+ const max = Math.max(counts.camelCase, counts.snake_case, counts.PascalCase);
467
+ if (max === 0)
468
+ return 'unknown';
469
+ if (counts.snake_case === max)
470
+ return 'snake_case';
471
+ if (counts.PascalCase === max)
472
+ return 'PascalCase';
473
+ return 'camelCase';
474
+ }
475
+ // ---------------------------------------------------------------------------
476
+ // Architecture inference
477
+ // ---------------------------------------------------------------------------
478
+ /** Known directory role patterns. */
479
+ const DIR_ROLES = [
480
+ [/^src$/i, 'source'],
481
+ [/^lib$/i, 'library'],
482
+ [/^test(s)?$/i, 'tests'],
483
+ [/^spec(s)?$/i, 'tests'],
484
+ [/^__tests__$/i, 'tests'],
485
+ [/^doc(s)?$/i, 'documentation'],
486
+ [/^config$/i, 'configuration'],
487
+ [/^scripts?$/i, 'scripts'],
488
+ [/^bin$/i, 'binaries'],
489
+ [/^public$/i, 'static assets'],
490
+ [/^static$/i, 'static assets'],
491
+ [/^assets$/i, 'assets'],
492
+ [/^components?$/i, 'components'],
493
+ [/^pages?$/i, 'pages/routes'],
494
+ [/^api$/i, 'API layer'],
495
+ [/^routes?$/i, 'routes'],
496
+ [/^middleware$/i, 'middleware'],
497
+ [/^models?$/i, 'models/data'],
498
+ [/^services?$/i, 'services'],
499
+ [/^utils?$/i, 'utilities'],
500
+ [/^helpers?$/i, 'helpers'],
501
+ [/^types?$/i, 'type definitions'],
502
+ [/^styles?$/i, 'styles'],
503
+ [/^migrations?$/i, 'database migrations'],
504
+ ];
505
+ /**
506
+ * Infer architecture from directory structure and file presence.
507
+ */
508
+ function inferArchitecture(root, files) {
509
+ // Detect directory roles from top-level directories
510
+ const topDirs = getTopLevelDirs(root);
511
+ const roles = [];
512
+ for (const dir of topDirs) {
513
+ for (const [pattern, role] of DIR_ROLES) {
514
+ if (pattern.test(dir)) {
515
+ roles.push(`${dir}/ → ${role}`);
516
+ break;
517
+ }
518
+ }
519
+ }
520
+ const structure = roles.length > 0
521
+ ? roles.join(', ')
522
+ : 'flat structure';
523
+ // Detect entry points
524
+ const entryPoints = detectEntryPoints(root, files);
525
+ // Detect config approach
526
+ const configApproach = detectConfigApproach(root);
527
+ return { structure, entryPoints, configApproach };
528
+ }
529
+ function getTopLevelDirs(root) {
530
+ try {
531
+ return readdirSync(root)
532
+ .filter((entry) => {
533
+ if (entry.startsWith('.'))
534
+ return false;
535
+ if (SKIP_DIRS.has(entry))
536
+ return false;
537
+ try {
538
+ return statSync(join(root, entry)).isDirectory();
539
+ }
540
+ catch {
541
+ return false;
542
+ }
543
+ })
544
+ .sort();
545
+ }
546
+ catch {
547
+ return [];
548
+ }
549
+ }
550
+ function detectEntryPoints(root, files) {
551
+ const entryPoints = [];
552
+ // Check package.json for main/bin entries
553
+ const pkgPath = join(root, 'package.json');
554
+ if (existsSync(pkgPath)) {
555
+ try {
556
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
557
+ if (pkg.main)
558
+ entryPoints.push(pkg.main);
559
+ if (typeof pkg.bin === 'string') {
560
+ entryPoints.push(pkg.bin);
561
+ }
562
+ else if (pkg.bin && typeof pkg.bin === 'object') {
563
+ entryPoints.push(...Object.values(pkg.bin));
564
+ }
565
+ }
566
+ catch {
567
+ // skip
568
+ }
569
+ }
570
+ // Common entry point file names
571
+ const commonEntries = [
572
+ 'index.ts', 'index.js', 'main.ts', 'main.js',
573
+ 'app.ts', 'app.js', 'server.ts', 'server.js',
574
+ 'main.py', 'app.py', 'manage.py',
575
+ 'main.go', 'cmd/main.go',
576
+ 'main.rs', 'src/main.rs', 'src/lib.rs',
577
+ ];
578
+ for (const entry of commonEntries) {
579
+ const fullPath = join(root, entry);
580
+ if (existsSync(fullPath)) {
581
+ if (!entryPoints.includes(entry)) {
582
+ entryPoints.push(entry);
583
+ }
584
+ }
585
+ }
586
+ // Also check for entry points in found files at src/ root
587
+ for (const file of files) {
588
+ const rel = relative(root, file);
589
+ if (rel.match(/^src\/(?:index|main|app|cli)\.[tj]s$/)) {
590
+ if (!entryPoints.includes(rel)) {
591
+ entryPoints.push(rel);
592
+ }
593
+ }
594
+ }
595
+ return entryPoints.slice(0, 10); // Cap to avoid excessive output
596
+ }
597
+ function detectConfigApproach(root) {
598
+ const approaches = [];
599
+ const configFiles = [
600
+ ['.env', 'environment variables (.env)'],
601
+ ['.env.example', 'environment variables (.env)'],
602
+ ['config.yaml', 'YAML configuration'],
603
+ ['config.yml', 'YAML configuration'],
604
+ ['config.json', 'JSON configuration'],
605
+ ['tsconfig.json', 'TypeScript config'],
606
+ ['webpack.config.js', 'Webpack'],
607
+ ['vite.config.ts', 'Vite'],
608
+ ['vite.config.js', 'Vite'],
609
+ ['vitest.config.ts', 'Vitest'],
610
+ ['eslint.config.js', 'ESLint'],
611
+ ['.eslintrc.json', 'ESLint'],
612
+ ['.eslintrc.js', 'ESLint'],
613
+ ['prettier.config.js', 'Prettier'],
614
+ ['.prettierrc', 'Prettier'],
615
+ ['docker-compose.yml', 'Docker Compose'],
616
+ ['Dockerfile', 'Docker'],
617
+ ];
618
+ const seen = new Set();
619
+ for (const [file, approach] of configFiles) {
620
+ if (existsSync(join(root, file)) && !seen.has(approach)) {
621
+ seen.add(approach);
622
+ approaches.push(approach);
623
+ }
624
+ }
625
+ if (approaches.length === 0)
626
+ return 'none detected';
627
+ return approaches.join(', ');
628
+ }
629
+ // ---------------------------------------------------------------------------
630
+ // Main scanner
631
+ // ---------------------------------------------------------------------------
632
+ /**
633
+ * Scan a codebase and produce a structured scan result.
634
+ * Static analysis only — no build, no execution (Req 15.7).
635
+ *
636
+ * @param projectRoot - Absolute path to the project root.
637
+ * @returns Structured scan result with languages, frameworks, conventions, and architecture.
638
+ */
639
+ export function scanCodebase(projectRoot) {
640
+ const gitignorePatterns = loadGitignore(projectRoot);
641
+ const files = walkFiles(projectRoot, gitignorePatterns);
642
+ const languages = detectLanguages(files);
643
+ const frameworks = detectFrameworks(projectRoot);
644
+ const conventions = inferConventions(files, projectRoot);
645
+ const architecture = inferArchitecture(projectRoot, files);
646
+ return {
647
+ languages,
648
+ frameworks,
649
+ conventions,
650
+ architecture,
651
+ filesScanned: files.length,
652
+ };
653
+ }
654
+ // ---------------------------------------------------------------------------
655
+ // Output renderers
656
+ // ---------------------------------------------------------------------------
657
+ /**
658
+ * Render a style-guide.md Context_Doc from scan results.
659
+ * Includes `generated_by: aidlc-discover` frontmatter.
660
+ */
661
+ export function renderStyleGuide(result) {
662
+ const lines = [
663
+ '---',
664
+ 'generated_by: aidlc-discover',
665
+ '---',
666
+ '',
667
+ '# Style Guide',
668
+ '',
669
+ '## Languages',
670
+ '',
671
+ ];
672
+ if (result.languages.length > 0) {
673
+ lines.push('| Language | Files | Percentage |');
674
+ lines.push('|----------|-------|------------|');
675
+ for (const lang of result.languages) {
676
+ lines.push(`| ${lang.name} | ${lang.fileCount} | ${lang.percentage}% |`);
677
+ }
678
+ }
679
+ else {
680
+ lines.push('No source files detected.');
681
+ }
682
+ lines.push('');
683
+ lines.push('## Conventions');
684
+ lines.push('');
685
+ lines.push(`- **Indentation**: ${result.conventions.indentation}`);
686
+ lines.push(`- **Naming convention**: ${result.conventions.naming}`);
687
+ lines.push(`- **Import style**: ${result.conventions.importStyle}`);
688
+ lines.push('');
689
+ lines.push('## Frameworks & Tools');
690
+ lines.push('');
691
+ if (result.frameworks.length > 0) {
692
+ for (const fw of result.frameworks) {
693
+ lines.push(`- ${fw}`);
694
+ }
695
+ }
696
+ else {
697
+ lines.push('No frameworks detected.');
698
+ }
699
+ lines.push('');
700
+ return lines.join('\n');
701
+ }
702
+ /**
703
+ * Render an architecture.md Context_Doc from scan results.
704
+ * Includes `generated_by: aidlc-discover` frontmatter.
705
+ */
706
+ export function renderArchitectureDoc(result) {
707
+ const lines = [
708
+ '---',
709
+ 'generated_by: aidlc-discover',
710
+ '---',
711
+ '',
712
+ '# Architecture Overview',
713
+ '',
714
+ '## Tech Stack',
715
+ '',
716
+ ];
717
+ if (result.languages.length > 0) {
718
+ const primary = result.languages.slice(0, 3).map((l) => l.name);
719
+ lines.push(`Primary language(s): ${primary.join(', ')}`);
720
+ }
721
+ if (result.frameworks.length > 0) {
722
+ lines.push(`Frameworks/tools: ${result.frameworks.join(', ')}`);
723
+ }
724
+ lines.push('');
725
+ lines.push('## Directory Structure');
726
+ lines.push('');
727
+ lines.push(`Layout: ${result.architecture.structure}`);
728
+ lines.push('');
729
+ lines.push('## Entry Points');
730
+ lines.push('');
731
+ if (result.architecture.entryPoints.length > 0) {
732
+ for (const ep of result.architecture.entryPoints) {
733
+ lines.push(`- \`${ep}\``);
734
+ }
735
+ }
736
+ else {
737
+ lines.push('No entry points detected.');
738
+ }
739
+ lines.push('');
740
+ lines.push('## Configuration');
741
+ lines.push('');
742
+ lines.push(`Approach: ${result.architecture.configApproach}`);
743
+ lines.push('');
744
+ return lines.join('\n');
745
+ }
746
+ // ---------------------------------------------------------------------------
747
+ // Write Context_Docs to disk
748
+ // ---------------------------------------------------------------------------
749
+ /**
750
+ * Write generated Context_Docs to `.aidlc/context/`.
751
+ * Creates the directory if it doesn't exist.
752
+ * Total output ≤ 100KB, ≤ 10 files (Req 15.5, 17.2).
753
+ *
754
+ * @param projectRoot - Absolute path to the project root.
755
+ * @param result - Scan result to render.
756
+ * @returns Paths of written files relative to projectRoot.
757
+ */
758
+ export function writeContextDocs(projectRoot, result) {
759
+ const contextDir = join(projectRoot, '.aidlc', 'context');
760
+ mkdirSync(contextDir, { recursive: true });
761
+ const styleGuide = renderStyleGuide(result);
762
+ const architectureDoc = renderArchitectureDoc(result);
763
+ const stylePath = join(contextDir, 'style-guide.md');
764
+ const archPath = join(contextDir, 'architecture.md');
765
+ writeFileSync(stylePath, styleGuide, 'utf8');
766
+ writeFileSync(archPath, architectureDoc, 'utf8');
767
+ return [
768
+ '.aidlc/context/style-guide.md',
769
+ '.aidlc/context/architecture.md',
770
+ ];
771
+ }
772
+ //# sourceMappingURL=scanner.js.map