spec-gen-cli 1.2.5 → 1.2.7

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 (76) hide show
  1. package/README.md +66 -6
  2. package/dist/api/analyze.d.ts.map +1 -1
  3. package/dist/api/analyze.js +6 -1
  4. package/dist/api/analyze.js.map +1 -1
  5. package/dist/api/audit.d.ts +10 -0
  6. package/dist/api/audit.d.ts.map +1 -0
  7. package/dist/api/audit.js +117 -0
  8. package/dist/api/audit.js.map +1 -0
  9. package/dist/api/generate.d.ts.map +1 -1
  10. package/dist/api/generate.js +9 -1
  11. package/dist/api/generate.js.map +1 -1
  12. package/dist/api/index.d.ts +3 -2
  13. package/dist/api/index.d.ts.map +1 -1
  14. package/dist/api/index.js +1 -0
  15. package/dist/api/index.js.map +1 -1
  16. package/dist/api/run.d.ts.map +1 -1
  17. package/dist/api/run.js +5 -1
  18. package/dist/api/run.js.map +1 -1
  19. package/dist/api/types.d.ts +13 -4
  20. package/dist/api/types.d.ts.map +1 -1
  21. package/dist/cli/commands/audit.d.ts +9 -0
  22. package/dist/cli/commands/audit.d.ts.map +1 -0
  23. package/dist/cli/commands/audit.js +98 -0
  24. package/dist/cli/commands/audit.js.map +1 -0
  25. package/dist/cli/commands/generate.d.ts.map +1 -1
  26. package/dist/cli/commands/generate.js +4 -2
  27. package/dist/cli/commands/generate.js.map +1 -1
  28. package/dist/cli/commands/mcp.d.ts +327 -2
  29. package/dist/cli/commands/mcp.d.ts.map +1 -1
  30. package/dist/cli/commands/mcp.js +147 -3
  31. package/dist/cli/commands/mcp.js.map +1 -1
  32. package/dist/cli/commands/refresh-stories.d.ts +10 -0
  33. package/dist/cli/commands/refresh-stories.d.ts.map +1 -0
  34. package/dist/cli/commands/refresh-stories.js +314 -0
  35. package/dist/cli/commands/refresh-stories.js.map +1 -0
  36. package/dist/cli/index.js +4 -0
  37. package/dist/cli/index.js.map +1 -1
  38. package/dist/constants.d.ts +4 -0
  39. package/dist/constants.d.ts.map +1 -1
  40. package/dist/constants.js +4 -0
  41. package/dist/constants.js.map +1 -1
  42. package/dist/core/analyzer/spec-snapshot-generator.d.ts +17 -0
  43. package/dist/core/analyzer/spec-snapshot-generator.d.ts.map +1 -0
  44. package/dist/core/analyzer/spec-snapshot-generator.js +201 -0
  45. package/dist/core/analyzer/spec-snapshot-generator.js.map +1 -0
  46. package/dist/core/analyzer/unified-search.d.ts +107 -0
  47. package/dist/core/analyzer/unified-search.d.ts.map +1 -0
  48. package/dist/core/analyzer/unified-search.js +237 -0
  49. package/dist/core/analyzer/unified-search.js.map +1 -0
  50. package/dist/core/generator/openspec-format-generator.js +1 -1
  51. package/dist/core/generator/openspec-format-generator.js.map +1 -1
  52. package/dist/core/services/chat-tools.d.ts.map +1 -1
  53. package/dist/core/services/chat-tools.js +54 -6
  54. package/dist/core/services/chat-tools.js.map +1 -1
  55. package/dist/core/services/llm-service.d.ts +18 -1
  56. package/dist/core/services/llm-service.d.ts.map +1 -1
  57. package/dist/core/services/llm-service.js +112 -4
  58. package/dist/core/services/llm-service.js.map +1 -1
  59. package/dist/core/services/mcp-handlers/analysis.d.ts +5 -0
  60. package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -1
  61. package/dist/core/services/mcp-handlers/analysis.js +20 -0
  62. package/dist/core/services/mcp-handlers/analysis.js.map +1 -1
  63. package/dist/core/services/mcp-handlers/change.d.ts +14 -0
  64. package/dist/core/services/mcp-handlers/change.d.ts.map +1 -0
  65. package/dist/core/services/mcp-handlers/change.js +416 -0
  66. package/dist/core/services/mcp-handlers/change.js.map +1 -0
  67. package/dist/core/services/mcp-handlers/semantic.d.ts +4 -0
  68. package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -1
  69. package/dist/core/services/mcp-handlers/semantic.js +101 -32
  70. package/dist/core/services/mcp-handlers/semantic.js.map +1 -1
  71. package/dist/types/index.d.ts +70 -1
  72. package/dist/types/index.d.ts.map +1 -1
  73. package/package.json +4 -1
  74. package/src/viewer/InteractiveGraphViewer.jsx +46 -4
  75. package/src/viewer/components/ChatPanel.jsx +2 -3
  76. package/src/viewer/components/ClusterGraph.jsx +22 -6
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Spec Snapshot Generator
3
+ *
4
+ * Derives a compact coverage summary from existing analysis artifacts —
5
+ * no LLM required. Reads llm-context.json, mapping.json, and spec files
6
+ * to produce spec-snapshot.json.
7
+ */
8
+ import { execFile } from 'node:child_process';
9
+ import { promisify } from 'node:util';
10
+ import { readFile, stat, readdir, writeFile } from 'node:fs/promises';
11
+ import { join, relative } from 'node:path';
12
+ import { SPEC_GEN_DIR, SPEC_GEN_ANALYSIS_SUBDIR, ARTIFACT_LLM_CONTEXT, ARTIFACT_MAPPING, ARTIFACT_SPEC_SNAPSHOT, OPENSPEC_DIR, OPENSPEC_SPECS_SUBDIR, } from '../../constants.js';
13
+ const execFileAsync = promisify(execFile);
14
+ // ============================================================================
15
+ // GIT HELPERS
16
+ // ============================================================================
17
+ async function getGitState(rootPath) {
18
+ try {
19
+ const [commitResult, branchResult, statusResult] = await Promise.all([
20
+ execFileAsync('git', ['rev-parse', '--short', 'HEAD'], { cwd: rootPath }).catch(() => ({ stdout: '' })),
21
+ execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: rootPath }).catch(() => ({ stdout: '' })),
22
+ execFileAsync('git', ['status', '--porcelain'], { cwd: rootPath }).catch(() => ({ stdout: '' })),
23
+ ]);
24
+ return {
25
+ commit: commitResult.stdout.trim() || 'unknown',
26
+ branch: branchResult.stdout.trim() || 'unknown',
27
+ dirty: statusResult.stdout.trim().length > 0,
28
+ };
29
+ }
30
+ catch {
31
+ return { commit: 'unknown', branch: 'unknown', dirty: false };
32
+ }
33
+ }
34
+ // ============================================================================
35
+ // SPEC FILE HELPERS
36
+ // ============================================================================
37
+ /** Count H2/H3 headings in a spec.md as a proxy for requirement count. */
38
+ function countRequirements(content) {
39
+ const matches = content.match(/^#{2,3} /gm);
40
+ return matches ? matches.length : 0;
41
+ }
42
+ /** Get max mtime across a list of file paths (returns epoch ISO string if none exist). */
43
+ async function maxMtime(filePaths, rootPath) {
44
+ let max = 0;
45
+ for (const rel of filePaths) {
46
+ try {
47
+ const s = await stat(join(rootPath, rel));
48
+ if (s.mtimeMs > max)
49
+ max = s.mtimeMs;
50
+ }
51
+ catch { /* file may not exist */ }
52
+ }
53
+ return max > 0 ? new Date(max).toISOString() : new Date(0).toISOString();
54
+ }
55
+ async function discoverSpecDomains(openspecPath, rootPath) {
56
+ const specsDir = join(openspecPath, OPENSPEC_SPECS_SUBDIR);
57
+ let entries;
58
+ try {
59
+ entries = await readdir(specsDir);
60
+ }
61
+ catch {
62
+ return [];
63
+ }
64
+ const domains = [];
65
+ for (const entry of entries) {
66
+ const specFilePath = join(specsDir, entry, 'spec.md');
67
+ try {
68
+ const [content, s] = await Promise.all([
69
+ readFile(specFilePath, 'utf-8'),
70
+ stat(specFilePath),
71
+ ]);
72
+ domains.push({
73
+ name: entry,
74
+ specFile: relative(rootPath, specFilePath),
75
+ specModifiedAt: s.mtime.toISOString(),
76
+ requirementCount: countRequirements(content),
77
+ });
78
+ }
79
+ catch { /* skip missing spec.md */ }
80
+ }
81
+ return domains;
82
+ }
83
+ // ============================================================================
84
+ // COVERAGE COMPUTATION
85
+ // ============================================================================
86
+ function buildCoveredFunctionSet(mapping) {
87
+ const covered = new Set();
88
+ for (const m of mapping.mappings) {
89
+ for (const fn of m.functions) {
90
+ if (fn.name && fn.name !== '*') {
91
+ covered.add(`${fn.file}::${fn.name}`);
92
+ covered.add(fn.name); // also by name alone for looser matching
93
+ }
94
+ }
95
+ }
96
+ return covered;
97
+ }
98
+ function isFunctionCovered(node, covered) {
99
+ const byFileAndName = `${node.filePath}::${node.name}`;
100
+ return covered.has(byFileAndName) || covered.has(node.name);
101
+ }
102
+ // ============================================================================
103
+ // PUBLIC API
104
+ // ============================================================================
105
+ export class SpecSnapshotGenerator {
106
+ rootPath;
107
+ openspecRelPath;
108
+ constructor(rootPath, openspecRelPath = OPENSPEC_DIR) {
109
+ this.rootPath = rootPath;
110
+ this.openspecRelPath = openspecRelPath;
111
+ }
112
+ async generate() {
113
+ const analysisDir = join(this.rootPath, SPEC_GEN_DIR, SPEC_GEN_ANALYSIS_SUBDIR);
114
+ const openspecPath = join(this.rootPath, this.openspecRelPath);
115
+ // Load artifacts in parallel
116
+ const [llmContextRaw, mappingRaw, git, specDomains] = await Promise.all([
117
+ readFile(join(analysisDir, ARTIFACT_LLM_CONTEXT), 'utf-8').catch(() => null),
118
+ readFile(join(analysisDir, ARTIFACT_MAPPING), 'utf-8').catch(() => null),
119
+ getGitState(this.rootPath),
120
+ discoverSpecDomains(openspecPath, this.rootPath),
121
+ ]);
122
+ const llmContext = llmContextRaw ? JSON.parse(llmContextRaw) : null;
123
+ const mapping = mappingRaw ? JSON.parse(mappingRaw) : null;
124
+ const callGraph = llmContext?.callGraph;
125
+ const allNodes = callGraph?.nodes ?? [];
126
+ const hubNodes = callGraph?.hubFunctions ?? [];
127
+ // Build coverage index from mapping
128
+ const covered = mapping ? buildCoveredFunctionSet(mapping) : new Set();
129
+ const coveredCount = mapping ? allNodes.filter(n => isFunctionCovered(n, covered)).length : 0;
130
+ const orphanCount = mapping?.orphanFunctions?.length ?? 0;
131
+ const totalFunctions = allNodes.length;
132
+ // Build per-domain info
133
+ // For each spec domain, find which mapping entries belong to it
134
+ const domainMappings = new Map();
135
+ if (mapping) {
136
+ for (const m of mapping.mappings) {
137
+ const entry = domainMappings.get(m.domain) ?? { mappedFunctions: new Set(), sourceFiles: new Set() };
138
+ for (const fn of m.functions) {
139
+ if (fn.name && fn.name !== '*')
140
+ entry.mappedFunctions.add(fn.name);
141
+ if (fn.file && fn.file !== '*')
142
+ entry.sourceFiles.add(fn.file);
143
+ }
144
+ domainMappings.set(m.domain, entry);
145
+ }
146
+ }
147
+ const domains = await Promise.all(specDomains.map(async (d) => {
148
+ const dm = domainMappings.get(d.name);
149
+ const sourceFiles = dm ? Array.from(dm.sourceFiles) : [];
150
+ const mappedFunctionCount = dm ? dm.mappedFunctions.size : 0;
151
+ const sourcesModifiedAt = await maxMtime(sourceFiles, this.rootPath);
152
+ const coveragePct = d.requirementCount > 0
153
+ ? Math.round((mappedFunctionCount / Math.max(d.requirementCount, mappedFunctionCount)) * 100)
154
+ : 0;
155
+ return {
156
+ name: d.name,
157
+ specFile: d.specFile,
158
+ sourceFiles,
159
+ requirementCount: d.requirementCount,
160
+ mappedFunctionCount,
161
+ coveragePct,
162
+ specModifiedAt: d.specModifiedAt,
163
+ sourcesModifiedAt,
164
+ };
165
+ }));
166
+ // Hub coverage
167
+ const hubs = hubNodes.map(n => ({
168
+ name: n.name,
169
+ file: n.filePath,
170
+ fanIn: n.fanIn,
171
+ covered: isFunctionCovered(n, covered),
172
+ }));
173
+ const snapshot = {
174
+ version: '1',
175
+ generatedAt: new Date().toISOString(),
176
+ git,
177
+ coverage: {
178
+ totalFunctions,
179
+ coveredFunctions: coveredCount,
180
+ orphanFunctions: orphanCount,
181
+ coveragePct: totalFunctions > 0 ? Math.round((coveredCount / totalFunctions) * 100) : 0,
182
+ },
183
+ domains,
184
+ hubs,
185
+ };
186
+ // Persist
187
+ await writeFile(join(analysisDir, ARTIFACT_SPEC_SNAPSHOT), JSON.stringify(snapshot, null, 2));
188
+ return snapshot;
189
+ }
190
+ /** Load a previously generated snapshot, or return null if not found. */
191
+ static async load(rootPath) {
192
+ try {
193
+ const raw = await readFile(join(rootPath, SPEC_GEN_DIR, SPEC_GEN_ANALYSIS_SUBDIR, ARTIFACT_SPEC_SNAPSHOT), 'utf-8');
194
+ return JSON.parse(raw);
195
+ }
196
+ catch {
197
+ return null;
198
+ }
199
+ }
200
+ }
201
+ //# sourceMappingURL=spec-snapshot-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-snapshot-generator.js","sourceRoot":"","sources":["../../../src/core/analyzer/spec-snapshot-generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Unified Search
3
+ *
4
+ * Combines code and spec indexes with cross-scoring to boost results
5
+ * that are linked through bidirectional mappings.
6
+ *
7
+ * Usage:
8
+ * const results = await UnifiedSearch.unifiedSearch(
9
+ * outputDir,
10
+ * "validate user authentication",
11
+ * embedSvc,
12
+ * { limit: 10 }
13
+ * );
14
+ */
15
+ import type { EmbeddingService } from './embedding-service.js';
16
+ import type { SearchResult as CodeSearchResult } from './vector-index.js';
17
+ import type { SpecSearchResult } from './spec-vector-index.js';
18
+ export interface UnifiedSearchResult {
19
+ id: string;
20
+ type: 'code' | 'spec' | 'both';
21
+ score: number;
22
+ baseScore: number;
23
+ mappingBoost: number;
24
+ source: {
25
+ filePath?: string;
26
+ functionName?: string;
27
+ className?: string;
28
+ domain?: string;
29
+ section?: string;
30
+ title?: string;
31
+ language?: string;
32
+ };
33
+ linkedArtifacts: Array<{
34
+ type: 'code' | 'spec';
35
+ id: string;
36
+ score: number;
37
+ }>;
38
+ }
39
+ export interface CrossScoringConfig {
40
+ directMappingBoost: number;
41
+ reverseMappingBoost: number;
42
+ mutualMappingBoost: number;
43
+ additionalLinkBoost: number;
44
+ maxAdditionalBoost: number;
45
+ }
46
+ /**
47
+ * Build a bidirectional mapping index from mapping.json
48
+ */
49
+ export declare function buildBidirectionalMapping(mappings: any[]): {
50
+ functionToRequirements: Map<string, Array<{
51
+ domain: string;
52
+ requirement: string;
53
+ }>>;
54
+ requirementToFunctions: Map<string, Array<{
55
+ file: string;
56
+ name: string;
57
+ }>>;
58
+ };
59
+ /**
60
+ * Calculate cross-scoring boost based on bidirectional mappings
61
+ */
62
+ export declare function calculateCrossScore(result: CodeSearchResult | SpecSearchResult, mappingIndex: {
63
+ functionToRequirements: Map<string, Array<{
64
+ domain: string;
65
+ requirement: string;
66
+ }>>;
67
+ requirementToFunctions: Map<string, Array<{
68
+ file: string;
69
+ name: string;
70
+ }>>;
71
+ }, config: CrossScoringConfig): {
72
+ mappingBoost: number;
73
+ linkedArtifacts: Array<{
74
+ type: 'code' | 'spec';
75
+ id: string;
76
+ score: number;
77
+ }>;
78
+ };
79
+ /**
80
+ * Determine result type based on source and linked artifacts
81
+ */
82
+ export declare function determineResultType(result: CodeSearchResult | SpecSearchResult, linkedArtifacts: Array<{
83
+ type: 'code' | 'spec';
84
+ id: string;
85
+ score: number;
86
+ }>): 'code' | 'spec' | 'both';
87
+ /**
88
+ * Extract source metadata from result
89
+ */
90
+ export declare function extractSourceMetadata(result: CodeSearchResult | SpecSearchResult): UnifiedSearchResult['source'];
91
+ export declare class UnifiedSearch {
92
+ /**
93
+ * Unified search that combines code and spec indexes with cross-scoring
94
+ */
95
+ static unifiedSearch(outputDir: string, query: string, embedSvc: EmbeddingService | null | undefined, opts?: {
96
+ limit?: number;
97
+ language?: string;
98
+ domain?: string;
99
+ section?: string;
100
+ config?: Partial<CrossScoringConfig>;
101
+ }): Promise<UnifiedSearchResult[]>;
102
+ }
103
+ /**
104
+ * Check if unified search is available
105
+ */
106
+ export declare function unifiedSearchAvailable(outputDir: string): Promise<boolean>;
107
+ //# sourceMappingURL=unified-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unified-search.d.ts","sourceRoot":"","sources":["../../../src/core/analyzer/unified-search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Unified Search
3
+ *
4
+ * Combines code and spec indexes with cross-scoring to boost results
5
+ * that are linked through bidirectional mappings.
6
+ *
7
+ * Usage:
8
+ * const results = await UnifiedSearch.unifiedSearch(
9
+ * outputDir,
10
+ * "validate user authentication",
11
+ * embedSvc,
12
+ * { limit: 10 }
13
+ * );
14
+ */
15
+ import { join } from 'node:path';
16
+ // ============================================================================
17
+ // CONSTANTS
18
+ // ============================================================================
19
+ const DEFAULT_CONFIG = {
20
+ directMappingBoost: 0.3,
21
+ reverseMappingBoost: 0.3,
22
+ mutualMappingBoost: 0.5,
23
+ additionalLinkBoost: 0.1,
24
+ maxAdditionalBoost: 0.3,
25
+ };
26
+ // ============================================================================
27
+ // MAPPING INDEX
28
+ // ============================================================================
29
+ /**
30
+ * Build a bidirectional mapping index from mapping.json
31
+ */
32
+ export function buildBidirectionalMapping(mappings) {
33
+ const functionToRequirements = new Map();
34
+ const requirementToFunctions = new Map();
35
+ for (const m of mappings) {
36
+ if (!m.requirement || !m.domain || !m.functions)
37
+ continue;
38
+ // Use dot+camelCase format to match spec vector index IDs (e.g. 'auth.validateToken')
39
+ const reqCamel = m.requirement.charAt(0).toLowerCase() + m.requirement.slice(1);
40
+ const requirementKey = `${m.domain}.${reqCamel}`;
41
+ // Build function → requirements mapping
42
+ for (const fn of m.functions) {
43
+ if (!fn.file || !fn.name)
44
+ continue;
45
+ const functionKey = `${fn.file}::${fn.name}`;
46
+ const existing = functionToRequirements.get(functionKey) ?? [];
47
+ existing.push({ domain: m.domain, requirement: m.requirement });
48
+ functionToRequirements.set(functionKey, existing);
49
+ }
50
+ // Build requirement → functions mapping
51
+ const functions = m.functions
52
+ .filter((f) => f.file && f.name)
53
+ .map((f) => ({ file: f.file, name: f.name }));
54
+ if (functions.length > 0) {
55
+ requirementToFunctions.set(requirementKey, functions);
56
+ }
57
+ }
58
+ return { functionToRequirements, requirementToFunctions };
59
+ }
60
+ // ============================================================================
61
+ // CROSS-SCORING ALGORITHM
62
+ // ============================================================================
63
+ /**
64
+ * Calculate cross-scoring boost based on bidirectional mappings
65
+ */
66
+ export function calculateCrossScore(result, mappingIndex, config) {
67
+ const linkedArtifacts = [];
68
+ let mappingBoost = 0;
69
+ if ('filePath' in result.record) {
70
+ // This is a code result - look for linked requirements
71
+ const rec = result.record;
72
+ const functionKey = `${rec.filePath}::${rec.name}`;
73
+ const requirements = mappingIndex.functionToRequirements.get(functionKey);
74
+ if (requirements && requirements.length > 0) {
75
+ mappingBoost += config.directMappingBoost;
76
+ // Add linked requirements as artifacts
77
+ for (const req of requirements) {
78
+ const reqCamel = req.requirement.charAt(0).toLowerCase() + req.requirement.slice(1);
79
+ const artifactId = `${req.domain}.${reqCamel}`;
80
+ linkedArtifacts.push({
81
+ type: 'spec',
82
+ id: artifactId,
83
+ score: result.score,
84
+ });
85
+ }
86
+ // Additional boost for multiple links (capped)
87
+ const additionalBoost = Math.min((requirements.length - 1) * config.additionalLinkBoost, config.maxAdditionalBoost);
88
+ mappingBoost += additionalBoost;
89
+ }
90
+ }
91
+ else {
92
+ // This is a spec result - look for linked functions
93
+ const specRec = result.record;
94
+ const requirementKey = specRec.id;
95
+ const functions = mappingIndex.requirementToFunctions.get(requirementKey);
96
+ if (functions && functions.length > 0) {
97
+ mappingBoost += config.reverseMappingBoost;
98
+ // Add linked functions as artifacts
99
+ for (const fn of functions) {
100
+ const artifactId = `${fn.file}::${fn.name}`;
101
+ linkedArtifacts.push({
102
+ type: 'code',
103
+ id: artifactId,
104
+ score: result.score,
105
+ });
106
+ }
107
+ // Additional boost for multiple links (capped)
108
+ const additionalBoost = Math.min((functions.length - 1) * config.additionalLinkBoost, config.maxAdditionalBoost);
109
+ mappingBoost += additionalBoost;
110
+ }
111
+ }
112
+ return { mappingBoost, linkedArtifacts };
113
+ }
114
+ /**
115
+ * Determine result type based on source and linked artifacts
116
+ */
117
+ export function determineResultType(result, linkedArtifacts) {
118
+ const isCode = 'filePath' in result.record;
119
+ if (isCode && linkedArtifacts.some(art => art.type === 'spec'))
120
+ return 'both';
121
+ if (!isCode && linkedArtifacts.some(art => art.type === 'code'))
122
+ return 'both';
123
+ return isCode ? 'code' : 'spec';
124
+ }
125
+ /**
126
+ * Extract source metadata from result
127
+ */
128
+ export function extractSourceMetadata(result) {
129
+ if ('filePath' in result.record) {
130
+ const rec = result.record;
131
+ return {
132
+ filePath: rec.filePath,
133
+ functionName: rec.name,
134
+ className: rec.className || undefined,
135
+ language: rec.language,
136
+ };
137
+ }
138
+ else {
139
+ const rec = result.record;
140
+ return {
141
+ domain: rec.domain,
142
+ section: rec.section,
143
+ title: rec.title,
144
+ };
145
+ }
146
+ }
147
+ // ============================================================================
148
+ // UNIFIED SEARCH
149
+ // ============================================================================
150
+ export class UnifiedSearch {
151
+ /**
152
+ * Unified search that combines code and spec indexes with cross-scoring
153
+ */
154
+ static async unifiedSearch(outputDir, query, embedSvc, opts = {}) {
155
+ const { limit = 10, language, domain, section, config = {} } = opts;
156
+ const scoringConfig = { ...DEFAULT_CONFIG, ...config };
157
+ // Import index classes dynamically
158
+ const { VectorIndex } = await import('./vector-index.js');
159
+ const { SpecVectorIndex } = await import('./spec-vector-index.js');
160
+ // Load mapping index
161
+ const mappingJsonPath = join(outputDir, 'mapping.json');
162
+ let mappingIndex = {
163
+ functionToRequirements: new Map(),
164
+ requirementToFunctions: new Map(),
165
+ };
166
+ try {
167
+ const { readFile } = await import('node:fs/promises');
168
+ const raw = JSON.parse(await readFile(mappingJsonPath, 'utf-8'));
169
+ mappingIndex = buildBidirectionalMapping(raw.mappings ?? []);
170
+ }
171
+ catch {
172
+ // Non-fatal - proceed without cross-scoring
173
+ }
174
+ // Execute parallel searches
175
+ const svc = embedSvc ?? null;
176
+ const [codeResults, specResults] = await Promise.all([
177
+ VectorIndex.search(outputDir, query, svc, { limit: limit * 3, language }).catch(() => []),
178
+ svc
179
+ ? SpecVectorIndex.search(outputDir, query, svc, { limit: limit * 3, domain, section }).catch(() => [])
180
+ : Promise.resolve([]),
181
+ ]);
182
+ // Combine and score results
183
+ const allResults = [];
184
+ // Process code results
185
+ for (const result of codeResults) {
186
+ const { mappingBoost, linkedArtifacts } = calculateCrossScore(result, mappingIndex, scoringConfig);
187
+ const finalScore = result.score + mappingBoost;
188
+ const resultType = determineResultType(result, linkedArtifacts);
189
+ allResults.push({
190
+ result,
191
+ unified: {
192
+ id: result.record.id,
193
+ type: resultType,
194
+ score: finalScore,
195
+ baseScore: result.score,
196
+ mappingBoost,
197
+ source: extractSourceMetadata(result),
198
+ linkedArtifacts,
199
+ },
200
+ });
201
+ }
202
+ // Process spec results
203
+ for (const result of specResults) {
204
+ const { mappingBoost, linkedArtifacts } = calculateCrossScore(result, mappingIndex, scoringConfig);
205
+ const finalScore = result.score + mappingBoost;
206
+ const resultType = determineResultType(result, linkedArtifacts);
207
+ allResults.push({
208
+ result,
209
+ unified: {
210
+ id: result.record.id,
211
+ type: resultType,
212
+ score: finalScore,
213
+ baseScore: result.score,
214
+ mappingBoost,
215
+ source: extractSourceMetadata(result),
216
+ linkedArtifacts,
217
+ },
218
+ });
219
+ }
220
+ // Sort by final score
221
+ allResults.sort((a, b) => b.unified.score - a.unified.score);
222
+ // Return top results
223
+ return allResults.slice(0, limit).map(r => r.unified);
224
+ }
225
+ }
226
+ // ============================================================================
227
+ // UTILITIES
228
+ // ============================================================================
229
+ /**
230
+ * Check if unified search is available
231
+ */
232
+ export async function unifiedSearchAvailable(outputDir) {
233
+ const { VectorIndex } = await import('./vector-index.js');
234
+ const { SpecVectorIndex } = await import('./spec-vector-index.js');
235
+ return VectorIndex.exists(outputDir) && SpecVectorIndex.exists(outputDir);
236
+ }
237
+ //# sourceMappingURL=unified-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unified-search.js","sourceRoot":"","sources":["../../../src/core/analyzer/unified-search.ts"],"names":[],"mappings":"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"}
@@ -671,7 +671,7 @@ export class OpenSpecFormatGenerator {
671
671
  const order = { llm: 0, semantic: 1, heuristic: 2 };
672
672
  return (order[a.confidence] ?? 3) - (order[b.confidence] ?? 3);
673
673
  })[0];
674
- lines.push(`> Implementation: \`${best.file}:${best.line}\` · confidence: ${best.confidence}`);
674
+ lines.push(`> Implementation: \`${best.name}\` in \`${best.file}\` · confidence: ${best.confidence}`);
675
675
  lines.push('');
676
676
  }
677
677
  /**