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.
- package/LICENSE +21 -0
- package/README.md +1078 -0
- package/dist/api/analyze.d.ts +17 -0
- package/dist/api/analyze.d.ts.map +1 -0
- package/dist/api/analyze.js +109 -0
- package/dist/api/analyze.js.map +1 -0
- package/dist/api/drift.d.ts +21 -0
- package/dist/api/drift.d.ts.map +1 -0
- package/dist/api/drift.js +145 -0
- package/dist/api/drift.js.map +1 -0
- package/dist/api/generate.d.ts +18 -0
- package/dist/api/generate.d.ts.map +1 -0
- package/dist/api/generate.js +251 -0
- package/dist/api/generate.js.map +1 -0
- package/dist/api/index.d.ts +39 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +32 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/init.d.ts +18 -0
- package/dist/api/init.d.ts.map +1 -0
- package/dist/api/init.js +82 -0
- package/dist/api/init.js.map +1 -0
- package/dist/api/run.d.ts +19 -0
- package/dist/api/run.d.ts.map +1 -0
- package/dist/api/run.js +291 -0
- package/dist/api/run.js.map +1 -0
- package/dist/api/specs.d.ts +49 -0
- package/dist/api/specs.d.ts.map +1 -0
- package/dist/api/specs.js +136 -0
- package/dist/api/specs.js.map +1 -0
- package/dist/api/types.d.ts +176 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +9 -0
- package/dist/api/types.js.map +1 -0
- package/dist/api/verify.d.ts +20 -0
- package/dist/api/verify.d.ts.map +1 -0
- package/dist/api/verify.js +117 -0
- package/dist/api/verify.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +27 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +485 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/drift.d.ts +9 -0
- package/dist/cli/commands/drift.d.ts.map +1 -0
- package/dist/cli/commands/drift.js +540 -0
- package/dist/cli/commands/drift.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +9 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +633 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +171 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +638 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +574 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/run.d.ts +24 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +546 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/verify.d.ts +9 -0
- package/dist/cli/commands/verify.d.ts.map +1 -0
- package/dist/cli/commands/verify.js +417 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/commands/view.d.ts +9 -0
- package/dist/cli/commands/view.d.ts.map +1 -0
- package/dist/cli/commands/view.js +511 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +83 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/analyzer/architecture-writer.d.ts +67 -0
- package/dist/core/analyzer/architecture-writer.d.ts.map +1 -0
- package/dist/core/analyzer/architecture-writer.js +209 -0
- package/dist/core/analyzer/architecture-writer.js.map +1 -0
- package/dist/core/analyzer/artifact-generator.d.ts +222 -0
- package/dist/core/analyzer/artifact-generator.d.ts.map +1 -0
- package/dist/core/analyzer/artifact-generator.js +726 -0
- package/dist/core/analyzer/artifact-generator.js.map +1 -0
- package/dist/core/analyzer/call-graph.d.ts +83 -0
- package/dist/core/analyzer/call-graph.d.ts.map +1 -0
- package/dist/core/analyzer/call-graph.js +827 -0
- package/dist/core/analyzer/call-graph.js.map +1 -0
- package/dist/core/analyzer/code-shaper.d.ts +33 -0
- package/dist/core/analyzer/code-shaper.d.ts.map +1 -0
- package/dist/core/analyzer/code-shaper.js +149 -0
- package/dist/core/analyzer/code-shaper.js.map +1 -0
- package/dist/core/analyzer/dependency-graph.d.ts +179 -0
- package/dist/core/analyzer/dependency-graph.d.ts.map +1 -0
- package/dist/core/analyzer/dependency-graph.js +574 -0
- package/dist/core/analyzer/dependency-graph.js.map +1 -0
- package/dist/core/analyzer/duplicate-detector.d.ts +52 -0
- package/dist/core/analyzer/duplicate-detector.d.ts.map +1 -0
- package/dist/core/analyzer/duplicate-detector.js +279 -0
- package/dist/core/analyzer/duplicate-detector.js.map +1 -0
- package/dist/core/analyzer/embedding-service.d.ts +50 -0
- package/dist/core/analyzer/embedding-service.d.ts.map +1 -0
- package/dist/core/analyzer/embedding-service.js +104 -0
- package/dist/core/analyzer/embedding-service.js.map +1 -0
- package/dist/core/analyzer/file-walker.d.ts +78 -0
- package/dist/core/analyzer/file-walker.d.ts.map +1 -0
- package/dist/core/analyzer/file-walker.js +531 -0
- package/dist/core/analyzer/file-walker.js.map +1 -0
- package/dist/core/analyzer/import-parser.d.ts +91 -0
- package/dist/core/analyzer/import-parser.d.ts.map +1 -0
- package/dist/core/analyzer/import-parser.js +720 -0
- package/dist/core/analyzer/import-parser.js.map +1 -0
- package/dist/core/analyzer/index.d.ts +10 -0
- package/dist/core/analyzer/index.d.ts.map +1 -0
- package/dist/core/analyzer/index.js +10 -0
- package/dist/core/analyzer/index.js.map +1 -0
- package/dist/core/analyzer/refactor-analyzer.d.ts +80 -0
- package/dist/core/analyzer/refactor-analyzer.d.ts.map +1 -0
- package/dist/core/analyzer/refactor-analyzer.js +339 -0
- package/dist/core/analyzer/refactor-analyzer.js.map +1 -0
- package/dist/core/analyzer/repository-mapper.d.ts +150 -0
- package/dist/core/analyzer/repository-mapper.d.ts.map +1 -0
- package/dist/core/analyzer/repository-mapper.js +731 -0
- package/dist/core/analyzer/repository-mapper.js.map +1 -0
- package/dist/core/analyzer/signature-extractor.d.ts +31 -0
- package/dist/core/analyzer/signature-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/signature-extractor.js +387 -0
- package/dist/core/analyzer/signature-extractor.js.map +1 -0
- package/dist/core/analyzer/significance-scorer.d.ts +79 -0
- package/dist/core/analyzer/significance-scorer.d.ts.map +1 -0
- package/dist/core/analyzer/significance-scorer.js +407 -0
- package/dist/core/analyzer/significance-scorer.js.map +1 -0
- package/dist/core/analyzer/subgraph-extractor.d.ts +43 -0
- package/dist/core/analyzer/subgraph-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/subgraph-extractor.js +129 -0
- package/dist/core/analyzer/subgraph-extractor.js.map +1 -0
- package/dist/core/analyzer/vector-index.d.ts +63 -0
- package/dist/core/analyzer/vector-index.d.ts.map +1 -0
- package/dist/core/analyzer/vector-index.js +169 -0
- package/dist/core/analyzer/vector-index.js.map +1 -0
- package/dist/core/drift/drift-detector.d.ts +102 -0
- package/dist/core/drift/drift-detector.d.ts.map +1 -0
- package/dist/core/drift/drift-detector.js +597 -0
- package/dist/core/drift/drift-detector.js.map +1 -0
- package/dist/core/drift/git-diff.d.ts +55 -0
- package/dist/core/drift/git-diff.d.ts.map +1 -0
- package/dist/core/drift/git-diff.js +356 -0
- package/dist/core/drift/git-diff.js.map +1 -0
- package/dist/core/drift/index.d.ts +12 -0
- package/dist/core/drift/index.d.ts.map +1 -0
- package/dist/core/drift/index.js +9 -0
- package/dist/core/drift/index.js.map +1 -0
- package/dist/core/drift/spec-mapper.d.ts +73 -0
- package/dist/core/drift/spec-mapper.d.ts.map +1 -0
- package/dist/core/drift/spec-mapper.js +353 -0
- package/dist/core/drift/spec-mapper.js.map +1 -0
- package/dist/core/generator/adr-generator.d.ts +32 -0
- package/dist/core/generator/adr-generator.d.ts.map +1 -0
- package/dist/core/generator/adr-generator.js +192 -0
- package/dist/core/generator/adr-generator.js.map +1 -0
- package/dist/core/generator/index.d.ts +9 -0
- package/dist/core/generator/index.d.ts.map +1 -0
- package/dist/core/generator/index.js +12 -0
- package/dist/core/generator/index.js.map +1 -0
- package/dist/core/generator/mapping-generator.d.ts +54 -0
- package/dist/core/generator/mapping-generator.d.ts.map +1 -0
- package/dist/core/generator/mapping-generator.js +239 -0
- package/dist/core/generator/mapping-generator.js.map +1 -0
- package/dist/core/generator/openspec-compat.d.ts +160 -0
- package/dist/core/generator/openspec-compat.d.ts.map +1 -0
- package/dist/core/generator/openspec-compat.js +523 -0
- package/dist/core/generator/openspec-compat.js.map +1 -0
- package/dist/core/generator/openspec-format-generator.d.ts +111 -0
- package/dist/core/generator/openspec-format-generator.d.ts.map +1 -0
- package/dist/core/generator/openspec-format-generator.js +817 -0
- package/dist/core/generator/openspec-format-generator.js.map +1 -0
- package/dist/core/generator/openspec-writer.d.ts +131 -0
- package/dist/core/generator/openspec-writer.d.ts.map +1 -0
- package/dist/core/generator/openspec-writer.js +379 -0
- package/dist/core/generator/openspec-writer.js.map +1 -0
- package/dist/core/generator/prompts.d.ts +35 -0
- package/dist/core/generator/prompts.d.ts.map +1 -0
- package/dist/core/generator/prompts.js +212 -0
- package/dist/core/generator/prompts.js.map +1 -0
- package/dist/core/generator/spec-pipeline.d.ts +94 -0
- package/dist/core/generator/spec-pipeline.d.ts.map +1 -0
- package/dist/core/generator/spec-pipeline.js +474 -0
- package/dist/core/generator/spec-pipeline.js.map +1 -0
- package/dist/core/generator/stages/stage1-survey.d.ts +19 -0
- package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -0
- package/dist/core/generator/stages/stage1-survey.js +105 -0
- package/dist/core/generator/stages/stage1-survey.js.map +1 -0
- package/dist/core/generator/stages/stage2-entities.d.ts +11 -0
- package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -0
- package/dist/core/generator/stages/stage2-entities.js +67 -0
- package/dist/core/generator/stages/stage2-entities.js.map +1 -0
- package/dist/core/generator/stages/stage3-services.d.ts +11 -0
- package/dist/core/generator/stages/stage3-services.d.ts.map +1 -0
- package/dist/core/generator/stages/stage3-services.js +75 -0
- package/dist/core/generator/stages/stage3-services.js.map +1 -0
- package/dist/core/generator/stages/stage4-api.d.ts +11 -0
- package/dist/core/generator/stages/stage4-api.d.ts.map +1 -0
- package/dist/core/generator/stages/stage4-api.js +65 -0
- package/dist/core/generator/stages/stage4-api.js.map +1 -0
- package/dist/core/generator/stages/stage5-architecture.d.ts +10 -0
- package/dist/core/generator/stages/stage5-architecture.d.ts.map +1 -0
- package/dist/core/generator/stages/stage5-architecture.js +62 -0
- package/dist/core/generator/stages/stage5-architecture.js.map +1 -0
- package/dist/core/generator/stages/stage6-adr.d.ts +8 -0
- package/dist/core/generator/stages/stage6-adr.d.ts.map +1 -0
- package/dist/core/generator/stages/stage6-adr.js +41 -0
- package/dist/core/generator/stages/stage6-adr.js.map +1 -0
- package/dist/core/services/chat-agent.d.ts +45 -0
- package/dist/core/services/chat-agent.d.ts.map +1 -0
- package/dist/core/services/chat-agent.js +310 -0
- package/dist/core/services/chat-agent.js.map +1 -0
- package/dist/core/services/chat-tools.d.ts +32 -0
- package/dist/core/services/chat-tools.d.ts.map +1 -0
- package/dist/core/services/chat-tools.js +270 -0
- package/dist/core/services/chat-tools.js.map +1 -0
- package/dist/core/services/config-manager.d.ts +61 -0
- package/dist/core/services/config-manager.d.ts.map +1 -0
- package/dist/core/services/config-manager.js +143 -0
- package/dist/core/services/config-manager.js.map +1 -0
- package/dist/core/services/gitignore-manager.d.ts +29 -0
- package/dist/core/services/gitignore-manager.d.ts.map +1 -0
- package/dist/core/services/gitignore-manager.js +106 -0
- package/dist/core/services/gitignore-manager.js.map +1 -0
- package/dist/core/services/index.d.ts +8 -0
- package/dist/core/services/index.d.ts.map +1 -0
- package/dist/core/services/index.js +8 -0
- package/dist/core/services/index.js.map +1 -0
- package/dist/core/services/llm-service.d.ts +336 -0
- package/dist/core/services/llm-service.d.ts.map +1 -0
- package/dist/core/services/llm-service.js +1155 -0
- package/dist/core/services/llm-service.js.map +1 -0
- package/dist/core/services/mcp-handlers/analysis.d.ts +42 -0
- package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/analysis.js +300 -0
- package/dist/core/services/mcp-handlers/analysis.js.map +1 -0
- package/dist/core/services/mcp-handlers/graph.d.ts +65 -0
- package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/graph.js +509 -0
- package/dist/core/services/mcp-handlers/graph.js.map +1 -0
- package/dist/core/services/mcp-handlers/semantic.d.ts +38 -0
- package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/semantic.js +172 -0
- package/dist/core/services/mcp-handlers/semantic.js.map +1 -0
- package/dist/core/services/mcp-handlers/utils.d.ts +21 -0
- package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/utils.js +62 -0
- package/dist/core/services/mcp-handlers/utils.js.map +1 -0
- package/dist/core/services/project-detector.d.ts +32 -0
- package/dist/core/services/project-detector.d.ts.map +1 -0
- package/dist/core/services/project-detector.js +111 -0
- package/dist/core/services/project-detector.js.map +1 -0
- package/dist/core/verifier/index.d.ts +5 -0
- package/dist/core/verifier/index.d.ts.map +1 -0
- package/dist/core/verifier/index.js +5 -0
- package/dist/core/verifier/index.js.map +1 -0
- package/dist/core/verifier/verification-engine.d.ts +226 -0
- package/dist/core/verifier/verification-engine.d.ts.map +1 -0
- package/dist/core/verifier/verification-engine.js +681 -0
- package/dist/core/verifier/verification-engine.js.map +1 -0
- package/dist/types/index.d.ts +252 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/pipeline.d.ts +148 -0
- package/dist/types/pipeline.d.ts.map +1 -0
- package/dist/types/pipeline.js +5 -0
- package/dist/types/pipeline.js.map +1 -0
- package/dist/utils/errors.d.ts +51 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +128 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +149 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +331 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/progress.d.ts +142 -0
- package/dist/utils/progress.d.ts.map +1 -0
- package/dist/utils/progress.js +280 -0
- package/dist/utils/progress.js.map +1 -0
- package/dist/utils/prompts.d.ts +53 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +199 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/shutdown.d.ts +89 -0
- package/dist/utils/shutdown.d.ts.map +1 -0
- package/dist/utils/shutdown.js +237 -0
- package/dist/utils/shutdown.js.map +1 -0
- package/package.json +114 -0
- package/src/viewer/InteractiveGraphViewer.jsx +1486 -0
- package/src/viewer/app/index.html +17 -0
- package/src/viewer/app/main.jsx +13 -0
- package/src/viewer/components/ArchitectureView.jsx +177 -0
- package/src/viewer/components/ChatPanel.jsx +448 -0
- package/src/viewer/components/ClusterGraph.jsx +441 -0
- package/src/viewer/components/FilterBar.jsx +179 -0
- package/src/viewer/components/FlatGraph.jsx +275 -0
- package/src/viewer/components/MicroComponents.jsx +83 -0
- package/src/viewer/hooks/usePanZoom.js +79 -0
- package/src/viewer/utils/constants.js +47 -0
- package/src/viewer/utils/graph-helpers.js +291 -0
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analysis Artifact Generator
|
|
3
|
+
*
|
|
4
|
+
* Takes all analysis results and generates structured output files
|
|
5
|
+
* that will be consumed by the LLM generation phase and optionally by humans.
|
|
6
|
+
*/
|
|
7
|
+
import { writeFile, mkdir, readFile } from 'node:fs/promises';
|
|
8
|
+
import { join, basename } from 'node:path';
|
|
9
|
+
import { toMermaidFormat } from './dependency-graph.js';
|
|
10
|
+
/**
|
|
11
|
+
* Heuristic to detect test/spec files across languages.
|
|
12
|
+
* Excludes them from call graph analysis — test helpers inflate fanIn,
|
|
13
|
+
* and test functions are never "unreachable" by definition.
|
|
14
|
+
*
|
|
15
|
+
* Patterns covered:
|
|
16
|
+
* TypeScript/JS: *.test.ts, *.spec.ts, *.test.tsx, __tests__/*, test_*.ts
|
|
17
|
+
* Python: test_*.py, *_test.py, tests/*.py
|
|
18
|
+
* Go: *_test.go
|
|
19
|
+
* Rust: files with #[cfg(test)] (not detectable here — excluded by directory pattern)
|
|
20
|
+
* Java/Kotlin: *Test.java, *Spec.kt
|
|
21
|
+
*/
|
|
22
|
+
export function isTestFile(filePath) {
|
|
23
|
+
const name = filePath.replace(/\\/g, '/');
|
|
24
|
+
return (/\.(test|spec)\.(ts|tsx|js|jsx|mjs|cjs)$/.test(name) || // JS/TS: foo.test.ts
|
|
25
|
+
/(^|\/)__tests__\//.test(name) || // JS/TS: __tests__/
|
|
26
|
+
/(^|\/)test_[^/]+\.(ts|js|py)$/.test(name) || // Python/TS: test_foo.py
|
|
27
|
+
/[^/]+_test\.(py|go)$/.test(name) || // Python/Go: foo_test.py, foo_test.go
|
|
28
|
+
/(^|\/)tests?\/[^/]+\.(py|ts|js|rb|php)$/.test(name) || // tests/ directory
|
|
29
|
+
/[A-Z][a-zA-Z0-9]*Test\.(java|kt|scala)$/.test(name) || // Java: FooTest.java
|
|
30
|
+
/[A-Z][a-zA-Z0-9]*Spec\.(kt|scala|rb)$/.test(name) // Kotlin/Ruby: FooSpec.kt
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// ARTIFACT GENERATOR
|
|
35
|
+
// ============================================================================
|
|
36
|
+
/**
|
|
37
|
+
* Generates analysis artifacts from repository map and dependency graph
|
|
38
|
+
*/
|
|
39
|
+
export class AnalysisArtifactGenerator {
|
|
40
|
+
options;
|
|
41
|
+
constructor(options) {
|
|
42
|
+
this.options = {
|
|
43
|
+
rootDir: options.rootDir,
|
|
44
|
+
outputDir: options.outputDir,
|
|
45
|
+
maxDeepAnalysisFiles: options.maxDeepAnalysisFiles ?? 20,
|
|
46
|
+
maxValidationFiles: options.maxValidationFiles ?? 5,
|
|
47
|
+
tokensPerChar: options.tokensPerChar ?? 0.25, // ~4 chars per token
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generate all artifacts
|
|
52
|
+
*/
|
|
53
|
+
async generate(repoMap, depGraph) {
|
|
54
|
+
// Generate each artifact
|
|
55
|
+
const repoStructure = this.generateRepoStructure(repoMap, depGraph);
|
|
56
|
+
const summaryMarkdown = this.generateSummaryMarkdown(repoMap, depGraph, repoStructure);
|
|
57
|
+
const dependencyDiagram = this.generateDependencyDiagram(depGraph);
|
|
58
|
+
const llmContext = await this.generateLLMContext(repoMap, depGraph);
|
|
59
|
+
return {
|
|
60
|
+
repoStructure,
|
|
61
|
+
summaryMarkdown,
|
|
62
|
+
dependencyDiagram,
|
|
63
|
+
llmContext,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Generate and save all artifacts to disk
|
|
68
|
+
*/
|
|
69
|
+
async generateAndSave(repoMap, depGraph) {
|
|
70
|
+
const artifacts = await this.generate(repoMap, depGraph);
|
|
71
|
+
// Ensure output directory exists
|
|
72
|
+
await mkdir(this.options.outputDir, { recursive: true });
|
|
73
|
+
// Save each artifact
|
|
74
|
+
await Promise.all([
|
|
75
|
+
writeFile(join(this.options.outputDir, 'repo-structure.json'), JSON.stringify(artifacts.repoStructure, null, 2)),
|
|
76
|
+
writeFile(join(this.options.outputDir, 'SUMMARY.md'), artifacts.summaryMarkdown),
|
|
77
|
+
writeFile(join(this.options.outputDir, 'dependencies.mermaid'), artifacts.dependencyDiagram),
|
|
78
|
+
writeFile(join(this.options.outputDir, 'llm-context.json'), JSON.stringify(artifacts.llmContext, null, 2)),
|
|
79
|
+
]);
|
|
80
|
+
return artifacts;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Generate repo-structure.json
|
|
84
|
+
*/
|
|
85
|
+
generateRepoStructure(repoMap, depGraph) {
|
|
86
|
+
// Detect architecture pattern
|
|
87
|
+
const architecturePattern = this.detectArchitecturePattern(repoMap, depGraph);
|
|
88
|
+
// Generate layers
|
|
89
|
+
const layers = this.generateArchitectureLayers(repoMap);
|
|
90
|
+
// Generate domains from clusters
|
|
91
|
+
const domains = this.generateDomains(repoMap, depGraph);
|
|
92
|
+
// Generate entry points
|
|
93
|
+
const entryPoints = this.generateEntryPoints(repoMap);
|
|
94
|
+
// Generate data flow
|
|
95
|
+
const dataFlow = this.generateDataFlow(repoMap);
|
|
96
|
+
// Generate key files
|
|
97
|
+
const keyFiles = this.generateKeyFiles(repoMap);
|
|
98
|
+
// Calculate statistics
|
|
99
|
+
const avgScore = repoMap.allFiles.length > 0
|
|
100
|
+
? repoMap.allFiles.reduce((sum, f) => sum + f.score, 0) / repoMap.allFiles.length
|
|
101
|
+
: 0;
|
|
102
|
+
return {
|
|
103
|
+
projectName: repoMap.metadata.projectName,
|
|
104
|
+
projectType: this.formatProjectType(repoMap.metadata.projectType),
|
|
105
|
+
frameworks: repoMap.summary.frameworks.map(f => f.name),
|
|
106
|
+
architecture: {
|
|
107
|
+
pattern: architecturePattern,
|
|
108
|
+
layers,
|
|
109
|
+
},
|
|
110
|
+
domains,
|
|
111
|
+
entryPoints,
|
|
112
|
+
dataFlow,
|
|
113
|
+
keyFiles,
|
|
114
|
+
statistics: {
|
|
115
|
+
totalFiles: repoMap.summary.totalFiles,
|
|
116
|
+
analyzedFiles: repoMap.summary.analyzedFiles,
|
|
117
|
+
skippedFiles: repoMap.summary.skippedFiles,
|
|
118
|
+
avgFileScore: Math.round(avgScore * 10) / 10,
|
|
119
|
+
nodeCount: depGraph.statistics.nodeCount,
|
|
120
|
+
edgeCount: depGraph.statistics.edgeCount,
|
|
121
|
+
cycleCount: depGraph.statistics.cycleCount,
|
|
122
|
+
clusterCount: depGraph.statistics.clusterCount,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Format project type for display
|
|
128
|
+
*/
|
|
129
|
+
formatProjectType(type) {
|
|
130
|
+
const mapping = {
|
|
131
|
+
nodejs: 'node-typescript',
|
|
132
|
+
python: 'python',
|
|
133
|
+
rust: 'rust',
|
|
134
|
+
go: 'go',
|
|
135
|
+
java: 'java',
|
|
136
|
+
ruby: 'ruby',
|
|
137
|
+
php: 'php',
|
|
138
|
+
unknown: 'unknown',
|
|
139
|
+
};
|
|
140
|
+
return mapping[type] ?? type;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Detect architecture pattern from code structure
|
|
144
|
+
*/
|
|
145
|
+
detectArchitecturePattern(repoMap, _depGraph) {
|
|
146
|
+
const dirs = repoMap.summary.directories;
|
|
147
|
+
const dirNames = dirs.map(d => basename(d.path).toLowerCase());
|
|
148
|
+
// Check for layered architecture indicators
|
|
149
|
+
const layeredIndicators = ['controllers', 'services', 'repositories', 'routes', 'models', 'views'];
|
|
150
|
+
const hasLayeredStructure = layeredIndicators.filter(i => dirNames.some(d => d.includes(i))).length >= 3;
|
|
151
|
+
// Check for modular/domain-driven indicators
|
|
152
|
+
const moduleIndicators = ['modules', 'features', 'domains'];
|
|
153
|
+
const hasModularStructure = moduleIndicators.some(i => dirNames.includes(i));
|
|
154
|
+
// Check for microservices indicators
|
|
155
|
+
const hasMultiplePackageJson = repoMap.configFiles.filter(f => f.name === 'package.json').length > 1;
|
|
156
|
+
const hasDockerCompose = repoMap.configFiles.some(f => f.name.includes('docker-compose'));
|
|
157
|
+
// Determine pattern
|
|
158
|
+
if (hasMultiplePackageJson && hasDockerCompose) {
|
|
159
|
+
return 'microservices';
|
|
160
|
+
}
|
|
161
|
+
if (hasModularStructure) {
|
|
162
|
+
return 'modular';
|
|
163
|
+
}
|
|
164
|
+
if (hasLayeredStructure) {
|
|
165
|
+
return 'layered';
|
|
166
|
+
}
|
|
167
|
+
if (repoMap.summary.totalFiles < 50) {
|
|
168
|
+
return 'monolith';
|
|
169
|
+
}
|
|
170
|
+
return 'unknown';
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Generate architecture layers
|
|
174
|
+
*/
|
|
175
|
+
generateArchitectureLayers(repoMap) {
|
|
176
|
+
const layers = [];
|
|
177
|
+
// API/Routes layer
|
|
178
|
+
const apiFiles = repoMap.allFiles.filter(f => f.directory.includes('routes') ||
|
|
179
|
+
f.directory.includes('controllers') ||
|
|
180
|
+
f.directory.includes('api') ||
|
|
181
|
+
f.name.includes('route') ||
|
|
182
|
+
f.name.includes('controller'));
|
|
183
|
+
if (apiFiles.length > 0) {
|
|
184
|
+
layers.push({
|
|
185
|
+
name: 'API Layer',
|
|
186
|
+
purpose: 'HTTP request handling and routing',
|
|
187
|
+
files: apiFiles.map(f => f.path),
|
|
188
|
+
representativeFile: apiFiles[0]?.path ?? null,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// Service/Business layer
|
|
192
|
+
const serviceFiles = repoMap.allFiles.filter(f => f.directory.includes('services') ||
|
|
193
|
+
f.directory.includes('business') ||
|
|
194
|
+
f.directory.includes('domain') ||
|
|
195
|
+
f.name.includes('service') ||
|
|
196
|
+
f.name.includes('manager'));
|
|
197
|
+
if (serviceFiles.length > 0) {
|
|
198
|
+
layers.push({
|
|
199
|
+
name: 'Service Layer',
|
|
200
|
+
purpose: 'Business logic and domain operations',
|
|
201
|
+
files: serviceFiles.map(f => f.path),
|
|
202
|
+
representativeFile: serviceFiles[0]?.path ?? null,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
// Data/Repository layer
|
|
206
|
+
const dataFiles = repoMap.allFiles.filter(f => f.directory.includes('repositories') ||
|
|
207
|
+
f.directory.includes('data') ||
|
|
208
|
+
f.directory.includes('database') ||
|
|
209
|
+
f.directory.includes('models') ||
|
|
210
|
+
f.name.includes('repository') ||
|
|
211
|
+
f.name.includes('model'));
|
|
212
|
+
if (dataFiles.length > 0) {
|
|
213
|
+
layers.push({
|
|
214
|
+
name: 'Data Layer',
|
|
215
|
+
purpose: 'Data access and persistence',
|
|
216
|
+
files: dataFiles.map(f => f.path),
|
|
217
|
+
representativeFile: dataFiles[0]?.path ?? null,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
// Infrastructure layer
|
|
221
|
+
const infraFiles = repoMap.allFiles.filter(f => f.directory.includes('infrastructure') ||
|
|
222
|
+
f.directory.includes('config') ||
|
|
223
|
+
f.directory.includes('middleware') ||
|
|
224
|
+
f.directory.includes('utils') ||
|
|
225
|
+
f.isConfig);
|
|
226
|
+
if (infraFiles.length > 0) {
|
|
227
|
+
layers.push({
|
|
228
|
+
name: 'Infrastructure Layer',
|
|
229
|
+
purpose: 'Configuration, middleware, and utilities',
|
|
230
|
+
files: infraFiles.map(f => f.path),
|
|
231
|
+
representativeFile: infraFiles[0]?.path ?? null,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return layers;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Generate domains from clusters
|
|
238
|
+
*/
|
|
239
|
+
generateDomains(repoMap, depGraph) {
|
|
240
|
+
const domains = [];
|
|
241
|
+
// Use directory-based clusters from repo map
|
|
242
|
+
for (const [dirName, files] of Object.entries(repoMap.clusters.byDomain)) {
|
|
243
|
+
if (files.length === 0)
|
|
244
|
+
continue;
|
|
245
|
+
// Skip infrastructure directories
|
|
246
|
+
const skipDirs = ['utils', 'helpers', 'common', 'shared', 'config', 'middleware'];
|
|
247
|
+
if (skipDirs.includes(dirName.toLowerCase()))
|
|
248
|
+
continue;
|
|
249
|
+
// Extract potential entities from file names
|
|
250
|
+
const entities = this.extractEntities(files);
|
|
251
|
+
// Find the key file (highest score in domain)
|
|
252
|
+
const keyFile = files.sort((a, b) => b.score - a.score)[0];
|
|
253
|
+
// Generate suggested spec path
|
|
254
|
+
const domainName = this.normalizeDomainName(dirName);
|
|
255
|
+
domains.push({
|
|
256
|
+
name: domainName,
|
|
257
|
+
suggestedSpecPath: `openspec/specs/${domainName}/spec.md`,
|
|
258
|
+
files: files.map(f => f.path),
|
|
259
|
+
entities,
|
|
260
|
+
keyFile: keyFile?.path ?? null,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
// Also consider clusters from dependency graph
|
|
264
|
+
for (const cluster of depGraph.clusters) {
|
|
265
|
+
const clusterName = this.normalizeDomainName(cluster.suggestedDomain);
|
|
266
|
+
// Skip if already covered
|
|
267
|
+
if (domains.some(d => d.name === clusterName))
|
|
268
|
+
continue;
|
|
269
|
+
// Skip small clusters
|
|
270
|
+
if (cluster.files.length < 2)
|
|
271
|
+
continue;
|
|
272
|
+
// Get file details
|
|
273
|
+
const files = cluster.files
|
|
274
|
+
.map(id => depGraph.nodes.find(n => n.id === id)?.file)
|
|
275
|
+
.filter((f) => f !== undefined);
|
|
276
|
+
if (files.length === 0)
|
|
277
|
+
continue;
|
|
278
|
+
const entities = this.extractEntities(files);
|
|
279
|
+
const keyFile = files.sort((a, b) => b.score - a.score)[0];
|
|
280
|
+
domains.push({
|
|
281
|
+
name: clusterName,
|
|
282
|
+
suggestedSpecPath: `openspec/specs/${clusterName}/spec.md`,
|
|
283
|
+
files: files.map(f => f.path),
|
|
284
|
+
entities,
|
|
285
|
+
keyFile: keyFile?.path ?? null,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return domains;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Normalize domain name for OpenSpec path
|
|
292
|
+
*/
|
|
293
|
+
normalizeDomainName(name) {
|
|
294
|
+
return name
|
|
295
|
+
.toLowerCase()
|
|
296
|
+
.replace(/[^a-z0-9]/g, '-')
|
|
297
|
+
.replace(/-+/g, '-')
|
|
298
|
+
.replace(/^-|-$/g, '') || 'misc';
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Extract potential entity names from files
|
|
302
|
+
*/
|
|
303
|
+
extractEntities(files) {
|
|
304
|
+
const entities = new Set();
|
|
305
|
+
for (const file of files) {
|
|
306
|
+
// Extract from file name
|
|
307
|
+
const name = file.name.replace(/\.(ts|js|tsx|jsx|py)$/, '');
|
|
308
|
+
// Convert to PascalCase as potential entity name
|
|
309
|
+
const entityName = name
|
|
310
|
+
.split(/[-_.]/)
|
|
311
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
312
|
+
.join('');
|
|
313
|
+
// Skip generic names
|
|
314
|
+
const skipNames = ['Index', 'Types', 'Utils', 'Helpers', 'Constants', 'Test', 'Spec'];
|
|
315
|
+
if (!skipNames.includes(entityName) && entityName.length > 2) {
|
|
316
|
+
entities.add(entityName);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return Array.from(entities).slice(0, 5); // Limit to top 5
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Generate entry points information
|
|
323
|
+
*/
|
|
324
|
+
generateEntryPoints(repoMap) {
|
|
325
|
+
return repoMap.entryPoints.map(file => {
|
|
326
|
+
// Determine entry point type
|
|
327
|
+
let type = 'application-entry';
|
|
328
|
+
if (file.name.includes('test') || file.name.includes('spec')) {
|
|
329
|
+
type = 'test-entry';
|
|
330
|
+
}
|
|
331
|
+
else if (file.name.includes('route') || file.name.includes('api')) {
|
|
332
|
+
type = 'api-entry';
|
|
333
|
+
}
|
|
334
|
+
else if (file.name.includes('build') || file.name.includes('webpack')) {
|
|
335
|
+
type = 'build-entry';
|
|
336
|
+
}
|
|
337
|
+
// Infer what gets initialized (simplified)
|
|
338
|
+
const initializes = [];
|
|
339
|
+
if (file.name.includes('app') || file.name === 'index.ts') {
|
|
340
|
+
initializes.push('application');
|
|
341
|
+
}
|
|
342
|
+
if (file.directory.includes('database')) {
|
|
343
|
+
initializes.push('database');
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
file: file.path,
|
|
347
|
+
type,
|
|
348
|
+
initializes,
|
|
349
|
+
};
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Generate data flow information
|
|
354
|
+
*/
|
|
355
|
+
generateDataFlow(repoMap) {
|
|
356
|
+
const sources = [];
|
|
357
|
+
const sinks = [];
|
|
358
|
+
const transformers = [];
|
|
359
|
+
for (const file of repoMap.allFiles) {
|
|
360
|
+
const dir = file.directory.toLowerCase();
|
|
361
|
+
const name = file.name.toLowerCase();
|
|
362
|
+
// Sources: routes, controllers, APIs
|
|
363
|
+
if (dir.includes('routes') || dir.includes('controllers') || dir.includes('api')) {
|
|
364
|
+
sources.push(file.path);
|
|
365
|
+
}
|
|
366
|
+
// Sinks: repositories, database, storage
|
|
367
|
+
else if (dir.includes('repositories') || dir.includes('database') || dir.includes('storage')) {
|
|
368
|
+
sinks.push(file.path);
|
|
369
|
+
}
|
|
370
|
+
// Transformers: services, middleware
|
|
371
|
+
else if (dir.includes('services') || dir.includes('middleware') || name.includes('service')) {
|
|
372
|
+
transformers.push(file.path);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return { sources, sinks, transformers };
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Generate key files by category
|
|
379
|
+
*/
|
|
380
|
+
generateKeyFiles(repoMap) {
|
|
381
|
+
const keyFiles = {
|
|
382
|
+
schemas: [],
|
|
383
|
+
config: [],
|
|
384
|
+
auth: [],
|
|
385
|
+
database: [],
|
|
386
|
+
routes: [],
|
|
387
|
+
services: [],
|
|
388
|
+
};
|
|
389
|
+
for (const file of repoMap.allFiles) {
|
|
390
|
+
const dir = file.directory.toLowerCase();
|
|
391
|
+
const name = file.name.toLowerCase();
|
|
392
|
+
if (dir.includes('models') || dir.includes('schemas') || name.includes('schema')) {
|
|
393
|
+
keyFiles.schemas.push(file.path);
|
|
394
|
+
}
|
|
395
|
+
if (file.isConfig || dir.includes('config')) {
|
|
396
|
+
keyFiles.config.push(file.path);
|
|
397
|
+
}
|
|
398
|
+
if (dir.includes('auth') || name.includes('auth')) {
|
|
399
|
+
keyFiles.auth.push(file.path);
|
|
400
|
+
}
|
|
401
|
+
if (dir.includes('database') || dir.includes('db') || name.includes('database')) {
|
|
402
|
+
keyFiles.database.push(file.path);
|
|
403
|
+
}
|
|
404
|
+
if (dir.includes('routes') || name.includes('route')) {
|
|
405
|
+
keyFiles.routes.push(file.path);
|
|
406
|
+
}
|
|
407
|
+
if (dir.includes('services') || name.includes('service')) {
|
|
408
|
+
keyFiles.services.push(file.path);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return keyFiles;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Generate SUMMARY.md
|
|
415
|
+
*/
|
|
416
|
+
generateSummaryMarkdown(repoMap, depGraph, repoStructure) {
|
|
417
|
+
const lines = [];
|
|
418
|
+
// Header
|
|
419
|
+
lines.push(`# Repository Analysis: ${repoMap.metadata.projectName}`);
|
|
420
|
+
lines.push('');
|
|
421
|
+
// Overview
|
|
422
|
+
lines.push('## Overview');
|
|
423
|
+
lines.push(`- **Type**: ${this.formatProjectTypeReadable(repoMap.metadata.projectType)}`);
|
|
424
|
+
if (repoMap.summary.frameworks.length > 0) {
|
|
425
|
+
lines.push(`- **Frameworks**: ${repoMap.summary.frameworks.map(f => f.name).join(', ')}`);
|
|
426
|
+
}
|
|
427
|
+
lines.push(`- **Files Analyzed**: ${repoMap.summary.analyzedFiles} of ${repoMap.summary.totalFiles} (${repoMap.summary.skippedFiles} skipped)`);
|
|
428
|
+
lines.push(`- **Analysis Date**: ${repoMap.metadata.analyzedAt}`);
|
|
429
|
+
lines.push('');
|
|
430
|
+
// Architecture
|
|
431
|
+
lines.push('## Architecture Pattern');
|
|
432
|
+
lines.push(`This appears to be a **${repoStructure.architecture.pattern}** architecture.`);
|
|
433
|
+
if (repoStructure.architecture.layers.length > 0) {
|
|
434
|
+
lines.push('');
|
|
435
|
+
lines.push('**Detected Layers:**');
|
|
436
|
+
for (const layer of repoStructure.architecture.layers) {
|
|
437
|
+
lines.push(`- ${layer.name}: ${layer.purpose} (${layer.files.length} files)`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
lines.push('');
|
|
441
|
+
// Languages
|
|
442
|
+
if (repoMap.summary.languages.length > 0) {
|
|
443
|
+
lines.push('## Language Breakdown');
|
|
444
|
+
lines.push('| Language | Files | Percentage |');
|
|
445
|
+
lines.push('|----------|-------|------------|');
|
|
446
|
+
for (const lang of repoMap.summary.languages.slice(0, 5)) {
|
|
447
|
+
lines.push(`| ${lang.language} | ${lang.fileCount} | ${lang.percentage.toFixed(1)}% |`);
|
|
448
|
+
}
|
|
449
|
+
lines.push('');
|
|
450
|
+
}
|
|
451
|
+
// Domains
|
|
452
|
+
if (repoStructure.domains.length > 0) {
|
|
453
|
+
lines.push('## Detected Domains');
|
|
454
|
+
lines.push('These domains will become OpenSpec specifications:');
|
|
455
|
+
lines.push('');
|
|
456
|
+
lines.push('| Domain | Files | Key Entities | Spec Path |');
|
|
457
|
+
lines.push('|--------|-------|--------------|-----------|');
|
|
458
|
+
for (const domain of repoStructure.domains.slice(0, 10)) {
|
|
459
|
+
const entities = domain.entities.slice(0, 3).join(', ') || '-';
|
|
460
|
+
lines.push(`| ${domain.name} | ${domain.files.length} | ${entities} | \`${domain.suggestedSpecPath}\` |`);
|
|
461
|
+
}
|
|
462
|
+
lines.push('');
|
|
463
|
+
}
|
|
464
|
+
// Dependency insights
|
|
465
|
+
lines.push('## Dependency Insights');
|
|
466
|
+
// Most connected
|
|
467
|
+
const topConnected = depGraph.rankings.byConnectivity.slice(0, 3);
|
|
468
|
+
if (topConnected.length > 0) {
|
|
469
|
+
lines.push('');
|
|
470
|
+
lines.push('**Most Connected Files:**');
|
|
471
|
+
for (const nodeId of topConnected) {
|
|
472
|
+
const node = depGraph.nodes.find(n => n.id === nodeId);
|
|
473
|
+
if (node) {
|
|
474
|
+
const totalDegree = node.metrics.inDegree + node.metrics.outDegree;
|
|
475
|
+
lines.push(`- \`${node.file.path}\` (${totalDegree} connections)`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// Cycles
|
|
480
|
+
if (depGraph.cycles.length > 0) {
|
|
481
|
+
lines.push('');
|
|
482
|
+
lines.push(`**Circular Dependencies**: ${depGraph.cycles.length} cycle(s) detected`);
|
|
483
|
+
for (const cycle of depGraph.cycles.slice(0, 3)) {
|
|
484
|
+
const cycleFiles = cycle.map(id => {
|
|
485
|
+
const node = depGraph.nodes.find(n => n.id === id);
|
|
486
|
+
return node ? basename(node.file.path) : basename(id);
|
|
487
|
+
});
|
|
488
|
+
lines.push(`- ${cycleFiles.join(' → ')}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Orphans
|
|
492
|
+
if (depGraph.rankings.orphanNodes.length > 0) {
|
|
493
|
+
lines.push('');
|
|
494
|
+
lines.push(`**Orphan Files**: ${depGraph.rankings.orphanNodes.length} file(s) with no imports or exports`);
|
|
495
|
+
}
|
|
496
|
+
lines.push('');
|
|
497
|
+
// Top files
|
|
498
|
+
lines.push('## Files Selected for Deep Analysis');
|
|
499
|
+
lines.push('The following files were selected as most significant:');
|
|
500
|
+
lines.push('');
|
|
501
|
+
const topFiles = repoMap.highValueFiles.slice(0, 15);
|
|
502
|
+
for (let i = 0; i < topFiles.length; i++) {
|
|
503
|
+
const file = topFiles[i];
|
|
504
|
+
const tags = file.tags.length > 0 ? ` - ${file.tags.join(', ')}` : '';
|
|
505
|
+
lines.push(`${i + 1}. \`${file.path}\` (score: ${file.score})${tags}`);
|
|
506
|
+
}
|
|
507
|
+
lines.push('');
|
|
508
|
+
// Recommendations
|
|
509
|
+
lines.push('## Recommendations');
|
|
510
|
+
const recommendations = [];
|
|
511
|
+
if (depGraph.cycles.length > 0) {
|
|
512
|
+
recommendations.push(`- Consider breaking the ${depGraph.cycles.length} circular dependency cycle(s)`);
|
|
513
|
+
}
|
|
514
|
+
if (depGraph.rankings.orphanNodes.length > 0) {
|
|
515
|
+
recommendations.push(`- Review ${depGraph.rankings.orphanNodes.length} orphan file(s) that may be unused`);
|
|
516
|
+
}
|
|
517
|
+
if (depGraph.rankings.bridgeNodes.length > 0) {
|
|
518
|
+
recommendations.push(`- The following files are critical bridges: ${depGraph.rankings.bridgeNodes.slice(0, 3).map(id => {
|
|
519
|
+
const node = depGraph.nodes.find(n => n.id === id);
|
|
520
|
+
return node ? `\`${basename(node.file.path)}\`` : '';
|
|
521
|
+
}).filter(Boolean).join(', ')}`);
|
|
522
|
+
}
|
|
523
|
+
if (recommendations.length === 0) {
|
|
524
|
+
recommendations.push('- No immediate architectural concerns detected');
|
|
525
|
+
}
|
|
526
|
+
for (const rec of recommendations) {
|
|
527
|
+
lines.push(rec);
|
|
528
|
+
}
|
|
529
|
+
lines.push('');
|
|
530
|
+
// Footer
|
|
531
|
+
lines.push('---');
|
|
532
|
+
lines.push(`*Generated by spec-gen v${repoMap.metadata.version}*`);
|
|
533
|
+
return lines.join('\n');
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Format project type for human reading
|
|
537
|
+
*/
|
|
538
|
+
formatProjectTypeReadable(type) {
|
|
539
|
+
const mapping = {
|
|
540
|
+
nodejs: 'Node.js/TypeScript',
|
|
541
|
+
python: 'Python',
|
|
542
|
+
rust: 'Rust',
|
|
543
|
+
go: 'Go',
|
|
544
|
+
java: 'Java',
|
|
545
|
+
ruby: 'Ruby',
|
|
546
|
+
php: 'PHP',
|
|
547
|
+
unknown: 'Unknown',
|
|
548
|
+
};
|
|
549
|
+
return mapping[type] ?? type;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Generate dependency diagram in Mermaid format
|
|
553
|
+
*/
|
|
554
|
+
generateDependencyDiagram(depGraph) {
|
|
555
|
+
// Use the built-in Mermaid converter with clustering
|
|
556
|
+
const lines = ['```mermaid'];
|
|
557
|
+
// Generate diagram with top 30 files
|
|
558
|
+
const mermaid = toMermaidFormat(depGraph, 30);
|
|
559
|
+
lines.push(mermaid);
|
|
560
|
+
lines.push('```');
|
|
561
|
+
return lines.join('\n');
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Generate LLM context preparation
|
|
565
|
+
*/
|
|
566
|
+
async generateLLMContext(repoMap, depGraph) {
|
|
567
|
+
// Phase 1: Survey (repo structure summary)
|
|
568
|
+
const phase1 = {
|
|
569
|
+
purpose: 'Initial project categorization',
|
|
570
|
+
files: [
|
|
571
|
+
{
|
|
572
|
+
path: 'repo-structure.json',
|
|
573
|
+
tokens: 2000, // Estimate
|
|
574
|
+
},
|
|
575
|
+
],
|
|
576
|
+
// FIX 1: estimatedTokens → totalTokens pour cohérence avec phase2/phase3
|
|
577
|
+
totalTokens: 2000,
|
|
578
|
+
};
|
|
579
|
+
// Phase 2: Deep analysis (top files by importance, excluding test files)
|
|
580
|
+
const phase2Files = [];
|
|
581
|
+
const topFiles = repoMap.highValueFiles
|
|
582
|
+
.filter(f => !isTestFile(f.path))
|
|
583
|
+
.slice(0, this.options.maxDeepAnalysisFiles);
|
|
584
|
+
for (const file of topFiles) {
|
|
585
|
+
try {
|
|
586
|
+
const content = await readFile(file.absolutePath, 'utf-8');
|
|
587
|
+
const tokens = Math.ceil(content.length * this.options.tokensPerChar);
|
|
588
|
+
phase2Files.push({
|
|
589
|
+
path: file.path,
|
|
590
|
+
content: content.slice(0, 10000), // Limit content size
|
|
591
|
+
tokens,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
// File couldn't be read, skip
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const phase2 = {
|
|
599
|
+
purpose: 'Core entity and logic extraction',
|
|
600
|
+
files: phase2Files,
|
|
601
|
+
// FIX 2: tokens peut être undefined → utiliser ?? 0
|
|
602
|
+
totalTokens: phase2Files.reduce((sum, f) => sum + (f.tokens ?? 0), 0),
|
|
603
|
+
};
|
|
604
|
+
// Phase 3: Validation (random leaf nodes not in phase 2, excluding test files)
|
|
605
|
+
const phase2Paths = new Set(phase2Files.map(f => f.path));
|
|
606
|
+
const leafFiles = depGraph.rankings.leafNodes
|
|
607
|
+
.map(id => depGraph.nodes.find(n => n.id === id)?.file)
|
|
608
|
+
.filter((f) => f !== undefined)
|
|
609
|
+
.filter(f => !phase2Paths.has(f.path))
|
|
610
|
+
.filter(f => !isTestFile(f.path));
|
|
611
|
+
// FIX 3: Fisher-Yates shuffle (sort(() => Math.random()) est biaisé + mute le tableau original)
|
|
612
|
+
const shuffled = [...leafFiles];
|
|
613
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
614
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
615
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
616
|
+
}
|
|
617
|
+
const validationFiles = shuffled.slice(0, this.options.maxValidationFiles);
|
|
618
|
+
const phase3Files = [];
|
|
619
|
+
for (const file of validationFiles) {
|
|
620
|
+
try {
|
|
621
|
+
const content = await readFile(file.absolutePath, 'utf-8');
|
|
622
|
+
const tokens = Math.ceil(content.length * this.options.tokensPerChar);
|
|
623
|
+
phase3Files.push({
|
|
624
|
+
path: file.path,
|
|
625
|
+
content: content.slice(0, 5000),
|
|
626
|
+
tokens,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
catch {
|
|
630
|
+
// File couldn't be read, skip
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const phase3 = {
|
|
634
|
+
purpose: 'Verification samples',
|
|
635
|
+
files: phase3Files,
|
|
636
|
+
totalTokens: phase3Files.reduce((sum, f) => sum + (f.tokens ?? 0), 0),
|
|
637
|
+
};
|
|
638
|
+
// Signature extraction + call graph for ALL analyzed files
|
|
639
|
+
// Read each file once and reuse the content for both operations.
|
|
640
|
+
// All dynamic imports grouped here; CALL_GRAPH_LANGS hoisted out of the loop.
|
|
641
|
+
const { extractSignatures, detectLanguage } = await import('./signature-extractor.js');
|
|
642
|
+
const { CallGraphBuilder, serializeCallGraph } = await import('./call-graph.js');
|
|
643
|
+
const { detectDuplicates } = await import('./duplicate-detector.js');
|
|
644
|
+
const { analyzeForRefactoring } = await import('./refactor-analyzer.js');
|
|
645
|
+
const CALL_GRAPH_LANGS = new Set(['Python', 'TypeScript', 'JavaScript', 'Go', 'Rust', 'Ruby', 'Java']);
|
|
646
|
+
const signatures = [];
|
|
647
|
+
const callGraphFiles = [];
|
|
648
|
+
for (const file of repoMap.allFiles) {
|
|
649
|
+
try {
|
|
650
|
+
const content = await readFile(file.absolutePath, 'utf-8');
|
|
651
|
+
const isTest = isTestFile(file.path);
|
|
652
|
+
// Signatures: exclude test files
|
|
653
|
+
if (!isTest) {
|
|
654
|
+
const map = extractSignatures(file.path, content);
|
|
655
|
+
if (map.entries.length > 0) {
|
|
656
|
+
signatures.push(map);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
// Call graph — all supported languages, exclude test files
|
|
660
|
+
const lang = detectLanguage(file.path);
|
|
661
|
+
if (!isTest && CALL_GRAPH_LANGS.has(lang)) {
|
|
662
|
+
callGraphFiles.push({ path: file.path, content, language: lang });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
catch {
|
|
666
|
+
// skip unreadable files
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
// Build call graph
|
|
670
|
+
const builder = new CallGraphBuilder();
|
|
671
|
+
const callGraphResult = await builder.build(callGraphFiles);
|
|
672
|
+
const callGraph = serializeCallGraph(callGraphResult);
|
|
673
|
+
// Duplicate detection — static analysis, no LLM (Types 1-2-3)
|
|
674
|
+
const duplicates = detectDuplicates(callGraphFiles, callGraphResult);
|
|
675
|
+
// Save duplicates
|
|
676
|
+
try {
|
|
677
|
+
await writeFile(join(this.options.outputDir, 'duplicates.json'), JSON.stringify(duplicates, null, 2));
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
// non-fatal if output dir doesn't exist yet
|
|
681
|
+
}
|
|
682
|
+
// Refactoring priorities (structural — enriched after generate)
|
|
683
|
+
let mappings;
|
|
684
|
+
try {
|
|
685
|
+
const mappingRaw = await readFile(join(this.options.outputDir, 'mapping.json'), 'utf-8');
|
|
686
|
+
const mappingJson = JSON.parse(mappingRaw);
|
|
687
|
+
mappings = mappingJson.mappings;
|
|
688
|
+
}
|
|
689
|
+
catch {
|
|
690
|
+
// mapping.json not yet available — that's fine
|
|
691
|
+
}
|
|
692
|
+
const refactorReport = analyzeForRefactoring(callGraph, mappings, duplicates);
|
|
693
|
+
// Save refactor priorities
|
|
694
|
+
try {
|
|
695
|
+
await writeFile(join(this.options.outputDir, 'refactor-priorities.json'), JSON.stringify(refactorReport, null, 2));
|
|
696
|
+
}
|
|
697
|
+
catch {
|
|
698
|
+
// non-fatal
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
phase1_survey: phase1,
|
|
702
|
+
phase2_deep: phase2,
|
|
703
|
+
phase3_validation: phase3,
|
|
704
|
+
signatures,
|
|
705
|
+
callGraph,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
// ============================================================================
|
|
710
|
+
// CONVENIENCE FUNCTIONS
|
|
711
|
+
// ============================================================================
|
|
712
|
+
/**
|
|
713
|
+
* Generate all artifacts
|
|
714
|
+
*/
|
|
715
|
+
export async function generateArtifacts(repoMap, depGraph, options) {
|
|
716
|
+
const generator = new AnalysisArtifactGenerator(options);
|
|
717
|
+
return generator.generate(repoMap, depGraph);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Generate and save all artifacts
|
|
721
|
+
*/
|
|
722
|
+
export async function generateAndSaveArtifacts(repoMap, depGraph, options) {
|
|
723
|
+
const generator = new AnalysisArtifactGenerator(options);
|
|
724
|
+
return generator.generateAndSave(repoMap, depGraph);
|
|
725
|
+
}
|
|
726
|
+
//# sourceMappingURL=artifact-generator.js.map
|