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,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
|
+
}
|
package/src/core/ccp.ts
ADDED
|
@@ -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
|
+
}
|