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.
- package/LICENSE +21 -0
- package/README.md +660 -0
- package/apps/cli/bin/docorbit.js +8 -0
- package/apps/cli/src/commands/add.ts +44 -0
- package/apps/cli/src/commands/api.ts +38 -0
- package/apps/cli/src/commands/context.ts +47 -0
- package/apps/cli/src/commands/dashboard.ts +55 -0
- package/apps/cli/src/commands/diff.ts +30 -0
- package/apps/cli/src/commands/evaluate.ts +133 -0
- package/apps/cli/src/commands/examples.ts +39 -0
- package/apps/cli/src/commands/export.ts +89 -0
- package/apps/cli/src/commands/impact.ts +31 -0
- package/apps/cli/src/commands/init.ts +69 -0
- package/apps/cli/src/commands/inspect.ts +30 -0
- package/apps/cli/src/commands/mcp.ts +72 -0
- package/apps/cli/src/commands/pitfalls.ts +38 -0
- package/apps/cli/src/commands/recipes.ts +35 -0
- package/apps/cli/src/commands/search.ts +48 -0
- package/apps/cli/src/commands/update.ts +73 -0
- package/apps/cli/src/commands/verify.ts +48 -0
- package/apps/cli/src/formatters/colors.ts +23 -0
- package/apps/cli/src/formatters/inspection.ts +102 -0
- package/apps/cli/src/formatters/knowledge.ts +272 -0
- package/apps/cli/src/formatters/retrieval.ts +74 -0
- package/apps/cli/src/formatters/terminal.ts +6 -0
- package/apps/cli/src/formatters/verification.ts +126 -0
- package/apps/cli/src/index.ts +409 -0
- package/bin/docorbit.js +8 -0
- package/package.json +46 -0
- package/packages/core/src/dashboard/server.ts +314 -0
- package/packages/core/src/dashboard/ui.ts +586 -0
- package/packages/core/src/implementation-service.ts +451 -0
- package/packages/core/src/index.ts +7 -0
- package/packages/core/src/inspector.ts +71 -0
- package/packages/core/src/pipeline.ts +331 -0
- package/packages/crawler/src/config.ts +12 -0
- package/packages/crawler/src/fetcher.ts +185 -0
- package/packages/crawler/src/index.ts +2 -0
- package/packages/discovery/src/index.ts +31 -0
- package/packages/discovery/src/provider.ts +47 -0
- package/packages/discovery/src/providers/generic.ts +98 -0
- package/packages/discovery/src/providers/github.ts +61 -0
- package/packages/discovery/src/providers/llms-txt.ts +73 -0
- package/packages/discovery/src/providers/markdown.ts +48 -0
- package/packages/discovery/src/providers/openapi.ts +91 -0
- package/packages/discovery/src/providers/sitemap.ts +62 -0
- package/packages/discovery/src/providers/skill.ts +54 -0
- package/packages/discovery/src/ranker.ts +123 -0
- package/packages/evaluation/src/dataset.ts +963 -0
- package/packages/evaluation/src/index.ts +8 -0
- package/packages/evaluation/src/runner.ts +241 -0
- package/packages/evaluation/src/strategies/context7-runner.ts +269 -0
- package/packages/evaluation/src/strategies/docorbit-runner.ts +228 -0
- package/packages/evaluation/src/strategies/firecrawl-runner.ts +172 -0
- package/packages/evaluation/src/strategies/web-search-runner.ts +194 -0
- package/packages/evaluation/src/types.ts +34 -0
- package/packages/evaluation/src/version-matcher.ts +73 -0
- package/packages/export/src/agents-md.ts +200 -0
- package/packages/export/src/claude-md.ts +141 -0
- package/packages/export/src/docs-map.ts +150 -0
- package/packages/export/src/index.ts +6 -0
- package/packages/export/src/llms-txt.ts +96 -0
- package/packages/export/src/service.ts +250 -0
- package/packages/export/src/skill-md.ts +128 -0
- package/packages/mcp/src/index.ts +46 -0
- package/packages/mcp/src/resources/index.ts +189 -0
- package/packages/mcp/src/server.ts +278 -0
- package/packages/mcp/src/tools/analyze-impact.ts +74 -0
- package/packages/mcp/src/tools/check-api.ts +86 -0
- package/packages/mcp/src/tools/diff-docs.ts +68 -0
- package/packages/mcp/src/tools/export-context.ts +73 -0
- package/packages/mcp/src/tools/find-api.ts +99 -0
- package/packages/mcp/src/tools/find-example.ts +100 -0
- package/packages/mcp/src/tools/find-pitfall.ts +94 -0
- package/packages/mcp/src/tools/find-recipe.ts +98 -0
- package/packages/mcp/src/tools/get-doc.ts +130 -0
- package/packages/mcp/src/tools/get-docs-map.ts +64 -0
- package/packages/mcp/src/tools/get-version.ts +118 -0
- package/packages/mcp/src/tools/implementation-context.ts +88 -0
- package/packages/mcp/src/tools/index.ts +59 -0
- package/packages/mcp/src/tools/list-sources.ts +85 -0
- package/packages/mcp/src/tools/search-docs.ts +123 -0
- package/packages/mcp/src/tools/types.ts +28 -0
- package/packages/mcp/src/transports/http.ts +256 -0
- package/packages/mcp/src/transports/stdio.ts +105 -0
- package/packages/mcp/src/transports/types.ts +6 -0
- package/packages/mcp/src/types.ts +102 -0
- package/packages/normalizer/src/example-indexer.ts +240 -0
- package/packages/normalizer/src/html.ts +253 -0
- package/packages/normalizer/src/index.ts +8 -0
- package/packages/normalizer/src/llms.ts +83 -0
- package/packages/normalizer/src/openapi/endpoint-parser.ts +406 -0
- package/packages/normalizer/src/openapi/schema-resolver.ts +111 -0
- package/packages/normalizer/src/openapi.ts +2 -0
- package/packages/normalizer/src/page.ts +184 -0
- package/packages/normalizer/src/pitfall-extractor.ts +190 -0
- package/packages/normalizer/src/slicer.ts +455 -0
- package/packages/retrieval/src/engine.ts +120 -0
- package/packages/retrieval/src/index.ts +7 -0
- package/packages/retrieval/src/intent.ts +43 -0
- package/packages/retrieval/src/packer.ts +145 -0
- package/packages/retrieval/src/recipe-engine.ts +313 -0
- package/packages/retrieval/src/scorer.ts +139 -0
- package/packages/retrieval/src/weights.ts +31 -0
- package/packages/security/src/annotations.ts +112 -0
- package/packages/security/src/index.ts +2 -0
- package/packages/security/src/ssrf.ts +153 -0
- package/packages/shared/src/errors.ts +53 -0
- package/packages/shared/src/hashing.ts +23 -0
- package/packages/shared/src/index.ts +3 -0
- package/packages/shared/src/types.ts +881 -0
- package/packages/storage/src/db.ts +72 -0
- package/packages/storage/src/index.ts +11 -0
- package/packages/storage/src/interfaces.ts +115 -0
- package/packages/storage/src/repositories/api-repository.ts +219 -0
- package/packages/storage/src/repositories/chunk-repository.ts +316 -0
- package/packages/storage/src/repositories/example-repository.ts +206 -0
- package/packages/storage/src/repositories/page-repository.ts +205 -0
- package/packages/storage/src/repositories/pitfall-repository.ts +188 -0
- package/packages/storage/src/repositories/source-repository.ts +205 -0
- package/packages/storage/src/repository.ts +256 -0
- package/packages/storage/src/schema.ts +269 -0
- package/packages/storage/src/search-tokens.ts +28 -0
- package/packages/verification/src/diff-engine.ts +258 -0
- package/packages/verification/src/extractor.ts +339 -0
- package/packages/verification/src/impact-scanner.ts +203 -0
- package/packages/verification/src/index.ts +5 -0
- package/packages/verification/src/services.ts +238 -0
- package/packages/verification/src/verifier.ts +375 -0
- package/packages/workspace/src/detector.ts +143 -0
- package/packages/workspace/src/ecosystems/cargo.ts +84 -0
- package/packages/workspace/src/ecosystems/composer.ts +42 -0
- package/packages/workspace/src/ecosystems/go.ts +54 -0
- package/packages/workspace/src/ecosystems/index.ts +34 -0
- package/packages/workspace/src/ecosystems/maven.ts +34 -0
- package/packages/workspace/src/ecosystems/npm.ts +83 -0
- package/packages/workspace/src/ecosystems/pub.ts +40 -0
- package/packages/workspace/src/ecosystems/pypi.ts +100 -0
- package/packages/workspace/src/ecosystems/rubygems.ts +30 -0
- package/packages/workspace/src/ecosystems/types.ts +18 -0
- package/packages/workspace/src/index.ts +5 -0
- package/packages/workspace/src/lockfile.ts +194 -0
- package/packages/workspace/src/resolver.ts +234 -0
- 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,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
|
+
}
|