ai-first-cli 1.1.1 → 1.1.2
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/CHANGELOG.md +78 -0
- package/README.es.md +137 -1
- package/README.md +136 -4
- package/ai/ai_context.md +2 -2
- package/ai/architecture.md +3 -3
- package/ai/cache.json +85 -57
- package/ai/ccp/jira-123/context.json +7 -0
- package/ai/context/repo.json +56 -0
- package/ai/context/utils.json +7 -0
- package/ai/dependencies.json +51 -1026
- package/ai/files.json +195 -3
- package/ai/git/commit-activity.json +8646 -0
- package/ai/git/recent-features.json +1 -0
- package/ai/git/recent-files.json +52 -0
- package/ai/git/recent-flows.json +1 -0
- package/ai/graph/knowledge-graph.json +43643 -0
- package/ai/graph/module-graph.json +4 -0
- package/ai/graph/symbol-graph.json +3307 -879
- package/ai/graph/symbol-references.json +119 -32
- package/ai/index-state.json +843 -188
- package/ai/index.db +0 -0
- package/ai/modules.json +4 -0
- package/ai/repo-map.json +81 -17
- package/ai/repo_map.json +81 -17
- package/ai/repo_map.md +21 -7
- package/ai/summary.md +5 -5
- package/ai/symbols.json +1 -20287
- package/dist/analyzers/androidResources.d.ts +23 -0
- package/dist/analyzers/androidResources.d.ts.map +1 -0
- package/dist/analyzers/androidResources.js +93 -0
- package/dist/analyzers/androidResources.js.map +1 -0
- package/dist/analyzers/dependencies.d.ts.map +1 -1
- package/dist/analyzers/dependencies.js +37 -0
- package/dist/analyzers/dependencies.js.map +1 -1
- package/dist/analyzers/entrypoints.d.ts.map +1 -1
- package/dist/analyzers/entrypoints.js +71 -1
- package/dist/analyzers/entrypoints.js.map +1 -1
- package/dist/analyzers/gradleModules.d.ts +22 -0
- package/dist/analyzers/gradleModules.d.ts.map +1 -0
- package/dist/analyzers/gradleModules.js +75 -0
- package/dist/analyzers/gradleModules.js.map +1 -0
- package/dist/analyzers/techStack.d.ts +7 -0
- package/dist/analyzers/techStack.d.ts.map +1 -1
- package/dist/analyzers/techStack.js +44 -1
- package/dist/analyzers/techStack.js.map +1 -1
- package/dist/commands/ai-first.d.ts.map +1 -1
- package/dist/commands/ai-first.js +311 -1
- package/dist/commands/ai-first.js.map +1 -1
- package/dist/core/adapters/adapterRegistry.d.ts +39 -0
- package/dist/core/adapters/adapterRegistry.d.ts.map +1 -0
- package/dist/core/adapters/adapterRegistry.js +155 -0
- package/dist/core/adapters/adapterRegistry.js.map +1 -0
- package/dist/core/adapters/baseAdapter.d.ts +49 -0
- package/dist/core/adapters/baseAdapter.d.ts.map +1 -0
- package/dist/core/adapters/baseAdapter.js +28 -0
- package/dist/core/adapters/baseAdapter.js.map +1 -0
- package/dist/core/adapters/community/fastapiAdapter.d.ts +7 -0
- package/dist/core/adapters/community/fastapiAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/fastapiAdapter.js +40 -0
- package/dist/core/adapters/community/fastapiAdapter.js.map +1 -0
- package/dist/core/adapters/community/index.d.ts +11 -0
- package/dist/core/adapters/community/index.d.ts.map +1 -0
- package/dist/core/adapters/community/index.js +11 -0
- package/dist/core/adapters/community/index.js.map +1 -0
- package/dist/core/adapters/community/laravelAdapter.d.ts +7 -0
- package/dist/core/adapters/community/laravelAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/laravelAdapter.js +47 -0
- package/dist/core/adapters/community/laravelAdapter.js.map +1 -0
- package/dist/core/adapters/community/nestjsAdapter.d.ts +7 -0
- package/dist/core/adapters/community/nestjsAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/nestjsAdapter.js +48 -0
- package/dist/core/adapters/community/nestjsAdapter.js.map +1 -0
- package/dist/core/adapters/community/phoenixAdapter.d.ts +7 -0
- package/dist/core/adapters/community/phoenixAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/phoenixAdapter.js +45 -0
- package/dist/core/adapters/community/phoenixAdapter.js.map +1 -0
- package/dist/core/adapters/community/springBootAdapter.d.ts +7 -0
- package/dist/core/adapters/community/springBootAdapter.d.ts.map +1 -0
- package/dist/core/adapters/community/springBootAdapter.js +44 -0
- package/dist/core/adapters/community/springBootAdapter.js.map +1 -0
- package/dist/core/adapters/dotnetAdapter.d.ts +20 -0
- package/dist/core/adapters/dotnetAdapter.d.ts.map +1 -0
- package/dist/core/adapters/dotnetAdapter.js +86 -0
- package/dist/core/adapters/dotnetAdapter.js.map +1 -0
- package/dist/core/adapters/index.d.ts +18 -0
- package/dist/core/adapters/index.d.ts.map +1 -0
- package/dist/core/adapters/index.js +19 -0
- package/dist/core/adapters/index.js.map +1 -0
- package/dist/core/adapters/javascriptAdapter.d.ts +11 -0
- package/dist/core/adapters/javascriptAdapter.d.ts.map +1 -0
- package/dist/core/adapters/javascriptAdapter.js +47 -0
- package/dist/core/adapters/javascriptAdapter.js.map +1 -0
- package/dist/core/adapters/pythonAdapter.d.ts +20 -0
- package/dist/core/adapters/pythonAdapter.d.ts.map +1 -0
- package/dist/core/adapters/pythonAdapter.js +99 -0
- package/dist/core/adapters/pythonAdapter.js.map +1 -0
- package/dist/core/adapters/railsAdapter.d.ts +10 -0
- package/dist/core/adapters/railsAdapter.d.ts.map +1 -0
- package/dist/core/adapters/railsAdapter.js +52 -0
- package/dist/core/adapters/railsAdapter.js.map +1 -0
- package/dist/core/adapters/salesforceAdapter.d.ts +16 -0
- package/dist/core/adapters/salesforceAdapter.d.ts.map +1 -0
- package/dist/core/adapters/salesforceAdapter.js +64 -0
- package/dist/core/adapters/salesforceAdapter.js.map +1 -0
- package/dist/core/adapters/sdk.d.ts +83 -0
- package/dist/core/adapters/sdk.d.ts.map +1 -0
- package/dist/core/adapters/sdk.js +114 -0
- package/dist/core/adapters/sdk.js.map +1 -0
- package/dist/core/ccp.d.ts +37 -0
- package/dist/core/ccp.d.ts.map +1 -0
- package/dist/core/ccp.js +184 -0
- package/dist/core/ccp.js.map +1 -0
- package/dist/core/gitAnalyzer.d.ts +74 -0
- package/dist/core/gitAnalyzer.d.ts.map +1 -0
- package/dist/core/gitAnalyzer.js +298 -0
- package/dist/core/gitAnalyzer.js.map +1 -0
- package/dist/core/incrementalAnalyzer.d.ts +28 -0
- package/dist/core/incrementalAnalyzer.d.ts.map +1 -0
- package/dist/core/incrementalAnalyzer.js +343 -0
- package/dist/core/incrementalAnalyzer.js.map +1 -0
- package/dist/core/knowledgeGraphBuilder.d.ts +31 -0
- package/dist/core/knowledgeGraphBuilder.d.ts.map +1 -0
- package/dist/core/knowledgeGraphBuilder.js +197 -0
- package/dist/core/knowledgeGraphBuilder.js.map +1 -0
- package/dist/core/lazyAnalyzer.d.ts +57 -0
- package/dist/core/lazyAnalyzer.d.ts.map +1 -0
- package/dist/core/lazyAnalyzer.js +204 -0
- package/dist/core/lazyAnalyzer.js.map +1 -0
- package/dist/core/schema.d.ts +57 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +131 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/semanticContexts.d.ts +40 -0
- package/dist/core/semanticContexts.d.ts.map +1 -0
- package/dist/core/semanticContexts.js +454 -0
- package/dist/core/semanticContexts.js.map +1 -0
- package/docs/es/guide/adapters.md +143 -0
- package/docs/es/guide/ai-repository-schema.md +119 -0
- package/docs/es/guide/features.md +67 -0
- package/docs/es/guide/flows.md +134 -0
- package/docs/es/guide/git-intelligence.md +170 -0
- package/docs/es/guide/incremental-analysis.md +131 -0
- package/docs/es/guide/knowledge-graph.md +135 -0
- package/docs/es/guide/lazy-indexing.md +144 -0
- package/docs/es/guide/performance.md +125 -0
- package/docs/guide/adapters.md +225 -0
- package/docs/guide/ai-repository-schema.md +119 -0
- package/docs/guide/architecture.md +69 -1
- package/docs/guide/flows.md +134 -0
- package/docs/guide/git-intelligence.md +170 -0
- package/docs/guide/incremental-analysis.md +131 -0
- package/docs/guide/knowledge-graph.md +135 -0
- package/docs/guide/lazy-indexing.md +144 -0
- package/docs/guide/performance.md +125 -0
- package/package.json +5 -2
- package/src/analyzers/androidResources.ts +113 -0
- package/src/analyzers/dependencies.ts +41 -0
- package/src/analyzers/entrypoints.ts +80 -1
- package/src/analyzers/gradleModules.ts +100 -0
- package/src/analyzers/techStack.ts +56 -0
- package/src/commands/ai-first.ts +342 -1
- package/src/core/adapters/adapterRegistry.ts +187 -0
- package/src/core/adapters/baseAdapter.ts +82 -0
- package/src/core/adapters/community/fastapiAdapter.ts +50 -0
- package/src/core/adapters/community/index.ts +11 -0
- package/src/core/adapters/community/laravelAdapter.ts +56 -0
- package/src/core/adapters/community/nestjsAdapter.ts +57 -0
- package/src/core/adapters/community/phoenixAdapter.ts +54 -0
- package/src/core/adapters/community/springBootAdapter.ts +53 -0
- package/src/core/adapters/dotnetAdapter.ts +104 -0
- package/src/core/adapters/index.ts +24 -0
- package/src/core/adapters/javascriptAdapter.ts +56 -0
- package/src/core/adapters/pythonAdapter.ts +118 -0
- package/src/core/adapters/railsAdapter.ts +65 -0
- package/src/core/adapters/salesforceAdapter.ts +76 -0
- package/src/core/adapters/sdk.ts +172 -0
- package/src/core/ccp.ts +240 -0
- package/src/core/gitAnalyzer.ts +391 -0
- package/src/core/incrementalAnalyzer.ts +382 -0
- package/src/core/knowledgeGraphBuilder.ts +181 -0
- package/src/core/lazyAnalyzer.ts +261 -0
- package/src/core/schema.ts +157 -0
- package/src/core/semanticContexts.ts +575 -0
- package/tests/adapters.test.ts +159 -0
- package/tests/gitAnalyzer.test.ts +133 -0
- package/tests/incrementalAnalyzer.test.ts +83 -0
- package/tests/knowledgeGraph.test.ts +146 -0
- package/tests/lazyAnalyzer.test.ts +230 -0
- package/tests/schema.test.ts +203 -0
- package/tests/semanticContexts.test.ts +435 -0
- package/ai/context/analyzers.Symbol.json +0 -19
- package/ai/context/analyzers.extractSymbols.json +0 -19
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { writeFile, ensureDir } from "../utils/fileUtils.js";
|
|
4
|
+
import { scanRepo } from "./repoScanner.js";
|
|
5
|
+
import { generateRepoMap } from "./repoMapper.js";
|
|
6
|
+
import { detectTechStack } from "../analyzers/techStack.js";
|
|
7
|
+
import { discoverEntrypoints } from "../analyzers/entrypoints.js";
|
|
8
|
+
import { extractSymbols, Symbol } from "../analyzers/symbols.js";
|
|
9
|
+
import { analyzeDependencies, Dependency } from "../analyzers/dependencies.js";
|
|
10
|
+
import { generateSemanticContexts } from "./semanticContexts.js";
|
|
11
|
+
import { buildKnowledgeGraph } from "./knowledgeGraphBuilder.js";
|
|
12
|
+
|
|
13
|
+
export interface MinimalIndex {
|
|
14
|
+
repoMap: string;
|
|
15
|
+
languages: string[];
|
|
16
|
+
frameworks: string[];
|
|
17
|
+
entrypoints: string[];
|
|
18
|
+
generatedAt: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LazyIndexState {
|
|
22
|
+
stage1Complete: boolean;
|
|
23
|
+
stage2Complete: boolean;
|
|
24
|
+
featuresExpanded: string[];
|
|
25
|
+
flowsExpanded: string[];
|
|
26
|
+
lastUpdated: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build minimal index (Stage 1) - fast startup
|
|
31
|
+
* Generates only essential metadata needed for basic context
|
|
32
|
+
*/
|
|
33
|
+
export function buildMinimalIndex(rootDir: string, aiDir: string): MinimalIndex {
|
|
34
|
+
const scanResult = scanRepo(rootDir);
|
|
35
|
+
const techStack = detectTechStack(scanResult.files, rootDir);
|
|
36
|
+
const entrypoints = discoverEntrypoints(scanResult.files, rootDir);
|
|
37
|
+
|
|
38
|
+
// Generate minimal repo map
|
|
39
|
+
const repoMap = generateRepoMap(scanResult.files, { sortBy: "directory" });
|
|
40
|
+
|
|
41
|
+
// Write minimal index files
|
|
42
|
+
const minimalIndex: MinimalIndex = {
|
|
43
|
+
repoMap,
|
|
44
|
+
languages: techStack.languages,
|
|
45
|
+
frameworks: techStack.frameworks,
|
|
46
|
+
entrypoints: entrypoints.map(e => e.path),
|
|
47
|
+
generatedAt: new Date().toISOString()
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Save minimal index state
|
|
51
|
+
const state: LazyIndexState = {
|
|
52
|
+
stage1Complete: true,
|
|
53
|
+
stage2Complete: false,
|
|
54
|
+
featuresExpanded: [],
|
|
55
|
+
flowsExpanded: [],
|
|
56
|
+
lastUpdated: new Date().toISOString()
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const statePath = path.join(aiDir, "lazy-index-state.json");
|
|
60
|
+
writeFile(statePath, JSON.stringify(state, null, 2));
|
|
61
|
+
|
|
62
|
+
// Save minimal data
|
|
63
|
+
writeFile(path.join(aiDir, "minimal-index.json"), JSON.stringify(minimalIndex, null, 2));
|
|
64
|
+
|
|
65
|
+
return minimalIndex;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Expand context for a specific feature (Stage 2 - on demand)
|
|
70
|
+
*/
|
|
71
|
+
export function expandFeatureContext(
|
|
72
|
+
rootDir: string,
|
|
73
|
+
aiDir: string,
|
|
74
|
+
featureName: string
|
|
75
|
+
): { success: boolean; files?: string[]; error?: string } {
|
|
76
|
+
try {
|
|
77
|
+
const scanResult = scanRepo(rootDir);
|
|
78
|
+
const symbols = extractSymbols(scanResult.files);
|
|
79
|
+
|
|
80
|
+
// Filter symbols related to this feature
|
|
81
|
+
const featureSymbols = symbols.symbols.filter((s: Symbol) =>
|
|
82
|
+
s.file.includes(featureName) || s.name.toLowerCase().includes(featureName.toLowerCase())
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Load existing state
|
|
86
|
+
const statePath = path.join(aiDir, "lazy-index-state.json");
|
|
87
|
+
let state: LazyIndexState = {
|
|
88
|
+
stage1Complete: true,
|
|
89
|
+
stage2Complete: false,
|
|
90
|
+
featuresExpanded: [],
|
|
91
|
+
flowsExpanded: [],
|
|
92
|
+
lastUpdated: new Date().toISOString()
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (fs.existsSync(statePath)) {
|
|
96
|
+
state = JSON.parse(fs.readFileSync(statePath, "utf-8"));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Mark feature as expanded
|
|
100
|
+
if (!state.featuresExpanded.includes(featureName)) {
|
|
101
|
+
state.featuresExpanded.push(featureName);
|
|
102
|
+
state.lastUpdated = new Date().toISOString();
|
|
103
|
+
writeFile(statePath, JSON.stringify(state, null, 2));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Generate feature context file
|
|
107
|
+
const featureContextPath = path.join(aiDir, "context", "features", `${featureName}.json`);
|
|
108
|
+
const featureContext = {
|
|
109
|
+
feature: featureName,
|
|
110
|
+
symbols: featureSymbols,
|
|
111
|
+
generatedAt: new Date().toISOString()
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
ensureDir(path.dirname(featureContextPath));
|
|
115
|
+
writeFile(featureContextPath, JSON.stringify(featureContext, null, 2));
|
|
116
|
+
|
|
117
|
+
return { success: true, files: featureSymbols.map((s: Symbol) => s.file) };
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
error: error instanceof Error ? error.message : String(error)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Expand context for a specific flow (Stage 2 - on demand)
|
|
128
|
+
*/
|
|
129
|
+
export function expandFlowContext(
|
|
130
|
+
rootDir: string,
|
|
131
|
+
aiDir: string,
|
|
132
|
+
flowName: string
|
|
133
|
+
): { success: boolean; files?: string[]; error?: string } {
|
|
134
|
+
try {
|
|
135
|
+
const scanResult = scanRepo(rootDir);
|
|
136
|
+
const dependencies = analyzeDependencies(scanResult.files);
|
|
137
|
+
|
|
138
|
+
// Find files related to this flow
|
|
139
|
+
const flowFiles = dependencies.dependencies
|
|
140
|
+
.filter((d: Dependency) => d.source.includes(flowName) || d.target.includes(flowName))
|
|
141
|
+
.flatMap((d: Dependency) => [d.source, d.target]);
|
|
142
|
+
|
|
143
|
+
// Load existing state
|
|
144
|
+
const statePath = path.join(aiDir, "lazy-index-state.json");
|
|
145
|
+
let state: LazyIndexState = {
|
|
146
|
+
stage1Complete: true,
|
|
147
|
+
stage2Complete: false,
|
|
148
|
+
featuresExpanded: [],
|
|
149
|
+
flowsExpanded: [],
|
|
150
|
+
lastUpdated: new Date().toISOString()
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
if (fs.existsSync(statePath)) {
|
|
154
|
+
state = JSON.parse(fs.readFileSync(statePath, "utf-8"));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Mark flow as expanded
|
|
158
|
+
if (!state.flowsExpanded.includes(flowName)) {
|
|
159
|
+
state.flowsExpanded.push(flowName);
|
|
160
|
+
state.lastUpdated = new Date().toISOString();
|
|
161
|
+
writeFile(statePath, JSON.stringify(state, null, 2));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Generate flow context file
|
|
165
|
+
const flowContextPath = path.join(aiDir, "context", "flows", `${flowName}.json`);
|
|
166
|
+
const flowContext = {
|
|
167
|
+
name: flowName,
|
|
168
|
+
files: [...new Set(flowFiles)],
|
|
169
|
+
generatedAt: new Date().toISOString()
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
ensureDir(path.dirname(flowContextPath));
|
|
173
|
+
writeFile(flowContextPath, JSON.stringify(flowContext, null, 2));
|
|
174
|
+
|
|
175
|
+
return { success: true, files: [...new Set(flowFiles)] };
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return {
|
|
178
|
+
success: false,
|
|
179
|
+
error: error instanceof Error ? error.message : String(error)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Expand full context (Stage 2) - when needed
|
|
186
|
+
*/
|
|
187
|
+
export function expandFullContext(rootDir: string, aiDir: string): {
|
|
188
|
+
symbols: number;
|
|
189
|
+
dependencies: number;
|
|
190
|
+
features: number;
|
|
191
|
+
flows: number;
|
|
192
|
+
} {
|
|
193
|
+
const scanResult = scanRepo(rootDir);
|
|
194
|
+
|
|
195
|
+
// Extract symbols
|
|
196
|
+
const symbols = extractSymbols(scanResult.files);
|
|
197
|
+
const symbolsPath = path.join(aiDir, "symbols.json");
|
|
198
|
+
writeFile(symbolsPath, JSON.stringify(symbols, null, 2));
|
|
199
|
+
|
|
200
|
+
// Analyze dependencies
|
|
201
|
+
const dependencies = analyzeDependencies(scanResult.files);
|
|
202
|
+
const depsPath = path.join(aiDir, "dependencies.json");
|
|
203
|
+
writeFile(depsPath, JSON.stringify(dependencies, null, 2));
|
|
204
|
+
|
|
205
|
+
// Generate semantic contexts
|
|
206
|
+
const { features, flows } = generateSemanticContexts(aiDir);
|
|
207
|
+
|
|
208
|
+
// Build knowledge graph
|
|
209
|
+
buildKnowledgeGraph(rootDir, aiDir);
|
|
210
|
+
|
|
211
|
+
// Update state
|
|
212
|
+
const statePath = path.join(aiDir, "lazy-index-state.json");
|
|
213
|
+
const state: LazyIndexState = {
|
|
214
|
+
stage1Complete: true,
|
|
215
|
+
stage2Complete: true,
|
|
216
|
+
featuresExpanded: Array.isArray(features) ? features.map((f: any) => f.name || f.feature || String(f)) : [],
|
|
217
|
+
flowsExpanded: Array.isArray(flows) ? flows.map((f: any) => f.name || f.flow || String(f)) : [],
|
|
218
|
+
lastUpdated: new Date().toISOString()
|
|
219
|
+
};
|
|
220
|
+
writeFile(statePath, JSON.stringify(state, null, 2));
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
symbols: symbols.symbols?.length || 0,
|
|
224
|
+
dependencies: dependencies.dependencies?.length || 0,
|
|
225
|
+
features: features.length,
|
|
226
|
+
flows: flows.length
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get lazy index state
|
|
232
|
+
*/
|
|
233
|
+
export function getLazyIndexState(aiDir: string): LazyIndexState | null {
|
|
234
|
+
const statePath = path.join(aiDir, "lazy-index-state.json");
|
|
235
|
+
if (!fs.existsSync(statePath)) return null;
|
|
236
|
+
try {
|
|
237
|
+
return JSON.parse(fs.readFileSync(statePath, "utf-8"));
|
|
238
|
+
} catch {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Check if minimal index exists
|
|
245
|
+
*/
|
|
246
|
+
export function hasMinimalIndex(aiDir: string): boolean {
|
|
247
|
+
return fs.existsSync(path.join(aiDir, "minimal-index.json"));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Load minimal index
|
|
252
|
+
*/
|
|
253
|
+
export function loadMinimalIndex(aiDir: string): MinimalIndex | null {
|
|
254
|
+
const indexPath = path.join(aiDir, "minimal-index.json");
|
|
255
|
+
if (!fs.existsSync(indexPath)) return null;
|
|
256
|
+
try {
|
|
257
|
+
return JSON.parse(fs.readFileSync(indexPath, "utf-8"));
|
|
258
|
+
} catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Repository Schema System
|
|
3
|
+
*
|
|
4
|
+
* Defines the standard schema for AI-First repository metadata.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { writeFile, readJsonFile } from "../utils/fileUtils.js";
|
|
10
|
+
|
|
11
|
+
export const SCHEMA_VERSION = "1.0";
|
|
12
|
+
export const GENERATED_BY = "ai-first";
|
|
13
|
+
|
|
14
|
+
export interface SchemaInfo {
|
|
15
|
+
schemaVersion: string;
|
|
16
|
+
generatedBy: string;
|
|
17
|
+
generatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ProjectInfo {
|
|
21
|
+
name: string;
|
|
22
|
+
rootDir: string;
|
|
23
|
+
features: string[];
|
|
24
|
+
flows: string[];
|
|
25
|
+
languages: string[];
|
|
26
|
+
frameworks: string[];
|
|
27
|
+
generatedAt: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ToolsInfo {
|
|
31
|
+
compatibleAgents: string[];
|
|
32
|
+
schemaVersion: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AISchema {
|
|
36
|
+
schema: SchemaInfo;
|
|
37
|
+
project: ProjectInfo;
|
|
38
|
+
tools: ToolsInfo;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function generateSchema(aiDir: string): SchemaInfo {
|
|
42
|
+
const schema: SchemaInfo = {
|
|
43
|
+
schemaVersion: SCHEMA_VERSION,
|
|
44
|
+
generatedBy: GENERATED_BY,
|
|
45
|
+
generatedAt: new Date().toISOString()
|
|
46
|
+
};
|
|
47
|
+
writeFile(path.join(aiDir, "schema.json"), JSON.stringify(schema, null, 2));
|
|
48
|
+
return schema;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function generateProject(rootDir: string, aiDir: string, options: {
|
|
52
|
+
name?: string;
|
|
53
|
+
features?: string[];
|
|
54
|
+
flows?: string[];
|
|
55
|
+
languages?: string[];
|
|
56
|
+
frameworks?: string[];
|
|
57
|
+
} = {}): ProjectInfo {
|
|
58
|
+
const name = options.name || path.basename(rootDir);
|
|
59
|
+
|
|
60
|
+
let features = options.features || [];
|
|
61
|
+
const featuresDir = path.join(aiDir, "context", "features");
|
|
62
|
+
if (fs.existsSync(featuresDir) && features.length === 0) {
|
|
63
|
+
try {
|
|
64
|
+
features = fs.readdirSync(featuresDir).filter(f => f.endsWith(".json")).map(f => f.replace(".json", ""));
|
|
65
|
+
} catch { /* ignore */ }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let flows = options.flows || [];
|
|
69
|
+
const flowsDir = path.join(aiDir, "context", "flows");
|
|
70
|
+
if (fs.existsSync(flowsDir) && flows.length === 0) {
|
|
71
|
+
try {
|
|
72
|
+
flows = fs.readdirSync(flowsDir).filter(f => f.endsWith(".json")).map(f => f.replace(".json", ""));
|
|
73
|
+
} catch { /* ignore */ }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let languages = options.languages || [];
|
|
77
|
+
let frameworks = options.frameworks || [];
|
|
78
|
+
const techStackPath = path.join(aiDir, "tech_stack.md");
|
|
79
|
+
if (fs.existsSync(techStackPath)) {
|
|
80
|
+
try {
|
|
81
|
+
const content = fs.readFileSync(techStackPath, "utf-8");
|
|
82
|
+
const langMatch = content.match(/Languages?:\s*([^\n]+)/i);
|
|
83
|
+
if (langMatch) languages = langMatch[1].split(",").map(s => s.trim()).filter(Boolean);
|
|
84
|
+
const fwMatch = content.match(/Frameworks?:\s*([^\n]+)/i);
|
|
85
|
+
if (fwMatch) frameworks = fwMatch[1].split(",").map(s => s.trim()).filter(Boolean);
|
|
86
|
+
} catch { /* ignore */ }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const project: ProjectInfo = { name, rootDir, features, flows, languages, frameworks, generatedAt: new Date().toISOString() };
|
|
90
|
+
writeFile(path.join(aiDir, "project.json"), JSON.stringify(project, null, 2));
|
|
91
|
+
return project;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function generateTools(aiDir: string): ToolsInfo {
|
|
95
|
+
const tools: ToolsInfo = {
|
|
96
|
+
compatibleAgents: ["ai-first-bridge", "opencode", "cursor", "windsurf", "cline"],
|
|
97
|
+
schemaVersion: SCHEMA_VERSION
|
|
98
|
+
};
|
|
99
|
+
writeFile(path.join(aiDir, "tools.json"), JSON.stringify(tools, null, 2));
|
|
100
|
+
return tools;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function generateAllSchema(rootDir: string, aiDir: string, options: {
|
|
104
|
+
projectName?: string;
|
|
105
|
+
features?: string[];
|
|
106
|
+
flows?: string[];
|
|
107
|
+
languages?: string[];
|
|
108
|
+
frameworks?: string[];
|
|
109
|
+
} = {}): AISchema {
|
|
110
|
+
return {
|
|
111
|
+
schema: generateSchema(aiDir),
|
|
112
|
+
project: generateProject(rootDir, aiDir, options),
|
|
113
|
+
tools: generateTools(aiDir)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function loadSchema(aiDir: string): SchemaInfo | null {
|
|
118
|
+
const p = path.join(aiDir, "schema.json");
|
|
119
|
+
if (!fs.existsSync(p)) return null;
|
|
120
|
+
try { return readJsonFile(p) as unknown as SchemaInfo; } catch { return null; }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function loadProject(aiDir: string): ProjectInfo | null {
|
|
124
|
+
const p = path.join(aiDir, "project.json");
|
|
125
|
+
if (!fs.existsSync(p)) return null;
|
|
126
|
+
try { return readJsonFile(p) as unknown as ProjectInfo; } catch { return null; }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function loadTools(aiDir: string): ToolsInfo | null {
|
|
130
|
+
const p = path.join(aiDir, "tools.json");
|
|
131
|
+
if (!fs.existsSync(p)) return null;
|
|
132
|
+
try { return readJsonFile(p) as unknown as ToolsInfo; } catch { return null; }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function loadFullSchema(aiDir: string): AISchema | null {
|
|
136
|
+
const schema = loadSchema(aiDir);
|
|
137
|
+
const project = loadProject(aiDir);
|
|
138
|
+
const tools = loadTools(aiDir);
|
|
139
|
+
if (!schema || !project || !tools) return null;
|
|
140
|
+
return { schema, project, tools };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function isCompatible(targetVersion: string): boolean {
|
|
144
|
+
const [targetMajor] = targetVersion.split(".").map(Number);
|
|
145
|
+
const [schemaMajor] = SCHEMA_VERSION.split(".").map(Number);
|
|
146
|
+
return targetMajor === schemaMajor;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function validateSchema(aiDir: string): { valid: boolean; version?: string; errors: string[] } {
|
|
150
|
+
const errors: string[] = [];
|
|
151
|
+
const schema = loadSchema(aiDir);
|
|
152
|
+
if (!schema) errors.push("schema.json not found");
|
|
153
|
+
else if (!isCompatible(schema.schemaVersion)) errors.push(`Incompatible schema version: ${schema.schemaVersion}`);
|
|
154
|
+
if (!loadProject(aiDir)) errors.push("project.json not found");
|
|
155
|
+
if (!loadTools(aiDir)) errors.push("tools.json not found");
|
|
156
|
+
return { valid: errors.length === 0, version: schema?.schemaVersion, errors };
|
|
157
|
+
}
|