@deimoscloud/coreai 0.1.0
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/.prettierrc +9 -0
- package/AGENT_SPEC.md +347 -0
- package/ARCHITECTURE.md +547 -0
- package/DRAFT_PRD.md +1440 -0
- package/IMPLEMENTATION_PLAN.md +256 -0
- package/PRODUCT.md +473 -0
- package/README.md +303 -0
- package/WORKFLOWS.md +295 -0
- package/agents/_templates/ic-engineer.md +185 -0
- package/agents/_templates/reviewer.md +182 -0
- package/agents/backend-engineer.yaml +72 -0
- package/agents/devops-engineer.yaml +72 -0
- package/agents/engineering-manager.yaml +70 -0
- package/agents/examples/android-engineer.md +302 -0
- package/agents/examples/backend-engineer.md +320 -0
- package/agents/examples/devops-engineer.md +742 -0
- package/agents/examples/engineering-manager.md +469 -0
- package/agents/examples/frontend-engineer.md +58 -0
- package/agents/examples/product-manager.md +315 -0
- package/agents/examples/qa-engineer.md +371 -0
- package/agents/examples/security-engineer.md +525 -0
- package/agents/examples/solutions-architect.md +351 -0
- package/agents/examples/wearos-engineer.md +359 -0
- package/agents/frontend-engineer.yaml +72 -0
- package/commands/core/check-inbox.md +34 -0
- package/commands/core/delegate.md +30 -0
- package/commands/core/git-commit.md +144 -0
- package/commands/core/pr-create.md +193 -0
- package/commands/core/review.md +56 -0
- package/commands/core/sprint-status.md +65 -0
- package/commands/optional/docs-update.md +200 -0
- package/commands/optional/jira-create.md +200 -0
- package/commands/optional/jira-transition.md +184 -0
- package/commands/optional/worktree-cleanup.md +167 -0
- package/commands/optional/worktree-setup.md +110 -0
- package/dist/cli/index.js +4037 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +2978 -0
- package/dist/index.js +3867 -0
- package/dist/index.js.map +1 -0
- package/eslint.config.js +29 -0
- package/jest.config.js +22 -0
- package/knowledge-library/README.md +118 -0
- package/knowledge-library/android-engineer/context/current.txt +42 -0
- package/knowledge-library/android-engineer/control/decisions.txt +9 -0
- package/knowledge-library/android-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/android-engineer/control/objectives.txt +26 -0
- package/knowledge-library/android-engineer/history/.gitkeep +0 -0
- package/knowledge-library/android-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/android-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/android-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/architecture.txt +61 -0
- package/knowledge-library/backend-engineer/context/current.txt +42 -0
- package/knowledge-library/backend-engineer/control/decisions.txt +9 -0
- package/knowledge-library/backend-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/backend-engineer/control/objectives.txt +26 -0
- package/knowledge-library/backend-engineer/history/.gitkeep +0 -0
- package/knowledge-library/backend-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/backend-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/backend-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/context.txt +52 -0
- package/knowledge-library/devops-engineer/context/current.txt +42 -0
- package/knowledge-library/devops-engineer/control/decisions.txt +9 -0
- package/knowledge-library/devops-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/devops-engineer/control/objectives.txt +26 -0
- package/knowledge-library/devops-engineer/history/.gitkeep +0 -0
- package/knowledge-library/devops-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/devops-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/devops-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/context/current.txt +40 -0
- package/knowledge-library/engineering-manager/control/decisions.txt +9 -0
- package/knowledge-library/engineering-manager/control/objectives.txt +27 -0
- package/knowledge-library/engineering-manager/history/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/outbox/.gitkeep +0 -0
- package/knowledge-library/engineering-manager/tech/.gitkeep +0 -0
- package/knowledge-library/prd.txt +81 -0
- package/knowledge-library/product-manager/context/current.txt +42 -0
- package/knowledge-library/product-manager/control/decisions.txt +9 -0
- package/knowledge-library/product-manager/control/dependencies.txt +19 -0
- package/knowledge-library/product-manager/control/objectives.txt +26 -0
- package/knowledge-library/product-manager/history/.gitkeep +0 -0
- package/knowledge-library/product-manager/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/product-manager/outbox/.gitkeep +0 -0
- package/knowledge-library/product-manager/tech/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/context/current.txt +42 -0
- package/knowledge-library/qa-engineer/control/decisions.txt +9 -0
- package/knowledge-library/qa-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/qa-engineer/control/objectives.txt +26 -0
- package/knowledge-library/qa-engineer/history/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/qa-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/security-engineer/context/current.txt +42 -0
- package/knowledge-library/security-engineer/control/decisions.txt +9 -0
- package/knowledge-library/security-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/security-engineer/control/objectives.txt +26 -0
- package/knowledge-library/security-engineer/history/.gitkeep +0 -0
- package/knowledge-library/security-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/security-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/security-engineer/tech/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/context/current.txt +42 -0
- package/knowledge-library/solutions-architect/control/decisions.txt +9 -0
- package/knowledge-library/solutions-architect/control/dependencies.txt +19 -0
- package/knowledge-library/solutions-architect/control/objectives.txt +26 -0
- package/knowledge-library/solutions-architect/history/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/outbox/.gitkeep +0 -0
- package/knowledge-library/solutions-architect/tech/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/context/current.txt +42 -0
- package/knowledge-library/wearos-engineer/control/decisions.txt +9 -0
- package/knowledge-library/wearos-engineer/control/dependencies.txt +19 -0
- package/knowledge-library/wearos-engineer/control/objectives.txt +26 -0
- package/knowledge-library/wearos-engineer/history/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/inbox/processed/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/outbox/.gitkeep +0 -0
- package/knowledge-library/wearos-engineer/tech/.gitkeep +0 -0
- package/package.json +66 -0
- package/schemas/agent.schema.json +171 -0
- package/schemas/coreai.config.schema.json +257 -0
- package/scripts/add-agent.sh +323 -0
- package/scripts/install.sh +354 -0
- package/src/adapters/factory.test.ts +386 -0
- package/src/adapters/factory.ts +305 -0
- package/src/adapters/index.ts +113 -0
- package/src/adapters/interfaces.ts +268 -0
- package/src/adapters/mcp/client.test.ts +130 -0
- package/src/adapters/mcp/client.ts +451 -0
- package/src/adapters/mcp/discovery.test.ts +315 -0
- package/src/adapters/mcp/discovery.ts +340 -0
- package/src/adapters/mcp/index.ts +66 -0
- package/src/adapters/mcp/mapper.test.ts +218 -0
- package/src/adapters/mcp/mapper.ts +536 -0
- package/src/adapters/mcp/registry.test.ts +433 -0
- package/src/adapters/mcp/registry.ts +550 -0
- package/src/adapters/mcp/types.ts +258 -0
- package/src/adapters/native/filesystem.test.ts +350 -0
- package/src/adapters/native/filesystem.ts +393 -0
- package/src/adapters/native/github.test.ts +173 -0
- package/src/adapters/native/github.ts +627 -0
- package/src/adapters/native/index.ts +22 -0
- package/src/adapters/native/selector.test.ts +224 -0
- package/src/adapters/native/selector.ts +150 -0
- package/src/adapters/types.ts +270 -0
- package/src/agents/compiler.test.ts +399 -0
- package/src/agents/compiler.ts +359 -0
- package/src/agents/index.ts +36 -0
- package/src/agents/loader.test.ts +319 -0
- package/src/agents/loader.ts +143 -0
- package/src/agents/resolver.test.ts +282 -0
- package/src/agents/resolver.ts +262 -0
- package/src/agents/types.ts +87 -0
- package/src/cache/index.ts +38 -0
- package/src/cache/interfaces.ts +283 -0
- package/src/cache/manager.test.ts +266 -0
- package/src/cache/manager.ts +388 -0
- package/src/cache/provider.test.ts +485 -0
- package/src/cache/provider.ts +745 -0
- package/src/cache/types.test.ts +192 -0
- package/src/cache/types.ts +313 -0
- package/src/cli/commands/build.test.ts +248 -0
- package/src/cli/commands/build.ts +244 -0
- package/src/cli/commands/cache.test.ts +221 -0
- package/src/cli/commands/cache.ts +229 -0
- package/src/cli/commands/index.ts +63 -0
- package/src/cli/commands/init.test.ts +173 -0
- package/src/cli/commands/init.ts +296 -0
- package/src/cli/commands/skills.test.ts +272 -0
- package/src/cli/commands/skills.ts +348 -0
- package/src/cli/commands/status.test.ts +392 -0
- package/src/cli/commands/status.ts +332 -0
- package/src/cli/commands/sync.test.ts +213 -0
- package/src/cli/commands/sync.ts +251 -0
- package/src/cli/commands/validate.test.ts +216 -0
- package/src/cli/commands/validate.ts +340 -0
- package/src/cli/index.test.ts +190 -0
- package/src/cli/index.ts +493 -0
- package/src/commands/context.test.ts +163 -0
- package/src/commands/context.ts +111 -0
- package/src/commands/index.ts +56 -0
- package/src/commands/loader.test.ts +273 -0
- package/src/commands/loader.ts +355 -0
- package/src/commands/registry.test.ts +384 -0
- package/src/commands/registry.ts +248 -0
- package/src/commands/runner.test.ts +297 -0
- package/src/commands/runner.ts +222 -0
- package/src/commands/types.ts +361 -0
- package/src/config/index.ts +19 -0
- package/src/config/loader.test.ts +262 -0
- package/src/config/loader.ts +188 -0
- package/src/config/types.ts +154 -0
- package/src/context/index.ts +14 -0
- package/src/context/loader.test.ts +334 -0
- package/src/context/loader.ts +357 -0
- package/src/index.test.ts +13 -0
- package/src/index.ts +244 -0
- package/src/knowledge-library/index.ts +44 -0
- package/src/knowledge-library/manager.test.ts +536 -0
- package/src/knowledge-library/manager.ts +804 -0
- package/src/knowledge-library/types.ts +432 -0
- package/src/skills/generator.test.ts +602 -0
- package/src/skills/generator.ts +491 -0
- package/src/skills/index.ts +27 -0
- package/src/skills/templates.ts +520 -0
- package/src/skills/types.ts +251 -0
- package/templates/completion-report.md +72 -0
- package/templates/feedback.md +56 -0
- package/templates/project-files/CLAUDE.md.template +109 -0
- package/templates/project-files/coreai.json.example +47 -0
- package/templates/project-files/mcp.json.template +20 -0
- package/templates/review-complete.md +64 -0
- package/templates/review-request.md +67 -0
- package/templates/task-assignment.md +51 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +26 -0
- package/tsup.config.ts +23 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Loader
|
|
3
|
+
*
|
|
4
|
+
* Handles config file discovery, parsing, validation, and defaults.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync } from 'fs';
|
|
8
|
+
import { dirname, join, resolve } from 'path';
|
|
9
|
+
import { parse as parseYaml } from 'yaml';
|
|
10
|
+
import Ajv, { type ErrorObject } from 'ajv';
|
|
11
|
+
import addFormats from 'ajv-formats';
|
|
12
|
+
import type { CoreAIConfig, ResolvedCoreAIConfig } from './types.js';
|
|
13
|
+
|
|
14
|
+
// Import schema as JSON
|
|
15
|
+
import { createRequire } from 'module';
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const configSchema = require('../../schemas/coreai.config.schema.json') as object;
|
|
18
|
+
|
|
19
|
+
const CONFIG_FILE_NAME = 'coreai.config.yaml';
|
|
20
|
+
const CONFIG_FILE_NAMES = [CONFIG_FILE_NAME, 'coreai.config.yml'];
|
|
21
|
+
|
|
22
|
+
export class ConfigError extends Error {
|
|
23
|
+
constructor(
|
|
24
|
+
message: string,
|
|
25
|
+
public readonly code: ConfigErrorCode,
|
|
26
|
+
public readonly details?: unknown
|
|
27
|
+
) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = 'ConfigError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ConfigErrorCode = 'NOT_FOUND' | 'PARSE_ERROR' | 'VALIDATION_ERROR' | 'READ_ERROR';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Default team agents
|
|
37
|
+
*/
|
|
38
|
+
const DEFAULT_AGENTS = [
|
|
39
|
+
'backend-engineer',
|
|
40
|
+
'frontend-engineer',
|
|
41
|
+
'devops-engineer',
|
|
42
|
+
'engineering-manager',
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Find the config file by walking up the directory tree
|
|
47
|
+
*/
|
|
48
|
+
export function findConfigFile(startDir: string = process.cwd()): string | null {
|
|
49
|
+
let currentDir = resolve(startDir);
|
|
50
|
+
const root = dirname(currentDir);
|
|
51
|
+
|
|
52
|
+
while (currentDir !== root) {
|
|
53
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
54
|
+
const configPath = join(currentDir, fileName);
|
|
55
|
+
if (existsSync(configPath)) {
|
|
56
|
+
return configPath;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const parentDir = dirname(currentDir);
|
|
61
|
+
if (parentDir === currentDir) {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
currentDir = parentDir;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check root directory
|
|
68
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
69
|
+
const configPath = join(currentDir, fileName);
|
|
70
|
+
if (existsSync(configPath)) {
|
|
71
|
+
return configPath;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Parse YAML content into an object
|
|
80
|
+
*/
|
|
81
|
+
export function parseConfig(content: string, filePath?: string): unknown {
|
|
82
|
+
try {
|
|
83
|
+
return parseYaml(content);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : 'Unknown parse error';
|
|
86
|
+
throw new ConfigError(
|
|
87
|
+
`Failed to parse YAML${filePath ? ` in ${filePath}` : ''}: ${message}`,
|
|
88
|
+
'PARSE_ERROR',
|
|
89
|
+
error
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Validate config against JSON schema
|
|
96
|
+
*/
|
|
97
|
+
export function validateConfig(config: unknown): CoreAIConfig {
|
|
98
|
+
const ajv = new Ajv.default({ allErrors: true, strict: false });
|
|
99
|
+
addFormats.default(ajv);
|
|
100
|
+
|
|
101
|
+
const validate = ajv.compile<CoreAIConfig>(configSchema);
|
|
102
|
+
const valid = validate(config);
|
|
103
|
+
|
|
104
|
+
if (!valid) {
|
|
105
|
+
const errors: ErrorObject[] = validate.errors ?? [];
|
|
106
|
+
const errorMessages = errors.map((err: ErrorObject) => {
|
|
107
|
+
const path = err.instancePath || 'root';
|
|
108
|
+
return `${path}: ${err.message ?? 'unknown error'}`;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
throw new ConfigError(
|
|
112
|
+
`Configuration validation failed:\n - ${errorMessages.join('\n - ')}`,
|
|
113
|
+
'VALIDATION_ERROR',
|
|
114
|
+
errors
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return config as CoreAIConfig;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Apply default values to config
|
|
123
|
+
*/
|
|
124
|
+
export function applyDefaults(config: CoreAIConfig): ResolvedCoreAIConfig {
|
|
125
|
+
return {
|
|
126
|
+
...config,
|
|
127
|
+
project: {
|
|
128
|
+
name: config.project?.name ?? 'unnamed',
|
|
129
|
+
type: config.project?.type ?? 'software',
|
|
130
|
+
root: config.project?.root ?? process.cwd(),
|
|
131
|
+
},
|
|
132
|
+
team: {
|
|
133
|
+
agents: config.team?.agents ?? DEFAULT_AGENTS,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Load and validate configuration from a file path
|
|
140
|
+
*/
|
|
141
|
+
export function loadConfigFromFile(filePath: string): ResolvedCoreAIConfig {
|
|
142
|
+
let content: string;
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
content = readFileSync(filePath, 'utf-8');
|
|
146
|
+
} catch (error) {
|
|
147
|
+
const message = error instanceof Error ? error.message : 'Unknown read error';
|
|
148
|
+
throw new ConfigError(
|
|
149
|
+
`Failed to read config file ${filePath}: ${message}`,
|
|
150
|
+
'READ_ERROR',
|
|
151
|
+
error
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const parsed = parseConfig(content, filePath);
|
|
156
|
+
const validated = validateConfig(parsed);
|
|
157
|
+
return applyDefaults(validated);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Load configuration by searching for config file
|
|
162
|
+
*/
|
|
163
|
+
export function loadConfig(startDir?: string): ResolvedCoreAIConfig {
|
|
164
|
+
const configPath = findConfigFile(startDir);
|
|
165
|
+
|
|
166
|
+
if (!configPath) {
|
|
167
|
+
throw new ConfigError(
|
|
168
|
+
`Configuration file not found. Create a ${CONFIG_FILE_NAME} file or run 'coreai init'.`,
|
|
169
|
+
'NOT_FOUND'
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return loadConfigFromFile(configPath);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check if a config file exists in the given directory or parent directories
|
|
178
|
+
*/
|
|
179
|
+
export function configExists(startDir?: string): boolean {
|
|
180
|
+
return findConfigFile(startDir) !== null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get the path to the config file, or null if not found
|
|
185
|
+
*/
|
|
186
|
+
export function getConfigPath(startDir?: string): string | null {
|
|
187
|
+
return findConfigFile(startDir);
|
|
188
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CoreAI Configuration Types
|
|
3
|
+
*
|
|
4
|
+
* TypeScript types corresponding to the JSON schema at schemas/coreai.config.schema.json
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type ProjectType = 'software' | 'infrastructure' | 'data' | 'mobile';
|
|
8
|
+
|
|
9
|
+
export type IssueTrackerProvider = 'jira' | 'linear' | 'github-issues' | 'azure-devops';
|
|
10
|
+
export type GitProvider = 'github' | 'gitlab' | 'bitbucket' | 'azure-devops';
|
|
11
|
+
export type DocumentationProvider = 'confluence' | 'notion' | 'github-wiki' | 'local';
|
|
12
|
+
export type StateProvider = 'confluence' | 'notion' | 's3' | 'github-repo' | 'local';
|
|
13
|
+
|
|
14
|
+
export type AdapterStrategy = 'mcp' | 'native' | 'auto';
|
|
15
|
+
|
|
16
|
+
export interface ProjectConfig {
|
|
17
|
+
name: string;
|
|
18
|
+
type?: ProjectType;
|
|
19
|
+
root?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TeamConfig {
|
|
23
|
+
agents?: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface IssueTrackerConfig {
|
|
27
|
+
project_key?: string;
|
|
28
|
+
base_url?: string;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface IssueTrackerIntegration {
|
|
33
|
+
provider: IssueTrackerProvider;
|
|
34
|
+
config?: IssueTrackerConfig;
|
|
35
|
+
strategy?: AdapterStrategy;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface GitConfig {
|
|
39
|
+
owner?: string;
|
|
40
|
+
repo?: string;
|
|
41
|
+
default_branch?: string;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface GitIntegration {
|
|
46
|
+
provider: GitProvider;
|
|
47
|
+
config?: GitConfig;
|
|
48
|
+
strategy?: AdapterStrategy;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface DocumentationConfig {
|
|
52
|
+
space_key?: string;
|
|
53
|
+
base_url?: string;
|
|
54
|
+
base_path?: string;
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface DocumentationIntegration {
|
|
59
|
+
provider: DocumentationProvider;
|
|
60
|
+
config?: DocumentationConfig;
|
|
61
|
+
strategy?: AdapterStrategy;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface StateConfig {
|
|
65
|
+
space_key?: string;
|
|
66
|
+
base_path?: string;
|
|
67
|
+
bucket?: string;
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface StateIntegration {
|
|
72
|
+
provider: StateProvider;
|
|
73
|
+
config?: StateConfig;
|
|
74
|
+
strategy?: AdapterStrategy;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface IntegrationsConfig {
|
|
78
|
+
issue_tracker?: IssueTrackerIntegration;
|
|
79
|
+
git?: GitIntegration;
|
|
80
|
+
documentation?: DocumentationIntegration;
|
|
81
|
+
state?: StateIntegration;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface QualityGate {
|
|
85
|
+
command: string;
|
|
86
|
+
required?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type QualityGatesConfig = Record<string, QualityGate>;
|
|
90
|
+
|
|
91
|
+
export interface TechStackConfig {
|
|
92
|
+
primary_language?: string;
|
|
93
|
+
frameworks?: string[];
|
|
94
|
+
cloud?: string;
|
|
95
|
+
[key: string]: unknown;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface McpConfig {
|
|
99
|
+
expose_adapters?: boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Cache configuration
|
|
104
|
+
*/
|
|
105
|
+
export interface CacheConfig {
|
|
106
|
+
/**
|
|
107
|
+
* Enable caching (default: true)
|
|
108
|
+
*/
|
|
109
|
+
enabled?: boolean;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Cache directory path (default: .coreai/cache)
|
|
113
|
+
*/
|
|
114
|
+
path?: string;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Default TTL in seconds (default: 3600)
|
|
118
|
+
*/
|
|
119
|
+
ttl?: number;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Maximum cache size in bytes (default: 100MB)
|
|
123
|
+
*/
|
|
124
|
+
max_size?: number;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Maximum number of entries (default: 1000)
|
|
128
|
+
*/
|
|
129
|
+
max_entries?: number;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Enable automatic cleanup of expired entries (default: true)
|
|
133
|
+
*/
|
|
134
|
+
auto_cleanup?: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface CoreAIConfig {
|
|
138
|
+
version?: '1.0';
|
|
139
|
+
project?: ProjectConfig;
|
|
140
|
+
team?: TeamConfig;
|
|
141
|
+
integrations?: IntegrationsConfig;
|
|
142
|
+
quality_gates?: QualityGatesConfig;
|
|
143
|
+
tech_stack?: TechStackConfig;
|
|
144
|
+
mcp?: McpConfig;
|
|
145
|
+
cache?: CacheConfig;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Configuration with all defaults applied
|
|
150
|
+
*/
|
|
151
|
+
export interface ResolvedCoreAIConfig extends CoreAIConfig {
|
|
152
|
+
project: Required<ProjectConfig>;
|
|
153
|
+
team: Required<TeamConfig>;
|
|
154
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Module
|
|
3
|
+
*
|
|
4
|
+
* Provides context loading and resolution from remote documentation providers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
ContextSource,
|
|
9
|
+
ContextLoadResult,
|
|
10
|
+
ContextLoaderOptions,
|
|
11
|
+
LoadOptions,
|
|
12
|
+
} from './loader.js';
|
|
13
|
+
|
|
14
|
+
export { ContextLoader, createContextLoader } from './loader.js';
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Loader Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { promises as fs } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { tmpdir } from 'os';
|
|
8
|
+
import { ContextLoader, createContextLoader } from './loader.js';
|
|
9
|
+
import { FileCacheProvider } from '../cache/provider.js';
|
|
10
|
+
import { CacheManager } from '../cache/manager.js';
|
|
11
|
+
import type { RemoteFetcher, ContextSource } from '../index.js';
|
|
12
|
+
|
|
13
|
+
describe('ContextLoader', () => {
|
|
14
|
+
let testDir: string;
|
|
15
|
+
let cacheProvider: FileCacheProvider;
|
|
16
|
+
let cacheManager: CacheManager;
|
|
17
|
+
let loader: ContextLoader;
|
|
18
|
+
|
|
19
|
+
// Mock fetcher for testing
|
|
20
|
+
const createMockFetcher = (responses: Record<string, string>): RemoteFetcher => ({
|
|
21
|
+
async fetch(url: string) {
|
|
22
|
+
const content = responses[url];
|
|
23
|
+
if (!content) {
|
|
24
|
+
throw new Error(`Not found: ${url}`);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
content,
|
|
28
|
+
metadata: {
|
|
29
|
+
contentType: 'text/markdown',
|
|
30
|
+
title: `Doc: ${url}`,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
async hasChanged() {
|
|
35
|
+
return true;
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
beforeEach(async () => {
|
|
40
|
+
testDir = join(tmpdir(), `context-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
41
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
42
|
+
|
|
43
|
+
cacheProvider = new FileCacheProvider({ basePath: testDir });
|
|
44
|
+
await cacheProvider.initialize();
|
|
45
|
+
|
|
46
|
+
cacheManager = new CacheManager({ cache: cacheProvider });
|
|
47
|
+
|
|
48
|
+
loader = new ContextLoader({
|
|
49
|
+
cacheManager,
|
|
50
|
+
defaultTtl: 3600,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(async () => {
|
|
55
|
+
try {
|
|
56
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
57
|
+
} catch {
|
|
58
|
+
// Ignore cleanup errors
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('constructor', () => {
|
|
63
|
+
it('should create loader with options', () => {
|
|
64
|
+
expect(loader).toBeInstanceOf(ContextLoader);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('load', () => {
|
|
69
|
+
it('should load a single context', async () => {
|
|
70
|
+
const fetcher = createMockFetcher({
|
|
71
|
+
'https://github.com/docs/readme': '# README\nProject documentation',
|
|
72
|
+
});
|
|
73
|
+
cacheManager.registerFetcher('github', fetcher);
|
|
74
|
+
|
|
75
|
+
const source: ContextSource = {
|
|
76
|
+
key: 'readme',
|
|
77
|
+
url: 'https://github.com/docs/readme',
|
|
78
|
+
title: 'README',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const entry = await loader.load(source);
|
|
82
|
+
|
|
83
|
+
expect(entry.content).toBe('# README\nProject documentation');
|
|
84
|
+
expect(entry.metadata.key).toBe('readme');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should throw for required context if fetch fails', async () => {
|
|
88
|
+
const fetcher = createMockFetcher({}); // No responses
|
|
89
|
+
cacheManager.registerFetcher('github', fetcher);
|
|
90
|
+
|
|
91
|
+
const source: ContextSource = {
|
|
92
|
+
key: 'missing',
|
|
93
|
+
url: 'https://github.com/missing',
|
|
94
|
+
required: true,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
await expect(loader.load(source)).rejects.toThrow(/Failed to load required context/);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should apply tags from source', async () => {
|
|
101
|
+
const fetcher = createMockFetcher({
|
|
102
|
+
'https://github.com/docs': 'Content',
|
|
103
|
+
});
|
|
104
|
+
cacheManager.registerFetcher('github', fetcher);
|
|
105
|
+
|
|
106
|
+
const source: ContextSource = {
|
|
107
|
+
key: 'tagged',
|
|
108
|
+
url: 'https://github.com/docs',
|
|
109
|
+
tags: ['docs', 'architecture'],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
await loader.load(source);
|
|
113
|
+
|
|
114
|
+
const metadata = await cacheProvider.getMetadata('tagged');
|
|
115
|
+
expect(metadata?.tags).toEqual(['docs', 'architecture']);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('loadMany', () => {
|
|
120
|
+
it('should load multiple contexts', async () => {
|
|
121
|
+
const fetcher = createMockFetcher({
|
|
122
|
+
'https://github.com/docs/1': 'Content 1',
|
|
123
|
+
'https://github.com/docs/2': 'Content 2',
|
|
124
|
+
'https://github.com/docs/3': 'Content 3',
|
|
125
|
+
});
|
|
126
|
+
cacheManager.registerFetcher('github', fetcher);
|
|
127
|
+
|
|
128
|
+
const sources: ContextSource[] = [
|
|
129
|
+
{ key: 'doc1', url: 'https://github.com/docs/1' },
|
|
130
|
+
{ key: 'doc2', url: 'https://github.com/docs/2' },
|
|
131
|
+
{ key: 'doc3', url: 'https://github.com/docs/3' },
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
const result = await loader.loadMany(sources);
|
|
135
|
+
|
|
136
|
+
expect(result.loaded).toHaveLength(3);
|
|
137
|
+
expect(result.failed).toHaveLength(0);
|
|
138
|
+
expect(result.duration).toBeGreaterThan(0);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should report failures with continueOnError', async () => {
|
|
142
|
+
const fetcher = createMockFetcher({
|
|
143
|
+
'https://github.com/docs/1': 'Content 1',
|
|
144
|
+
// doc2 missing
|
|
145
|
+
});
|
|
146
|
+
cacheManager.registerFetcher('github', fetcher);
|
|
147
|
+
|
|
148
|
+
const sources: ContextSource[] = [
|
|
149
|
+
{ key: 'doc1', url: 'https://github.com/docs/1' },
|
|
150
|
+
{ key: 'doc2', url: 'https://github.com/docs/2' },
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
const result = await loader.loadMany(sources, { continueOnError: true });
|
|
154
|
+
|
|
155
|
+
expect(result.loaded).toHaveLength(1);
|
|
156
|
+
expect(result.failed).toHaveLength(1);
|
|
157
|
+
expect(result.failed[0].key).toBe('doc2');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should fail on required context by default', async () => {
|
|
161
|
+
const fetcher = createMockFetcher({
|
|
162
|
+
'https://github.com/docs/1': 'Content',
|
|
163
|
+
});
|
|
164
|
+
cacheManager.registerFetcher('github', fetcher);
|
|
165
|
+
|
|
166
|
+
const sources: ContextSource[] = [
|
|
167
|
+
{ key: 'doc1', url: 'https://github.com/docs/1' },
|
|
168
|
+
{ key: 'required-missing', url: 'https://github.com/missing', required: true },
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
await expect(loader.loadMany(sources)).rejects.toThrow(/Failed to load required context/);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should track fromCache status', async () => {
|
|
175
|
+
const fetcher = createMockFetcher({
|
|
176
|
+
'https://github.com/docs/cached': 'Cached content',
|
|
177
|
+
'https://github.com/docs/fresh': 'Fresh content',
|
|
178
|
+
});
|
|
179
|
+
cacheManager.registerFetcher('github', fetcher);
|
|
180
|
+
|
|
181
|
+
// Pre-cache one entry
|
|
182
|
+
await cacheProvider.set('cached-doc', 'Pre-cached content', {
|
|
183
|
+
source: 'github',
|
|
184
|
+
sourceUrl: 'https://github.com/docs/cached',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const sources: ContextSource[] = [
|
|
188
|
+
{ key: 'cached-doc', url: 'https://github.com/docs/cached' },
|
|
189
|
+
{ key: 'fresh-doc', url: 'https://github.com/docs/fresh' },
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const result = await loader.loadMany(sources);
|
|
193
|
+
|
|
194
|
+
const cachedEntry = result.loaded.find((l) => l.key === 'cached-doc');
|
|
195
|
+
const freshEntry = result.loaded.find((l) => l.key === 'fresh-doc');
|
|
196
|
+
|
|
197
|
+
expect(cachedEntry?.fromCache).toBe(true);
|
|
198
|
+
expect(freshEntry?.fromCache).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should call progress callback', async () => {
|
|
202
|
+
const fetcher = createMockFetcher({
|
|
203
|
+
'https://github.com/docs/1': 'Content 1',
|
|
204
|
+
'https://github.com/docs/2': 'Content 2',
|
|
205
|
+
});
|
|
206
|
+
cacheManager.registerFetcher('github', fetcher);
|
|
207
|
+
|
|
208
|
+
const progressCalls: { loaded: number; total: number; current: string }[] = [];
|
|
209
|
+
|
|
210
|
+
const sources: ContextSource[] = [
|
|
211
|
+
{ key: 'doc1', url: 'https://github.com/docs/1' },
|
|
212
|
+
{ key: 'doc2', url: 'https://github.com/docs/2' },
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
await loader.loadMany(sources, {
|
|
216
|
+
onProgress: (loaded, total, current) => {
|
|
217
|
+
progressCalls.push({ loaded, total, current });
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(progressCalls.length).toBeGreaterThan(0);
|
|
222
|
+
expect(progressCalls[progressCalls.length - 1].loaded).toBe(2);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('refresh', () => {
|
|
227
|
+
it('should force refresh all contexts', async () => {
|
|
228
|
+
const responses = {
|
|
229
|
+
'https://github.com/docs/1': 'Original',
|
|
230
|
+
};
|
|
231
|
+
const fetcher = createMockFetcher(responses);
|
|
232
|
+
cacheManager.registerFetcher('github', fetcher);
|
|
233
|
+
|
|
234
|
+
const sources: ContextSource[] = [{ key: 'doc1', url: 'https://github.com/docs/1' }];
|
|
235
|
+
|
|
236
|
+
// Initial load
|
|
237
|
+
await loader.loadMany(sources);
|
|
238
|
+
|
|
239
|
+
// Change content
|
|
240
|
+
responses['https://github.com/docs/1'] = 'Updated';
|
|
241
|
+
|
|
242
|
+
// Refresh
|
|
243
|
+
const result = await loader.refresh(sources);
|
|
244
|
+
|
|
245
|
+
expect(result.loaded).toHaveLength(1);
|
|
246
|
+
expect(result.loaded[0].content).toBe('Updated');
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('loadByTag', () => {
|
|
251
|
+
it('should load all contexts with a specific tag', async () => {
|
|
252
|
+
// Pre-cache entries with tags
|
|
253
|
+
await cacheProvider.set(
|
|
254
|
+
'doc1',
|
|
255
|
+
'Content 1',
|
|
256
|
+
{ source: 'github', sourceUrl: '' },
|
|
257
|
+
{ tags: ['architecture'] }
|
|
258
|
+
);
|
|
259
|
+
await cacheProvider.set(
|
|
260
|
+
'doc2',
|
|
261
|
+
'Content 2',
|
|
262
|
+
{ source: 'github', sourceUrl: '' },
|
|
263
|
+
{ tags: ['architecture', 'design'] }
|
|
264
|
+
);
|
|
265
|
+
await cacheProvider.set(
|
|
266
|
+
'doc3',
|
|
267
|
+
'Content 3',
|
|
268
|
+
{ source: 'github', sourceUrl: '' },
|
|
269
|
+
{ tags: ['other'] }
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const result = await loader.loadByTag('architecture');
|
|
273
|
+
|
|
274
|
+
expect(result.loaded).toHaveLength(2);
|
|
275
|
+
expect(result.loaded.map((l) => l.key).sort()).toEqual(['doc1', 'doc2']);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe('checkAvailability', () => {
|
|
280
|
+
it('should report available cached contexts', async () => {
|
|
281
|
+
cacheManager.registerFetcher('github', createMockFetcher({}));
|
|
282
|
+
|
|
283
|
+
// Pre-cache an entry
|
|
284
|
+
await cacheProvider.set('cached', 'Content', {
|
|
285
|
+
source: 'github',
|
|
286
|
+
sourceUrl: 'https://github.com/test',
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const sources: ContextSource[] = [
|
|
290
|
+
{ key: 'cached', url: 'https://github.com/test' },
|
|
291
|
+
{ key: 'missing', url: 'https://github.com/missing' },
|
|
292
|
+
];
|
|
293
|
+
|
|
294
|
+
const { available, missing } = await loader.checkAvailability(sources);
|
|
295
|
+
|
|
296
|
+
expect(available).toContain('cached');
|
|
297
|
+
expect(missing).toContain('missing');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should report errors for sources without fetchers', async () => {
|
|
301
|
+
const sources: ContextSource[] = [
|
|
302
|
+
{ key: 'no-fetcher', url: 'https://unknown-source.com/doc' },
|
|
303
|
+
];
|
|
304
|
+
|
|
305
|
+
const { errors } = await loader.checkAvailability(sources);
|
|
306
|
+
|
|
307
|
+
expect(errors.length).toBe(1);
|
|
308
|
+
expect(errors[0]).toContain('No fetcher');
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe('getCacheManager', () => {
|
|
313
|
+
it('should return the cache manager', () => {
|
|
314
|
+
expect(loader.getCacheManager()).toBe(cacheManager);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
describe('createContextLoader', () => {
|
|
320
|
+
it('should create a loader instance', async () => {
|
|
321
|
+
const testDir = join(tmpdir(), `create-loader-${Date.now()}`);
|
|
322
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
323
|
+
|
|
324
|
+
const cache = new FileCacheProvider({ basePath: testDir });
|
|
325
|
+
await cache.initialize();
|
|
326
|
+
|
|
327
|
+
const manager = new CacheManager({ cache });
|
|
328
|
+
const loader = createContextLoader({ cacheManager: manager });
|
|
329
|
+
|
|
330
|
+
expect(loader).toBeInstanceOf(ContextLoader);
|
|
331
|
+
|
|
332
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
333
|
+
});
|
|
334
|
+
});
|