docorbit 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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +660 -0
  3. package/apps/cli/bin/docorbit.js +8 -0
  4. package/apps/cli/src/commands/add.ts +44 -0
  5. package/apps/cli/src/commands/api.ts +38 -0
  6. package/apps/cli/src/commands/context.ts +47 -0
  7. package/apps/cli/src/commands/dashboard.ts +55 -0
  8. package/apps/cli/src/commands/diff.ts +30 -0
  9. package/apps/cli/src/commands/evaluate.ts +133 -0
  10. package/apps/cli/src/commands/examples.ts +39 -0
  11. package/apps/cli/src/commands/export.ts +89 -0
  12. package/apps/cli/src/commands/impact.ts +31 -0
  13. package/apps/cli/src/commands/init.ts +69 -0
  14. package/apps/cli/src/commands/inspect.ts +30 -0
  15. package/apps/cli/src/commands/mcp.ts +72 -0
  16. package/apps/cli/src/commands/pitfalls.ts +38 -0
  17. package/apps/cli/src/commands/recipes.ts +35 -0
  18. package/apps/cli/src/commands/search.ts +48 -0
  19. package/apps/cli/src/commands/update.ts +73 -0
  20. package/apps/cli/src/commands/verify.ts +48 -0
  21. package/apps/cli/src/formatters/colors.ts +23 -0
  22. package/apps/cli/src/formatters/inspection.ts +102 -0
  23. package/apps/cli/src/formatters/knowledge.ts +272 -0
  24. package/apps/cli/src/formatters/retrieval.ts +74 -0
  25. package/apps/cli/src/formatters/terminal.ts +6 -0
  26. package/apps/cli/src/formatters/verification.ts +126 -0
  27. package/apps/cli/src/index.ts +409 -0
  28. package/bin/docorbit.js +8 -0
  29. package/package.json +46 -0
  30. package/packages/core/src/dashboard/server.ts +314 -0
  31. package/packages/core/src/dashboard/ui.ts +586 -0
  32. package/packages/core/src/implementation-service.ts +451 -0
  33. package/packages/core/src/index.ts +7 -0
  34. package/packages/core/src/inspector.ts +71 -0
  35. package/packages/core/src/pipeline.ts +331 -0
  36. package/packages/crawler/src/config.ts +12 -0
  37. package/packages/crawler/src/fetcher.ts +185 -0
  38. package/packages/crawler/src/index.ts +2 -0
  39. package/packages/discovery/src/index.ts +31 -0
  40. package/packages/discovery/src/provider.ts +47 -0
  41. package/packages/discovery/src/providers/generic.ts +98 -0
  42. package/packages/discovery/src/providers/github.ts +61 -0
  43. package/packages/discovery/src/providers/llms-txt.ts +73 -0
  44. package/packages/discovery/src/providers/markdown.ts +48 -0
  45. package/packages/discovery/src/providers/openapi.ts +91 -0
  46. package/packages/discovery/src/providers/sitemap.ts +62 -0
  47. package/packages/discovery/src/providers/skill.ts +54 -0
  48. package/packages/discovery/src/ranker.ts +123 -0
  49. package/packages/evaluation/src/dataset.ts +963 -0
  50. package/packages/evaluation/src/index.ts +8 -0
  51. package/packages/evaluation/src/runner.ts +241 -0
  52. package/packages/evaluation/src/strategies/context7-runner.ts +269 -0
  53. package/packages/evaluation/src/strategies/docorbit-runner.ts +228 -0
  54. package/packages/evaluation/src/strategies/firecrawl-runner.ts +172 -0
  55. package/packages/evaluation/src/strategies/web-search-runner.ts +194 -0
  56. package/packages/evaluation/src/types.ts +34 -0
  57. package/packages/evaluation/src/version-matcher.ts +73 -0
  58. package/packages/export/src/agents-md.ts +200 -0
  59. package/packages/export/src/claude-md.ts +141 -0
  60. package/packages/export/src/docs-map.ts +150 -0
  61. package/packages/export/src/index.ts +6 -0
  62. package/packages/export/src/llms-txt.ts +96 -0
  63. package/packages/export/src/service.ts +250 -0
  64. package/packages/export/src/skill-md.ts +128 -0
  65. package/packages/mcp/src/index.ts +46 -0
  66. package/packages/mcp/src/resources/index.ts +189 -0
  67. package/packages/mcp/src/server.ts +278 -0
  68. package/packages/mcp/src/tools/analyze-impact.ts +74 -0
  69. package/packages/mcp/src/tools/check-api.ts +86 -0
  70. package/packages/mcp/src/tools/diff-docs.ts +68 -0
  71. package/packages/mcp/src/tools/export-context.ts +73 -0
  72. package/packages/mcp/src/tools/find-api.ts +99 -0
  73. package/packages/mcp/src/tools/find-example.ts +100 -0
  74. package/packages/mcp/src/tools/find-pitfall.ts +94 -0
  75. package/packages/mcp/src/tools/find-recipe.ts +98 -0
  76. package/packages/mcp/src/tools/get-doc.ts +130 -0
  77. package/packages/mcp/src/tools/get-docs-map.ts +64 -0
  78. package/packages/mcp/src/tools/get-version.ts +118 -0
  79. package/packages/mcp/src/tools/implementation-context.ts +88 -0
  80. package/packages/mcp/src/tools/index.ts +59 -0
  81. package/packages/mcp/src/tools/list-sources.ts +85 -0
  82. package/packages/mcp/src/tools/search-docs.ts +123 -0
  83. package/packages/mcp/src/tools/types.ts +28 -0
  84. package/packages/mcp/src/transports/http.ts +256 -0
  85. package/packages/mcp/src/transports/stdio.ts +105 -0
  86. package/packages/mcp/src/transports/types.ts +6 -0
  87. package/packages/mcp/src/types.ts +102 -0
  88. package/packages/normalizer/src/example-indexer.ts +240 -0
  89. package/packages/normalizer/src/html.ts +253 -0
  90. package/packages/normalizer/src/index.ts +8 -0
  91. package/packages/normalizer/src/llms.ts +83 -0
  92. package/packages/normalizer/src/openapi/endpoint-parser.ts +406 -0
  93. package/packages/normalizer/src/openapi/schema-resolver.ts +111 -0
  94. package/packages/normalizer/src/openapi.ts +2 -0
  95. package/packages/normalizer/src/page.ts +184 -0
  96. package/packages/normalizer/src/pitfall-extractor.ts +190 -0
  97. package/packages/normalizer/src/slicer.ts +455 -0
  98. package/packages/retrieval/src/engine.ts +120 -0
  99. package/packages/retrieval/src/index.ts +7 -0
  100. package/packages/retrieval/src/intent.ts +43 -0
  101. package/packages/retrieval/src/packer.ts +145 -0
  102. package/packages/retrieval/src/recipe-engine.ts +313 -0
  103. package/packages/retrieval/src/scorer.ts +139 -0
  104. package/packages/retrieval/src/weights.ts +31 -0
  105. package/packages/security/src/annotations.ts +112 -0
  106. package/packages/security/src/index.ts +2 -0
  107. package/packages/security/src/ssrf.ts +153 -0
  108. package/packages/shared/src/errors.ts +53 -0
  109. package/packages/shared/src/hashing.ts +23 -0
  110. package/packages/shared/src/index.ts +3 -0
  111. package/packages/shared/src/types.ts +881 -0
  112. package/packages/storage/src/db.ts +72 -0
  113. package/packages/storage/src/index.ts +11 -0
  114. package/packages/storage/src/interfaces.ts +115 -0
  115. package/packages/storage/src/repositories/api-repository.ts +219 -0
  116. package/packages/storage/src/repositories/chunk-repository.ts +316 -0
  117. package/packages/storage/src/repositories/example-repository.ts +206 -0
  118. package/packages/storage/src/repositories/page-repository.ts +205 -0
  119. package/packages/storage/src/repositories/pitfall-repository.ts +188 -0
  120. package/packages/storage/src/repositories/source-repository.ts +205 -0
  121. package/packages/storage/src/repository.ts +256 -0
  122. package/packages/storage/src/schema.ts +269 -0
  123. package/packages/storage/src/search-tokens.ts +28 -0
  124. package/packages/verification/src/diff-engine.ts +258 -0
  125. package/packages/verification/src/extractor.ts +339 -0
  126. package/packages/verification/src/impact-scanner.ts +203 -0
  127. package/packages/verification/src/index.ts +5 -0
  128. package/packages/verification/src/services.ts +238 -0
  129. package/packages/verification/src/verifier.ts +375 -0
  130. package/packages/workspace/src/detector.ts +143 -0
  131. package/packages/workspace/src/ecosystems/cargo.ts +84 -0
  132. package/packages/workspace/src/ecosystems/composer.ts +42 -0
  133. package/packages/workspace/src/ecosystems/go.ts +54 -0
  134. package/packages/workspace/src/ecosystems/index.ts +34 -0
  135. package/packages/workspace/src/ecosystems/maven.ts +34 -0
  136. package/packages/workspace/src/ecosystems/npm.ts +83 -0
  137. package/packages/workspace/src/ecosystems/pub.ts +40 -0
  138. package/packages/workspace/src/ecosystems/pypi.ts +100 -0
  139. package/packages/workspace/src/ecosystems/rubygems.ts +30 -0
  140. package/packages/workspace/src/ecosystems/types.ts +18 -0
  141. package/packages/workspace/src/index.ts +5 -0
  142. package/packages/workspace/src/lockfile.ts +194 -0
  143. package/packages/workspace/src/resolver.ts +234 -0
  144. package/packages/workspace/src/semver.ts +259 -0
