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,172 @@
1
+ /**
2
+ * Adapter SDK - Create custom adapters easily
3
+ *
4
+ * This module provides a developer-friendly API for creating
5
+ * ecosystem adapters for AI-First.
6
+ */
7
+
8
+ import { AnalysisAdapter, DetectionSignal, LayerRule } from './baseAdapter.js';
9
+
10
+ /**
11
+ * Configuration for creating a new adapter
12
+ */
13
+ export interface AdapterConfig {
14
+ /** Unique adapter name */
15
+ name: string;
16
+
17
+ /** Human-readable display name */
18
+ displayName: string;
19
+
20
+ /** Detection signals - what files/directories indicate this adapter */
21
+ detectionSignals?: DetectionSignal[];
22
+
23
+ /** Feature root directories */
24
+ featureRoots?: string[];
25
+
26
+ /** Folders to ignore */
27
+ ignoredFolders?: string[];
28
+
29
+ /** Entrypoint patterns */
30
+ entrypointPatterns?: string[];
31
+
32
+ /** Layer rules for flow detection */
33
+ layerRules?: LayerRule[];
34
+
35
+ /** Supported file extensions */
36
+ supportedExtensions?: string[];
37
+
38
+ /** Flow entrypoint patterns */
39
+ flowEntrypointPatterns?: string[];
40
+
41
+ /** Patterns to exclude from flows */
42
+ flowExcludePatterns?: string[];
43
+ }
44
+
45
+ /**
46
+ * Default layer rules for common architectures
47
+ */
48
+ export const DEFAULT_LAYER_RULES: LayerRule[] = [
49
+ { name: 'api', priority: 1, patterns: ['controller', 'handler', 'route', 'router', 'api', 'endpoint'] },
50
+ { name: 'service', priority: 2, patterns: ['service', 'services', 'usecase', 'interactor'] },
51
+ { name: 'data', priority: 3, patterns: ['repository', 'repo', 'dal', 'dao', 'data', 'persistence'] },
52
+ { name: 'domain', priority: 4, patterns: ['model', 'entity', 'schema', 'domain'] },
53
+ { name: 'util', priority: 5, patterns: ['util', 'helper', 'lib', 'common'] }
54
+ ];
55
+
56
+ /**
57
+ * Default ignored folders
58
+ */
59
+ export const DEFAULT_IGNORED_FOLDERS = [
60
+ 'utils', 'helpers', 'types', 'interfaces', 'constants', 'config',
61
+ 'dto', 'models', 'common', 'shared', 'node_modules', '.git',
62
+ 'dist', 'build', 'coverage', '__tests__', 'test', 'tests',
63
+ '.next', '.nuxt', '.vite'
64
+ ];
65
+
66
+ /**
67
+ * Create a new adapter with sensible defaults
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * import { createAdapter } from './adapters/sdk.js';
72
+ *
73
+ * export const myAdapter = createAdapter('laravel', {
74
+ * displayName: 'Laravel',
75
+ * detectionSignals: [
76
+ * { type: 'file', pattern: 'composer.json' },
77
+ * { type: 'file', pattern: 'artisan' }
78
+ * ],
79
+ * featureRoots: ['app/Http', 'app/Services', 'app/Models'],
80
+ * entrypointPatterns: ['Controller', 'Request', 'Command']
81
+ * });
82
+ * ```
83
+ */
84
+ export function createAdapter(config: AdapterConfig): AnalysisAdapter {
85
+ return {
86
+ name: config.name,
87
+ displayName: config.displayName,
88
+
89
+ detectionSignals: config.detectionSignals || [],
90
+
91
+ featureRoots: config.featureRoots || ['src', 'app', 'lib'],
92
+
93
+ ignoredFolders: config.ignoredFolders || DEFAULT_IGNORED_FOLDERS,
94
+
95
+ entrypointPatterns: config.entrypointPatterns || ['controller', 'service', 'handler'],
96
+
97
+ layerRules: config.layerRules || DEFAULT_LAYER_RULES,
98
+
99
+ supportedExtensions: config.supportedExtensions || ['.ts', '.js', '.php', '.rb', '.py'],
100
+
101
+ flowEntrypointPatterns: config.flowEntrypointPatterns || ['controller', 'route', 'handler', 'command'],
102
+
103
+ flowExcludePatterns: config.flowExcludePatterns || [
104
+ 'repository', 'model', 'utils', 'helper', 'test', 'spec', 'config'
105
+ ]
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Create a file-based detection signal
111
+ */
112
+ export function fileSignal(pattern: string): DetectionSignal {
113
+ return { type: 'file', pattern };
114
+ }
115
+
116
+ /**
117
+ * Create a directory-based detection signal
118
+ */
119
+ export function directorySignal(pattern: string): DetectionSignal {
120
+ return { type: 'directory', pattern };
121
+ }
122
+
123
+ /**
124
+ * Create a content-based detection signal
125
+ */
126
+ export function contentSignal(pattern: string, contentPattern?: string): DetectionSignal {
127
+ return { type: 'content', pattern, contentPattern };
128
+ }
129
+
130
+ /**
131
+ * Create a layer rule
132
+ */
133
+ export function layerRule(name: string, priority: number, patterns: string[]): LayerRule {
134
+ return { name, priority, patterns };
135
+ }
136
+
137
+ /**
138
+ * Validate an adapter configuration
139
+ */
140
+ export function validateAdapter(adapter: AnalysisAdapter): { valid: boolean; errors: string[] } {
141
+ const errors: string[] = [];
142
+
143
+ if (!adapter.name || adapter.name.trim() === '') {
144
+ errors.push('Adapter name is required');
145
+ }
146
+
147
+ if (!adapter.displayName || adapter.displayName.trim() === '') {
148
+ errors.push('Adapter displayName is required');
149
+ }
150
+
151
+ if (!adapter.featureRoots || adapter.featureRoots.length === 0) {
152
+ errors.push('At least one featureRoot is required');
153
+ }
154
+
155
+ if (!adapter.layerRules || adapter.layerRules.length === 0) {
156
+ errors.push('At least one layerRule is required');
157
+ }
158
+
159
+ // Check for duplicate layer names
160
+ const layerNames = new Set<string>();
161
+ for (const rule of adapter.layerRules || []) {
162
+ if (layerNames.has(rule.name)) {
163
+ errors.push(`Duplicate layer name: ${rule.name}`);
164
+ }
165
+ layerNames.add(rule.name);
166
+ }
167
+
168
+ return {
169
+ valid: errors.length === 0,
170
+ errors
171
+ };
172
+ }
@@ -0,0 +1,240 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { ensureDir, writeFile, readJsonFile } from "../utils/fileUtils.js";
4
+
5
+ export interface ContextModule {
6
+ name: string;
7
+ description: string;
8
+ files: string[];
9
+ symbols?: string[];
10
+ entrypoints?: string[];
11
+ dependencies?: string[];
12
+ }
13
+
14
+ export interface CCPContext {
15
+ task: string;
16
+ description: string;
17
+ includes: string[];
18
+ }
19
+
20
+ interface SymbolsData {
21
+ [key: string]: any;
22
+ }
23
+
24
+ interface DependenciesData {
25
+ byFile?: Record<string, string[]>;
26
+ [key: string]: any;
27
+ }
28
+
29
+ /**
30
+ * Generate context modules from repository analysis
31
+ */
32
+ export function generateContextModules(
33
+ rootDir: string,
34
+ aiDir: string
35
+ ): ContextModule[] {
36
+ const modules: ContextModule[] = [];
37
+ const contextDir = path.join(aiDir, "context");
38
+
39
+ // Try to read existing data
40
+ let symbolsData: SymbolsData = [];
41
+ let depsData: DependenciesData = {};
42
+ let repoFiles: string[] = [];
43
+
44
+ try {
45
+ const symbolsPath = path.join(aiDir, "symbols.json");
46
+ if (fs.existsSync(symbolsPath)) {
47
+ symbolsData = readJsonFile(symbolsPath);
48
+ }
49
+ } catch {}
50
+
51
+ try {
52
+ const depsPath = path.join(aiDir, "dependencies.json");
53
+ if (fs.existsSync(depsPath)) {
54
+ depsData = readJsonFile(depsPath);
55
+ }
56
+ } catch {}
57
+
58
+ try {
59
+ const filesPath = path.join(aiDir, "repo_map.json");
60
+ if (fs.existsSync(filesPath)) {
61
+ const repoMap: any = readJsonFile(filesPath);
62
+ repoFiles = (repoMap.files || []).map((f: any) => f.path);
63
+ }
64
+ } catch {}
65
+
66
+ // 1. Generate base repo.json
67
+ const repoModule: ContextModule = {
68
+ name: "repo",
69
+ description: "Base repository context with overall structure",
70
+ files: repoFiles.slice(0, 50),
71
+ };
72
+ modules.push(repoModule);
73
+
74
+ // 2. Try to detect feature modules based on directory structure
75
+ const featureDirs = detectFeatureDirectories(repoFiles);
76
+
77
+ for (const feature of featureDirs) {
78
+ const featureFiles = repoFiles.filter(f => f.startsWith(feature));
79
+ const featureSymbols = (Array.isArray(symbolsData) ? symbolsData : []).filter((s: any) => s.file?.startsWith(feature));
80
+ const byFile = depsData.byFile || {};
81
+ const featureDeps = Object.keys(byFile)
82
+ .filter(f => f.startsWith(feature))
83
+ .reduce((acc: Record<string, string[]>, f) => {
84
+ acc[f] = byFile[f];
85
+ return acc;
86
+ }, {});
87
+
88
+ modules.push({
89
+ name: feature.replace(/[\/\\]/g, "-"),
90
+ description: `Context for ${feature} feature`,
91
+ files: featureFiles,
92
+ symbols: featureSymbols.slice(0, 20).map((s: any) => s.id || s.name),
93
+ dependencies: Object.values(featureDeps).flat().slice(0, 20) as string[],
94
+ });
95
+ }
96
+
97
+ // Ensure context directory exists
98
+ ensureDir(contextDir);
99
+
100
+ // Write each context module
101
+ for (const mod of modules) {
102
+ const modPath = path.join(contextDir, `${mod.name}.json`);
103
+ writeFile(modPath, JSON.stringify(mod, null, 2));
104
+ }
105
+
106
+ return modules;
107
+ }
108
+
109
+ /**
110
+ * Detect feature directories from file structure
111
+ */
112
+ function detectFeatureDirectories(files: string[]): string[] {
113
+ const dirs = new Set<string>();
114
+
115
+ for (const file of files) {
116
+ const parts = file.split("/");
117
+ for (let i = 0; i < parts.length - 1; i++) {
118
+ const dir = parts[i];
119
+ const lowerDir = dir.toLowerCase();
120
+
121
+ // Common feature patterns
122
+ if (["auth", "authentication", "login", "users", "security",
123
+ "payments", "billing", "checkout", "subscription",
124
+ "search", "find", "query",
125
+ "api", "services", "endpoints",
126
+ "models", "entities", "schemas",
127
+ "utils", "helpers", "lib",
128
+ "components", "ui", "views", "pages",
129
+ "hooks", "store", "state",
130
+ "config", "settings"].includes(lowerDir)) {
131
+ dirs.add(dir);
132
+ }
133
+ }
134
+ }
135
+
136
+ return Array.from(dirs).slice(0, 10);
137
+ }
138
+
139
+ /**
140
+ * Create a new CCP (Context Control Pack)
141
+ */
142
+ export function createCCP(
143
+ rootDir: string,
144
+ name: string,
145
+ options: {
146
+ description?: string;
147
+ include?: string[];
148
+ } = {}
149
+ ): { success: boolean; path: string; error?: string } {
150
+ const aiDir = path.join(rootDir, "ai");
151
+ const ccpDir = path.join(aiDir, "ccp", name);
152
+
153
+ try {
154
+ if (!fs.existsSync(aiDir)) {
155
+ return {
156
+ success: false,
157
+ path: ccpDir,
158
+ error: "AI directory not found. Run 'ai-first init' first.",
159
+ };
160
+ }
161
+
162
+ const contextDir = path.join(aiDir, "context");
163
+ if (!fs.existsSync(contextDir)) {
164
+ generateContextModules(rootDir, aiDir);
165
+ }
166
+
167
+ const contextFiles = fs.readdirSync(contextDir).filter(f => f.endsWith(".json"));
168
+ const availableModules = contextFiles.map(f => `../../context/${f}`);
169
+
170
+ let includes: string[];
171
+ if (options.include && options.include.length > 0) {
172
+ includes = options.include.map(m => {
173
+ if (m.startsWith("../../context/")) return m;
174
+ if (m.startsWith("context/")) return `../../${m}`;
175
+ return `../../context/${m}.json`;
176
+ });
177
+ } else {
178
+ includes = ["../../context/repo.json"];
179
+ }
180
+
181
+ ensureDir(ccpDir);
182
+
183
+ const ccpContent: CCPContext = {
184
+ task: name,
185
+ description: options.description || "",
186
+ includes,
187
+ };
188
+
189
+ const contextPath = path.join(ccpDir, "context.json");
190
+ writeFile(contextPath, JSON.stringify(ccpContent, null, 2));
191
+
192
+ return {
193
+ success: true,
194
+ path: ccpDir,
195
+ };
196
+ } catch (error: any) {
197
+ return {
198
+ success: false,
199
+ path: ccpDir,
200
+ error: error.message,
201
+ };
202
+ }
203
+ }
204
+
205
+ /**
206
+ * List all CCPs
207
+ */
208
+ export function listCCPs(rootDir: string): string[] {
209
+ const ccpDir = path.join(rootDir, "ai", "ccp");
210
+
211
+ if (!fs.existsSync(ccpDir)) {
212
+ return [];
213
+ }
214
+
215
+ return fs.readdirSync(ccpDir).filter(stat => {
216
+ const fullPath = path.join(ccpDir, stat);
217
+ return fs.statSync(fullPath).isDirectory();
218
+ });
219
+ }
220
+
221
+ /**
222
+ * Get CCP details
223
+ */
224
+ export function getCCP(rootDir: string, name: string): CCPContext | null {
225
+ const contextPath = path.join(rootDir, "ai", "ccp", name, "context.json");
226
+
227
+ if (!fs.existsSync(contextPath)) {
228
+ return null;
229
+ }
230
+
231
+ try {
232
+ const data = readJsonFile(contextPath) as unknown as CCPContext;
233
+ if (data.task && data.includes) {
234
+ return data;
235
+ }
236
+ return null;
237
+ } catch {
238
+ return null;
239
+ }
240
+ }