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.
Files changed (192) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.es.md +137 -1
  3. package/README.md +136 -4
  4. package/ai/ai_context.md +2 -2
  5. package/ai/architecture.md +3 -3
  6. package/ai/cache.json +85 -57
  7. package/ai/ccp/jira-123/context.json +7 -0
  8. package/ai/context/repo.json +56 -0
  9. package/ai/context/utils.json +7 -0
  10. package/ai/dependencies.json +51 -1026
  11. package/ai/files.json +195 -3
  12. package/ai/git/commit-activity.json +8646 -0
  13. package/ai/git/recent-features.json +1 -0
  14. package/ai/git/recent-files.json +52 -0
  15. package/ai/git/recent-flows.json +1 -0
  16. package/ai/graph/knowledge-graph.json +43643 -0
  17. package/ai/graph/module-graph.json +4 -0
  18. package/ai/graph/symbol-graph.json +3307 -879
  19. package/ai/graph/symbol-references.json +119 -32
  20. package/ai/index-state.json +843 -188
  21. package/ai/index.db +0 -0
  22. package/ai/modules.json +4 -0
  23. package/ai/repo-map.json +81 -17
  24. package/ai/repo_map.json +81 -17
  25. package/ai/repo_map.md +21 -7
  26. package/ai/summary.md +5 -5
  27. package/ai/symbols.json +1 -20287
  28. package/dist/analyzers/androidResources.d.ts +23 -0
  29. package/dist/analyzers/androidResources.d.ts.map +1 -0
  30. package/dist/analyzers/androidResources.js +93 -0
  31. package/dist/analyzers/androidResources.js.map +1 -0
  32. package/dist/analyzers/dependencies.d.ts.map +1 -1
  33. package/dist/analyzers/dependencies.js +37 -0
  34. package/dist/analyzers/dependencies.js.map +1 -1
  35. package/dist/analyzers/entrypoints.d.ts.map +1 -1
  36. package/dist/analyzers/entrypoints.js +71 -1
  37. package/dist/analyzers/entrypoints.js.map +1 -1
  38. package/dist/analyzers/gradleModules.d.ts +22 -0
  39. package/dist/analyzers/gradleModules.d.ts.map +1 -0
  40. package/dist/analyzers/gradleModules.js +75 -0
  41. package/dist/analyzers/gradleModules.js.map +1 -0
  42. package/dist/analyzers/techStack.d.ts +7 -0
  43. package/dist/analyzers/techStack.d.ts.map +1 -1
  44. package/dist/analyzers/techStack.js +44 -1
  45. package/dist/analyzers/techStack.js.map +1 -1
  46. package/dist/commands/ai-first.d.ts.map +1 -1
  47. package/dist/commands/ai-first.js +311 -1
  48. package/dist/commands/ai-first.js.map +1 -1
  49. package/dist/core/adapters/adapterRegistry.d.ts +39 -0
  50. package/dist/core/adapters/adapterRegistry.d.ts.map +1 -0
  51. package/dist/core/adapters/adapterRegistry.js +155 -0
  52. package/dist/core/adapters/adapterRegistry.js.map +1 -0
  53. package/dist/core/adapters/baseAdapter.d.ts +49 -0
  54. package/dist/core/adapters/baseAdapter.d.ts.map +1 -0
  55. package/dist/core/adapters/baseAdapter.js +28 -0
  56. package/dist/core/adapters/baseAdapter.js.map +1 -0
  57. package/dist/core/adapters/community/fastapiAdapter.d.ts +7 -0
  58. package/dist/core/adapters/community/fastapiAdapter.d.ts.map +1 -0
  59. package/dist/core/adapters/community/fastapiAdapter.js +40 -0
  60. package/dist/core/adapters/community/fastapiAdapter.js.map +1 -0
  61. package/dist/core/adapters/community/index.d.ts +11 -0
  62. package/dist/core/adapters/community/index.d.ts.map +1 -0
  63. package/dist/core/adapters/community/index.js +11 -0
  64. package/dist/core/adapters/community/index.js.map +1 -0
  65. package/dist/core/adapters/community/laravelAdapter.d.ts +7 -0
  66. package/dist/core/adapters/community/laravelAdapter.d.ts.map +1 -0
  67. package/dist/core/adapters/community/laravelAdapter.js +47 -0
  68. package/dist/core/adapters/community/laravelAdapter.js.map +1 -0
  69. package/dist/core/adapters/community/nestjsAdapter.d.ts +7 -0
  70. package/dist/core/adapters/community/nestjsAdapter.d.ts.map +1 -0
  71. package/dist/core/adapters/community/nestjsAdapter.js +48 -0
  72. package/dist/core/adapters/community/nestjsAdapter.js.map +1 -0
  73. package/dist/core/adapters/community/phoenixAdapter.d.ts +7 -0
  74. package/dist/core/adapters/community/phoenixAdapter.d.ts.map +1 -0
  75. package/dist/core/adapters/community/phoenixAdapter.js +45 -0
  76. package/dist/core/adapters/community/phoenixAdapter.js.map +1 -0
  77. package/dist/core/adapters/community/springBootAdapter.d.ts +7 -0
  78. package/dist/core/adapters/community/springBootAdapter.d.ts.map +1 -0
  79. package/dist/core/adapters/community/springBootAdapter.js +44 -0
  80. package/dist/core/adapters/community/springBootAdapter.js.map +1 -0
  81. package/dist/core/adapters/dotnetAdapter.d.ts +20 -0
  82. package/dist/core/adapters/dotnetAdapter.d.ts.map +1 -0
  83. package/dist/core/adapters/dotnetAdapter.js +86 -0
  84. package/dist/core/adapters/dotnetAdapter.js.map +1 -0
  85. package/dist/core/adapters/index.d.ts +18 -0
  86. package/dist/core/adapters/index.d.ts.map +1 -0
  87. package/dist/core/adapters/index.js +19 -0
  88. package/dist/core/adapters/index.js.map +1 -0
  89. package/dist/core/adapters/javascriptAdapter.d.ts +11 -0
  90. package/dist/core/adapters/javascriptAdapter.d.ts.map +1 -0
  91. package/dist/core/adapters/javascriptAdapter.js +47 -0
  92. package/dist/core/adapters/javascriptAdapter.js.map +1 -0
  93. package/dist/core/adapters/pythonAdapter.d.ts +20 -0
  94. package/dist/core/adapters/pythonAdapter.d.ts.map +1 -0
  95. package/dist/core/adapters/pythonAdapter.js +99 -0
  96. package/dist/core/adapters/pythonAdapter.js.map +1 -0
  97. package/dist/core/adapters/railsAdapter.d.ts +10 -0
  98. package/dist/core/adapters/railsAdapter.d.ts.map +1 -0
  99. package/dist/core/adapters/railsAdapter.js +52 -0
  100. package/dist/core/adapters/railsAdapter.js.map +1 -0
  101. package/dist/core/adapters/salesforceAdapter.d.ts +16 -0
  102. package/dist/core/adapters/salesforceAdapter.d.ts.map +1 -0
  103. package/dist/core/adapters/salesforceAdapter.js +64 -0
  104. package/dist/core/adapters/salesforceAdapter.js.map +1 -0
  105. package/dist/core/adapters/sdk.d.ts +83 -0
  106. package/dist/core/adapters/sdk.d.ts.map +1 -0
  107. package/dist/core/adapters/sdk.js +114 -0
  108. package/dist/core/adapters/sdk.js.map +1 -0
  109. package/dist/core/ccp.d.ts +37 -0
  110. package/dist/core/ccp.d.ts.map +1 -0
  111. package/dist/core/ccp.js +184 -0
  112. package/dist/core/ccp.js.map +1 -0
  113. package/dist/core/gitAnalyzer.d.ts +74 -0
  114. package/dist/core/gitAnalyzer.d.ts.map +1 -0
  115. package/dist/core/gitAnalyzer.js +298 -0
  116. package/dist/core/gitAnalyzer.js.map +1 -0
  117. package/dist/core/incrementalAnalyzer.d.ts +28 -0
  118. package/dist/core/incrementalAnalyzer.d.ts.map +1 -0
  119. package/dist/core/incrementalAnalyzer.js +343 -0
  120. package/dist/core/incrementalAnalyzer.js.map +1 -0
  121. package/dist/core/knowledgeGraphBuilder.d.ts +31 -0
  122. package/dist/core/knowledgeGraphBuilder.d.ts.map +1 -0
  123. package/dist/core/knowledgeGraphBuilder.js +197 -0
  124. package/dist/core/knowledgeGraphBuilder.js.map +1 -0
  125. package/dist/core/lazyAnalyzer.d.ts +57 -0
  126. package/dist/core/lazyAnalyzer.d.ts.map +1 -0
  127. package/dist/core/lazyAnalyzer.js +204 -0
  128. package/dist/core/lazyAnalyzer.js.map +1 -0
  129. package/dist/core/schema.d.ts +57 -0
  130. package/dist/core/schema.d.ts.map +1 -0
  131. package/dist/core/schema.js +131 -0
  132. package/dist/core/schema.js.map +1 -0
  133. package/dist/core/semanticContexts.d.ts +40 -0
  134. package/dist/core/semanticContexts.d.ts.map +1 -0
  135. package/dist/core/semanticContexts.js +454 -0
  136. package/dist/core/semanticContexts.js.map +1 -0
  137. package/docs/es/guide/adapters.md +143 -0
  138. package/docs/es/guide/ai-repository-schema.md +119 -0
  139. package/docs/es/guide/features.md +67 -0
  140. package/docs/es/guide/flows.md +134 -0
  141. package/docs/es/guide/git-intelligence.md +170 -0
  142. package/docs/es/guide/incremental-analysis.md +131 -0
  143. package/docs/es/guide/knowledge-graph.md +135 -0
  144. package/docs/es/guide/lazy-indexing.md +144 -0
  145. package/docs/es/guide/performance.md +125 -0
  146. package/docs/guide/adapters.md +225 -0
  147. package/docs/guide/ai-repository-schema.md +119 -0
  148. package/docs/guide/architecture.md +69 -1
  149. package/docs/guide/flows.md +134 -0
  150. package/docs/guide/git-intelligence.md +170 -0
  151. package/docs/guide/incremental-analysis.md +131 -0
  152. package/docs/guide/knowledge-graph.md +135 -0
  153. package/docs/guide/lazy-indexing.md +144 -0
  154. package/docs/guide/performance.md +125 -0
  155. package/package.json +5 -2
  156. package/src/analyzers/androidResources.ts +113 -0
  157. package/src/analyzers/dependencies.ts +41 -0
  158. package/src/analyzers/entrypoints.ts +80 -1
  159. package/src/analyzers/gradleModules.ts +100 -0
  160. package/src/analyzers/techStack.ts +56 -0
  161. package/src/commands/ai-first.ts +342 -1
  162. package/src/core/adapters/adapterRegistry.ts +187 -0
  163. package/src/core/adapters/baseAdapter.ts +82 -0
  164. package/src/core/adapters/community/fastapiAdapter.ts +50 -0
  165. package/src/core/adapters/community/index.ts +11 -0
  166. package/src/core/adapters/community/laravelAdapter.ts +56 -0
  167. package/src/core/adapters/community/nestjsAdapter.ts +57 -0
  168. package/src/core/adapters/community/phoenixAdapter.ts +54 -0
  169. package/src/core/adapters/community/springBootAdapter.ts +53 -0
  170. package/src/core/adapters/dotnetAdapter.ts +104 -0
  171. package/src/core/adapters/index.ts +24 -0
  172. package/src/core/adapters/javascriptAdapter.ts +56 -0
  173. package/src/core/adapters/pythonAdapter.ts +118 -0
  174. package/src/core/adapters/railsAdapter.ts +65 -0
  175. package/src/core/adapters/salesforceAdapter.ts +76 -0
  176. package/src/core/adapters/sdk.ts +172 -0
  177. package/src/core/ccp.ts +240 -0
  178. package/src/core/gitAnalyzer.ts +391 -0
  179. package/src/core/incrementalAnalyzer.ts +382 -0
  180. package/src/core/knowledgeGraphBuilder.ts +181 -0
  181. package/src/core/lazyAnalyzer.ts +261 -0
  182. package/src/core/schema.ts +157 -0
  183. package/src/core/semanticContexts.ts +575 -0
  184. package/tests/adapters.test.ts +159 -0
  185. package/tests/gitAnalyzer.test.ts +133 -0
  186. package/tests/incrementalAnalyzer.test.ts +83 -0
  187. package/tests/knowledgeGraph.test.ts +146 -0
  188. package/tests/lazyAnalyzer.test.ts +230 -0
  189. package/tests/schema.test.ts +203 -0
  190. package/tests/semanticContexts.test.ts +435 -0
  191. package/ai/context/analyzers.Symbol.json +0 -19
  192. 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
+ }