@@ -0,0 +1,203 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import type {
4
+ AffectedFileLocation,
5
+ DocDiffResult,
6
+ ImpactAnalysisResult,
7
+ } from '../../shared/src/index.ts';
8
+
9
+ const SOURCE_EXTENSIONS = new Set([
10
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
11
+ '.py', '.go', '.json',
12
+ ]);
13
+
14
+ const IGNORED_DIRS = new Set([
15
+ 'node_modules', '.git', '.docorbit', 'dist', 'build', '.next', 'coverage',
16
+ ]);
17
+
18
+ export class WorkspaceImpactScanner {
19
+ /**
20
+ * Scans a workspace project directory and identifies files affected by documentation diffs.
21
+ */
22
+ scan(projectDir: string, diff: DocDiffResult): ImpactAnalysisResult {
23
+ const files = this.collectFiles(projectDir);
24
+ const affectedLocations: AffectedFileLocation[] = [];
25
+
26
+ for (const filePath of files) {
27
+ this.analyzeFile(filePath, projectDir, diff, affectedLocations);
28
+ }
29
+
30
+ const uniqueFiles = new Set(affectedLocations.map(loc => loc.filePath));
31
+
32
+ let summary: string;
33
+ if (affectedLocations.length === 0) {
34
+ summary = `No project files affected across ${files.length} scanned file(s).`;
35
+ } else {
36
+ summary = `Found ${affectedLocations.length} affected location(s) across ${uniqueFiles.size} file(s).`;
37
+ }
38
+
39
+ return {
40
+ projectDir,
41
+ fromVersion: diff.fromVersion,
42
+ toVersion: diff.toVersion,
43
+ totalFilesScanned: files.length,
44
+ affectedFilesCount: uniqueFiles.size,
45
+ affectedLocations,
46
+ summary,
47
+ };
48
+ }
49
+
50
+ private collectFiles(dir: string): string[] {
51
+ const results: string[] = [];
52
+ if (!fs.existsSync(dir)) return results;
53
+
54
+ const traverse = (currentDir: string) => {
55
+ let entries: fs.Dirent[];
56
+ try {
57
+ entries = fs.readdirSync(currentDir, { withFileTypes: true });
58
+ } catch {
59
+ return;
60
+ }
61
+
62
+ for (const entry of entries) {
63
+ if (IGNORED_DIRS.has(entry.name)) continue;
64
+
65
+ const fullPath = path.join(currentDir, entry.name);
66
+ if (entry.isDirectory()) {
67
+ traverse(fullPath);
68
+ } else if (entry.isFile()) {
69
+ const ext = path.extname(entry.name).toLowerCase();
70
+ if (SOURCE_EXTENSIONS.has(ext)) {
71
+ results.push(fullPath);
72
+ }
73
+ }
74
+ }
75
+ };
76
+
77
+ traverse(dir);
78
+ return results;
79
+ }
80
+
81
+ private analyzeFile(
82
+ fullPath: string,
83
+ projectDir: string,
84
+ diff: DocDiffResult,
85
+ out: AffectedFileLocation[]
86
+ ): void {
87
+ let content: string;
88
+ try {
89
+ content = fs.readFileSync(fullPath, 'utf8');
90
+ } catch {
91
+ return;
92
+ }
93
+
94
+ const relPath = path.relative(projectDir, fullPath);
95
+ const lines = content.split('\n');
96
+
97
+ // Quick pre-filter: check imports
98
+ const hasStripe = content.includes('stripe');
99
+ const hasNext = content.includes('next');
100
+
101
+ // 1. Scan for API endpoint removals, deprecations, and parameter modifications
102
+ for (const apiChange of diff.apiChanges) {
103
+ const epPath = apiChange.path;
104
+ const cleanPath = epPath.replace(/^\//, '');
105
+
106
+ for (let i = 0; i < lines.length; i++) {
107
+ const lineText = lines[i];
108
+
109
+ // Check if line contains endpoint path
110
+ if (lineText.includes(epPath) || lineText.includes(`'${cleanPath}'`) || lineText.includes(`"${cleanPath}"`)) {
111
+ if (apiChange.changeType === 'removed') {
112
+ out.push({
113
+ filePath: relPath,
114
+ line: i + 1,
115
+ snippet: lineText.trim(),
116
+ reason: `Endpoint "${apiChange.method.toUpperCase()} ${epPath}" was removed in API documentation.`,
117
+ matchedPattern: epPath,
118
+ changeCategory: 'removed_api',
119
+ certainty: 'high',
120
+ confidence: 0.95,
121
+ relatedChange: { endpoint: epPath },
122
+ });
123
+ } else if (apiChange.changeType === 'deprecated') {
124
+ out.push({
125
+ filePath: relPath,
126
+ line: i + 1,
127
+ snippet: lineText.trim(),
128
+ reason: `Endpoint "${apiChange.method.toUpperCase()} ${epPath}" is deprecated in API documentation.`,
129
+ matchedPattern: epPath,
130
+ changeCategory: 'deprecated_api',
131
+ certainty: 'high',
132
+ confidence: 0.9,
133
+ relatedChange: { endpoint: epPath },
134
+ });
135
+ } else if (apiChange.changeType === 'modified') {
136
+ out.push({
137
+ filePath: relPath,
138
+ line: i + 1,
139
+ snippet: lineText.trim(),
140
+ reason: `Endpoint parameters/schemas changed: ${(apiChange.changes || []).join('; ')}`,
141
+ matchedPattern: epPath,
142
+ changeCategory: 'modified_parameters',
143
+ certainty: 'high',
144
+ confidence: 0.85,
145
+ relatedChange: { endpoint: epPath },
146
+ });
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ // 2. Scan for breaking pitfalls and deprecations
153
+ for (const pitChange of diff.pitfallChanges) {
154
+ const pit = pitChange.pitfall;
155
+ if (pitChange.changeType !== 'added') continue;
156
+
157
+ const matchedLinesForThisPitfall = new Set<number>();
158
+
159
+ // Next.js Route Params sync deprecation / removal
160
+ if (pit.content.includes('params.slug') || pit.title.includes('route params') || pit.title.includes('Route Parameters')) {
161
+ for (let i = 0; i < lines.length; i++) {
162
+ const lineText = lines[i];
163
+ if (lineText.includes('params.') && !lineText.includes('await params') && hasNext) {
164
+ matchedLinesForThisPitfall.add(i + 1);
165
+ out.push({
166
+ filePath: relPath,
167
+ line: i + 1,
168
+ snippet: lineText.trim(),
169
+ reason: `Synchronous route parameter access detected. Documentation deprecation notice: ${pit.title}`,
170
+ matchedPattern: 'params.',
171
+ changeCategory: 'breaking_pitfall',
172
+ certainty: hasNext ? 'medium' : 'heuristic',
173
+ confidence: hasNext ? 0.85 : 0.6,
174
+ relatedChange: { pitfallTitle: pit.title },
175
+ });
176
+ }
177
+ }
178
+ }
179
+
180
+ // Check symbol references
181
+ if (pit.relatedSymbol) {
182
+ for (let i = 0; i < lines.length; i++) {
183
+ if (matchedLinesForThisPitfall.has(i + 1)) continue;
184
+ const lineText = lines[i];
185
+ if (lineText.includes(pit.relatedSymbol)) {
186
+ matchedLinesForThisPitfall.add(i + 1);
187
+ out.push({
188
+ filePath: relPath,
189
+ line: i + 1,
190
+ snippet: lineText.trim(),
191
+ reason: `Symbol "${pit.relatedSymbol}" referenced in file has documented pitfall: ${pit.title}`,
192
+ matchedPattern: pit.relatedSymbol,
193
+ changeCategory: 'breaking_pitfall',
194
+ certainty: (hasStripe || hasNext) ? 'medium' : 'heuristic',
195
+ confidence: (hasStripe || hasNext) ? 0.8 : 0.5,
196
+ relatedChange: { symbol: pit.relatedSymbol, pitfallTitle: pit.title },
197
+ });
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,5 @@
1
+ export * from './extractor.ts';
2
+ export * from './verifier.ts';
3
+ export * from './diff-engine.ts';
4
+ export * from './impact-scanner.ts';
5
+ export * from './services.ts';
@@ -0,0 +1,238 @@
1
+ import type {
2
+ DocDiffResult,
3
+ ImpactAnalysisResult,
4
+ VerificationResult,
5
+ } from '../../shared/src/index.ts';
6
+ import type { DocOrbitRepository } from '../../storage/src/index.ts';
7
+ import { WorkspaceResolver } from '../../workspace/src/index.ts';
8
+ import { CodeApiExtractor } from './extractor.ts';
9
+ import { SchemaVerifier } from './verifier.ts';
10
+ import { DocDiffEngine, type DiffOptions } from './diff-engine.ts';
11
+ import { WorkspaceImpactScanner } from './impact-scanner.ts';
12
+
13
+ export interface VerifyCodeRequest {
14
+ code: string;
15
+ language?: string;
16
+ library?: string;
17
+ version?: string;
18
+ projectDir?: string;
19
+ filePath?: string;
20
+ }
21
+
22
+ export class VerificationService {
23
+ private repo: DocOrbitRepository;
24
+ private extractor: CodeApiExtractor;
25
+ private verifier: SchemaVerifier;
26
+ private resolver: WorkspaceResolver;
27
+
28
+ constructor(repo: DocOrbitRepository) {
29
+ this.repo = repo;
30
+ this.extractor = new CodeApiExtractor();
31
+ this.verifier = new SchemaVerifier(repo);
32
+ this.resolver = new WorkspaceResolver(repo);
33
+ }
34
+
35
+ verifyCode(request: VerifyCodeRequest): { result: VerificationResult; markdown: string } {
36
+ let effectiveDocVersion = request.version;
37
+ let projectVersion: string | undefined;
38
+
39
+ // Resolve project version context if projectDir is provided
40
+ if (request.projectDir) {
41
+ try {
42
+ const scan = this.resolver.scanWorkspace(request.projectDir);
43
+ const resolution = this.resolver.resolveWorkspace(scan);
44
+
45
+ const libName = (request.library || '').toLowerCase();
46
+ let matched = resolution.matches.find(m => m.dependency.name.toLowerCase() === libName);
47
+
48
+ if (!matched && resolution.matches.length > 0) {
49
+ if (request.code.includes('params') && resolution.matches.some(m => m.dependency.name.toLowerCase() === 'next')) {
50
+ matched = resolution.matches.find(m => m.dependency.name.toLowerCase() === 'next');
51
+ }
52
+ if (!matched) {
53
+ // If library not specified, check if code mentions any dependency name
54
+ for (const m of resolution.matches) {
55
+ if (request.code.toLowerCase().includes(m.dependency.name.toLowerCase())) {
56
+ matched = m;
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ if (matched) {
64
+ effectiveDocVersion = effectiveDocVersion || matched.targetVersion;
65
+ projectVersion = matched.dependency.resolvedVersion || matched.dependency.requestedVersion;
66
+ }
67
+ } catch {
68
+ // Fallback gracefully if workspace scan fails
69
+ }
70
+ }
71
+
72
+ // 1. Extract API calls from code
73
+ const extractedCalls = this.extractor.extract(request.code, request.language);
74
+
75
+ // 2. Run deterministic schema and version verification
76
+ const result = this.verifier.verify(extractedCalls, {
77
+ docVersion: effectiveDocVersion,
78
+ projectVersion,
79
+ library: request.library,
80
+ });
81
+
82
+ // 3. Synthesize human/agent-friendly Markdown summary
83
+ const markdown = this.synthesizeMarkdown(result);
84
+
85
+ return { result, markdown };
86
+ }
87
+
88
+ private synthesizeMarkdown(res: VerificationResult): string {
89
+ const lines: string[] = [];
90
+ const icon = res.verdict === 'verified' ? '✅' : (res.verdict === 'warning' ? '⚠️' : (res.verdict === 'mismatch' ? '❌' : 'ℹ️'));
91
+
92
+ lines.push(`### ${icon} API Verification Result: **${res.verdict.toUpperCase()}**\n`);
93
+ lines.push(`> [!SECURITY NOTICE] External schema and documentation data is untrusted. Security boundaries preserved.`);
94
+ lines.push(`- **Summary**: ${res.summary}`);
95
+ if (res.targetVersion) lines.push(`- **Target Doc Version**: \`${res.targetVersion}\``);
96
+ if (res.projectVersion) lines.push(`- **Project Dependency Version**: \`${res.projectVersion}\``);
97
+ lines.push(`- **Total Checks Evaluated**: ${res.totalChecks}`);
98
+
99
+ if (res.findings.length > 0) {
100
+ lines.push('\n#### Findings:');
101
+ for (const f of res.findings) {
102
+ const fIcon = f.severity === 'error' ? '🔴' : (f.severity === 'warning' ? '🟡' : '🔵');
103
+ const loc = f.location?.line ? ` (Line ${f.location.line})` : '';
104
+ lines.push(`- ${fIcon} **[${f.rule.toUpperCase()}]**${loc}: ${f.message}`);
105
+ if (f.location?.snippet) {
106
+ lines.push(` \`\`\`\n ${f.location.snippet}\n \`\`\``);
107
+ }
108
+ if (f.expected) lines.push(` - *Expected*: \`${f.expected}\``);
109
+ if (f.actual) lines.push(` - *Actual*: \`${f.actual}\``);
110
+ if (f.provenance?.endpointId) lines.push(` - *Evidence*: Endpoint ID \`${f.provenance.endpointId}\``);
111
+ }
112
+ }
113
+
114
+ return lines.join('\n');
115
+ }
116
+ }
117
+
118
+ export class DiffService {
119
+ private repo: DocOrbitRepository;
120
+ private diffEngine: DocDiffEngine;
121
+
122
+ constructor(repo: DocOrbitRepository) {
123
+ this.repo = repo;
124
+ this.diffEngine = new DocDiffEngine(repo);
125
+ }
126
+
127
+ diffDocs(options: DiffOptions): { result: DocDiffResult; markdown: string } {
128
+ const result = this.diffEngine.diff(options);
129
+ const markdown = this.synthesizeMarkdown(result);
130
+ return { result, markdown };
131
+ }
132
+
133
+ private synthesizeMarkdown(diff: DocDiffResult): string {
134
+ const lines: string[] = [];
135
+ const fromLabel = diff.fromVersion ? `v${diff.fromVersion.replace(/^v/, '')}` : (diff.fromSnapshotId || 'previous');
136
+ const toLabel = diff.toVersion ? `v${diff.toVersion.replace(/^v/, '')}` : (diff.toSnapshotId || 'latest');
137
+
138
+ lines.push(`### 📊 Documentation Diff: \`${fromLabel}\` ➔ \`${toLabel}\`\n`);
139
+ lines.push(`| Category | Added | Removed | Modified | Deprecated |`);
140
+ lines.push(`| :--- | :---: | :---: | :---: | :---: |`);
141
+ lines.push(`| **Endpoints** | ${diff.summary.endpointsAdded} | ${diff.summary.endpointsRemoved} | ${diff.summary.endpointsModified} | ${diff.summary.endpointsDeprecated} |`);
142
+ lines.push(`| **Pitfalls** | ${diff.summary.pitfallsAdded} | ${diff.summary.pitfallsRemoved} | - | - |`);
143
+ lines.push(`| **Sections** | - | - | ${diff.summary.contentChanged} | - |\n`);
144
+
145
+ if (diff.apiChanges.length > 0) {
146
+ lines.push('#### API Endpoint Changes:');
147
+ for (const ch of diff.apiChanges) {
148
+ const badge = ch.changeType === 'added' ? '🟢 ADDED' : (ch.changeType === 'removed' ? '🔴 REMOVED' : (ch.changeType === 'deprecated' ? '🟡 DEPRECATED' : '🔵 MODIFIED'));
149
+ lines.push(`- **[${badge}]** \`${ch.method.toUpperCase()} ${ch.path}\``);
150
+ if (ch.changes && ch.changes.length > 0) {
151
+ for (const detail of ch.changes) {
152
+ lines.push(` - ${detail}`);
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ if (diff.pitfallChanges.length > 0) {
159
+ lines.push('\n#### Pitfall & Deprecation Changes:');
160
+ for (const p of diff.pitfallChanges) {
161
+ const badge = p.changeType === 'added' ? '⚠️ NEW' : 'REMOVED';
162
+ lines.push(`- **[${badge}]** \`${p.pitfall.kind}\`: ${p.pitfall.title}`);
163
+ }
164
+ }
165
+
166
+ return lines.join('\n');
167
+ }
168
+ }
169
+
170
+ export class ImpactAnalysisService {
171
+ private repo?: DocOrbitRepository;
172
+ private diffService?: DiffService;
173
+ private scanner: WorkspaceImpactScanner;
174
+
175
+ constructor(repo?: DocOrbitRepository) {
176
+ this.repo = repo;
177
+ if (repo) {
178
+ this.diffService = new DiffService(repo);
179
+ }
180
+ this.scanner = new WorkspaceImpactScanner();
181
+ }
182
+
183
+ analyzeWorkspaceImpact(projectDir: string, diff: DocDiffResult): { structured: ImpactAnalysisResult; markdown: string } {
184
+ const structured = this.scanner.scan(projectDir, diff);
185
+ const markdown = this.synthesizeMarkdown(structured);
186
+ return { structured, markdown };
187
+ }
188
+
189
+ analyzeImpact(options: {
190
+ projectDir: string;
191
+ fromVersion?: string;
192
+ toVersion?: string;
193
+ fromSnapshotId?: string;
194
+ toSnapshotId?: string;
195
+ sourceId?: string;
196
+ }): { result: ImpactAnalysisResult; markdown: string } {
197
+ if (!this.diffService) {
198
+ throw new Error('Repository is required for diff-based impact analysis');
199
+ }
200
+ const { result: diff } = this.diffService.diffDocs({
201
+ fromVersion: options.fromVersion,
202
+ toVersion: options.toVersion,
203
+ fromSnapshotId: options.fromSnapshotId,
204
+ toSnapshotId: options.toSnapshotId,
205
+ sourceId: options.sourceId,
206
+ });
207
+
208
+ const result = this.scanner.scan(options.projectDir, diff);
209
+ const markdown = this.synthesizeMarkdown(result);
210
+
211
+ return { result, markdown };
212
+ }
213
+
214
+ private synthesizeMarkdown(res: ImpactAnalysisResult): string {
215
+ const lines: string[] = [];
216
+ lines.push(`### 🔍 Project Impact Analysis`);
217
+ lines.push(`- **Scanned Workspace**: \`${res.projectDir}\``);
218
+ lines.push(`- **Files Scanned**: ${res.totalFilesScanned}`);
219
+ lines.push(`- **Impacted Files**: ${res.affectedFilesCount}`);
220
+ lines.push(`- **Total Affected Locations**: ${res.affectedLocations.length}\n`);
221
+
222
+ if (res.affectedLocations.length === 0) {
223
+ lines.push(`✅ No breaking changes or deprecated API usages detected in current project files.`);
224
+ return lines.join('\n');
225
+ }
226
+
227
+ lines.push(`#### Affected Locations:\n`);
228
+ for (const loc of res.affectedLocations) {
229
+ const certBadge = loc.certainty === 'high' ? '🎯 High Certainty' : (loc.certainty === 'medium' ? '⚖️ Medium' : '🔎 Heuristic');
230
+ lines.push(`- **\`${loc.filePath}:${loc.line}\`** (${certBadge}, Confidence: ${Math.round(loc.confidence * 100)}%)`);
231
+ lines.push(` - **Reason**: ${loc.reason}`);
232
+ lines.push(` - **Matched**: \`${loc.matchedPattern}\``);
233
+ lines.push(` \`\`\`\n ${loc.snippet}\n \`\`\``);
234
+ }
235
+
236
+ return lines.join('\n');
237
+ }
238
+ }