@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,4037 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
import { join as join11, dirname as dirname4 } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
|
|
8
|
+
// src/config/loader.ts
|
|
9
|
+
import { existsSync, readFileSync } from "fs";
|
|
10
|
+
import { dirname, join, resolve } from "path";
|
|
11
|
+
import { parse as parseYaml } from "yaml";
|
|
12
|
+
import Ajv from "ajv";
|
|
13
|
+
import addFormats from "ajv-formats";
|
|
14
|
+
import { createRequire } from "module";
|
|
15
|
+
var require2 = createRequire(import.meta.url);
|
|
16
|
+
var configSchema = require2("../../schemas/coreai.config.schema.json");
|
|
17
|
+
var CONFIG_FILE_NAME = "coreai.config.yaml";
|
|
18
|
+
var CONFIG_FILE_NAMES = [CONFIG_FILE_NAME, "coreai.config.yml"];
|
|
19
|
+
var ConfigError = class extends Error {
|
|
20
|
+
constructor(message, code, details) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.details = details;
|
|
24
|
+
this.name = "ConfigError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var DEFAULT_AGENTS = [
|
|
28
|
+
"backend-engineer",
|
|
29
|
+
"frontend-engineer",
|
|
30
|
+
"devops-engineer",
|
|
31
|
+
"engineering-manager"
|
|
32
|
+
];
|
|
33
|
+
function findConfigFile(startDir = process.cwd()) {
|
|
34
|
+
let currentDir = resolve(startDir);
|
|
35
|
+
const root = dirname(currentDir);
|
|
36
|
+
while (currentDir !== root) {
|
|
37
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
38
|
+
const configPath = join(currentDir, fileName);
|
|
39
|
+
if (existsSync(configPath)) {
|
|
40
|
+
return configPath;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const parentDir = dirname(currentDir);
|
|
44
|
+
if (parentDir === currentDir) {
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
currentDir = parentDir;
|
|
48
|
+
}
|
|
49
|
+
for (const fileName of CONFIG_FILE_NAMES) {
|
|
50
|
+
const configPath = join(currentDir, fileName);
|
|
51
|
+
if (existsSync(configPath)) {
|
|
52
|
+
return configPath;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function parseConfig(content, filePath) {
|
|
58
|
+
try {
|
|
59
|
+
return parseYaml(content);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const message = error instanceof Error ? error.message : "Unknown parse error";
|
|
62
|
+
throw new ConfigError(
|
|
63
|
+
`Failed to parse YAML${filePath ? ` in ${filePath}` : ""}: ${message}`,
|
|
64
|
+
"PARSE_ERROR",
|
|
65
|
+
error
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function validateConfig(config) {
|
|
70
|
+
const ajv = new Ajv.default({ allErrors: true, strict: false });
|
|
71
|
+
addFormats.default(ajv);
|
|
72
|
+
const validate2 = ajv.compile(configSchema);
|
|
73
|
+
const valid = validate2(config);
|
|
74
|
+
if (!valid) {
|
|
75
|
+
const errors = validate2.errors ?? [];
|
|
76
|
+
const errorMessages = errors.map((err) => {
|
|
77
|
+
const path = err.instancePath || "root";
|
|
78
|
+
return `${path}: ${err.message ?? "unknown error"}`;
|
|
79
|
+
});
|
|
80
|
+
throw new ConfigError(
|
|
81
|
+
`Configuration validation failed:
|
|
82
|
+
- ${errorMessages.join("\n - ")}`,
|
|
83
|
+
"VALIDATION_ERROR",
|
|
84
|
+
errors
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return config;
|
|
88
|
+
}
|
|
89
|
+
function applyDefaults(config) {
|
|
90
|
+
return {
|
|
91
|
+
...config,
|
|
92
|
+
project: {
|
|
93
|
+
name: config.project?.name ?? "unnamed",
|
|
94
|
+
type: config.project?.type ?? "software",
|
|
95
|
+
root: config.project?.root ?? process.cwd()
|
|
96
|
+
},
|
|
97
|
+
team: {
|
|
98
|
+
agents: config.team?.agents ?? DEFAULT_AGENTS
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function loadConfigFromFile(filePath) {
|
|
103
|
+
let content;
|
|
104
|
+
try {
|
|
105
|
+
content = readFileSync(filePath, "utf-8");
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const message = error instanceof Error ? error.message : "Unknown read error";
|
|
108
|
+
throw new ConfigError(
|
|
109
|
+
`Failed to read config file ${filePath}: ${message}`,
|
|
110
|
+
"READ_ERROR",
|
|
111
|
+
error
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
const parsed = parseConfig(content, filePath);
|
|
115
|
+
const validated = validateConfig(parsed);
|
|
116
|
+
return applyDefaults(validated);
|
|
117
|
+
}
|
|
118
|
+
function loadConfig(startDir) {
|
|
119
|
+
const configPath = findConfigFile(startDir);
|
|
120
|
+
if (!configPath) {
|
|
121
|
+
throw new ConfigError(
|
|
122
|
+
`Configuration file not found. Create a ${CONFIG_FILE_NAME} file or run 'coreai init'.`,
|
|
123
|
+
"NOT_FOUND"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return loadConfigFromFile(configPath);
|
|
127
|
+
}
|
|
128
|
+
function configExists(startDir) {
|
|
129
|
+
return findConfigFile(startDir) !== null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/agents/loader.ts
|
|
133
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
134
|
+
import { basename, extname, join as join2 } from "path";
|
|
135
|
+
import { parse as parseYaml2 } from "yaml";
|
|
136
|
+
import Ajv2 from "ajv";
|
|
137
|
+
import { createRequire as createRequire2 } from "module";
|
|
138
|
+
var require3 = createRequire2(import.meta.url);
|
|
139
|
+
var agentSchema = require3("../../schemas/agent.schema.json");
|
|
140
|
+
var AgentError = class extends Error {
|
|
141
|
+
constructor(message, code, details) {
|
|
142
|
+
super(message);
|
|
143
|
+
this.code = code;
|
|
144
|
+
this.details = details;
|
|
145
|
+
this.name = "AgentError";
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
function parseAgentYaml(content, filePath) {
|
|
149
|
+
try {
|
|
150
|
+
return parseYaml2(content);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
const message = error instanceof Error ? error.message : "Unknown parse error";
|
|
153
|
+
throw new AgentError(
|
|
154
|
+
`Failed to parse agent YAML${filePath ? ` in ${filePath}` : ""}: ${message}`,
|
|
155
|
+
"PARSE_ERROR",
|
|
156
|
+
error
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function validateAgentDefinition(agent) {
|
|
161
|
+
const ajv = new Ajv2.default({ allErrors: true, strict: false });
|
|
162
|
+
const validate2 = ajv.compile(agentSchema);
|
|
163
|
+
const valid = validate2(agent);
|
|
164
|
+
if (!valid) {
|
|
165
|
+
const errors = validate2.errors ?? [];
|
|
166
|
+
const errorMessages = errors.map((err) => {
|
|
167
|
+
const path = err.instancePath || "root";
|
|
168
|
+
return `${path}: ${err.message ?? "unknown error"}`;
|
|
169
|
+
});
|
|
170
|
+
throw new AgentError(
|
|
171
|
+
`Agent validation failed:
|
|
172
|
+
- ${errorMessages.join("\n - ")}`,
|
|
173
|
+
"VALIDATION_ERROR",
|
|
174
|
+
errors
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return agent;
|
|
178
|
+
}
|
|
179
|
+
function loadAgentFromFile(filePath) {
|
|
180
|
+
if (!existsSync2(filePath)) {
|
|
181
|
+
throw new AgentError(`Agent file not found: ${filePath}`, "NOT_FOUND");
|
|
182
|
+
}
|
|
183
|
+
let content;
|
|
184
|
+
try {
|
|
185
|
+
content = readFileSync2(filePath, "utf-8");
|
|
186
|
+
} catch (error) {
|
|
187
|
+
const message = error instanceof Error ? error.message : "Unknown read error";
|
|
188
|
+
throw new AgentError(`Failed to read agent file ${filePath}: ${message}`, "READ_ERROR", error);
|
|
189
|
+
}
|
|
190
|
+
const parsed = parseAgentYaml(content, filePath);
|
|
191
|
+
return validateAgentDefinition(parsed);
|
|
192
|
+
}
|
|
193
|
+
function listYamlFiles(dir) {
|
|
194
|
+
if (!existsSync2(dir)) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
return readdirSync(dir).filter((file) => {
|
|
198
|
+
const ext = extname(file).toLowerCase();
|
|
199
|
+
return ext === ".yaml" || ext === ".yml";
|
|
200
|
+
}).map((file) => join2(dir, file));
|
|
201
|
+
}
|
|
202
|
+
function loadAgentsFromDirectory(dir, source) {
|
|
203
|
+
const agents2 = /* @__PURE__ */ new Map();
|
|
204
|
+
const files = listYamlFiles(dir);
|
|
205
|
+
for (const filePath of files) {
|
|
206
|
+
try {
|
|
207
|
+
const definition = loadAgentFromFile(filePath);
|
|
208
|
+
agents2.set(definition.role, {
|
|
209
|
+
definition,
|
|
210
|
+
source,
|
|
211
|
+
filePath
|
|
212
|
+
});
|
|
213
|
+
} catch (error) {
|
|
214
|
+
const fileName = basename(filePath);
|
|
215
|
+
console.warn(`Warning: Failed to load agent from ${fileName}: ${error.message}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return agents2;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/agents/resolver.ts
|
|
222
|
+
var ResolutionError = class extends Error {
|
|
223
|
+
constructor(message, variable, path) {
|
|
224
|
+
super(message);
|
|
225
|
+
this.variable = variable;
|
|
226
|
+
this.path = path;
|
|
227
|
+
this.name = "ResolutionError";
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
var VARIABLE_PATTERN = /\$\{([a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)*)\}/gi;
|
|
231
|
+
function getNestedValue(obj, path) {
|
|
232
|
+
const parts = path.split(".");
|
|
233
|
+
let current = obj;
|
|
234
|
+
for (const part of parts) {
|
|
235
|
+
if (current === null || current === void 0) {
|
|
236
|
+
return void 0;
|
|
237
|
+
}
|
|
238
|
+
if (typeof current !== "object") {
|
|
239
|
+
return void 0;
|
|
240
|
+
}
|
|
241
|
+
current = current[part];
|
|
242
|
+
}
|
|
243
|
+
return current;
|
|
244
|
+
}
|
|
245
|
+
function resolveVariable(variable, context, options) {
|
|
246
|
+
const parts = variable.split(".");
|
|
247
|
+
const namespace = parts[0];
|
|
248
|
+
const path = parts.slice(1).join(".");
|
|
249
|
+
let value;
|
|
250
|
+
switch (namespace) {
|
|
251
|
+
case "config":
|
|
252
|
+
if (!context.config) {
|
|
253
|
+
if (options.strict) {
|
|
254
|
+
throw new ResolutionError(
|
|
255
|
+
`Cannot resolve \${${variable}}: no config context provided`,
|
|
256
|
+
variable
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return void 0;
|
|
260
|
+
}
|
|
261
|
+
value = getNestedValue(context.config, path);
|
|
262
|
+
break;
|
|
263
|
+
case "agent":
|
|
264
|
+
if (!context.agent) {
|
|
265
|
+
if (options.strict) {
|
|
266
|
+
throw new ResolutionError(
|
|
267
|
+
`Cannot resolve \${${variable}}: no agent context provided`,
|
|
268
|
+
variable
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return void 0;
|
|
272
|
+
}
|
|
273
|
+
value = getNestedValue(context.agent, path);
|
|
274
|
+
break;
|
|
275
|
+
case "remote":
|
|
276
|
+
value = resolveRemoteVariable(path, context, options);
|
|
277
|
+
break;
|
|
278
|
+
default:
|
|
279
|
+
if (options.strict) {
|
|
280
|
+
throw new ResolutionError(`Unknown variable namespace: ${namespace}`, variable);
|
|
281
|
+
}
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
if (value === void 0) {
|
|
285
|
+
if (options.strict) {
|
|
286
|
+
throw new ResolutionError(`Cannot resolve \${${variable}}: path not found`, variable, path);
|
|
287
|
+
}
|
|
288
|
+
return void 0;
|
|
289
|
+
}
|
|
290
|
+
if (typeof value === "string") {
|
|
291
|
+
return value;
|
|
292
|
+
}
|
|
293
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
294
|
+
return String(value);
|
|
295
|
+
}
|
|
296
|
+
return JSON.stringify(value);
|
|
297
|
+
}
|
|
298
|
+
function resolveRemoteVariable(path, context, options) {
|
|
299
|
+
if (!context.config?.integrations) {
|
|
300
|
+
if (options.strict) {
|
|
301
|
+
throw new ResolutionError(
|
|
302
|
+
`Cannot resolve \${remote.${path}}: no integrations configured`,
|
|
303
|
+
`remote.${path}`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
return void 0;
|
|
307
|
+
}
|
|
308
|
+
const integrations = context.config.integrations;
|
|
309
|
+
switch (path) {
|
|
310
|
+
case "documentation":
|
|
311
|
+
return integrations.documentation?.config?.base_url ?? integrations.documentation?.config?.base_path;
|
|
312
|
+
case "issues":
|
|
313
|
+
return integrations.issue_tracker?.config?.base_url;
|
|
314
|
+
case "git":
|
|
315
|
+
return integrations.git?.config?.repo;
|
|
316
|
+
case "state":
|
|
317
|
+
return integrations.state?.config?.base_path ?? integrations.state?.config?.bucket;
|
|
318
|
+
default:
|
|
319
|
+
return getNestedValue(integrations, path);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function resolveString(input, context, options = {}) {
|
|
323
|
+
return input.replace(VARIABLE_PATTERN, (match, variable) => {
|
|
324
|
+
const resolved = resolveVariable(variable, context, options);
|
|
325
|
+
return resolved !== void 0 ? resolved : match;
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function resolveObject(obj, context, options = {}) {
|
|
329
|
+
if (obj === null || obj === void 0) {
|
|
330
|
+
return obj;
|
|
331
|
+
}
|
|
332
|
+
if (typeof obj === "string") {
|
|
333
|
+
return resolveString(obj, context, options);
|
|
334
|
+
}
|
|
335
|
+
if (Array.isArray(obj)) {
|
|
336
|
+
return obj.map((item) => resolveObject(item, context, options));
|
|
337
|
+
}
|
|
338
|
+
if (typeof obj === "object") {
|
|
339
|
+
const result = {};
|
|
340
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
341
|
+
result[key] = resolveObject(value, context, options);
|
|
342
|
+
}
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
return obj;
|
|
346
|
+
}
|
|
347
|
+
function resolveAgentDefinition(agent, config, options = {}) {
|
|
348
|
+
const context = {
|
|
349
|
+
agent
|
|
350
|
+
};
|
|
351
|
+
if (config) {
|
|
352
|
+
context.config = config;
|
|
353
|
+
}
|
|
354
|
+
return resolveObject(agent, context, options);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/agents/compiler.ts
|
|
358
|
+
import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
|
|
359
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
360
|
+
function generateAgentMarkdown(agent) {
|
|
361
|
+
const lines = [];
|
|
362
|
+
lines.push(`# ${agent.display_name}`);
|
|
363
|
+
lines.push("");
|
|
364
|
+
lines.push(`**Role:** ${agent.role}`);
|
|
365
|
+
lines.push(`**Type:** ${agent.type}`);
|
|
366
|
+
lines.push("");
|
|
367
|
+
lines.push("## Description");
|
|
368
|
+
lines.push("");
|
|
369
|
+
lines.push(agent.description.trim());
|
|
370
|
+
lines.push("");
|
|
371
|
+
if (agent.responsibilities && agent.responsibilities.length > 0) {
|
|
372
|
+
lines.push("## Responsibilities");
|
|
373
|
+
lines.push("");
|
|
374
|
+
for (const responsibility of agent.responsibilities) {
|
|
375
|
+
lines.push(`- ${responsibility}`);
|
|
376
|
+
}
|
|
377
|
+
lines.push("");
|
|
378
|
+
}
|
|
379
|
+
if (agent.expertise) {
|
|
380
|
+
lines.push("## Expertise");
|
|
381
|
+
lines.push("");
|
|
382
|
+
if (agent.expertise.primary && agent.expertise.primary.length > 0) {
|
|
383
|
+
lines.push("### Primary Areas");
|
|
384
|
+
lines.push("");
|
|
385
|
+
for (const area of agent.expertise.primary) {
|
|
386
|
+
lines.push(`- ${area}`);
|
|
387
|
+
}
|
|
388
|
+
lines.push("");
|
|
389
|
+
}
|
|
390
|
+
if (agent.expertise.tech_stack) {
|
|
391
|
+
lines.push("### Tech Stack");
|
|
392
|
+
lines.push("");
|
|
393
|
+
const techStack = agent.expertise.tech_stack;
|
|
394
|
+
if (typeof techStack === "string") {
|
|
395
|
+
lines.push(techStack);
|
|
396
|
+
} else if (typeof techStack === "object") {
|
|
397
|
+
lines.push("```json");
|
|
398
|
+
lines.push(JSON.stringify(techStack, null, 2));
|
|
399
|
+
lines.push("```");
|
|
400
|
+
}
|
|
401
|
+
lines.push("");
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (agent.skills && agent.skills.length > 0) {
|
|
405
|
+
lines.push("## Skills");
|
|
406
|
+
lines.push("");
|
|
407
|
+
for (const skill of agent.skills) {
|
|
408
|
+
lines.push(`- ${skill}`);
|
|
409
|
+
}
|
|
410
|
+
lines.push("");
|
|
411
|
+
}
|
|
412
|
+
if (agent.principles) {
|
|
413
|
+
lines.push("## Principles");
|
|
414
|
+
lines.push("");
|
|
415
|
+
for (const [category, items] of Object.entries(agent.principles)) {
|
|
416
|
+
if (items && Array.isArray(items) && items.length > 0) {
|
|
417
|
+
const title = formatTitle(category);
|
|
418
|
+
lines.push(`### ${title}`);
|
|
419
|
+
lines.push("");
|
|
420
|
+
for (const item of items) {
|
|
421
|
+
lines.push(`- ${item}`);
|
|
422
|
+
}
|
|
423
|
+
lines.push("");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (agent.behaviors) {
|
|
428
|
+
lines.push("## Behaviors");
|
|
429
|
+
lines.push("");
|
|
430
|
+
if (agent.behaviors.workflow) {
|
|
431
|
+
lines.push(`**Workflow:** ${agent.behaviors.workflow}`);
|
|
432
|
+
lines.push("");
|
|
433
|
+
}
|
|
434
|
+
if (agent.behaviors.quality_gates) {
|
|
435
|
+
lines.push("### Quality Gates");
|
|
436
|
+
lines.push("");
|
|
437
|
+
const gates = agent.behaviors.quality_gates;
|
|
438
|
+
if (typeof gates === "string") {
|
|
439
|
+
lines.push(gates);
|
|
440
|
+
} else if (typeof gates === "object") {
|
|
441
|
+
lines.push("```json");
|
|
442
|
+
lines.push(JSON.stringify(gates, null, 2));
|
|
443
|
+
lines.push("```");
|
|
444
|
+
}
|
|
445
|
+
lines.push("");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (agent.context_sources) {
|
|
449
|
+
lines.push("## Context Sources");
|
|
450
|
+
lines.push("");
|
|
451
|
+
if (agent.context_sources.shared && agent.context_sources.shared.length > 0) {
|
|
452
|
+
lines.push("### Shared");
|
|
453
|
+
lines.push("");
|
|
454
|
+
for (const source of agent.context_sources.shared) {
|
|
455
|
+
lines.push(`- ${source}`);
|
|
456
|
+
}
|
|
457
|
+
lines.push("");
|
|
458
|
+
}
|
|
459
|
+
if (agent.context_sources.personal && agent.context_sources.personal.length > 0) {
|
|
460
|
+
lines.push("### Personal");
|
|
461
|
+
lines.push("");
|
|
462
|
+
for (const source of agent.context_sources.personal) {
|
|
463
|
+
lines.push(`- ${source}`);
|
|
464
|
+
}
|
|
465
|
+
lines.push("");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (agent.communication) {
|
|
469
|
+
lines.push("## Communication");
|
|
470
|
+
lines.push("");
|
|
471
|
+
if (agent.communication.inbox) {
|
|
472
|
+
lines.push(`**Inbox:** ${agent.communication.inbox}`);
|
|
473
|
+
}
|
|
474
|
+
if (agent.communication.outbox) {
|
|
475
|
+
lines.push(`**Outbox:** ${agent.communication.outbox}`);
|
|
476
|
+
}
|
|
477
|
+
lines.push("");
|
|
478
|
+
}
|
|
479
|
+
lines.push("---");
|
|
480
|
+
lines.push("");
|
|
481
|
+
lines.push("*Generated by CoreAI*");
|
|
482
|
+
lines.push("");
|
|
483
|
+
return lines.join("\n");
|
|
484
|
+
}
|
|
485
|
+
function formatTitle(str) {
|
|
486
|
+
return str.replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
487
|
+
}
|
|
488
|
+
function compileAgent(agent, config) {
|
|
489
|
+
const resolved = resolveAgentDefinition(agent, config);
|
|
490
|
+
return generateAgentMarkdown(resolved);
|
|
491
|
+
}
|
|
492
|
+
function loadAllAgents(options = {}) {
|
|
493
|
+
const agents2 = /* @__PURE__ */ new Map();
|
|
494
|
+
if (options.coreAgentsDir && existsSync3(options.coreAgentsDir)) {
|
|
495
|
+
const coreAgents = loadAgentsFromDirectory(options.coreAgentsDir, "core");
|
|
496
|
+
for (const [role, metadata] of coreAgents) {
|
|
497
|
+
agents2.set(role, metadata);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (options.customAgentsDir && existsSync3(options.customAgentsDir)) {
|
|
501
|
+
const customAgents = loadAgentsFromDirectory(options.customAgentsDir, "custom");
|
|
502
|
+
for (const [role, metadata] of customAgents) {
|
|
503
|
+
if (agents2.has(role)) {
|
|
504
|
+
metadata.source = "override";
|
|
505
|
+
}
|
|
506
|
+
agents2.set(role, metadata);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return agents2;
|
|
510
|
+
}
|
|
511
|
+
function filterAgentsByTeam(agents2, config) {
|
|
512
|
+
if (!config?.team?.agents || config.team.agents.length === 0) {
|
|
513
|
+
return agents2;
|
|
514
|
+
}
|
|
515
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
516
|
+
for (const role of config.team.agents) {
|
|
517
|
+
const metadata = agents2.get(role);
|
|
518
|
+
if (metadata) {
|
|
519
|
+
filtered.set(role, metadata);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return filtered;
|
|
523
|
+
}
|
|
524
|
+
function compileAgents(config, options = {}) {
|
|
525
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
526
|
+
const outputDir = options.outputDir ?? join3(projectRoot, ".claude", "agents");
|
|
527
|
+
const customAgentsDir = options.customAgentsDir ?? join3(projectRoot, "coreai", "agents");
|
|
528
|
+
const result = {
|
|
529
|
+
compiled: [],
|
|
530
|
+
errors: []
|
|
531
|
+
};
|
|
532
|
+
const allAgents = loadAllAgents({
|
|
533
|
+
...options,
|
|
534
|
+
customAgentsDir
|
|
535
|
+
});
|
|
536
|
+
const agents2 = filterAgentsByTeam(allAgents, config);
|
|
537
|
+
if (!existsSync3(outputDir)) {
|
|
538
|
+
mkdirSync(outputDir, { recursive: true });
|
|
539
|
+
}
|
|
540
|
+
for (const [role, metadata] of agents2) {
|
|
541
|
+
if (options.filter && !options.filter(metadata.definition)) {
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
const markdown = compileAgent(metadata.definition, config);
|
|
546
|
+
const outputPath = join3(outputDir, `${role}.md`);
|
|
547
|
+
writeFileSync(outputPath, markdown, "utf-8");
|
|
548
|
+
result.compiled.push({
|
|
549
|
+
role,
|
|
550
|
+
source: metadata.source,
|
|
551
|
+
outputPath
|
|
552
|
+
});
|
|
553
|
+
} catch (error) {
|
|
554
|
+
result.errors.push({
|
|
555
|
+
role,
|
|
556
|
+
source: metadata.source,
|
|
557
|
+
error: error instanceof Error ? error.message : String(error)
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return result;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/adapters/mcp/client.ts
|
|
565
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
566
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
567
|
+
|
|
568
|
+
// src/adapters/native/github.ts
|
|
569
|
+
import { execFile } from "child_process";
|
|
570
|
+
import { promisify } from "util";
|
|
571
|
+
var execFileAsync = promisify(execFile);
|
|
572
|
+
|
|
573
|
+
// src/cache/types.ts
|
|
574
|
+
var CacheError = class extends Error {
|
|
575
|
+
constructor(message, code, key, cause) {
|
|
576
|
+
super(message);
|
|
577
|
+
this.code = code;
|
|
578
|
+
this.key = key;
|
|
579
|
+
this.cause = cause;
|
|
580
|
+
this.name = "CacheError";
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
var CACHE_PATHS = {
|
|
584
|
+
/**
|
|
585
|
+
* Root cache directory (relative to project root)
|
|
586
|
+
*/
|
|
587
|
+
ROOT: ".coreai/cache",
|
|
588
|
+
/**
|
|
589
|
+
* Content storage directory
|
|
590
|
+
*/
|
|
591
|
+
CONTENT: ".coreai/cache/content",
|
|
592
|
+
/**
|
|
593
|
+
* Metadata storage directory
|
|
594
|
+
*/
|
|
595
|
+
METADATA: ".coreai/cache/metadata",
|
|
596
|
+
/**
|
|
597
|
+
* Index file for fast lookups
|
|
598
|
+
*/
|
|
599
|
+
INDEX: ".coreai/cache/index.json",
|
|
600
|
+
/**
|
|
601
|
+
* Lock file for concurrent access
|
|
602
|
+
*/
|
|
603
|
+
LOCK: ".coreai/cache/.lock"
|
|
604
|
+
};
|
|
605
|
+
var DEFAULT_CACHE_CONFIG = {
|
|
606
|
+
/**
|
|
607
|
+
* Default TTL in seconds (1 hour)
|
|
608
|
+
*/
|
|
609
|
+
ttl: 3600,
|
|
610
|
+
/**
|
|
611
|
+
* Maximum cache size in bytes (100MB)
|
|
612
|
+
*/
|
|
613
|
+
maxSize: 100 * 1024 * 1024,
|
|
614
|
+
/**
|
|
615
|
+
* Maximum number of entries
|
|
616
|
+
*/
|
|
617
|
+
maxEntries: 1e3,
|
|
618
|
+
/**
|
|
619
|
+
* Enable automatic cleanup of expired entries
|
|
620
|
+
*/
|
|
621
|
+
autoCleanup: true,
|
|
622
|
+
/**
|
|
623
|
+
* Cleanup interval in seconds (1 hour)
|
|
624
|
+
*/
|
|
625
|
+
cleanupInterval: 3600
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// src/cache/provider.ts
|
|
629
|
+
import { promises as fs } from "fs";
|
|
630
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
631
|
+
import { createHash } from "crypto";
|
|
632
|
+
function noop() {
|
|
633
|
+
}
|
|
634
|
+
var INDEX_VERSION = 1;
|
|
635
|
+
var FileCacheProvider = class {
|
|
636
|
+
basePath;
|
|
637
|
+
cachePath;
|
|
638
|
+
contentPath;
|
|
639
|
+
metadataPath;
|
|
640
|
+
indexPath;
|
|
641
|
+
ttl;
|
|
642
|
+
maxSize;
|
|
643
|
+
maxEntries;
|
|
644
|
+
initialized = false;
|
|
645
|
+
index = null;
|
|
646
|
+
constructor(options) {
|
|
647
|
+
this.basePath = options.basePath;
|
|
648
|
+
this.cachePath = join4(this.basePath, CACHE_PATHS.ROOT);
|
|
649
|
+
this.contentPath = join4(this.basePath, CACHE_PATHS.CONTENT);
|
|
650
|
+
this.metadataPath = join4(this.basePath, CACHE_PATHS.METADATA);
|
|
651
|
+
this.indexPath = join4(this.basePath, CACHE_PATHS.INDEX);
|
|
652
|
+
this.ttl = options.ttl ?? DEFAULT_CACHE_CONFIG.ttl;
|
|
653
|
+
this.maxSize = options.maxSize ?? DEFAULT_CACHE_CONFIG.maxSize;
|
|
654
|
+
this.maxEntries = options.maxEntries ?? DEFAULT_CACHE_CONFIG.maxEntries;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Initialize the cache (create directories, load index)
|
|
658
|
+
*/
|
|
659
|
+
async initialize() {
|
|
660
|
+
if (this.initialized) return;
|
|
661
|
+
try {
|
|
662
|
+
await fs.mkdir(this.contentPath, { recursive: true });
|
|
663
|
+
await fs.mkdir(this.metadataPath, { recursive: true });
|
|
664
|
+
this.index = await this.loadIndex();
|
|
665
|
+
await this.saveIndex();
|
|
666
|
+
this.initialized = true;
|
|
667
|
+
} catch (error) {
|
|
668
|
+
throw new CacheError(
|
|
669
|
+
`Failed to initialize cache: ${error instanceof Error ? error.message : String(error)}`,
|
|
670
|
+
"write_failed",
|
|
671
|
+
void 0,
|
|
672
|
+
error instanceof Error ? error : void 0
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Check if the cache is initialized
|
|
678
|
+
*/
|
|
679
|
+
isInitialized() {
|
|
680
|
+
return this.initialized;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Get a cached entry by key
|
|
684
|
+
*/
|
|
685
|
+
async get(key, options) {
|
|
686
|
+
this.ensureInitialized();
|
|
687
|
+
if (options?.skipCache) {
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
const indexEntry = this.index?.entries[key];
|
|
691
|
+
if (!indexEntry) {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
if (!options?.forceRefresh) {
|
|
695
|
+
const status2 = this.getEntryStatus(indexEntry);
|
|
696
|
+
if (status2 === "expired") {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
try {
|
|
701
|
+
const metadataContent = await fs.readFile(indexEntry.metadataFile, "utf-8");
|
|
702
|
+
const metadata = JSON.parse(metadataContent);
|
|
703
|
+
const contentRaw = await fs.readFile(indexEntry.contentFile, "utf-8");
|
|
704
|
+
let content;
|
|
705
|
+
if (metadata.contentType.includes("json")) {
|
|
706
|
+
content = JSON.parse(contentRaw);
|
|
707
|
+
} else {
|
|
708
|
+
content = contentRaw;
|
|
709
|
+
}
|
|
710
|
+
return { metadata, content };
|
|
711
|
+
} catch {
|
|
712
|
+
await this.delete(key);
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Get the content only (convenience method)
|
|
718
|
+
*/
|
|
719
|
+
async getContent(key, options) {
|
|
720
|
+
const entry = await this.get(key, options);
|
|
721
|
+
return entry?.content ?? null;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Set a cache entry
|
|
725
|
+
*/
|
|
726
|
+
async set(key, content, metadata, options) {
|
|
727
|
+
this.ensureInitialized();
|
|
728
|
+
const now = /* @__PURE__ */ new Date();
|
|
729
|
+
const ttl = options?.ttl ?? this.ttl;
|
|
730
|
+
const expiresAt = new Date(now.getTime() + ttl * 1e3);
|
|
731
|
+
const contentStr = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
732
|
+
const contentHash = this.hashContent(contentStr);
|
|
733
|
+
const size = Buffer.byteLength(contentStr, "utf-8");
|
|
734
|
+
const safeKey = this.sanitizeKey(key);
|
|
735
|
+
const contentFile = join4(this.contentPath, `${safeKey}.cache`);
|
|
736
|
+
const metadataFile = join4(this.metadataPath, `${safeKey}.json`);
|
|
737
|
+
const fullMetadata = {
|
|
738
|
+
key,
|
|
739
|
+
source: metadata.source ?? "custom",
|
|
740
|
+
sourceUrl: metadata.sourceUrl ?? "",
|
|
741
|
+
cachedAt: now.toISOString(),
|
|
742
|
+
expiresAt: expiresAt.toISOString(),
|
|
743
|
+
contentHash,
|
|
744
|
+
size,
|
|
745
|
+
contentType: metadata.contentType ?? (typeof content === "string" ? "text/plain" : "application/json")
|
|
746
|
+
};
|
|
747
|
+
if (metadata.etag) {
|
|
748
|
+
fullMetadata.etag = metadata.etag;
|
|
749
|
+
}
|
|
750
|
+
if (metadata.title) {
|
|
751
|
+
fullMetadata.title = metadata.title;
|
|
752
|
+
}
|
|
753
|
+
if (metadata.lastModified) {
|
|
754
|
+
fullMetadata.lastModified = metadata.lastModified;
|
|
755
|
+
}
|
|
756
|
+
const tags = options?.tags ?? metadata.tags;
|
|
757
|
+
if (tags) {
|
|
758
|
+
fullMetadata.tags = tags;
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
await fs.mkdir(dirname3(contentFile), { recursive: true });
|
|
762
|
+
await fs.mkdir(dirname3(metadataFile), { recursive: true });
|
|
763
|
+
await fs.writeFile(contentFile, contentStr, "utf-8");
|
|
764
|
+
await fs.writeFile(metadataFile, JSON.stringify(fullMetadata, null, 2), "utf-8");
|
|
765
|
+
if (this.index) {
|
|
766
|
+
const oldEntry = this.index.entries[key];
|
|
767
|
+
if (oldEntry) {
|
|
768
|
+
this.index.stats.totalSize -= oldEntry.size;
|
|
769
|
+
} else {
|
|
770
|
+
this.index.stats.entryCount++;
|
|
771
|
+
}
|
|
772
|
+
this.index.entries[key] = {
|
|
773
|
+
key,
|
|
774
|
+
source: fullMetadata.source,
|
|
775
|
+
cachedAt: fullMetadata.cachedAt,
|
|
776
|
+
expiresAt: fullMetadata.expiresAt,
|
|
777
|
+
size,
|
|
778
|
+
contentFile,
|
|
779
|
+
metadataFile
|
|
780
|
+
};
|
|
781
|
+
this.index.stats.totalSize += size;
|
|
782
|
+
await this.saveIndex();
|
|
783
|
+
}
|
|
784
|
+
} catch (error) {
|
|
785
|
+
throw new CacheError(
|
|
786
|
+
`Failed to write cache entry: ${error instanceof Error ? error.message : String(error)}`,
|
|
787
|
+
"write_failed",
|
|
788
|
+
key,
|
|
789
|
+
error instanceof Error ? error : void 0
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Check if a key exists in the cache
|
|
795
|
+
*/
|
|
796
|
+
async has(key) {
|
|
797
|
+
this.ensureInitialized();
|
|
798
|
+
return key in (this.index?.entries ?? {});
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Delete a cache entry
|
|
802
|
+
*/
|
|
803
|
+
async delete(key) {
|
|
804
|
+
this.ensureInitialized();
|
|
805
|
+
const indexEntry = this.index?.entries[key];
|
|
806
|
+
if (!indexEntry) {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
try {
|
|
810
|
+
await fs.unlink(indexEntry.contentFile).catch(noop);
|
|
811
|
+
await fs.unlink(indexEntry.metadataFile).catch(noop);
|
|
812
|
+
if (this.index) {
|
|
813
|
+
this.index.stats.totalSize -= indexEntry.size;
|
|
814
|
+
this.index.stats.entryCount--;
|
|
815
|
+
const { [key]: _removed, ...remaining } = this.index.entries;
|
|
816
|
+
this.index.entries = remaining;
|
|
817
|
+
await this.saveIndex();
|
|
818
|
+
}
|
|
819
|
+
return true;
|
|
820
|
+
} catch (error) {
|
|
821
|
+
throw new CacheError(
|
|
822
|
+
`Failed to delete cache entry: ${error instanceof Error ? error.message : String(error)}`,
|
|
823
|
+
"write_failed",
|
|
824
|
+
key,
|
|
825
|
+
error instanceof Error ? error : void 0
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Get the status of a cache entry
|
|
831
|
+
*/
|
|
832
|
+
async getStatus(key) {
|
|
833
|
+
this.ensureInitialized();
|
|
834
|
+
const indexEntry = this.index?.entries[key];
|
|
835
|
+
if (!indexEntry) {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
return this.getEntryStatus(indexEntry);
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Get metadata for a cache entry
|
|
842
|
+
*/
|
|
843
|
+
async getMetadata(key) {
|
|
844
|
+
this.ensureInitialized();
|
|
845
|
+
const indexEntry = this.index?.entries[key];
|
|
846
|
+
if (!indexEntry) {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
try {
|
|
850
|
+
const content = await fs.readFile(indexEntry.metadataFile, "utf-8");
|
|
851
|
+
return JSON.parse(content);
|
|
852
|
+
} catch {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Update metadata for a cache entry
|
|
858
|
+
*/
|
|
859
|
+
async updateMetadata(key, metadata) {
|
|
860
|
+
this.ensureInitialized();
|
|
861
|
+
const existing = await this.getMetadata(key);
|
|
862
|
+
if (!existing) {
|
|
863
|
+
throw new CacheError(`Cache entry not found: ${key}`, "not_found", key);
|
|
864
|
+
}
|
|
865
|
+
const indexEntry = this.index?.entries[key];
|
|
866
|
+
if (!indexEntry) {
|
|
867
|
+
throw new CacheError(`Cache entry not found: ${key}`, "not_found", key);
|
|
868
|
+
}
|
|
869
|
+
const updated = {
|
|
870
|
+
...existing,
|
|
871
|
+
...metadata,
|
|
872
|
+
key: existing.key
|
|
873
|
+
// Can't change key
|
|
874
|
+
};
|
|
875
|
+
try {
|
|
876
|
+
await fs.writeFile(indexEntry.metadataFile, JSON.stringify(updated, null, 2), "utf-8");
|
|
877
|
+
if (this.index && indexEntry) {
|
|
878
|
+
if (metadata.source) indexEntry.source = metadata.source;
|
|
879
|
+
if (metadata.expiresAt) indexEntry.expiresAt = metadata.expiresAt;
|
|
880
|
+
await this.saveIndex();
|
|
881
|
+
}
|
|
882
|
+
} catch (error) {
|
|
883
|
+
throw new CacheError(
|
|
884
|
+
`Failed to update metadata: ${error instanceof Error ? error.message : String(error)}`,
|
|
885
|
+
"write_failed",
|
|
886
|
+
key,
|
|
887
|
+
error instanceof Error ? error : void 0
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* List cache entries
|
|
893
|
+
*/
|
|
894
|
+
async list(options) {
|
|
895
|
+
this.ensureInitialized();
|
|
896
|
+
if (!this.index) return [];
|
|
897
|
+
let entries = Object.values(this.index.entries);
|
|
898
|
+
if (options?.source) {
|
|
899
|
+
entries = entries.filter((e) => e.source === options.source);
|
|
900
|
+
}
|
|
901
|
+
if (options?.status) {
|
|
902
|
+
entries = entries.filter((e) => this.getEntryStatus(e) === options.status);
|
|
903
|
+
}
|
|
904
|
+
const metadataPromises = entries.map(async (e) => {
|
|
905
|
+
const metadata = await this.getMetadata(e.key);
|
|
906
|
+
return metadata;
|
|
907
|
+
});
|
|
908
|
+
let results = (await Promise.all(metadataPromises)).filter(
|
|
909
|
+
(m) => m !== null
|
|
910
|
+
);
|
|
911
|
+
if (options?.tag) {
|
|
912
|
+
const tag = options.tag;
|
|
913
|
+
results = results.filter((m) => m.tags?.includes(tag));
|
|
914
|
+
}
|
|
915
|
+
if (options?.limit && results.length > options.limit) {
|
|
916
|
+
results = results.slice(0, options.limit);
|
|
917
|
+
}
|
|
918
|
+
return results;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Get cache statistics
|
|
922
|
+
*/
|
|
923
|
+
async getStats() {
|
|
924
|
+
this.ensureInitialized();
|
|
925
|
+
if (!this.index) {
|
|
926
|
+
return {
|
|
927
|
+
totalEntries: 0,
|
|
928
|
+
totalSize: 0,
|
|
929
|
+
validEntries: 0,
|
|
930
|
+
staleEntries: 0,
|
|
931
|
+
expiredEntries: 0,
|
|
932
|
+
bySource: {
|
|
933
|
+
confluence: 0,
|
|
934
|
+
github: 0,
|
|
935
|
+
notion: 0,
|
|
936
|
+
local: 0,
|
|
937
|
+
custom: 0
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
const entries = Object.values(this.index.entries);
|
|
942
|
+
const bySource = {
|
|
943
|
+
confluence: 0,
|
|
944
|
+
github: 0,
|
|
945
|
+
notion: 0,
|
|
946
|
+
local: 0,
|
|
947
|
+
custom: 0
|
|
948
|
+
};
|
|
949
|
+
let validEntries = 0;
|
|
950
|
+
let staleEntries = 0;
|
|
951
|
+
let expiredEntries = 0;
|
|
952
|
+
let oldestEntry;
|
|
953
|
+
let newestEntry;
|
|
954
|
+
for (const entry of entries) {
|
|
955
|
+
bySource[entry.source]++;
|
|
956
|
+
const status2 = this.getEntryStatus(entry);
|
|
957
|
+
if (status2 === "valid") validEntries++;
|
|
958
|
+
else if (status2 === "stale") staleEntries++;
|
|
959
|
+
else if (status2 === "expired") expiredEntries++;
|
|
960
|
+
if (!oldestEntry || entry.cachedAt < oldestEntry) {
|
|
961
|
+
oldestEntry = entry.cachedAt;
|
|
962
|
+
}
|
|
963
|
+
if (!newestEntry || entry.cachedAt > newestEntry) {
|
|
964
|
+
newestEntry = entry.cachedAt;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const stats = {
|
|
968
|
+
totalEntries: this.index.stats.entryCount,
|
|
969
|
+
totalSize: this.index.stats.totalSize,
|
|
970
|
+
validEntries,
|
|
971
|
+
staleEntries,
|
|
972
|
+
expiredEntries,
|
|
973
|
+
bySource
|
|
974
|
+
};
|
|
975
|
+
if (oldestEntry) stats.oldestEntry = oldestEntry;
|
|
976
|
+
if (newestEntry) stats.newestEntry = newestEntry;
|
|
977
|
+
return stats;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Clear all cache entries
|
|
981
|
+
*/
|
|
982
|
+
async clear() {
|
|
983
|
+
this.ensureInitialized();
|
|
984
|
+
if (!this.index) return 0;
|
|
985
|
+
const count = this.index.stats.entryCount;
|
|
986
|
+
try {
|
|
987
|
+
await fs.rm(this.contentPath, { recursive: true, force: true });
|
|
988
|
+
await fs.rm(this.metadataPath, { recursive: true, force: true });
|
|
989
|
+
await fs.mkdir(this.contentPath, { recursive: true });
|
|
990
|
+
await fs.mkdir(this.metadataPath, { recursive: true });
|
|
991
|
+
this.index = this.createEmptyIndex();
|
|
992
|
+
await this.saveIndex();
|
|
993
|
+
return count;
|
|
994
|
+
} catch (error) {
|
|
995
|
+
throw new CacheError(
|
|
996
|
+
`Failed to clear cache: ${error instanceof Error ? error.message : String(error)}`,
|
|
997
|
+
"write_failed",
|
|
998
|
+
void 0,
|
|
999
|
+
error instanceof Error ? error : void 0
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Clear expired entries only
|
|
1005
|
+
*/
|
|
1006
|
+
async clearExpired() {
|
|
1007
|
+
this.ensureInitialized();
|
|
1008
|
+
if (!this.index) return 0;
|
|
1009
|
+
const expiredKeys = Object.entries(this.index.entries).filter(([, entry]) => this.getEntryStatus(entry) === "expired").map(([key]) => key);
|
|
1010
|
+
for (const key of expiredKeys) {
|
|
1011
|
+
await this.delete(key);
|
|
1012
|
+
}
|
|
1013
|
+
if (this.index) {
|
|
1014
|
+
this.index.stats.lastCleanup = (/* @__PURE__ */ new Date()).toISOString();
|
|
1015
|
+
await this.saveIndex();
|
|
1016
|
+
}
|
|
1017
|
+
return expiredKeys.length;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Clear entries by source
|
|
1021
|
+
*/
|
|
1022
|
+
async clearBySource(source) {
|
|
1023
|
+
this.ensureInitialized();
|
|
1024
|
+
if (!this.index) return 0;
|
|
1025
|
+
const keys = Object.entries(this.index.entries).filter(([, entry]) => entry.source === source).map(([key]) => key);
|
|
1026
|
+
for (const key of keys) {
|
|
1027
|
+
await this.delete(key);
|
|
1028
|
+
}
|
|
1029
|
+
return keys.length;
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Clear entries by tag
|
|
1033
|
+
*/
|
|
1034
|
+
async clearByTag(tag) {
|
|
1035
|
+
this.ensureInitialized();
|
|
1036
|
+
const entries = await this.list({ tag });
|
|
1037
|
+
for (const entry of entries) {
|
|
1038
|
+
await this.delete(entry.key);
|
|
1039
|
+
}
|
|
1040
|
+
return entries.length;
|
|
1041
|
+
}
|
|
1042
|
+
// Private helpers
|
|
1043
|
+
ensureInitialized() {
|
|
1044
|
+
if (!this.initialized) {
|
|
1045
|
+
throw new CacheError("Cache not initialized. Call initialize() first.", "invalid_config");
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
async loadIndex() {
|
|
1049
|
+
try {
|
|
1050
|
+
const content = await fs.readFile(this.indexPath, "utf-8");
|
|
1051
|
+
const index = JSON.parse(content);
|
|
1052
|
+
if (index.version !== INDEX_VERSION) {
|
|
1053
|
+
return this.rebuildIndex();
|
|
1054
|
+
}
|
|
1055
|
+
return index;
|
|
1056
|
+
} catch {
|
|
1057
|
+
return this.createEmptyIndex();
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async saveIndex() {
|
|
1061
|
+
if (!this.index) return;
|
|
1062
|
+
try {
|
|
1063
|
+
await fs.writeFile(this.indexPath, JSON.stringify(this.index, null, 2), "utf-8");
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
throw new CacheError(
|
|
1066
|
+
`Failed to save cache index: ${error instanceof Error ? error.message : String(error)}`,
|
|
1067
|
+
"write_failed",
|
|
1068
|
+
void 0,
|
|
1069
|
+
error instanceof Error ? error : void 0
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
createEmptyIndex() {
|
|
1074
|
+
return {
|
|
1075
|
+
version: INDEX_VERSION,
|
|
1076
|
+
entries: {},
|
|
1077
|
+
stats: {
|
|
1078
|
+
totalSize: 0,
|
|
1079
|
+
entryCount: 0,
|
|
1080
|
+
lastCleanup: null
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
async rebuildIndex() {
|
|
1085
|
+
const index = this.createEmptyIndex();
|
|
1086
|
+
try {
|
|
1087
|
+
const metadataFiles = await fs.readdir(this.metadataPath);
|
|
1088
|
+
for (const file of metadataFiles) {
|
|
1089
|
+
if (!file.endsWith(".json")) continue;
|
|
1090
|
+
try {
|
|
1091
|
+
const metadataPath = join4(this.metadataPath, file);
|
|
1092
|
+
const content = await fs.readFile(metadataPath, "utf-8");
|
|
1093
|
+
const metadata = JSON.parse(content);
|
|
1094
|
+
const safeKey = this.sanitizeKey(metadata.key);
|
|
1095
|
+
const contentFile = join4(this.contentPath, `${safeKey}.cache`);
|
|
1096
|
+
await fs.access(contentFile);
|
|
1097
|
+
index.entries[metadata.key] = {
|
|
1098
|
+
key: metadata.key,
|
|
1099
|
+
source: metadata.source,
|
|
1100
|
+
cachedAt: metadata.cachedAt,
|
|
1101
|
+
expiresAt: metadata.expiresAt,
|
|
1102
|
+
size: metadata.size,
|
|
1103
|
+
contentFile,
|
|
1104
|
+
metadataFile: metadataPath
|
|
1105
|
+
};
|
|
1106
|
+
index.stats.totalSize += metadata.size;
|
|
1107
|
+
index.stats.entryCount++;
|
|
1108
|
+
} catch {
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
} catch {
|
|
1112
|
+
}
|
|
1113
|
+
return index;
|
|
1114
|
+
}
|
|
1115
|
+
getEntryStatus(entry) {
|
|
1116
|
+
const now = /* @__PURE__ */ new Date();
|
|
1117
|
+
const expiresAt = new Date(entry.expiresAt);
|
|
1118
|
+
const cachedAt = new Date(entry.cachedAt);
|
|
1119
|
+
if (now > expiresAt) {
|
|
1120
|
+
return "expired";
|
|
1121
|
+
}
|
|
1122
|
+
const totalTtl = expiresAt.getTime() - cachedAt.getTime();
|
|
1123
|
+
const elapsed = now.getTime() - cachedAt.getTime();
|
|
1124
|
+
if (elapsed > totalTtl * 0.8) {
|
|
1125
|
+
return "stale";
|
|
1126
|
+
}
|
|
1127
|
+
return "valid";
|
|
1128
|
+
}
|
|
1129
|
+
sanitizeKey(key) {
|
|
1130
|
+
return createHash("sha256").update(key).digest("hex").substring(0, 32);
|
|
1131
|
+
}
|
|
1132
|
+
hashContent(content) {
|
|
1133
|
+
return createHash("sha256").update(content).digest("hex");
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
// src/cache/manager.ts
|
|
1138
|
+
var CacheManager = class {
|
|
1139
|
+
cache;
|
|
1140
|
+
fetchers = /* @__PURE__ */ new Map();
|
|
1141
|
+
defaultTtl;
|
|
1142
|
+
defaultSource;
|
|
1143
|
+
constructor(options) {
|
|
1144
|
+
this.cache = options.cache;
|
|
1145
|
+
this.defaultTtl = options.defaultTtl ?? 3600;
|
|
1146
|
+
this.defaultSource = options.defaultSource ?? "custom";
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Get content with cache-first strategy
|
|
1150
|
+
*/
|
|
1151
|
+
async getWithFallback(key, url, options) {
|
|
1152
|
+
if (!options?.skipCache && !options?.forceRefresh) {
|
|
1153
|
+
const cached = await this.cache.get(key);
|
|
1154
|
+
if (cached) {
|
|
1155
|
+
const status2 = await this.cache.getStatus(key);
|
|
1156
|
+
if (status2 === "valid") {
|
|
1157
|
+
return cached;
|
|
1158
|
+
}
|
|
1159
|
+
if (status2 === "stale") {
|
|
1160
|
+
try {
|
|
1161
|
+
return await this.fetchAndCache(key, url, options);
|
|
1162
|
+
} catch {
|
|
1163
|
+
return cached;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return this.fetchAndCache(key, url, options);
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Sync all entries from a source
|
|
1172
|
+
*/
|
|
1173
|
+
async syncSource(source, options) {
|
|
1174
|
+
const startTime = Date.now();
|
|
1175
|
+
const result = {
|
|
1176
|
+
added: 0,
|
|
1177
|
+
updated: 0,
|
|
1178
|
+
removed: 0,
|
|
1179
|
+
failed: 0,
|
|
1180
|
+
errors: [],
|
|
1181
|
+
duration: 0
|
|
1182
|
+
};
|
|
1183
|
+
const entries = await this.cache.list({ source });
|
|
1184
|
+
const concurrency = options?.concurrency ?? 5;
|
|
1185
|
+
const chunks = this.chunkArray(entries, concurrency);
|
|
1186
|
+
for (const chunk of chunks) {
|
|
1187
|
+
const promises = chunk.map(async (entry) => {
|
|
1188
|
+
try {
|
|
1189
|
+
const fetcher = this.fetchers.get(source);
|
|
1190
|
+
if (!fetcher) {
|
|
1191
|
+
throw new Error(`No fetcher registered for source: ${source}`);
|
|
1192
|
+
}
|
|
1193
|
+
const hasChanged = await fetcher.hasChanged(entry.sourceUrl, entry.etag);
|
|
1194
|
+
if (hasChanged || options?.force) {
|
|
1195
|
+
const fetchOptions = {};
|
|
1196
|
+
if (entry.etag) {
|
|
1197
|
+
fetchOptions.etag = entry.etag;
|
|
1198
|
+
}
|
|
1199
|
+
const { content, metadata } = await fetcher.fetch(entry.sourceUrl, fetchOptions);
|
|
1200
|
+
await this.cache.set(entry.key, content, {
|
|
1201
|
+
...entry,
|
|
1202
|
+
...metadata
|
|
1203
|
+
});
|
|
1204
|
+
result.updated++;
|
|
1205
|
+
}
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
result.failed++;
|
|
1208
|
+
result.errors.push({
|
|
1209
|
+
key: entry.key,
|
|
1210
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1211
|
+
});
|
|
1212
|
+
if (!options?.continueOnError) {
|
|
1213
|
+
throw error;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
await Promise.all(promises);
|
|
1218
|
+
}
|
|
1219
|
+
if (options?.onProgress) {
|
|
1220
|
+
options.onProgress(entries.length, entries.length);
|
|
1221
|
+
}
|
|
1222
|
+
result.duration = Date.now() - startTime;
|
|
1223
|
+
return result;
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Sync specific entries
|
|
1227
|
+
*/
|
|
1228
|
+
async syncEntries(keys, options) {
|
|
1229
|
+
const startTime = Date.now();
|
|
1230
|
+
const result = {
|
|
1231
|
+
added: 0,
|
|
1232
|
+
updated: 0,
|
|
1233
|
+
removed: 0,
|
|
1234
|
+
failed: 0,
|
|
1235
|
+
errors: [],
|
|
1236
|
+
duration: 0
|
|
1237
|
+
};
|
|
1238
|
+
const concurrency = options?.concurrency ?? 5;
|
|
1239
|
+
const chunks = this.chunkArray(keys, concurrency);
|
|
1240
|
+
for (const chunk of chunks) {
|
|
1241
|
+
const promises = chunk.map(async (key) => {
|
|
1242
|
+
try {
|
|
1243
|
+
const metadata = await this.cache.getMetadata(key);
|
|
1244
|
+
if (!metadata) {
|
|
1245
|
+
result.failed++;
|
|
1246
|
+
result.errors.push({ key, error: "Entry not found in cache" });
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
const fetcher = this.fetchers.get(metadata.source);
|
|
1250
|
+
if (!fetcher) {
|
|
1251
|
+
result.failed++;
|
|
1252
|
+
result.errors.push({ key, error: `No fetcher for source: ${metadata.source}` });
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
const hasChanged = await fetcher.hasChanged(metadata.sourceUrl, metadata.etag);
|
|
1256
|
+
if (hasChanged || options?.force) {
|
|
1257
|
+
const fetchOpts = {};
|
|
1258
|
+
if (metadata.etag) {
|
|
1259
|
+
fetchOpts.etag = metadata.etag;
|
|
1260
|
+
}
|
|
1261
|
+
const { content, metadata: newMeta } = await fetcher.fetch(
|
|
1262
|
+
metadata.sourceUrl,
|
|
1263
|
+
fetchOpts
|
|
1264
|
+
);
|
|
1265
|
+
await this.cache.set(key, content, {
|
|
1266
|
+
...metadata,
|
|
1267
|
+
...newMeta
|
|
1268
|
+
});
|
|
1269
|
+
result.updated++;
|
|
1270
|
+
}
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
result.failed++;
|
|
1273
|
+
result.errors.push({
|
|
1274
|
+
key,
|
|
1275
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1276
|
+
});
|
|
1277
|
+
if (!options?.continueOnError) {
|
|
1278
|
+
throw error;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
await Promise.all(promises);
|
|
1283
|
+
}
|
|
1284
|
+
if (options?.onProgress) {
|
|
1285
|
+
options.onProgress(keys.length, keys.length);
|
|
1286
|
+
}
|
|
1287
|
+
result.duration = Date.now() - startTime;
|
|
1288
|
+
return result;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Register a fetcher for a source
|
|
1292
|
+
*/
|
|
1293
|
+
registerFetcher(source, fetcher) {
|
|
1294
|
+
this.fetchers.set(source, fetcher);
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Get the underlying cache provider
|
|
1298
|
+
*/
|
|
1299
|
+
getCache() {
|
|
1300
|
+
return this.cache;
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Check if a fetcher is registered for a source
|
|
1304
|
+
*/
|
|
1305
|
+
hasFetcher(source) {
|
|
1306
|
+
return this.fetchers.has(source);
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Get all registered sources
|
|
1310
|
+
*/
|
|
1311
|
+
getRegisteredSources() {
|
|
1312
|
+
return Array.from(this.fetchers.keys());
|
|
1313
|
+
}
|
|
1314
|
+
// Private helpers
|
|
1315
|
+
async fetchAndCache(key, url, options) {
|
|
1316
|
+
const source = this.getSourceFromUrl(url);
|
|
1317
|
+
const fetcher = this.fetchers.get(source);
|
|
1318
|
+
if (!fetcher) {
|
|
1319
|
+
throw new CacheError(
|
|
1320
|
+
`No fetcher registered for source: ${source}. Register one with registerFetcher().`,
|
|
1321
|
+
"fetch_failed",
|
|
1322
|
+
key
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
try {
|
|
1326
|
+
const fetchOpts = {};
|
|
1327
|
+
if (options?.timeout) {
|
|
1328
|
+
fetchOpts.timeout = options.timeout;
|
|
1329
|
+
}
|
|
1330
|
+
if (options?.headers) {
|
|
1331
|
+
fetchOpts.headers = options.headers;
|
|
1332
|
+
}
|
|
1333
|
+
const { content, metadata } = await fetcher.fetch(url, fetchOpts);
|
|
1334
|
+
const fullMetadata = {
|
|
1335
|
+
source,
|
|
1336
|
+
sourceUrl: url,
|
|
1337
|
+
contentType: metadata.contentType ?? "text/plain"
|
|
1338
|
+
};
|
|
1339
|
+
if (metadata.etag) {
|
|
1340
|
+
fullMetadata.etag = metadata.etag;
|
|
1341
|
+
}
|
|
1342
|
+
if (metadata.title) {
|
|
1343
|
+
fullMetadata.title = metadata.title;
|
|
1344
|
+
}
|
|
1345
|
+
if (metadata.lastModified) {
|
|
1346
|
+
fullMetadata.lastModified = metadata.lastModified;
|
|
1347
|
+
}
|
|
1348
|
+
if (options?.tags) {
|
|
1349
|
+
fullMetadata.tags = options.tags;
|
|
1350
|
+
}
|
|
1351
|
+
const cacheOpts = {
|
|
1352
|
+
ttl: options?.ttl ?? this.defaultTtl
|
|
1353
|
+
};
|
|
1354
|
+
if (options?.tags) {
|
|
1355
|
+
cacheOpts.tags = options.tags;
|
|
1356
|
+
}
|
|
1357
|
+
await this.cache.set(key, content, fullMetadata, cacheOpts);
|
|
1358
|
+
const entry = await this.cache.get(key);
|
|
1359
|
+
if (!entry) {
|
|
1360
|
+
throw new CacheError("Failed to retrieve cached entry after write", "read_failed", key);
|
|
1361
|
+
}
|
|
1362
|
+
return entry;
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
if (error instanceof CacheError) {
|
|
1365
|
+
throw error;
|
|
1366
|
+
}
|
|
1367
|
+
throw new CacheError(
|
|
1368
|
+
`Failed to fetch content: ${error instanceof Error ? error.message : String(error)}`,
|
|
1369
|
+
"fetch_failed",
|
|
1370
|
+
key,
|
|
1371
|
+
error instanceof Error ? error : void 0
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
getSourceFromUrl(url) {
|
|
1376
|
+
try {
|
|
1377
|
+
const parsed = new URL(url);
|
|
1378
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
1379
|
+
if (hostname.includes("confluence") || hostname.includes("atlassian.net")) {
|
|
1380
|
+
return "confluence";
|
|
1381
|
+
}
|
|
1382
|
+
if (hostname.includes("github.com") || hostname.includes("github")) {
|
|
1383
|
+
return "github";
|
|
1384
|
+
}
|
|
1385
|
+
if (hostname.includes("notion.so") || hostname.includes("notion")) {
|
|
1386
|
+
return "notion";
|
|
1387
|
+
}
|
|
1388
|
+
return this.defaultSource;
|
|
1389
|
+
} catch {
|
|
1390
|
+
return "local";
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
chunkArray(array, size) {
|
|
1394
|
+
const chunks = [];
|
|
1395
|
+
for (let i = 0; i < array.length; i += size) {
|
|
1396
|
+
chunks.push(array.slice(i, i + size));
|
|
1397
|
+
}
|
|
1398
|
+
return chunks;
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
|
|
1402
|
+
// src/skills/generator.ts
|
|
1403
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
1404
|
+
import { join as join5, basename as basename2 } from "path";
|
|
1405
|
+
|
|
1406
|
+
// src/skills/templates.ts
|
|
1407
|
+
var checkInboxSkill = {
|
|
1408
|
+
name: "check-inbox",
|
|
1409
|
+
description: "Check your inbox for pending tasks and messages",
|
|
1410
|
+
category: "core",
|
|
1411
|
+
content: `---
|
|
1412
|
+
description: Check your inbox for pending tasks and messages
|
|
1413
|
+
---
|
|
1414
|
+
|
|
1415
|
+
# Check Inbox
|
|
1416
|
+
|
|
1417
|
+
Check the inbox at \`KnowledgeLibrary/{{AGENT_NAME}}/inbox/\` for new messages.
|
|
1418
|
+
|
|
1419
|
+
## Instructions
|
|
1420
|
+
|
|
1421
|
+
1. Read all unprocessed messages in the inbox directory
|
|
1422
|
+
2. For each message:
|
|
1423
|
+
- Parse the frontmatter (type, from, date, priority)
|
|
1424
|
+
- Understand the request or task
|
|
1425
|
+
- Take appropriate action based on message type
|
|
1426
|
+
3. After processing, move message to \`inbox/processed/\`
|
|
1427
|
+
4. Update your context file at \`KnowledgeLibrary/{{AGENT_NAME}}/context/current.txt\`
|
|
1428
|
+
|
|
1429
|
+
## Message Types
|
|
1430
|
+
|
|
1431
|
+
- **task-assignment**: New work assigned to you
|
|
1432
|
+
- **completion-report**: Another agent completed work
|
|
1433
|
+
- **review-request**: Code review needed
|
|
1434
|
+
- **feedback**: Response to your previous work
|
|
1435
|
+
|
|
1436
|
+
## Output Format
|
|
1437
|
+
|
|
1438
|
+
Report what messages were processed and actions taken.
|
|
1439
|
+
`
|
|
1440
|
+
};
|
|
1441
|
+
var delegateSkill = {
|
|
1442
|
+
name: "delegate",
|
|
1443
|
+
description: "Delegate a task to another agent via inbox messaging",
|
|
1444
|
+
argumentHint: "<task-description> to <agent-name>",
|
|
1445
|
+
category: "core",
|
|
1446
|
+
content: `---
|
|
1447
|
+
description: Delegate a task to another agent via inbox messaging
|
|
1448
|
+
argument-hint: <task-description> to <agent-name>
|
|
1449
|
+
---
|
|
1450
|
+
|
|
1451
|
+
# Delegate Task
|
|
1452
|
+
|
|
1453
|
+
Delegate work to another agent using the inbox-based messaging system.
|
|
1454
|
+
|
|
1455
|
+
## CRITICAL: Do NOT use the Task tool
|
|
1456
|
+
|
|
1457
|
+
Agent-to-agent delegation MUST use file-based messaging, not the Task tool.
|
|
1458
|
+
The Task tool spawns subagents that lose MCP access.
|
|
1459
|
+
|
|
1460
|
+
## Instructions
|
|
1461
|
+
|
|
1462
|
+
1. Parse the delegation request to identify:
|
|
1463
|
+
- The task to delegate
|
|
1464
|
+
- The target agent
|
|
1465
|
+
|
|
1466
|
+
2. Create a message file in the target agent's inbox:
|
|
1467
|
+
- Path: \`KnowledgeLibrary/<target-agent>/inbox/\`
|
|
1468
|
+
- Filename: \`YYYYMMDD_HHMM-{{AGENT_NAME}}-<subject>.md\`
|
|
1469
|
+
|
|
1470
|
+
3. Use this message format:
|
|
1471
|
+
\`\`\`markdown
|
|
1472
|
+
---
|
|
1473
|
+
type: task-assignment
|
|
1474
|
+
from: {{AGENT_NAME}}
|
|
1475
|
+
to: <target-agent>
|
|
1476
|
+
date: YYYY-MM-DD HH:MM
|
|
1477
|
+
priority: P2
|
|
1478
|
+
---
|
|
1479
|
+
|
|
1480
|
+
## Task Assignment
|
|
1481
|
+
|
|
1482
|
+
### Description
|
|
1483
|
+
<Clear description of what needs to be done>
|
|
1484
|
+
|
|
1485
|
+
### Context
|
|
1486
|
+
<Any relevant context or background>
|
|
1487
|
+
|
|
1488
|
+
### Expected Deliverables
|
|
1489
|
+
1. <Deliverable 1>
|
|
1490
|
+
2. <Deliverable 2>
|
|
1491
|
+
|
|
1492
|
+
### Success Criteria
|
|
1493
|
+
- <Criterion 1>
|
|
1494
|
+
- <Criterion 2>
|
|
1495
|
+
\`\`\`
|
|
1496
|
+
|
|
1497
|
+
4. Tell the user to invoke the target agent:
|
|
1498
|
+
"Please invoke @<target-agent> to process this task."
|
|
1499
|
+
|
|
1500
|
+
## Output
|
|
1501
|
+
|
|
1502
|
+
Confirm the delegation message was created and instruct user to invoke the agent.
|
|
1503
|
+
`
|
|
1504
|
+
};
|
|
1505
|
+
var gitCommitSkill = {
|
|
1506
|
+
name: "git-commit",
|
|
1507
|
+
description: "Create a quality-gated git commit",
|
|
1508
|
+
argumentHint: "<commit-message>",
|
|
1509
|
+
category: "core",
|
|
1510
|
+
dependencies: [{ type: "git", required: false, description: "For commit operations" }],
|
|
1511
|
+
content: `---
|
|
1512
|
+
description: Create a quality-gated git commit
|
|
1513
|
+
argument-hint: <commit-message>
|
|
1514
|
+
optional: [git]
|
|
1515
|
+
---
|
|
1516
|
+
|
|
1517
|
+
# Git Commit
|
|
1518
|
+
|
|
1519
|
+
Create a git commit after passing all quality gates.
|
|
1520
|
+
|
|
1521
|
+
## Quality Gates
|
|
1522
|
+
|
|
1523
|
+
Run these checks before committing:
|
|
1524
|
+
|
|
1525
|
+
1. **Lint**: \`{{LINT_CMD}}\`
|
|
1526
|
+
2. **Tests**: \`{{TEST_CMD}}\`
|
|
1527
|
+
3. **Build**: \`{{BUILD_CMD}}\`
|
|
1528
|
+
|
|
1529
|
+
## Instructions
|
|
1530
|
+
|
|
1531
|
+
1. Stage the relevant files with \`git add\`
|
|
1532
|
+
2. Run all quality gate commands
|
|
1533
|
+
3. If any gate fails:
|
|
1534
|
+
- Fix the issues
|
|
1535
|
+
- Re-run the failed gate
|
|
1536
|
+
- Continue only when all pass
|
|
1537
|
+
4. Create the commit with the provided message
|
|
1538
|
+
5. Format: Include ticket reference if applicable
|
|
1539
|
+
|
|
1540
|
+
## Commit Message Format
|
|
1541
|
+
|
|
1542
|
+
\`\`\`
|
|
1543
|
+
<type>(<scope>): <description>
|
|
1544
|
+
|
|
1545
|
+
[optional body]
|
|
1546
|
+
|
|
1547
|
+
[optional footer]
|
|
1548
|
+
\`\`\`
|
|
1549
|
+
|
|
1550
|
+
Types: feat, fix, docs, style, refactor, test, chore
|
|
1551
|
+
|
|
1552
|
+
## Fallbacks
|
|
1553
|
+
|
|
1554
|
+
If quality gate commands are not configured:
|
|
1555
|
+
- Ask the user what commands to run
|
|
1556
|
+
- Or skip gates with user confirmation
|
|
1557
|
+
`
|
|
1558
|
+
};
|
|
1559
|
+
var prCreateSkill = {
|
|
1560
|
+
name: "pr-create",
|
|
1561
|
+
description: "Create a pull request with proper format",
|
|
1562
|
+
argumentHint: "[branch-name]",
|
|
1563
|
+
category: "core",
|
|
1564
|
+
dependencies: [{ type: "git", required: true, description: "For PR creation" }],
|
|
1565
|
+
content: `---
|
|
1566
|
+
description: Create a pull request with proper format
|
|
1567
|
+
argument-hint: [branch-name]
|
|
1568
|
+
requires: [git]
|
|
1569
|
+
---
|
|
1570
|
+
|
|
1571
|
+
# Create Pull Request
|
|
1572
|
+
|
|
1573
|
+
Create a well-formatted pull request for the current branch.
|
|
1574
|
+
|
|
1575
|
+
## Instructions
|
|
1576
|
+
|
|
1577
|
+
1. Ensure all changes are committed
|
|
1578
|
+
2. Push the branch to remote
|
|
1579
|
+
3. Create PR with proper format:
|
|
1580
|
+
|
|
1581
|
+
\`\`\`markdown
|
|
1582
|
+
## Summary
|
|
1583
|
+
<Brief description of changes>
|
|
1584
|
+
|
|
1585
|
+
## Changes
|
|
1586
|
+
- <Change 1>
|
|
1587
|
+
- <Change 2>
|
|
1588
|
+
|
|
1589
|
+
## Test Plan
|
|
1590
|
+
- [ ] <Test case 1>
|
|
1591
|
+
- [ ] <Test case 2>
|
|
1592
|
+
|
|
1593
|
+
## Related Issues
|
|
1594
|
+
Closes {{JIRA_PROJECT}}-XXX
|
|
1595
|
+
\`\`\`
|
|
1596
|
+
|
|
1597
|
+
4. Request reviewers if specified
|
|
1598
|
+
|
|
1599
|
+
## Using GitHub CLI
|
|
1600
|
+
|
|
1601
|
+
\`\`\`bash
|
|
1602
|
+
gh pr create --title "<title>" --body "<body>"
|
|
1603
|
+
\`\`\`
|
|
1604
|
+
|
|
1605
|
+
## Output
|
|
1606
|
+
|
|
1607
|
+
Provide the PR URL and summary of what was created.
|
|
1608
|
+
`
|
|
1609
|
+
};
|
|
1610
|
+
var reviewSkill = {
|
|
1611
|
+
name: "review",
|
|
1612
|
+
description: "Request or perform a code review",
|
|
1613
|
+
argumentHint: "<pr-number-or-url>",
|
|
1614
|
+
category: "core",
|
|
1615
|
+
dependencies: [{ type: "git", required: true, description: "For PR review" }],
|
|
1616
|
+
content: `---
|
|
1617
|
+
description: Request or perform a code review
|
|
1618
|
+
argument-hint: <pr-number-or-url>
|
|
1619
|
+
requires: [git]
|
|
1620
|
+
---
|
|
1621
|
+
|
|
1622
|
+
# Code Review
|
|
1623
|
+
|
|
1624
|
+
Review a pull request or delegate review to a specialist.
|
|
1625
|
+
|
|
1626
|
+
## Instructions
|
|
1627
|
+
|
|
1628
|
+
### If you are reviewing:
|
|
1629
|
+
|
|
1630
|
+
1. Fetch the PR details and diff
|
|
1631
|
+
2. Review for:
|
|
1632
|
+
- Code quality and style
|
|
1633
|
+
- Logic errors or bugs
|
|
1634
|
+
- Security concerns
|
|
1635
|
+
- Test coverage
|
|
1636
|
+
- Documentation
|
|
1637
|
+
3. Provide feedback with specific line references
|
|
1638
|
+
4. Approve, request changes, or comment
|
|
1639
|
+
|
|
1640
|
+
### If delegating to a reviewer:
|
|
1641
|
+
|
|
1642
|
+
1. Identify the appropriate reviewer based on the changes
|
|
1643
|
+
2. Create a review request message in their inbox
|
|
1644
|
+
3. Include PR link and context
|
|
1645
|
+
|
|
1646
|
+
## Review Checklist
|
|
1647
|
+
|
|
1648
|
+
- [ ] Code follows project conventions
|
|
1649
|
+
- [ ] No obvious bugs or edge cases
|
|
1650
|
+
- [ ] Tests cover the changes
|
|
1651
|
+
- [ ] Documentation updated if needed
|
|
1652
|
+
- [ ] No security vulnerabilities
|
|
1653
|
+
|
|
1654
|
+
## Output
|
|
1655
|
+
|
|
1656
|
+
Provide review summary with actionable feedback.
|
|
1657
|
+
`
|
|
1658
|
+
};
|
|
1659
|
+
var sprintStatusSkill = {
|
|
1660
|
+
name: "sprint-status",
|
|
1661
|
+
description: "Get current sprint status and progress",
|
|
1662
|
+
category: "optional",
|
|
1663
|
+
dependencies: [{ type: "issue_tracker", required: true, description: "For sprint data" }],
|
|
1664
|
+
content: `---
|
|
1665
|
+
description: Get current sprint status and progress
|
|
1666
|
+
requires: [issue_tracker]
|
|
1667
|
+
---
|
|
1668
|
+
|
|
1669
|
+
# Sprint Status
|
|
1670
|
+
|
|
1671
|
+
Get the current sprint status and team progress.
|
|
1672
|
+
|
|
1673
|
+
## Instructions
|
|
1674
|
+
|
|
1675
|
+
1. Query the active sprint for project {{JIRA_PROJECT}}
|
|
1676
|
+
2. Gather metrics:
|
|
1677
|
+
- Total story points committed
|
|
1678
|
+
- Points completed
|
|
1679
|
+
- Points in progress
|
|
1680
|
+
- Points remaining
|
|
1681
|
+
3. List tickets by status
|
|
1682
|
+
4. Identify blockers or at-risk items
|
|
1683
|
+
|
|
1684
|
+
## Output Format
|
|
1685
|
+
|
|
1686
|
+
\`\`\`
|
|
1687
|
+
Sprint: <sprint-name>
|
|
1688
|
+
Progress: XX/YY points (ZZ%)
|
|
1689
|
+
|
|
1690
|
+
## Completed
|
|
1691
|
+
- [{{JIRA_PROJECT}}-123] Task name (3 pts)
|
|
1692
|
+
|
|
1693
|
+
## In Progress
|
|
1694
|
+
- [{{JIRA_PROJECT}}-124] Task name (5 pts) - @assignee
|
|
1695
|
+
|
|
1696
|
+
## To Do
|
|
1697
|
+
- [{{JIRA_PROJECT}}-125] Task name (2 pts)
|
|
1698
|
+
|
|
1699
|
+
## Blocked
|
|
1700
|
+
- [{{JIRA_PROJECT}}-126] Task name - Reason
|
|
1701
|
+
\`\`\`
|
|
1702
|
+
|
|
1703
|
+
## Fallbacks
|
|
1704
|
+
|
|
1705
|
+
If issue tracker is unavailable:
|
|
1706
|
+
- Report that sprint data cannot be fetched
|
|
1707
|
+
- Suggest checking the issue tracker directly at {{JIRA_URL}}
|
|
1708
|
+
`
|
|
1709
|
+
};
|
|
1710
|
+
var jiraCreateSkill = {
|
|
1711
|
+
name: "jira-create",
|
|
1712
|
+
description: "Create a new Jira ticket",
|
|
1713
|
+
argumentHint: "<ticket-type> <summary>",
|
|
1714
|
+
category: "optional",
|
|
1715
|
+
dependencies: [{ type: "issue_tracker", required: true, description: "For ticket creation" }],
|
|
1716
|
+
content: `---
|
|
1717
|
+
description: Create a new Jira ticket
|
|
1718
|
+
argument-hint: <ticket-type> <summary>
|
|
1719
|
+
requires: [issue_tracker]
|
|
1720
|
+
---
|
|
1721
|
+
|
|
1722
|
+
# Create Jira Ticket
|
|
1723
|
+
|
|
1724
|
+
Create a new ticket in project {{JIRA_PROJECT}}.
|
|
1725
|
+
|
|
1726
|
+
## Instructions
|
|
1727
|
+
|
|
1728
|
+
1. Parse the ticket type and summary from arguments
|
|
1729
|
+
2. Gather additional details:
|
|
1730
|
+
- Description
|
|
1731
|
+
- Priority
|
|
1732
|
+
- Labels
|
|
1733
|
+
- Components (if applicable)
|
|
1734
|
+
3. Create the ticket via the issue tracker integration
|
|
1735
|
+
4. Return the ticket key and URL
|
|
1736
|
+
|
|
1737
|
+
## Ticket Types
|
|
1738
|
+
|
|
1739
|
+
- **Story**: User-facing feature
|
|
1740
|
+
- **Bug**: Defect to fix
|
|
1741
|
+
- **Task**: Technical work
|
|
1742
|
+
- **Spike**: Research/investigation
|
|
1743
|
+
|
|
1744
|
+
## Output
|
|
1745
|
+
|
|
1746
|
+
\`\`\`
|
|
1747
|
+
Created: {{JIRA_PROJECT}}-XXX
|
|
1748
|
+
URL: {{JIRA_URL}}/browse/{{JIRA_PROJECT}}-XXX
|
|
1749
|
+
Summary: <summary>
|
|
1750
|
+
Type: <type>
|
|
1751
|
+
\`\`\`
|
|
1752
|
+
|
|
1753
|
+
## Fallbacks
|
|
1754
|
+
|
|
1755
|
+
If issue tracker is unavailable:
|
|
1756
|
+
- Provide the user with manual creation instructions
|
|
1757
|
+
- Include all details they should enter
|
|
1758
|
+
`
|
|
1759
|
+
};
|
|
1760
|
+
var jiraTransitionSkill = {
|
|
1761
|
+
name: "jira-transition",
|
|
1762
|
+
description: "Transition a Jira ticket to a new status",
|
|
1763
|
+
argumentHint: "<ticket-key> to <status>",
|
|
1764
|
+
category: "optional",
|
|
1765
|
+
dependencies: [{ type: "issue_tracker", required: true, description: "For ticket transitions" }],
|
|
1766
|
+
content: `---
|
|
1767
|
+
description: Transition a Jira ticket to a new status
|
|
1768
|
+
argument-hint: <ticket-key> to <status>
|
|
1769
|
+
requires: [issue_tracker]
|
|
1770
|
+
---
|
|
1771
|
+
|
|
1772
|
+
# Transition Jira Ticket
|
|
1773
|
+
|
|
1774
|
+
Update the status of a ticket in {{JIRA_PROJECT}}.
|
|
1775
|
+
|
|
1776
|
+
## Status Mapping
|
|
1777
|
+
|
|
1778
|
+
| Workflow Status | Jira Status |
|
|
1779
|
+
|-----------------|-------------|
|
|
1780
|
+
| BACKLOG | Backlog |
|
|
1781
|
+
| IN_PROGRESS | In Progress |
|
|
1782
|
+
| PR_CREATED | In Review |
|
|
1783
|
+
| IN_REVIEW | In Review |
|
|
1784
|
+
| APPROVED | Ready to Merge |
|
|
1785
|
+
| MERGED | Done |
|
|
1786
|
+
| DONE | Done |
|
|
1787
|
+
|
|
1788
|
+
## Instructions
|
|
1789
|
+
|
|
1790
|
+
1. Parse ticket key and target status
|
|
1791
|
+
2. Validate the transition is allowed
|
|
1792
|
+
3. Add a comment explaining the transition
|
|
1793
|
+
4. Execute the transition
|
|
1794
|
+
|
|
1795
|
+
## Transition Comment Format
|
|
1796
|
+
|
|
1797
|
+
\`\`\`
|
|
1798
|
+
Status updated to <status>.
|
|
1799
|
+
<optional context about why>
|
|
1800
|
+
\`\`\`
|
|
1801
|
+
|
|
1802
|
+
## Fallbacks
|
|
1803
|
+
|
|
1804
|
+
If issue tracker is unavailable:
|
|
1805
|
+
- Instruct user to manually transition at {{JIRA_URL}}
|
|
1806
|
+
- Provide the target status name
|
|
1807
|
+
`
|
|
1808
|
+
};
|
|
1809
|
+
var docsUpdateSkill = {
|
|
1810
|
+
name: "docs-update",
|
|
1811
|
+
description: "Update project documentation",
|
|
1812
|
+
argumentHint: "<doc-path-or-topic>",
|
|
1813
|
+
category: "optional",
|
|
1814
|
+
dependencies: [{ type: "documentation", required: false, description: "For remote docs" }],
|
|
1815
|
+
content: `---
|
|
1816
|
+
description: Update project documentation
|
|
1817
|
+
argument-hint: <doc-path-or-topic>
|
|
1818
|
+
optional: [documentation]
|
|
1819
|
+
---
|
|
1820
|
+
|
|
1821
|
+
# Update Documentation
|
|
1822
|
+
|
|
1823
|
+
Update project documentation for a specific topic or file.
|
|
1824
|
+
|
|
1825
|
+
## Instructions
|
|
1826
|
+
|
|
1827
|
+
1. Identify the documentation to update:
|
|
1828
|
+
- Local: \`{{DOCS_PATH}}/\`
|
|
1829
|
+
- Remote: {{CONFLUENCE_SPACE}} (if configured)
|
|
1830
|
+
|
|
1831
|
+
2. Make the necessary updates:
|
|
1832
|
+
- Keep formatting consistent
|
|
1833
|
+
- Update examples if code changed
|
|
1834
|
+
- Add/update version information
|
|
1835
|
+
|
|
1836
|
+
3. For remote documentation:
|
|
1837
|
+
- Use the documentation integration
|
|
1838
|
+
- Or provide content for manual update
|
|
1839
|
+
|
|
1840
|
+
## Documentation Types
|
|
1841
|
+
|
|
1842
|
+
- **README**: Project overview and setup
|
|
1843
|
+
- **API Docs**: Endpoint documentation
|
|
1844
|
+
- **Architecture**: Design decisions
|
|
1845
|
+
- **Runbooks**: Operational procedures
|
|
1846
|
+
|
|
1847
|
+
## Fallbacks
|
|
1848
|
+
|
|
1849
|
+
If documentation integration is unavailable:
|
|
1850
|
+
- Create/update local markdown files
|
|
1851
|
+
- Provide instructions for manual remote update
|
|
1852
|
+
`
|
|
1853
|
+
};
|
|
1854
|
+
var builtInSkills = [
|
|
1855
|
+
checkInboxSkill,
|
|
1856
|
+
delegateSkill,
|
|
1857
|
+
gitCommitSkill,
|
|
1858
|
+
prCreateSkill,
|
|
1859
|
+
reviewSkill,
|
|
1860
|
+
sprintStatusSkill,
|
|
1861
|
+
jiraCreateSkill,
|
|
1862
|
+
jiraTransitionSkill,
|
|
1863
|
+
docsUpdateSkill
|
|
1864
|
+
];
|
|
1865
|
+
|
|
1866
|
+
// src/skills/generator.ts
|
|
1867
|
+
function extractVariables2(config) {
|
|
1868
|
+
const vars = {};
|
|
1869
|
+
if (!config) {
|
|
1870
|
+
return vars;
|
|
1871
|
+
}
|
|
1872
|
+
vars.PROJECT_NAME = config.project.name;
|
|
1873
|
+
vars.PROJECT_ROOT = config.project.root;
|
|
1874
|
+
if (config.project.type) {
|
|
1875
|
+
vars.PROJECT_TYPE = config.project.type;
|
|
1876
|
+
}
|
|
1877
|
+
if (config.integrations?.issue_tracker) {
|
|
1878
|
+
const tracker = config.integrations.issue_tracker;
|
|
1879
|
+
if (tracker.config?.project_key) {
|
|
1880
|
+
vars.JIRA_PROJECT = tracker.config.project_key;
|
|
1881
|
+
}
|
|
1882
|
+
if (tracker.config?.base_url) {
|
|
1883
|
+
vars.JIRA_URL = tracker.config.base_url;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
if (config.integrations?.git) {
|
|
1887
|
+
const git = config.integrations.git;
|
|
1888
|
+
if (git.config?.repo) {
|
|
1889
|
+
vars.GITHUB_REPO = git.config.repo;
|
|
1890
|
+
}
|
|
1891
|
+
if (git.config?.owner) {
|
|
1892
|
+
vars.GITHUB_OWNER = git.config.owner;
|
|
1893
|
+
}
|
|
1894
|
+
if (git.config?.default_branch) {
|
|
1895
|
+
vars.DEFAULT_BRANCH = git.config.default_branch;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (config.integrations?.documentation) {
|
|
1899
|
+
const docs = config.integrations.documentation;
|
|
1900
|
+
if (docs.config?.space_key) {
|
|
1901
|
+
vars.CONFLUENCE_SPACE = docs.config.space_key;
|
|
1902
|
+
}
|
|
1903
|
+
if (docs.config?.base_url) {
|
|
1904
|
+
vars.CONFLUENCE_URL = docs.config.base_url;
|
|
1905
|
+
}
|
|
1906
|
+
if (docs.config?.base_path) {
|
|
1907
|
+
vars.DOCS_PATH = docs.config.base_path;
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
if (config.quality_gates) {
|
|
1911
|
+
const gates = config.quality_gates;
|
|
1912
|
+
const gateMappings = {
|
|
1913
|
+
lint: "LINT_CMD",
|
|
1914
|
+
test: "TEST_CMD",
|
|
1915
|
+
build: "BUILD_CMD",
|
|
1916
|
+
static_analysis: "STATIC_ANALYSIS_CMD",
|
|
1917
|
+
staticAnalysis: "STATIC_ANALYSIS_CMD"
|
|
1918
|
+
};
|
|
1919
|
+
for (const [gateName, gate] of Object.entries(gates)) {
|
|
1920
|
+
const varName = gateMappings[gateName];
|
|
1921
|
+
if (varName) {
|
|
1922
|
+
vars[varName] = gate.command;
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
if (config.tech_stack?.primary_language) {
|
|
1927
|
+
vars.PRIMARY_LANGUAGE = config.tech_stack.primary_language;
|
|
1928
|
+
}
|
|
1929
|
+
return vars;
|
|
1930
|
+
}
|
|
1931
|
+
function substituteVariables(content, variables) {
|
|
1932
|
+
let result = content;
|
|
1933
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
1934
|
+
if (value !== void 0) {
|
|
1935
|
+
const pattern = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
|
1936
|
+
result = result.replace(pattern, value);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
return result;
|
|
1940
|
+
}
|
|
1941
|
+
function checkDependencies(skill, config) {
|
|
1942
|
+
if (!skill.dependencies || skill.dependencies.length === 0) {
|
|
1943
|
+
return { satisfied: true, missing: [] };
|
|
1944
|
+
}
|
|
1945
|
+
const missing = [];
|
|
1946
|
+
for (const dep of skill.dependencies) {
|
|
1947
|
+
if (!dep.required) {
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
let hasIntegration = false;
|
|
1951
|
+
if (config?.integrations) {
|
|
1952
|
+
switch (dep.type) {
|
|
1953
|
+
case "issue_tracker":
|
|
1954
|
+
hasIntegration = !!config.integrations.issue_tracker;
|
|
1955
|
+
break;
|
|
1956
|
+
case "git":
|
|
1957
|
+
hasIntegration = !!config.integrations.git;
|
|
1958
|
+
break;
|
|
1959
|
+
case "documentation":
|
|
1960
|
+
hasIntegration = !!config.integrations.documentation;
|
|
1961
|
+
break;
|
|
1962
|
+
case "state":
|
|
1963
|
+
hasIntegration = !!config.integrations.state;
|
|
1964
|
+
break;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
if (!hasIntegration) {
|
|
1968
|
+
missing.push(dep);
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
return {
|
|
1972
|
+
satisfied: missing.length === 0,
|
|
1973
|
+
missing
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
function loadCustomTemplates(templatesDir) {
|
|
1977
|
+
const templates = [];
|
|
1978
|
+
if (!existsSync4(templatesDir)) {
|
|
1979
|
+
return templates;
|
|
1980
|
+
}
|
|
1981
|
+
const files = readdirSync2(templatesDir);
|
|
1982
|
+
for (const file of files) {
|
|
1983
|
+
if (!file.endsWith(".md")) continue;
|
|
1984
|
+
const filePath = join5(templatesDir, file);
|
|
1985
|
+
const stat = statSync(filePath);
|
|
1986
|
+
if (!stat.isFile()) continue;
|
|
1987
|
+
try {
|
|
1988
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
1989
|
+
const template = parseSkillTemplate(file, content);
|
|
1990
|
+
templates.push(template);
|
|
1991
|
+
} catch {
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
return templates;
|
|
1995
|
+
}
|
|
1996
|
+
function parseSkillTemplate(filename, content) {
|
|
1997
|
+
const name = basename2(filename, ".md");
|
|
1998
|
+
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
1999
|
+
let description = name;
|
|
2000
|
+
let argumentHint;
|
|
2001
|
+
const dependencies = [];
|
|
2002
|
+
if (frontmatterMatch) {
|
|
2003
|
+
const frontmatter = frontmatterMatch[1] ?? "";
|
|
2004
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
2005
|
+
for (const line of lines) {
|
|
2006
|
+
const colonIndex = line.indexOf(":");
|
|
2007
|
+
if (colonIndex === -1) continue;
|
|
2008
|
+
const key = line.slice(0, colonIndex).trim();
|
|
2009
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
2010
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
2011
|
+
value = value.slice(1, -1);
|
|
2012
|
+
}
|
|
2013
|
+
switch (key) {
|
|
2014
|
+
case "description":
|
|
2015
|
+
description = value;
|
|
2016
|
+
break;
|
|
2017
|
+
case "argument-hint":
|
|
2018
|
+
argumentHint = value;
|
|
2019
|
+
break;
|
|
2020
|
+
case "requires":
|
|
2021
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
2022
|
+
const items = value.slice(1, -1).split(",").map((s) => s.trim().replace(/['"]/g, ""));
|
|
2023
|
+
for (const item of items) {
|
|
2024
|
+
if (item) {
|
|
2025
|
+
dependencies.push({
|
|
2026
|
+
type: mapIntegrationName(item),
|
|
2027
|
+
required: true
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
break;
|
|
2033
|
+
case "optional":
|
|
2034
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
2035
|
+
const items = value.slice(1, -1).split(",").map((s) => s.trim().replace(/['"]/g, ""));
|
|
2036
|
+
for (const item of items) {
|
|
2037
|
+
if (item) {
|
|
2038
|
+
dependencies.push({
|
|
2039
|
+
type: mapIntegrationName(item),
|
|
2040
|
+
required: false
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
break;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
const template = {
|
|
2050
|
+
name,
|
|
2051
|
+
description,
|
|
2052
|
+
category: "custom",
|
|
2053
|
+
content
|
|
2054
|
+
};
|
|
2055
|
+
if (argumentHint) {
|
|
2056
|
+
template.argumentHint = argumentHint;
|
|
2057
|
+
}
|
|
2058
|
+
if (dependencies.length > 0) {
|
|
2059
|
+
template.dependencies = dependencies;
|
|
2060
|
+
}
|
|
2061
|
+
return template;
|
|
2062
|
+
}
|
|
2063
|
+
function mapIntegrationName(name) {
|
|
2064
|
+
const lower = name.toLowerCase();
|
|
2065
|
+
if (["jira", "linear", "issue_tracker", "issues", "github-issues"].includes(lower)) {
|
|
2066
|
+
return "issue_tracker";
|
|
2067
|
+
}
|
|
2068
|
+
if (["git", "github", "gitlab", "bitbucket"].includes(lower)) {
|
|
2069
|
+
return "git";
|
|
2070
|
+
}
|
|
2071
|
+
if (["docs", "documentation", "confluence", "notion", "wiki"].includes(lower)) {
|
|
2072
|
+
return "documentation";
|
|
2073
|
+
}
|
|
2074
|
+
return "state";
|
|
2075
|
+
}
|
|
2076
|
+
function generateSkills(config, options = {}) {
|
|
2077
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
2078
|
+
const outputDir = options.outputDir ?? join5(projectRoot, ".claude", "commands");
|
|
2079
|
+
const includeCoreSkills = options.includeCoreSkills ?? true;
|
|
2080
|
+
const includeOptionalSkills = options.includeOptionalSkills ?? true;
|
|
2081
|
+
const overwrite = options.overwrite ?? false;
|
|
2082
|
+
const result = {
|
|
2083
|
+
generated: [],
|
|
2084
|
+
errors: [],
|
|
2085
|
+
variables: {}
|
|
2086
|
+
};
|
|
2087
|
+
const variables = extractVariables2(config);
|
|
2088
|
+
if (options.variables) {
|
|
2089
|
+
Object.assign(variables, options.variables);
|
|
2090
|
+
}
|
|
2091
|
+
result.variables = variables;
|
|
2092
|
+
let templates = [];
|
|
2093
|
+
if (includeCoreSkills) {
|
|
2094
|
+
templates.push(...builtInSkills.filter((s) => s.category === "core"));
|
|
2095
|
+
}
|
|
2096
|
+
if (includeOptionalSkills) {
|
|
2097
|
+
templates.push(...builtInSkills.filter((s) => s.category === "optional"));
|
|
2098
|
+
}
|
|
2099
|
+
if (options.customTemplatesDir && existsSync4(options.customTemplatesDir)) {
|
|
2100
|
+
templates.push(...loadCustomTemplates(options.customTemplatesDir));
|
|
2101
|
+
}
|
|
2102
|
+
if (options.skills && options.skills.length > 0) {
|
|
2103
|
+
const skillsToInclude = options.skills;
|
|
2104
|
+
templates = templates.filter((t) => skillsToInclude.includes(t.name));
|
|
2105
|
+
}
|
|
2106
|
+
if (!existsSync4(outputDir)) {
|
|
2107
|
+
mkdirSync2(outputDir, { recursive: true });
|
|
2108
|
+
}
|
|
2109
|
+
for (const template of templates) {
|
|
2110
|
+
try {
|
|
2111
|
+
const generated = generateSkill(template, variables, config, outputDir, overwrite);
|
|
2112
|
+
result.generated.push(generated);
|
|
2113
|
+
} catch (error) {
|
|
2114
|
+
result.errors.push({
|
|
2115
|
+
name: template.name,
|
|
2116
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
return result;
|
|
2121
|
+
}
|
|
2122
|
+
function generateSkill(template, variables, config, outputDir, overwrite) {
|
|
2123
|
+
const outputPath = join5(outputDir, `${template.name}.md`);
|
|
2124
|
+
if (existsSync4(outputPath) && !overwrite) {
|
|
2125
|
+
return {
|
|
2126
|
+
name: template.name,
|
|
2127
|
+
category: template.category,
|
|
2128
|
+
outputPath,
|
|
2129
|
+
action: "skipped",
|
|
2130
|
+
skipReason: "File already exists"
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
const { satisfied, missing } = checkDependencies(template, config);
|
|
2134
|
+
if (!satisfied) {
|
|
2135
|
+
const missingTypes = missing.map((d) => d.type).join(", ");
|
|
2136
|
+
return {
|
|
2137
|
+
name: template.name,
|
|
2138
|
+
category: template.category,
|
|
2139
|
+
outputPath,
|
|
2140
|
+
action: "skipped",
|
|
2141
|
+
skipReason: `Missing required integrations: ${missingTypes}`
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
const content = substituteVariables(template.content, variables);
|
|
2145
|
+
const isUpdate = existsSync4(outputPath);
|
|
2146
|
+
writeFileSync2(outputPath, content, "utf-8");
|
|
2147
|
+
return {
|
|
2148
|
+
name: template.name,
|
|
2149
|
+
category: template.category,
|
|
2150
|
+
outputPath,
|
|
2151
|
+
action: isUpdate ? "updated" : "created"
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
function formatGenerateResult(result) {
|
|
2155
|
+
const lines = [];
|
|
2156
|
+
const created = result.generated.filter((g) => g.action === "created");
|
|
2157
|
+
const updated = result.generated.filter((g) => g.action === "updated");
|
|
2158
|
+
const skipped = result.generated.filter((g) => g.action === "skipped");
|
|
2159
|
+
if (created.length > 0) {
|
|
2160
|
+
lines.push(`Created ${created.length} skill(s):`);
|
|
2161
|
+
for (const skill of created) {
|
|
2162
|
+
lines.push(` \u2713 ${skill.name}`);
|
|
2163
|
+
}
|
|
2164
|
+
lines.push("");
|
|
2165
|
+
}
|
|
2166
|
+
if (updated.length > 0) {
|
|
2167
|
+
lines.push(`Updated ${updated.length} skill(s):`);
|
|
2168
|
+
for (const skill of updated) {
|
|
2169
|
+
lines.push(` \u2713 ${skill.name}`);
|
|
2170
|
+
}
|
|
2171
|
+
lines.push("");
|
|
2172
|
+
}
|
|
2173
|
+
if (skipped.length > 0) {
|
|
2174
|
+
lines.push(`Skipped ${skipped.length} skill(s):`);
|
|
2175
|
+
for (const skill of skipped) {
|
|
2176
|
+
lines.push(` - ${skill.name}: ${skill.skipReason}`);
|
|
2177
|
+
}
|
|
2178
|
+
lines.push("");
|
|
2179
|
+
}
|
|
2180
|
+
if (result.errors.length > 0) {
|
|
2181
|
+
lines.push(`Failed to generate ${result.errors.length} skill(s):`);
|
|
2182
|
+
for (const error of result.errors) {
|
|
2183
|
+
lines.push(` \u2717 ${error.name}: ${error.error}`);
|
|
2184
|
+
}
|
|
2185
|
+
lines.push("");
|
|
2186
|
+
}
|
|
2187
|
+
const total = created.length + updated.length;
|
|
2188
|
+
if (total > 0) {
|
|
2189
|
+
lines.push(`Generated ${total} skill(s) to .claude/commands/`);
|
|
2190
|
+
} else if (result.generated.length === 0 && result.errors.length === 0) {
|
|
2191
|
+
lines.push("No skills to generate.");
|
|
2192
|
+
}
|
|
2193
|
+
return lines.join("\n");
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
// src/knowledge-library/manager.ts
|
|
2197
|
+
import {
|
|
2198
|
+
existsSync as existsSync5,
|
|
2199
|
+
mkdirSync as mkdirSync3,
|
|
2200
|
+
writeFileSync as writeFileSync3,
|
|
2201
|
+
readFileSync as readFileSync4,
|
|
2202
|
+
readdirSync as readdirSync3,
|
|
2203
|
+
renameSync,
|
|
2204
|
+
statSync as statSync2
|
|
2205
|
+
} from "fs";
|
|
2206
|
+
import { join as join6, basename as basename3 } from "path";
|
|
2207
|
+
var DEFAULT_KNOWLEDGE_LIBRARY_PATH = "KnowledgeLibrary";
|
|
2208
|
+
var STANDARD_FILES = {
|
|
2209
|
+
context: "context.txt",
|
|
2210
|
+
architecture: "architecture.txt",
|
|
2211
|
+
prd: "prd.txt"
|
|
2212
|
+
};
|
|
2213
|
+
var AGENT_DIRECTORIES = [
|
|
2214
|
+
"inbox",
|
|
2215
|
+
"inbox/processed",
|
|
2216
|
+
"outbox",
|
|
2217
|
+
"context",
|
|
2218
|
+
"control",
|
|
2219
|
+
"history",
|
|
2220
|
+
"tech"
|
|
2221
|
+
];
|
|
2222
|
+
var CONTROL_FILES = {
|
|
2223
|
+
objectives: "objectives.txt",
|
|
2224
|
+
decisions: "decisions.txt",
|
|
2225
|
+
dependencies: "dependencies.txt"
|
|
2226
|
+
};
|
|
2227
|
+
function getAgentDirectories(basePath, agentName) {
|
|
2228
|
+
const root = join6(basePath, agentName);
|
|
2229
|
+
return {
|
|
2230
|
+
root,
|
|
2231
|
+
inbox: join6(root, "inbox"),
|
|
2232
|
+
inboxProcessed: join6(root, "inbox", "processed"),
|
|
2233
|
+
outbox: join6(root, "outbox"),
|
|
2234
|
+
context: join6(root, "context"),
|
|
2235
|
+
control: join6(root, "control"),
|
|
2236
|
+
history: join6(root, "history"),
|
|
2237
|
+
tech: join6(root, "tech")
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
function initKnowledgeLibrary(options = {}) {
|
|
2241
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
2242
|
+
const basePath = join6(projectRoot, options.basePath ?? DEFAULT_KNOWLEDGE_LIBRARY_PATH);
|
|
2243
|
+
const createDefaults = options.createDefaults ?? true;
|
|
2244
|
+
const createdDirs = [];
|
|
2245
|
+
const createdFiles = [];
|
|
2246
|
+
try {
|
|
2247
|
+
if (!existsSync5(basePath)) {
|
|
2248
|
+
mkdirSync3(basePath, { recursive: true });
|
|
2249
|
+
createdDirs.push(basePath);
|
|
2250
|
+
}
|
|
2251
|
+
if (createDefaults) {
|
|
2252
|
+
const contextPath = join6(basePath, STANDARD_FILES.context);
|
|
2253
|
+
if (!existsSync5(contextPath)) {
|
|
2254
|
+
writeFileSync3(
|
|
2255
|
+
contextPath,
|
|
2256
|
+
`# Project Context
|
|
2257
|
+
|
|
2258
|
+
## Current Sprint
|
|
2259
|
+
- Sprint: [Sprint name/number]
|
|
2260
|
+
- Goals: [Sprint goals]
|
|
2261
|
+
- Deadline: [Sprint deadline]
|
|
2262
|
+
|
|
2263
|
+
## Priorities
|
|
2264
|
+
1. [Priority 1]
|
|
2265
|
+
2. [Priority 2]
|
|
2266
|
+
3. [Priority 3]
|
|
2267
|
+
|
|
2268
|
+
## Notes
|
|
2269
|
+
[Project-wide notes and context]
|
|
2270
|
+
`,
|
|
2271
|
+
"utf-8"
|
|
2272
|
+
);
|
|
2273
|
+
createdFiles.push(contextPath);
|
|
2274
|
+
}
|
|
2275
|
+
const architecturePath = join6(basePath, STANDARD_FILES.architecture);
|
|
2276
|
+
if (!existsSync5(architecturePath)) {
|
|
2277
|
+
writeFileSync3(
|
|
2278
|
+
architecturePath,
|
|
2279
|
+
`# Architecture Notes
|
|
2280
|
+
|
|
2281
|
+
## Technical Decisions
|
|
2282
|
+
[Record major technical decisions here]
|
|
2283
|
+
|
|
2284
|
+
## System Overview
|
|
2285
|
+
[High-level system architecture]
|
|
2286
|
+
|
|
2287
|
+
## Key Components
|
|
2288
|
+
[Important system components]
|
|
2289
|
+
`,
|
|
2290
|
+
"utf-8"
|
|
2291
|
+
);
|
|
2292
|
+
createdFiles.push(architecturePath);
|
|
2293
|
+
}
|
|
2294
|
+
const prdPath = join6(basePath, STANDARD_FILES.prd);
|
|
2295
|
+
if (!existsSync5(prdPath)) {
|
|
2296
|
+
writeFileSync3(
|
|
2297
|
+
prdPath,
|
|
2298
|
+
`# Product Requirements Document
|
|
2299
|
+
|
|
2300
|
+
## Overview
|
|
2301
|
+
[Product overview]
|
|
2302
|
+
|
|
2303
|
+
## Goals
|
|
2304
|
+
[Product goals]
|
|
2305
|
+
|
|
2306
|
+
## Features
|
|
2307
|
+
[Feature list]
|
|
2308
|
+
|
|
2309
|
+
## Requirements
|
|
2310
|
+
[Detailed requirements]
|
|
2311
|
+
`,
|
|
2312
|
+
"utf-8"
|
|
2313
|
+
);
|
|
2314
|
+
createdFiles.push(prdPath);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
return {
|
|
2318
|
+
success: true,
|
|
2319
|
+
createdDirs,
|
|
2320
|
+
createdFiles
|
|
2321
|
+
};
|
|
2322
|
+
} catch (error) {
|
|
2323
|
+
return {
|
|
2324
|
+
success: false,
|
|
2325
|
+
createdDirs,
|
|
2326
|
+
createdFiles,
|
|
2327
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
function initAgentKnowledgeLibrary(agentName, options = {}) {
|
|
2332
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
2333
|
+
const basePath = join6(projectRoot, options.basePath ?? DEFAULT_KNOWLEDGE_LIBRARY_PATH);
|
|
2334
|
+
const createDefaults = options.createDefaults ?? true;
|
|
2335
|
+
const createdDirs = [];
|
|
2336
|
+
const createdFiles = [];
|
|
2337
|
+
try {
|
|
2338
|
+
if (!existsSync5(basePath)) {
|
|
2339
|
+
const baseResult = initKnowledgeLibrary(options);
|
|
2340
|
+
if (!baseResult.success) {
|
|
2341
|
+
return baseResult;
|
|
2342
|
+
}
|
|
2343
|
+
createdDirs.push(...baseResult.createdDirs);
|
|
2344
|
+
createdFiles.push(...baseResult.createdFiles);
|
|
2345
|
+
}
|
|
2346
|
+
const dirs = getAgentDirectories(basePath, agentName);
|
|
2347
|
+
for (const dir of AGENT_DIRECTORIES) {
|
|
2348
|
+
const dirPath = join6(dirs.root, dir);
|
|
2349
|
+
if (!existsSync5(dirPath)) {
|
|
2350
|
+
mkdirSync3(dirPath, { recursive: true });
|
|
2351
|
+
createdDirs.push(dirPath);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
if (createDefaults) {
|
|
2355
|
+
const currentContextPath = join6(dirs.context, "current.txt");
|
|
2356
|
+
if (!existsSync5(currentContextPath)) {
|
|
2357
|
+
writeFileSync3(
|
|
2358
|
+
currentContextPath,
|
|
2359
|
+
`# ${agentName} Current Context
|
|
2360
|
+
|
|
2361
|
+
## Status
|
|
2362
|
+
- State: idle
|
|
2363
|
+
- Current Task: None
|
|
2364
|
+
- Last Updated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
2365
|
+
|
|
2366
|
+
## Notes
|
|
2367
|
+
[Agent-specific context notes]
|
|
2368
|
+
`,
|
|
2369
|
+
"utf-8"
|
|
2370
|
+
);
|
|
2371
|
+
createdFiles.push(currentContextPath);
|
|
2372
|
+
}
|
|
2373
|
+
const objectivesPath = join6(dirs.control, CONTROL_FILES.objectives);
|
|
2374
|
+
if (!existsSync5(objectivesPath)) {
|
|
2375
|
+
writeFileSync3(
|
|
2376
|
+
objectivesPath,
|
|
2377
|
+
`# ${agentName} Objectives
|
|
2378
|
+
|
|
2379
|
+
## Current Objectives
|
|
2380
|
+
[None]
|
|
2381
|
+
|
|
2382
|
+
## Success Criteria
|
|
2383
|
+
[Define success criteria for objectives]
|
|
2384
|
+
`,
|
|
2385
|
+
"utf-8"
|
|
2386
|
+
);
|
|
2387
|
+
createdFiles.push(objectivesPath);
|
|
2388
|
+
}
|
|
2389
|
+
const decisionsPath = join6(dirs.control, CONTROL_FILES.decisions);
|
|
2390
|
+
if (!existsSync5(decisionsPath)) {
|
|
2391
|
+
writeFileSync3(
|
|
2392
|
+
decisionsPath,
|
|
2393
|
+
`# ${agentName} Decision Log
|
|
2394
|
+
|
|
2395
|
+
## Decisions
|
|
2396
|
+
[Record important decisions here]
|
|
2397
|
+
|
|
2398
|
+
Format:
|
|
2399
|
+
- Date: YYYY-MM-DD
|
|
2400
|
+
- Decision: [What was decided]
|
|
2401
|
+
- Reasoning: [Why]
|
|
2402
|
+
- Related: [Ticket or task reference]
|
|
2403
|
+
`,
|
|
2404
|
+
"utf-8"
|
|
2405
|
+
);
|
|
2406
|
+
createdFiles.push(decisionsPath);
|
|
2407
|
+
}
|
|
2408
|
+
const dependenciesPath = join6(dirs.control, CONTROL_FILES.dependencies);
|
|
2409
|
+
if (!existsSync5(dependenciesPath)) {
|
|
2410
|
+
writeFileSync3(
|
|
2411
|
+
dependenciesPath,
|
|
2412
|
+
`# ${agentName} Dependencies
|
|
2413
|
+
|
|
2414
|
+
## Blocked By
|
|
2415
|
+
[Tasks/agents blocking this agent]
|
|
2416
|
+
|
|
2417
|
+
## Blocking
|
|
2418
|
+
[Tasks/agents this agent is blocking]
|
|
2419
|
+
|
|
2420
|
+
Last Updated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
2421
|
+
`,
|
|
2422
|
+
"utf-8"
|
|
2423
|
+
);
|
|
2424
|
+
createdFiles.push(dependenciesPath);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
return {
|
|
2428
|
+
success: true,
|
|
2429
|
+
createdDirs,
|
|
2430
|
+
createdFiles
|
|
2431
|
+
};
|
|
2432
|
+
} catch (error) {
|
|
2433
|
+
return {
|
|
2434
|
+
success: false,
|
|
2435
|
+
createdDirs,
|
|
2436
|
+
createdFiles,
|
|
2437
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
function agentKnowledgeLibraryExists(agentName, options = {}) {
|
|
2442
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
2443
|
+
const basePath = join6(projectRoot, options.basePath ?? DEFAULT_KNOWLEDGE_LIBRARY_PATH);
|
|
2444
|
+
const agentRoot = join6(basePath, agentName);
|
|
2445
|
+
return existsSync5(agentRoot) && existsSync5(join6(agentRoot, "inbox"));
|
|
2446
|
+
}
|
|
2447
|
+
function getAgentKnowledgeState(agentName, options = {}) {
|
|
2448
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
2449
|
+
const basePath = join6(projectRoot, options.basePath ?? DEFAULT_KNOWLEDGE_LIBRARY_PATH);
|
|
2450
|
+
const dirs = getAgentDirectories(basePath, agentName);
|
|
2451
|
+
const initialized = agentKnowledgeLibraryExists(agentName, options);
|
|
2452
|
+
const pendingMessages = initialized ? readInboxMessages(agentName, options) : [];
|
|
2453
|
+
let context;
|
|
2454
|
+
const currentContextPath = join6(dirs.context, "current.txt");
|
|
2455
|
+
if (existsSync5(currentContextPath)) {
|
|
2456
|
+
const content = readFileSync4(currentContextPath, "utf-8");
|
|
2457
|
+
context = parseContextFile(content);
|
|
2458
|
+
}
|
|
2459
|
+
const state = {
|
|
2460
|
+
agentName,
|
|
2461
|
+
directories: dirs,
|
|
2462
|
+
pendingMessages,
|
|
2463
|
+
initialized
|
|
2464
|
+
};
|
|
2465
|
+
if (context) {
|
|
2466
|
+
state.context = context;
|
|
2467
|
+
}
|
|
2468
|
+
return state;
|
|
2469
|
+
}
|
|
2470
|
+
function parseContextFile(content) {
|
|
2471
|
+
const context = {
|
|
2472
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
2473
|
+
};
|
|
2474
|
+
const statusMatch = content.match(/State:\s*(.+)/i);
|
|
2475
|
+
if (statusMatch && statusMatch[1]) {
|
|
2476
|
+
context.status = statusMatch[1].trim();
|
|
2477
|
+
}
|
|
2478
|
+
const taskMatch = content.match(/Current Task:\s*(.+)/i);
|
|
2479
|
+
if (taskMatch && taskMatch[1] && taskMatch[1].trim() !== "None") {
|
|
2480
|
+
context.currentTask = taskMatch[1].trim();
|
|
2481
|
+
}
|
|
2482
|
+
const ticketMatch = content.match(/Current Ticket:\s*([A-Z]+-\d+)/i);
|
|
2483
|
+
if (ticketMatch && ticketMatch[1]) {
|
|
2484
|
+
context.currentTicket = ticketMatch[1];
|
|
2485
|
+
}
|
|
2486
|
+
const updatedMatch = content.match(/Last Updated:\s*(.+)/i);
|
|
2487
|
+
if (updatedMatch) {
|
|
2488
|
+
const dateStr = updatedMatch[1]?.trim();
|
|
2489
|
+
if (dateStr) {
|
|
2490
|
+
const parsed = new Date(dateStr);
|
|
2491
|
+
if (!isNaN(parsed.getTime())) {
|
|
2492
|
+
context.lastUpdated = parsed;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
return context;
|
|
2497
|
+
}
|
|
2498
|
+
function parseMessageFrontmatter(content) {
|
|
2499
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
2500
|
+
if (!match) {
|
|
2501
|
+
return { metadata: {}, body: content };
|
|
2502
|
+
}
|
|
2503
|
+
const frontmatter = match[1] ?? "";
|
|
2504
|
+
const body = match[2] ?? "";
|
|
2505
|
+
const metadata = {};
|
|
2506
|
+
for (const line of frontmatter.split(/\r?\n/)) {
|
|
2507
|
+
const colonIndex = line.indexOf(":");
|
|
2508
|
+
if (colonIndex === -1) continue;
|
|
2509
|
+
const key = line.slice(0, colonIndex).trim();
|
|
2510
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
2511
|
+
switch (key) {
|
|
2512
|
+
case "type":
|
|
2513
|
+
metadata.type = value;
|
|
2514
|
+
break;
|
|
2515
|
+
case "from":
|
|
2516
|
+
metadata.from = value;
|
|
2517
|
+
break;
|
|
2518
|
+
case "to":
|
|
2519
|
+
metadata.to = value;
|
|
2520
|
+
break;
|
|
2521
|
+
case "date":
|
|
2522
|
+
metadata.date = new Date(value);
|
|
2523
|
+
break;
|
|
2524
|
+
case "ticket":
|
|
2525
|
+
metadata.ticket = value;
|
|
2526
|
+
break;
|
|
2527
|
+
case "priority":
|
|
2528
|
+
if (value === "P0" || value === "P1" || value === "P2" || value === "P3") {
|
|
2529
|
+
metadata.priority = value;
|
|
2530
|
+
}
|
|
2531
|
+
break;
|
|
2532
|
+
case "subject":
|
|
2533
|
+
metadata.subject = value;
|
|
2534
|
+
break;
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
return { metadata, body };
|
|
2538
|
+
}
|
|
2539
|
+
function readInboxMessages(agentName, options = {}) {
|
|
2540
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
2541
|
+
const basePath = join6(projectRoot, options.basePath ?? DEFAULT_KNOWLEDGE_LIBRARY_PATH);
|
|
2542
|
+
const dirs = getAgentDirectories(basePath, agentName);
|
|
2543
|
+
const messages = [];
|
|
2544
|
+
if (existsSync5(dirs.inbox)) {
|
|
2545
|
+
const files = readdirSync3(dirs.inbox).filter(
|
|
2546
|
+
(f) => f.endsWith(".md") && statSync2(join6(dirs.inbox, f)).isFile()
|
|
2547
|
+
);
|
|
2548
|
+
for (const file of files) {
|
|
2549
|
+
const filePath = join6(dirs.inbox, file);
|
|
2550
|
+
const rawContent = readFileSync4(filePath, "utf-8");
|
|
2551
|
+
const { metadata, body } = parseMessageFrontmatter(rawContent);
|
|
2552
|
+
if (options.type && metadata.type !== options.type) continue;
|
|
2553
|
+
if (options.from && metadata.from !== options.from) continue;
|
|
2554
|
+
if (options.priority && metadata.priority !== options.priority) continue;
|
|
2555
|
+
messages.push({
|
|
2556
|
+
filename: file,
|
|
2557
|
+
path: filePath,
|
|
2558
|
+
metadata,
|
|
2559
|
+
rawContent,
|
|
2560
|
+
body
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
if (options.includeProcessed && existsSync5(dirs.inboxProcessed)) {
|
|
2565
|
+
const files = readdirSync3(dirs.inboxProcessed).filter(
|
|
2566
|
+
(f) => f.endsWith(".md") && statSync2(join6(dirs.inboxProcessed, f)).isFile()
|
|
2567
|
+
);
|
|
2568
|
+
for (const file of files) {
|
|
2569
|
+
const filePath = join6(dirs.inboxProcessed, file);
|
|
2570
|
+
const rawContent = readFileSync4(filePath, "utf-8");
|
|
2571
|
+
const { metadata, body } = parseMessageFrontmatter(rawContent);
|
|
2572
|
+
if (options.type && metadata.type !== options.type) continue;
|
|
2573
|
+
if (options.from && metadata.from !== options.from) continue;
|
|
2574
|
+
if (options.priority && metadata.priority !== options.priority) continue;
|
|
2575
|
+
messages.push({
|
|
2576
|
+
filename: file,
|
|
2577
|
+
path: filePath,
|
|
2578
|
+
metadata,
|
|
2579
|
+
rawContent,
|
|
2580
|
+
body
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
messages.sort((a, b) => {
|
|
2585
|
+
const dateA = a.metadata.date?.getTime() ?? 0;
|
|
2586
|
+
const dateB = b.metadata.date?.getTime() ?? 0;
|
|
2587
|
+
return dateB - dateA;
|
|
2588
|
+
});
|
|
2589
|
+
if (options.limit && messages.length > options.limit) {
|
|
2590
|
+
return messages.slice(0, options.limit);
|
|
2591
|
+
}
|
|
2592
|
+
return messages;
|
|
2593
|
+
}
|
|
2594
|
+
function getKnowledgeLibraryState(options = {}) {
|
|
2595
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
2596
|
+
const basePath = join6(projectRoot, options.basePath ?? DEFAULT_KNOWLEDGE_LIBRARY_PATH);
|
|
2597
|
+
if (!existsSync5(basePath)) {
|
|
2598
|
+
return null;
|
|
2599
|
+
}
|
|
2600
|
+
const agents2 = [];
|
|
2601
|
+
const entries = readdirSync3(basePath);
|
|
2602
|
+
for (const entry of entries) {
|
|
2603
|
+
const entryPath = join6(basePath, entry);
|
|
2604
|
+
if (statSync2(entryPath).isDirectory() && existsSync5(join6(entryPath, "inbox"))) {
|
|
2605
|
+
agents2.push(entry);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
return {
|
|
2609
|
+
basePath,
|
|
2610
|
+
contextPath: join6(basePath, STANDARD_FILES.context),
|
|
2611
|
+
architecturePath: join6(basePath, STANDARD_FILES.architecture),
|
|
2612
|
+
prdPath: join6(basePath, STANDARD_FILES.prd),
|
|
2613
|
+
agents: agents2
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
// src/index.ts
|
|
2618
|
+
var VERSION = "0.1.0";
|
|
2619
|
+
|
|
2620
|
+
// src/cli/commands/cache.ts
|
|
2621
|
+
import { join as join7 } from "path";
|
|
2622
|
+
function getCachePath(options) {
|
|
2623
|
+
if (options.cachePath) {
|
|
2624
|
+
return options.cachePath;
|
|
2625
|
+
}
|
|
2626
|
+
const root = options.projectRoot ?? process.cwd();
|
|
2627
|
+
return join7(root, CACHE_PATHS.base);
|
|
2628
|
+
}
|
|
2629
|
+
async function cacheStatus(options = {}) {
|
|
2630
|
+
const cachePath = getCachePath(options);
|
|
2631
|
+
const provider = new FileCacheProvider({ basePath: cachePath });
|
|
2632
|
+
try {
|
|
2633
|
+
await provider.initialize();
|
|
2634
|
+
} catch {
|
|
2635
|
+
return {
|
|
2636
|
+
initialized: false,
|
|
2637
|
+
path: cachePath,
|
|
2638
|
+
stats: null,
|
|
2639
|
+
entries: []
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
const stats = await provider.getStats();
|
|
2643
|
+
const metadataList = await provider.list();
|
|
2644
|
+
const entries = await Promise.all(
|
|
2645
|
+
metadataList.map(async (metadata) => {
|
|
2646
|
+
const status2 = await provider.getStatus(metadata.key);
|
|
2647
|
+
return {
|
|
2648
|
+
key: metadata.key,
|
|
2649
|
+
source: metadata.source,
|
|
2650
|
+
status: status2 ?? "unknown",
|
|
2651
|
+
size: metadata.size,
|
|
2652
|
+
cachedAt: metadata.cachedAt,
|
|
2653
|
+
expiresAt: metadata.expiresAt
|
|
2654
|
+
};
|
|
2655
|
+
})
|
|
2656
|
+
);
|
|
2657
|
+
return {
|
|
2658
|
+
initialized: true,
|
|
2659
|
+
path: cachePath,
|
|
2660
|
+
stats,
|
|
2661
|
+
entries
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
async function cacheClear(options = {}) {
|
|
2665
|
+
const cachePath = getCachePath(options);
|
|
2666
|
+
const provider = new FileCacheProvider({ basePath: cachePath });
|
|
2667
|
+
try {
|
|
2668
|
+
await provider.initialize();
|
|
2669
|
+
const cleared = await provider.clear();
|
|
2670
|
+
return { cleared, success: true };
|
|
2671
|
+
} catch (error) {
|
|
2672
|
+
return {
|
|
2673
|
+
cleared: 0,
|
|
2674
|
+
success: false,
|
|
2675
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
async function cacheClearExpired(options = {}) {
|
|
2680
|
+
const cachePath = getCachePath(options);
|
|
2681
|
+
const provider = new FileCacheProvider({ basePath: cachePath });
|
|
2682
|
+
try {
|
|
2683
|
+
await provider.initialize();
|
|
2684
|
+
const cleared = await provider.clearExpired();
|
|
2685
|
+
return { cleared, success: true };
|
|
2686
|
+
} catch (error) {
|
|
2687
|
+
return {
|
|
2688
|
+
cleared: 0,
|
|
2689
|
+
success: false,
|
|
2690
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
function formatBytes(bytes) {
|
|
2695
|
+
if (bytes === 0) return "0 B";
|
|
2696
|
+
const k = 1024;
|
|
2697
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
2698
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
2699
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
2700
|
+
}
|
|
2701
|
+
function formatCacheStatus(result) {
|
|
2702
|
+
const lines = [];
|
|
2703
|
+
lines.push("Cache Status");
|
|
2704
|
+
lines.push("============");
|
|
2705
|
+
lines.push("");
|
|
2706
|
+
if (!result.initialized) {
|
|
2707
|
+
lines.push(`Path: ${result.path}`);
|
|
2708
|
+
lines.push("Status: Not initialized (no cache found)");
|
|
2709
|
+
return lines.join("\n");
|
|
2710
|
+
}
|
|
2711
|
+
lines.push(`Path: ${result.path}`);
|
|
2712
|
+
lines.push("Status: Initialized");
|
|
2713
|
+
lines.push("");
|
|
2714
|
+
if (result.stats) {
|
|
2715
|
+
lines.push("Statistics:");
|
|
2716
|
+
lines.push(` Entries: ${result.stats.totalEntries}`);
|
|
2717
|
+
lines.push(` Total size: ${formatBytes(result.stats.totalSize)}`);
|
|
2718
|
+
lines.push(
|
|
2719
|
+
` Valid: ${result.stats.validEntries}, Stale: ${result.stats.staleEntries}, Expired: ${result.stats.expiredEntries}`
|
|
2720
|
+
);
|
|
2721
|
+
if (result.stats.newestEntry) {
|
|
2722
|
+
lines.push(` Last updated: ${result.stats.newestEntry}`);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
if (result.entries.length > 0) {
|
|
2726
|
+
lines.push("");
|
|
2727
|
+
lines.push("Entries:");
|
|
2728
|
+
lines.push("");
|
|
2729
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
2730
|
+
for (const entry of result.entries) {
|
|
2731
|
+
const source = entry.source;
|
|
2732
|
+
if (!bySource.has(source)) {
|
|
2733
|
+
bySource.set(source, []);
|
|
2734
|
+
}
|
|
2735
|
+
const entries = bySource.get(source);
|
|
2736
|
+
if (entries) {
|
|
2737
|
+
entries.push(entry);
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
for (const [source, entries] of bySource) {
|
|
2741
|
+
lines.push(` ${source}:`);
|
|
2742
|
+
for (const entry of entries) {
|
|
2743
|
+
const statusIcon = entry.status === "valid" ? "\u2713" : entry.status === "stale" ? "\u25CB" : "\u2717";
|
|
2744
|
+
lines.push(` ${statusIcon} ${entry.key} (${formatBytes(entry.size)})`);
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
} else {
|
|
2748
|
+
lines.push("");
|
|
2749
|
+
lines.push("No entries in cache.");
|
|
2750
|
+
}
|
|
2751
|
+
return lines.join("\n");
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
// src/cli/commands/sync.ts
|
|
2755
|
+
import { join as join8 } from "path";
|
|
2756
|
+
function createPlaceholderFetcher(source) {
|
|
2757
|
+
return {
|
|
2758
|
+
async fetch(url) {
|
|
2759
|
+
throw new Error(
|
|
2760
|
+
`No fetcher implementation for source "${source}". URL: ${url}. Configure a documentation provider in coreai.config.yaml.`
|
|
2761
|
+
);
|
|
2762
|
+
},
|
|
2763
|
+
async hasChanged(_url, _etag) {
|
|
2764
|
+
return true;
|
|
2765
|
+
}
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
function getCachePath2(options) {
|
|
2769
|
+
if (options.cachePath) {
|
|
2770
|
+
return options.cachePath;
|
|
2771
|
+
}
|
|
2772
|
+
const root = options.projectRoot ?? process.cwd();
|
|
2773
|
+
return join8(root, CACHE_PATHS.base);
|
|
2774
|
+
}
|
|
2775
|
+
async function sync(options = {}) {
|
|
2776
|
+
const cachePath = getCachePath2(options);
|
|
2777
|
+
const provider = new FileCacheProvider({ basePath: cachePath });
|
|
2778
|
+
try {
|
|
2779
|
+
await provider.initialize();
|
|
2780
|
+
} catch (error) {
|
|
2781
|
+
return {
|
|
2782
|
+
success: false,
|
|
2783
|
+
result: null,
|
|
2784
|
+
error: `Failed to initialize cache: ${error instanceof Error ? error.message : String(error)}`,
|
|
2785
|
+
sources: []
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
const entries = await provider.list({ source: options.source });
|
|
2789
|
+
if (entries.length === 0) {
|
|
2790
|
+
return {
|
|
2791
|
+
success: true,
|
|
2792
|
+
result: {
|
|
2793
|
+
added: 0,
|
|
2794
|
+
updated: 0,
|
|
2795
|
+
removed: 0,
|
|
2796
|
+
failed: 0,
|
|
2797
|
+
errors: [],
|
|
2798
|
+
duration: 0
|
|
2799
|
+
},
|
|
2800
|
+
sources: []
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
const manager = new CacheManager({
|
|
2804
|
+
cache: provider,
|
|
2805
|
+
defaultTtl: 3600
|
|
2806
|
+
});
|
|
2807
|
+
const sources = /* @__PURE__ */ new Set();
|
|
2808
|
+
for (const entry of entries) {
|
|
2809
|
+
sources.add(entry.source);
|
|
2810
|
+
}
|
|
2811
|
+
for (const source of sources) {
|
|
2812
|
+
if (!manager.hasFetcher(source)) {
|
|
2813
|
+
manager.registerFetcher(source, createPlaceholderFetcher(source));
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
const keys = entries.map((e) => e.key);
|
|
2817
|
+
if (options.onProgress) {
|
|
2818
|
+
options.onProgress(0, keys.length, "Starting sync...");
|
|
2819
|
+
}
|
|
2820
|
+
try {
|
|
2821
|
+
const result = await manager.syncEntries(keys, {
|
|
2822
|
+
force: options.force,
|
|
2823
|
+
continueOnError: options.continueOnError ?? true,
|
|
2824
|
+
concurrency: options.concurrency ?? 5,
|
|
2825
|
+
onProgress: (completed, total) => {
|
|
2826
|
+
if (options.onProgress) {
|
|
2827
|
+
options.onProgress(completed, total, `Syncing ${completed}/${total} entries...`);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
});
|
|
2831
|
+
return {
|
|
2832
|
+
success: result.failed === 0 || options.continueOnError === true,
|
|
2833
|
+
result,
|
|
2834
|
+
sources: Array.from(sources)
|
|
2835
|
+
};
|
|
2836
|
+
} catch (error) {
|
|
2837
|
+
return {
|
|
2838
|
+
success: false,
|
|
2839
|
+
result: null,
|
|
2840
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2841
|
+
sources: Array.from(sources)
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
function formatSyncResult(result) {
|
|
2846
|
+
const lines = [];
|
|
2847
|
+
lines.push("Sync Results");
|
|
2848
|
+
lines.push("============");
|
|
2849
|
+
lines.push("");
|
|
2850
|
+
if (!result.success && result.error) {
|
|
2851
|
+
lines.push(`Error: ${result.error}`);
|
|
2852
|
+
return lines.join("\n");
|
|
2853
|
+
}
|
|
2854
|
+
if (!result.result) {
|
|
2855
|
+
lines.push("No sync operation performed.");
|
|
2856
|
+
return lines.join("\n");
|
|
2857
|
+
}
|
|
2858
|
+
const r = result.result;
|
|
2859
|
+
if (r.added === 0 && r.updated === 0 && r.removed === 0 && r.failed === 0) {
|
|
2860
|
+
lines.push("Cache is empty. Nothing to sync.");
|
|
2861
|
+
lines.push("");
|
|
2862
|
+
lines.push("Tip: Add context sources to your coreai.config.yaml to fetch remote content.");
|
|
2863
|
+
return lines.join("\n");
|
|
2864
|
+
}
|
|
2865
|
+
lines.push(`Duration: ${r.duration}ms`);
|
|
2866
|
+
lines.push("");
|
|
2867
|
+
if (result.sources.length > 0) {
|
|
2868
|
+
lines.push(`Sources: ${result.sources.join(", ")}`);
|
|
2869
|
+
lines.push("");
|
|
2870
|
+
}
|
|
2871
|
+
lines.push("Summary:");
|
|
2872
|
+
if (r.added > 0) lines.push(` \u2713 Added: ${r.added}`);
|
|
2873
|
+
if (r.updated > 0) lines.push(` \u2713 Updated: ${r.updated}`);
|
|
2874
|
+
if (r.removed > 0) lines.push(` \u25CB Removed: ${r.removed}`);
|
|
2875
|
+
if (r.failed > 0) lines.push(` \u2717 Failed: ${r.failed}`);
|
|
2876
|
+
if (r.errors.length > 0) {
|
|
2877
|
+
lines.push("");
|
|
2878
|
+
lines.push("Errors:");
|
|
2879
|
+
for (const err of r.errors.slice(0, 10)) {
|
|
2880
|
+
lines.push(` - ${err.key}: ${err.error}`);
|
|
2881
|
+
}
|
|
2882
|
+
if (r.errors.length > 10) {
|
|
2883
|
+
lines.push(` ... and ${r.errors.length - 10} more errors`);
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
return lines.join("\n");
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
// src/cli/commands/init.ts
|
|
2890
|
+
import { existsSync as existsSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5 } from "fs";
|
|
2891
|
+
import { join as join9, basename as basename4 } from "path";
|
|
2892
|
+
import { execSync } from "child_process";
|
|
2893
|
+
function detectGitInfo() {
|
|
2894
|
+
try {
|
|
2895
|
+
const remoteUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
2896
|
+
const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/([^/.]+)/);
|
|
2897
|
+
if (httpsMatch) {
|
|
2898
|
+
return { provider: "github", owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
2899
|
+
}
|
|
2900
|
+
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/([^/.]+)/);
|
|
2901
|
+
if (sshMatch) {
|
|
2902
|
+
return { provider: "github", owner: sshMatch[1], repo: sshMatch[2] };
|
|
2903
|
+
}
|
|
2904
|
+
const gitlabHttps = remoteUrl.match(/https:\/\/gitlab\.com\/([^/]+)\/([^/.]+)/);
|
|
2905
|
+
if (gitlabHttps) {
|
|
2906
|
+
return { provider: "gitlab", owner: gitlabHttps[1], repo: gitlabHttps[2] };
|
|
2907
|
+
}
|
|
2908
|
+
const gitlabSsh = remoteUrl.match(/git@gitlab\.com:([^/]+)\/([^/.]+)/);
|
|
2909
|
+
if (gitlabSsh) {
|
|
2910
|
+
return { provider: "gitlab", owner: gitlabSsh[1], repo: gitlabSsh[2] };
|
|
2911
|
+
}
|
|
2912
|
+
return null;
|
|
2913
|
+
} catch {
|
|
2914
|
+
return null;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
function detectProjectName(projectRoot) {
|
|
2918
|
+
const packageJsonPath = join9(projectRoot, "package.json");
|
|
2919
|
+
if (existsSync6(packageJsonPath)) {
|
|
2920
|
+
try {
|
|
2921
|
+
const content = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
|
|
2922
|
+
if (content.name) {
|
|
2923
|
+
return content.name;
|
|
2924
|
+
}
|
|
2925
|
+
} catch {
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
return basename4(projectRoot);
|
|
2929
|
+
}
|
|
2930
|
+
function generateConfigYaml(options) {
|
|
2931
|
+
let yaml = `# CoreAI Configuration
|
|
2932
|
+
# See https://coreai.dev/docs/config for full schema
|
|
2933
|
+
|
|
2934
|
+
version: "1.0"
|
|
2935
|
+
|
|
2936
|
+
project:
|
|
2937
|
+
name: "${options.name}"
|
|
2938
|
+
type: ${options.type}
|
|
2939
|
+
|
|
2940
|
+
team:
|
|
2941
|
+
agents:
|
|
2942
|
+
- backend-engineer
|
|
2943
|
+
- frontend-engineer
|
|
2944
|
+
- devops-engineer
|
|
2945
|
+
- engineering-manager
|
|
2946
|
+
`;
|
|
2947
|
+
if (options.gitInfo?.provider) {
|
|
2948
|
+
yaml += `
|
|
2949
|
+
integrations:
|
|
2950
|
+
git:
|
|
2951
|
+
provider: ${options.gitInfo.provider}
|
|
2952
|
+
config:
|
|
2953
|
+
owner: "${options.gitInfo.owner}"
|
|
2954
|
+
repo: "${options.gitInfo.repo}"
|
|
2955
|
+
`;
|
|
2956
|
+
}
|
|
2957
|
+
yaml += `
|
|
2958
|
+
# quality_gates:
|
|
2959
|
+
# lint:
|
|
2960
|
+
# command: npm run lint
|
|
2961
|
+
# required: true
|
|
2962
|
+
# test:
|
|
2963
|
+
# command: npm test
|
|
2964
|
+
# required: true
|
|
2965
|
+
# build:
|
|
2966
|
+
# command: npm run build
|
|
2967
|
+
# required: true
|
|
2968
|
+
|
|
2969
|
+
# tech_stack:
|
|
2970
|
+
# primary_language: typescript
|
|
2971
|
+
# frameworks:
|
|
2972
|
+
# - node.js
|
|
2973
|
+
# - express
|
|
2974
|
+
`;
|
|
2975
|
+
return yaml;
|
|
2976
|
+
}
|
|
2977
|
+
function createDirectories(projectRoot) {
|
|
2978
|
+
const dirs = [
|
|
2979
|
+
join9(projectRoot, "coreai", "agents"),
|
|
2980
|
+
join9(projectRoot, "coreai", "commands"),
|
|
2981
|
+
join9(projectRoot, ".coreai", "cache")
|
|
2982
|
+
];
|
|
2983
|
+
const created = [];
|
|
2984
|
+
for (const dir of dirs) {
|
|
2985
|
+
if (!existsSync6(dir)) {
|
|
2986
|
+
mkdirSync4(dir, { recursive: true });
|
|
2987
|
+
created.push(dir);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
return created;
|
|
2991
|
+
}
|
|
2992
|
+
function init(options = {}) {
|
|
2993
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
2994
|
+
if (configExists(projectRoot) && !options.force) {
|
|
2995
|
+
return {
|
|
2996
|
+
success: false,
|
|
2997
|
+
error: "CoreAI configuration already exists. Use --force to overwrite."
|
|
2998
|
+
};
|
|
2999
|
+
}
|
|
3000
|
+
const gitInfo = detectGitInfo();
|
|
3001
|
+
const name = options.name ?? detectProjectName(projectRoot);
|
|
3002
|
+
const type = options.type ?? "software";
|
|
3003
|
+
const configContent = generateConfigYaml({ name, type, gitInfo });
|
|
3004
|
+
const configPath = join9(projectRoot, "coreai.config.yaml");
|
|
3005
|
+
try {
|
|
3006
|
+
writeFileSync4(configPath, configContent, "utf-8");
|
|
3007
|
+
} catch (error) {
|
|
3008
|
+
return {
|
|
3009
|
+
success: false,
|
|
3010
|
+
error: `Failed to write config file: ${error instanceof Error ? error.message : String(error)}`
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
let createdDirs = [];
|
|
3014
|
+
if (!options.skipDirs) {
|
|
3015
|
+
try {
|
|
3016
|
+
createdDirs = createDirectories(projectRoot);
|
|
3017
|
+
} catch (error) {
|
|
3018
|
+
return {
|
|
3019
|
+
success: false,
|
|
3020
|
+
configPath,
|
|
3021
|
+
error: `Failed to create directories: ${error instanceof Error ? error.message : String(error)}`
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
return {
|
|
3026
|
+
success: true,
|
|
3027
|
+
configPath,
|
|
3028
|
+
createdDirs,
|
|
3029
|
+
gitInfo: gitInfo ?? void 0
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
function formatInitResult(result) {
|
|
3033
|
+
if (!result.success) {
|
|
3034
|
+
return `Error: ${result.error}`;
|
|
3035
|
+
}
|
|
3036
|
+
const lines = [];
|
|
3037
|
+
lines.push("CoreAI project initialized successfully!\n");
|
|
3038
|
+
if (result.configPath) {
|
|
3039
|
+
lines.push(`Created: ${result.configPath}`);
|
|
3040
|
+
}
|
|
3041
|
+
if (result.createdDirs && result.createdDirs.length > 0) {
|
|
3042
|
+
lines.push("\nCreated directories:");
|
|
3043
|
+
for (const dir of result.createdDirs) {
|
|
3044
|
+
lines.push(` - ${dir}`);
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
if (result.gitInfo?.provider) {
|
|
3048
|
+
lines.push(
|
|
3049
|
+
`
|
|
3050
|
+
Detected ${result.gitInfo.provider} repository: ${result.gitInfo.owner}/${result.gitInfo.repo}`
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
lines.push("\nNext steps:");
|
|
3054
|
+
lines.push(" 1. Edit coreai.config.yaml to configure your project");
|
|
3055
|
+
lines.push(" 2. Run `coreai build` to compile agents");
|
|
3056
|
+
lines.push(" 3. Run `coreai validate` to check your setup");
|
|
3057
|
+
return lines.join("\n");
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
// src/cli/commands/build.ts
|
|
3061
|
+
function build(options = {}) {
|
|
3062
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
3063
|
+
const warnings = [];
|
|
3064
|
+
let config;
|
|
3065
|
+
if (configExists(projectRoot)) {
|
|
3066
|
+
try {
|
|
3067
|
+
config = loadConfig(projectRoot);
|
|
3068
|
+
} catch (error) {
|
|
3069
|
+
if (error instanceof ConfigError) {
|
|
3070
|
+
return {
|
|
3071
|
+
success: false,
|
|
3072
|
+
error: `Configuration error: ${error.message}`
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
return {
|
|
3076
|
+
success: false,
|
|
3077
|
+
error: `Failed to load config: ${error instanceof Error ? error.message : String(error)}`
|
|
3078
|
+
};
|
|
3079
|
+
}
|
|
3080
|
+
} else {
|
|
3081
|
+
warnings.push("No configuration file found. Building with defaults.");
|
|
3082
|
+
}
|
|
3083
|
+
const compileOptions = {
|
|
3084
|
+
projectRoot
|
|
3085
|
+
};
|
|
3086
|
+
if (options.coreAgentsDir) {
|
|
3087
|
+
compileOptions.coreAgentsDir = options.coreAgentsDir;
|
|
3088
|
+
}
|
|
3089
|
+
if (options.outputDir) {
|
|
3090
|
+
compileOptions.outputDir = options.outputDir;
|
|
3091
|
+
}
|
|
3092
|
+
if (options.agents && options.agents.length > 0) {
|
|
3093
|
+
const agentsList = options.agents;
|
|
3094
|
+
compileOptions.filter = (agent) => agentsList.includes(agent.role);
|
|
3095
|
+
}
|
|
3096
|
+
try {
|
|
3097
|
+
const result = compileAgents(config, compileOptions);
|
|
3098
|
+
if (result.errors.length > 0) {
|
|
3099
|
+
return {
|
|
3100
|
+
success: false,
|
|
3101
|
+
result,
|
|
3102
|
+
config,
|
|
3103
|
+
error: `Failed to compile ${result.errors.length} agent(s)`,
|
|
3104
|
+
warnings
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
let knowledgeLibraryInitialized;
|
|
3108
|
+
if (options.initKnowledgeLibrary) {
|
|
3109
|
+
knowledgeLibraryInitialized = [];
|
|
3110
|
+
for (const compiled of result.compiled) {
|
|
3111
|
+
const initResult = initAgentKnowledgeLibrary(compiled.role, {
|
|
3112
|
+
projectRoot,
|
|
3113
|
+
createDefaults: true
|
|
3114
|
+
});
|
|
3115
|
+
if (initResult.success && initResult.createdDirs.length > 0) {
|
|
3116
|
+
knowledgeLibraryInitialized.push(compiled.role);
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
return {
|
|
3121
|
+
success: true,
|
|
3122
|
+
result,
|
|
3123
|
+
config,
|
|
3124
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
3125
|
+
knowledgeLibraryInitialized
|
|
3126
|
+
};
|
|
3127
|
+
} catch (error) {
|
|
3128
|
+
return {
|
|
3129
|
+
success: false,
|
|
3130
|
+
error: `Build failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3131
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
3132
|
+
};
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
function formatBuildResult(result) {
|
|
3136
|
+
const lines = [];
|
|
3137
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
3138
|
+
for (const warning of result.warnings) {
|
|
3139
|
+
lines.push(`\u26A0 ${warning}`);
|
|
3140
|
+
}
|
|
3141
|
+
lines.push("");
|
|
3142
|
+
}
|
|
3143
|
+
if (!result.success) {
|
|
3144
|
+
lines.push(`Build failed: ${result.error}`);
|
|
3145
|
+
if (result.result?.errors && result.result.errors.length > 0) {
|
|
3146
|
+
lines.push("");
|
|
3147
|
+
for (const error of result.result.errors) {
|
|
3148
|
+
lines.push(` \u2717 ${error.role}: ${error.error}`);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
return lines.join("\n");
|
|
3152
|
+
}
|
|
3153
|
+
const compileResult = result.result;
|
|
3154
|
+
if (!compileResult) {
|
|
3155
|
+
lines.push("No agents to compile.");
|
|
3156
|
+
return lines.join("\n");
|
|
3157
|
+
}
|
|
3158
|
+
if (compileResult.compiled.length === 0) {
|
|
3159
|
+
lines.push("No agents to compile.");
|
|
3160
|
+
return lines.join("\n");
|
|
3161
|
+
}
|
|
3162
|
+
lines.push(`Compiled ${compileResult.compiled.length} agent(s):
|
|
3163
|
+
`);
|
|
3164
|
+
const coreAgents = compileResult.compiled.filter((a) => a.source === "core");
|
|
3165
|
+
const customAgents = compileResult.compiled.filter((a) => a.source === "custom");
|
|
3166
|
+
const overrideAgents = compileResult.compiled.filter((a) => a.source === "override");
|
|
3167
|
+
if (coreAgents.length > 0) {
|
|
3168
|
+
lines.push("Core agents:");
|
|
3169
|
+
for (const agent of coreAgents) {
|
|
3170
|
+
lines.push(` \u2713 ${agent.role}`);
|
|
3171
|
+
lines.push(` \u2192 ${agent.outputPath}`);
|
|
3172
|
+
}
|
|
3173
|
+
lines.push("");
|
|
3174
|
+
}
|
|
3175
|
+
if (customAgents.length > 0) {
|
|
3176
|
+
lines.push("Custom agents:");
|
|
3177
|
+
for (const agent of customAgents) {
|
|
3178
|
+
lines.push(` \u2713 ${agent.role}`);
|
|
3179
|
+
lines.push(` \u2192 ${agent.outputPath}`);
|
|
3180
|
+
}
|
|
3181
|
+
lines.push("");
|
|
3182
|
+
}
|
|
3183
|
+
if (overrideAgents.length > 0) {
|
|
3184
|
+
lines.push("Override agents:");
|
|
3185
|
+
for (const agent of overrideAgents) {
|
|
3186
|
+
lines.push(` \u2713 ${agent.role} (overrides core)`);
|
|
3187
|
+
lines.push(` \u2192 ${agent.outputPath}`);
|
|
3188
|
+
}
|
|
3189
|
+
lines.push("");
|
|
3190
|
+
}
|
|
3191
|
+
if (result.knowledgeLibraryInitialized && result.knowledgeLibraryInitialized.length > 0) {
|
|
3192
|
+
lines.push(
|
|
3193
|
+
`KnowledgeLibrary initialized for ${result.knowledgeLibraryInitialized.length} agent(s):`
|
|
3194
|
+
);
|
|
3195
|
+
for (const agent of result.knowledgeLibraryInitialized) {
|
|
3196
|
+
lines.push(` \u2713 ${agent}`);
|
|
3197
|
+
}
|
|
3198
|
+
lines.push("");
|
|
3199
|
+
}
|
|
3200
|
+
lines.push("Build complete!");
|
|
3201
|
+
return lines.join("\n");
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// src/cli/commands/validate.ts
|
|
3205
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3206
|
+
import { join as join10 } from "path";
|
|
3207
|
+
function validateConfig2(projectRoot) {
|
|
3208
|
+
const issues = [];
|
|
3209
|
+
if (!configExists(projectRoot)) {
|
|
3210
|
+
issues.push({
|
|
3211
|
+
level: "error",
|
|
3212
|
+
category: "config",
|
|
3213
|
+
message: "No CoreAI configuration file found",
|
|
3214
|
+
fix: "Run `coreai init` to create a configuration file"
|
|
3215
|
+
});
|
|
3216
|
+
return { issues };
|
|
3217
|
+
}
|
|
3218
|
+
try {
|
|
3219
|
+
const config = loadConfig(projectRoot);
|
|
3220
|
+
return { config, issues };
|
|
3221
|
+
} catch (error) {
|
|
3222
|
+
if (error instanceof ConfigError) {
|
|
3223
|
+
issues.push({
|
|
3224
|
+
level: "error",
|
|
3225
|
+
category: "config",
|
|
3226
|
+
message: `Configuration error: ${error.message}`,
|
|
3227
|
+
fix: "Check coreai.config.yaml for syntax errors"
|
|
3228
|
+
});
|
|
3229
|
+
} else {
|
|
3230
|
+
issues.push({
|
|
3231
|
+
level: "error",
|
|
3232
|
+
category: "config",
|
|
3233
|
+
message: `Failed to load config: ${error instanceof Error ? error.message : String(error)}`
|
|
3234
|
+
});
|
|
3235
|
+
}
|
|
3236
|
+
return { issues };
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
function validateAgents(config, coreAgentsDir, projectRoot) {
|
|
3240
|
+
const issues = [];
|
|
3241
|
+
const customAgentsDir = join10(projectRoot, "coreai", "agents");
|
|
3242
|
+
let availableAgents;
|
|
3243
|
+
try {
|
|
3244
|
+
availableAgents = loadAllAgents({
|
|
3245
|
+
coreAgentsDir,
|
|
3246
|
+
customAgentsDir
|
|
3247
|
+
});
|
|
3248
|
+
} catch (error) {
|
|
3249
|
+
issues.push({
|
|
3250
|
+
level: "warning",
|
|
3251
|
+
category: "agents",
|
|
3252
|
+
message: `Could not load agents: ${error instanceof Error ? error.message : String(error)}`
|
|
3253
|
+
});
|
|
3254
|
+
return issues;
|
|
3255
|
+
}
|
|
3256
|
+
const configuredAgents = config.team.agents;
|
|
3257
|
+
for (const agentRole of configuredAgents) {
|
|
3258
|
+
if (!availableAgents.has(agentRole)) {
|
|
3259
|
+
issues.push({
|
|
3260
|
+
level: "error",
|
|
3261
|
+
category: "agents",
|
|
3262
|
+
message: `Configured agent '${agentRole}' not found`,
|
|
3263
|
+
fix: `Add agent definition to coreai/agents/${agentRole}.yaml or remove from config`
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
const unconfigured = Array.from(availableAgents.keys()).filter(
|
|
3268
|
+
(role) => !configuredAgents.includes(role)
|
|
3269
|
+
);
|
|
3270
|
+
if (unconfigured.length > 0) {
|
|
3271
|
+
issues.push({
|
|
3272
|
+
level: "info",
|
|
3273
|
+
category: "agents",
|
|
3274
|
+
message: `${unconfigured.length} available agent(s) not configured: ${unconfigured.join(", ")}`
|
|
3275
|
+
});
|
|
3276
|
+
}
|
|
3277
|
+
return issues;
|
|
3278
|
+
}
|
|
3279
|
+
function validateDirectories(projectRoot) {
|
|
3280
|
+
const issues = [];
|
|
3281
|
+
const requiredDirs = [{ path: "coreai", description: "CoreAI configuration directory" }];
|
|
3282
|
+
const optionalDirs = [
|
|
3283
|
+
{ path: "coreai/agents", description: "Custom agent definitions" },
|
|
3284
|
+
{ path: "coreai/commands", description: "Custom command definitions" },
|
|
3285
|
+
{ path: ".coreai", description: "CoreAI runtime directory" },
|
|
3286
|
+
{ path: ".coreai/cache", description: "Cache directory" },
|
|
3287
|
+
{ path: ".claude/agents", description: "Compiled agents output" }
|
|
3288
|
+
];
|
|
3289
|
+
for (const dir of requiredDirs) {
|
|
3290
|
+
const fullPath = join10(projectRoot, dir.path);
|
|
3291
|
+
if (!existsSync7(fullPath)) {
|
|
3292
|
+
issues.push({
|
|
3293
|
+
level: "warning",
|
|
3294
|
+
category: "directories",
|
|
3295
|
+
message: `Missing directory: ${dir.path} (${dir.description})`,
|
|
3296
|
+
fix: `Run mkdir -p ${dir.path}`
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
for (const dir of optionalDirs) {
|
|
3301
|
+
const fullPath = join10(projectRoot, dir.path);
|
|
3302
|
+
if (!existsSync7(fullPath)) {
|
|
3303
|
+
issues.push({
|
|
3304
|
+
level: "info",
|
|
3305
|
+
category: "directories",
|
|
3306
|
+
message: `Optional directory not found: ${dir.path} (${dir.description})`
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
return issues;
|
|
3311
|
+
}
|
|
3312
|
+
function validateIntegrations(config) {
|
|
3313
|
+
const issues = [];
|
|
3314
|
+
if (!config.integrations) {
|
|
3315
|
+
issues.push({
|
|
3316
|
+
level: "info",
|
|
3317
|
+
category: "integrations",
|
|
3318
|
+
message: "No integrations configured"
|
|
3319
|
+
});
|
|
3320
|
+
return issues;
|
|
3321
|
+
}
|
|
3322
|
+
if (config.integrations.git) {
|
|
3323
|
+
const git = config.integrations.git;
|
|
3324
|
+
if (!git.config?.owner || !git.config?.repo) {
|
|
3325
|
+
issues.push({
|
|
3326
|
+
level: "warning",
|
|
3327
|
+
category: "integrations",
|
|
3328
|
+
message: "Git integration configured but owner/repo not specified",
|
|
3329
|
+
fix: "Add integrations.git.config.owner and integrations.git.config.repo to config"
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
if (config.integrations.issue_tracker) {
|
|
3334
|
+
const tracker = config.integrations.issue_tracker;
|
|
3335
|
+
if (tracker.provider === "jira" && !tracker.config?.base_url) {
|
|
3336
|
+
issues.push({
|
|
3337
|
+
level: "warning",
|
|
3338
|
+
category: "integrations",
|
|
3339
|
+
message: "Jira integration configured but base_url not specified",
|
|
3340
|
+
fix: "Add integrations.issue_tracker.config.base_url to config"
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
return issues;
|
|
3345
|
+
}
|
|
3346
|
+
function validate(options = {}) {
|
|
3347
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
3348
|
+
const coreAgentsDir = options.coreAgentsDir ?? join10(projectRoot, "node_modules", "@coreai", "cli", "agents");
|
|
3349
|
+
const checkAgents = options.checkAgents ?? true;
|
|
3350
|
+
const checkDirs = options.checkDirs ?? true;
|
|
3351
|
+
const allIssues = [];
|
|
3352
|
+
const { config, issues: configIssues } = validateConfig2(projectRoot);
|
|
3353
|
+
allIssues.push(...configIssues);
|
|
3354
|
+
if (config) {
|
|
3355
|
+
if (checkAgents) {
|
|
3356
|
+
allIssues.push(...validateAgents(config, coreAgentsDir, projectRoot));
|
|
3357
|
+
}
|
|
3358
|
+
allIssues.push(...validateIntegrations(config));
|
|
3359
|
+
}
|
|
3360
|
+
if (checkDirs) {
|
|
3361
|
+
allIssues.push(...validateDirectories(projectRoot));
|
|
3362
|
+
}
|
|
3363
|
+
const summary = {
|
|
3364
|
+
errors: allIssues.filter((i) => i.level === "error").length,
|
|
3365
|
+
warnings: allIssues.filter((i) => i.level === "warning").length,
|
|
3366
|
+
info: allIssues.filter((i) => i.level === "info").length
|
|
3367
|
+
};
|
|
3368
|
+
return {
|
|
3369
|
+
success: true,
|
|
3370
|
+
valid: summary.errors === 0,
|
|
3371
|
+
config,
|
|
3372
|
+
issues: allIssues,
|
|
3373
|
+
summary
|
|
3374
|
+
};
|
|
3375
|
+
}
|
|
3376
|
+
function formatValidateResult(result) {
|
|
3377
|
+
const lines = [];
|
|
3378
|
+
if (result.valid) {
|
|
3379
|
+
lines.push("\u2713 Configuration is valid\n");
|
|
3380
|
+
} else {
|
|
3381
|
+
lines.push("\u2717 Configuration has errors\n");
|
|
3382
|
+
}
|
|
3383
|
+
const categories = ["config", "agents", "integrations", "directories", "quality_gates"];
|
|
3384
|
+
for (const category of categories) {
|
|
3385
|
+
const categoryIssues = result.issues.filter((i) => i.category === category);
|
|
3386
|
+
if (categoryIssues.length === 0) continue;
|
|
3387
|
+
const categoryLabel = category.charAt(0).toUpperCase() + category.slice(1).replace("_", " ");
|
|
3388
|
+
lines.push(`${categoryLabel}:`);
|
|
3389
|
+
for (const issue of categoryIssues) {
|
|
3390
|
+
const prefix = issue.level === "error" ? " \u2717" : issue.level === "warning" ? " \u26A0" : " \u2139";
|
|
3391
|
+
lines.push(`${prefix} ${issue.message}`);
|
|
3392
|
+
if (issue.fix) {
|
|
3393
|
+
lines.push(` \u2192 ${issue.fix}`);
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
lines.push("");
|
|
3397
|
+
}
|
|
3398
|
+
lines.push("Summary:");
|
|
3399
|
+
lines.push(` Errors: ${result.summary.errors}`);
|
|
3400
|
+
lines.push(` Warnings: ${result.summary.warnings}`);
|
|
3401
|
+
lines.push(` Info: ${result.summary.info}`);
|
|
3402
|
+
return lines.join("\n");
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
// src/cli/commands/skills.ts
|
|
3406
|
+
function skillsGenerate(options = {}) {
|
|
3407
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
3408
|
+
const warnings = [];
|
|
3409
|
+
let config;
|
|
3410
|
+
if (configExists(projectRoot)) {
|
|
3411
|
+
try {
|
|
3412
|
+
config = loadConfig(projectRoot);
|
|
3413
|
+
} catch (error) {
|
|
3414
|
+
if (error instanceof ConfigError) {
|
|
3415
|
+
return {
|
|
3416
|
+
success: false,
|
|
3417
|
+
error: `Configuration error: ${error.message}`
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
return {
|
|
3421
|
+
success: false,
|
|
3422
|
+
error: `Failed to load config: ${error instanceof Error ? error.message : String(error)}`
|
|
3423
|
+
};
|
|
3424
|
+
}
|
|
3425
|
+
} else {
|
|
3426
|
+
warnings.push("No configuration file found. Generating skills with defaults.");
|
|
3427
|
+
}
|
|
3428
|
+
const generateOptions = {
|
|
3429
|
+
projectRoot,
|
|
3430
|
+
includeCoreSkills: options.includeCoreSkills ?? true,
|
|
3431
|
+
includeOptionalSkills: options.includeOptionalSkills ?? true,
|
|
3432
|
+
overwrite: options.overwrite ?? false
|
|
3433
|
+
};
|
|
3434
|
+
if (options.outputDir) {
|
|
3435
|
+
generateOptions.outputDir = options.outputDir;
|
|
3436
|
+
}
|
|
3437
|
+
if (options.customTemplatesDir) {
|
|
3438
|
+
generateOptions.customTemplatesDir = options.customTemplatesDir;
|
|
3439
|
+
}
|
|
3440
|
+
if (options.skills && options.skills.length > 0) {
|
|
3441
|
+
generateOptions.skills = options.skills;
|
|
3442
|
+
}
|
|
3443
|
+
if (options.variables) {
|
|
3444
|
+
generateOptions.variables = options.variables;
|
|
3445
|
+
}
|
|
3446
|
+
try {
|
|
3447
|
+
const result = generateSkills(config, generateOptions);
|
|
3448
|
+
if (result.errors.length > 0) {
|
|
3449
|
+
return {
|
|
3450
|
+
success: false,
|
|
3451
|
+
result,
|
|
3452
|
+
config,
|
|
3453
|
+
error: `Failed to generate ${result.errors.length} skill(s)`,
|
|
3454
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
3455
|
+
};
|
|
3456
|
+
}
|
|
3457
|
+
return {
|
|
3458
|
+
success: true,
|
|
3459
|
+
result,
|
|
3460
|
+
config,
|
|
3461
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
3462
|
+
};
|
|
3463
|
+
} catch (error) {
|
|
3464
|
+
return {
|
|
3465
|
+
success: false,
|
|
3466
|
+
error: `Skill generation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3467
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
function formatSkillsGenerateResult(result) {
|
|
3472
|
+
const lines = [];
|
|
3473
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
3474
|
+
for (const warning of result.warnings) {
|
|
3475
|
+
lines.push(`\u26A0 ${warning}`);
|
|
3476
|
+
}
|
|
3477
|
+
lines.push("");
|
|
3478
|
+
}
|
|
3479
|
+
if (!result.success) {
|
|
3480
|
+
lines.push(`Skill generation failed: ${result.error}`);
|
|
3481
|
+
if (result.result?.errors && result.result.errors.length > 0) {
|
|
3482
|
+
lines.push("");
|
|
3483
|
+
for (const error of result.result.errors) {
|
|
3484
|
+
lines.push(` \u2717 ${error.name}: ${error.error}`);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
return lines.join("\n");
|
|
3488
|
+
}
|
|
3489
|
+
if (result.result) {
|
|
3490
|
+
return formatGenerateResult(result.result);
|
|
3491
|
+
}
|
|
3492
|
+
lines.push("No skills to generate.");
|
|
3493
|
+
return lines.join("\n");
|
|
3494
|
+
}
|
|
3495
|
+
function skillsList(options = {}) {
|
|
3496
|
+
try {
|
|
3497
|
+
const skills = [];
|
|
3498
|
+
const includeCoreSkills = options.includeCoreSkills ?? true;
|
|
3499
|
+
const includeOptionalSkills = options.includeOptionalSkills ?? true;
|
|
3500
|
+
for (const skill of builtInSkills) {
|
|
3501
|
+
if (skill.category === "core" && !includeCoreSkills) continue;
|
|
3502
|
+
if (skill.category === "optional" && !includeOptionalSkills) continue;
|
|
3503
|
+
skills.push({
|
|
3504
|
+
name: skill.name,
|
|
3505
|
+
description: skill.description,
|
|
3506
|
+
category: skill.category,
|
|
3507
|
+
dependencies: skill.dependencies?.filter((d) => d.required).map((d) => d.type),
|
|
3508
|
+
argumentHint: skill.argumentHint
|
|
3509
|
+
});
|
|
3510
|
+
}
|
|
3511
|
+
if (options.customTemplatesDir) {
|
|
3512
|
+
const customSkills = loadCustomTemplates(options.customTemplatesDir);
|
|
3513
|
+
for (const skill of customSkills) {
|
|
3514
|
+
skills.push({
|
|
3515
|
+
name: skill.name,
|
|
3516
|
+
description: skill.description,
|
|
3517
|
+
category: skill.category,
|
|
3518
|
+
dependencies: skill.dependencies?.filter((d) => d.required).map((d) => d.type),
|
|
3519
|
+
argumentHint: skill.argumentHint
|
|
3520
|
+
});
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
return {
|
|
3524
|
+
success: true,
|
|
3525
|
+
skills
|
|
3526
|
+
};
|
|
3527
|
+
} catch (error) {
|
|
3528
|
+
return {
|
|
3529
|
+
success: false,
|
|
3530
|
+
error: `Failed to list skills: ${error instanceof Error ? error.message : String(error)}`
|
|
3531
|
+
};
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
function formatSkillsListResult(result) {
|
|
3535
|
+
if (!result.success) {
|
|
3536
|
+
return `Error: ${result.error}`;
|
|
3537
|
+
}
|
|
3538
|
+
if (!result.skills || result.skills.length === 0) {
|
|
3539
|
+
return "No skills available.";
|
|
3540
|
+
}
|
|
3541
|
+
const lines = [];
|
|
3542
|
+
lines.push("Available skills:\n");
|
|
3543
|
+
const coreSkills = result.skills.filter((s) => s.category === "core");
|
|
3544
|
+
const optionalSkills = result.skills.filter((s) => s.category === "optional");
|
|
3545
|
+
const customSkills = result.skills.filter((s) => s.category === "custom");
|
|
3546
|
+
if (coreSkills.length > 0) {
|
|
3547
|
+
lines.push("Core skills:");
|
|
3548
|
+
for (const skill of coreSkills) {
|
|
3549
|
+
lines.push(` ${skill.name}`);
|
|
3550
|
+
lines.push(` ${skill.description}`);
|
|
3551
|
+
if (skill.argumentHint) {
|
|
3552
|
+
lines.push(` Argument: ${skill.argumentHint}`);
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
lines.push("");
|
|
3556
|
+
}
|
|
3557
|
+
if (optionalSkills.length > 0) {
|
|
3558
|
+
lines.push("Optional skills:");
|
|
3559
|
+
for (const skill of optionalSkills) {
|
|
3560
|
+
const deps = skill.dependencies?.length ? ` (requires: ${skill.dependencies.join(", ")})` : "";
|
|
3561
|
+
lines.push(` ${skill.name}${deps}`);
|
|
3562
|
+
lines.push(` ${skill.description}`);
|
|
3563
|
+
if (skill.argumentHint) {
|
|
3564
|
+
lines.push(` Argument: ${skill.argumentHint}`);
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
lines.push("");
|
|
3568
|
+
}
|
|
3569
|
+
if (customSkills.length > 0) {
|
|
3570
|
+
lines.push("Custom skills:");
|
|
3571
|
+
for (const skill of customSkills) {
|
|
3572
|
+
lines.push(` ${skill.name}`);
|
|
3573
|
+
lines.push(` ${skill.description}`);
|
|
3574
|
+
if (skill.argumentHint) {
|
|
3575
|
+
lines.push(` Argument: ${skill.argumentHint}`);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
lines.push("");
|
|
3579
|
+
}
|
|
3580
|
+
return lines.join("\n");
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
// src/cli/commands/status.ts
|
|
3584
|
+
function status(options = {}) {
|
|
3585
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
3586
|
+
const warnings = [];
|
|
3587
|
+
let config;
|
|
3588
|
+
let configuredAgents = [];
|
|
3589
|
+
if (configExists(projectRoot)) {
|
|
3590
|
+
try {
|
|
3591
|
+
config = loadConfig(projectRoot);
|
|
3592
|
+
if (config.team?.agents) {
|
|
3593
|
+
configuredAgents = config.team.agents;
|
|
3594
|
+
}
|
|
3595
|
+
} catch (error) {
|
|
3596
|
+
if (error instanceof ConfigError) {
|
|
3597
|
+
warnings.push(`Configuration error: ${error.message}`);
|
|
3598
|
+
} else {
|
|
3599
|
+
warnings.push(
|
|
3600
|
+
`Failed to load config: ${error instanceof Error ? error.message : String(error)}`
|
|
3601
|
+
);
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
} else {
|
|
3605
|
+
warnings.push("No configuration file found.");
|
|
3606
|
+
}
|
|
3607
|
+
let klState = getKnowledgeLibraryState({ projectRoot });
|
|
3608
|
+
if (!klState && options.init) {
|
|
3609
|
+
const initResult = initKnowledgeLibrary({ projectRoot, createDefaults: true });
|
|
3610
|
+
if (initResult.success) {
|
|
3611
|
+
klState = getKnowledgeLibraryState({ projectRoot });
|
|
3612
|
+
} else {
|
|
3613
|
+
return {
|
|
3614
|
+
success: false,
|
|
3615
|
+
agents: [],
|
|
3616
|
+
error: `Failed to initialize KnowledgeLibrary: ${initResult.error}`,
|
|
3617
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
3618
|
+
};
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
if (!klState) {
|
|
3622
|
+
return {
|
|
3623
|
+
success: true,
|
|
3624
|
+
agents: [],
|
|
3625
|
+
warnings: [
|
|
3626
|
+
...warnings,
|
|
3627
|
+
"KnowledgeLibrary not initialized. Run `coreai status --init` to create it."
|
|
3628
|
+
]
|
|
3629
|
+
};
|
|
3630
|
+
}
|
|
3631
|
+
let agentsToCheck;
|
|
3632
|
+
if (options.agent) {
|
|
3633
|
+
agentsToCheck = [options.agent];
|
|
3634
|
+
} else {
|
|
3635
|
+
agentsToCheck = [.../* @__PURE__ */ new Set([...configuredAgents, ...klState.agents])];
|
|
3636
|
+
}
|
|
3637
|
+
const agentStatuses = [];
|
|
3638
|
+
for (const agentName of agentsToCheck) {
|
|
3639
|
+
const agentState = getAgentKnowledgeState(agentName, { projectRoot });
|
|
3640
|
+
const agentStatus = {
|
|
3641
|
+
name: agentName,
|
|
3642
|
+
initialized: agentState.initialized,
|
|
3643
|
+
pendingMessages: agentState.pendingMessages.length
|
|
3644
|
+
};
|
|
3645
|
+
if (agentState.context) {
|
|
3646
|
+
agentStatus.status = agentState.context.status;
|
|
3647
|
+
agentStatus.currentTask = agentState.context.currentTask;
|
|
3648
|
+
agentStatus.currentTicket = agentState.context.currentTicket;
|
|
3649
|
+
agentStatus.lastActivity = agentState.context.lastUpdated;
|
|
3650
|
+
}
|
|
3651
|
+
if (options.detailed && agentState.pendingMessages.length > 0) {
|
|
3652
|
+
agentStatus.messageDetails = agentState.pendingMessages.map((msg) => ({
|
|
3653
|
+
type: msg.metadata.type ?? "unknown",
|
|
3654
|
+
from: msg.metadata.from ?? "unknown",
|
|
3655
|
+
subject: msg.metadata.subject ?? msg.filename,
|
|
3656
|
+
date: msg.metadata.date
|
|
3657
|
+
}));
|
|
3658
|
+
}
|
|
3659
|
+
agentStatuses.push(agentStatus);
|
|
3660
|
+
}
|
|
3661
|
+
agentStatuses.sort((a, b) => {
|
|
3662
|
+
if (b.pendingMessages !== a.pendingMessages) {
|
|
3663
|
+
return b.pendingMessages - a.pendingMessages;
|
|
3664
|
+
}
|
|
3665
|
+
return a.name.localeCompare(b.name);
|
|
3666
|
+
});
|
|
3667
|
+
return {
|
|
3668
|
+
success: true,
|
|
3669
|
+
knowledgeLibrary: klState,
|
|
3670
|
+
agents: agentStatuses,
|
|
3671
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
3674
|
+
function formatStatusResult(result) {
|
|
3675
|
+
const lines = [];
|
|
3676
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
3677
|
+
for (const warning of result.warnings) {
|
|
3678
|
+
lines.push(`\u26A0 ${warning}`);
|
|
3679
|
+
}
|
|
3680
|
+
lines.push("");
|
|
3681
|
+
}
|
|
3682
|
+
if (!result.success) {
|
|
3683
|
+
lines.push(`Error: ${result.error}`);
|
|
3684
|
+
return lines.join("\n");
|
|
3685
|
+
}
|
|
3686
|
+
if (result.agents.length === 0) {
|
|
3687
|
+
lines.push("No agents found.");
|
|
3688
|
+
return lines.join("\n");
|
|
3689
|
+
}
|
|
3690
|
+
lines.push("Agent Status\n");
|
|
3691
|
+
const activeAgents = result.agents.filter((a) => a.status === "working");
|
|
3692
|
+
const idleAgents = result.agents.filter(
|
|
3693
|
+
(a) => a.initialized && (!a.status || a.status === "idle")
|
|
3694
|
+
);
|
|
3695
|
+
const uninitializedAgents = result.agents.filter((a) => !a.initialized);
|
|
3696
|
+
const totalPending = result.agents.reduce((sum, a) => sum + a.pendingMessages, 0);
|
|
3697
|
+
lines.push(`Total agents: ${result.agents.length}`);
|
|
3698
|
+
if (activeAgents.length > 0) {
|
|
3699
|
+
lines.push(` Active: ${activeAgents.length}`);
|
|
3700
|
+
}
|
|
3701
|
+
if (idleAgents.length > 0) {
|
|
3702
|
+
lines.push(` Idle: ${idleAgents.length}`);
|
|
3703
|
+
}
|
|
3704
|
+
if (uninitializedAgents.length > 0) {
|
|
3705
|
+
lines.push(` Not initialized: ${uninitializedAgents.length}`);
|
|
3706
|
+
}
|
|
3707
|
+
if (totalPending > 0) {
|
|
3708
|
+
lines.push(` Total pending messages: ${totalPending}`);
|
|
3709
|
+
}
|
|
3710
|
+
lines.push("");
|
|
3711
|
+
for (const agent of result.agents) {
|
|
3712
|
+
const statusIcon = !agent.initialized ? "\u25CB" : agent.pendingMessages > 0 ? "\u25CF" : "\u25CE";
|
|
3713
|
+
const statusText = !agent.initialized ? "not initialized" : agent.status ?? "idle";
|
|
3714
|
+
lines.push(`${statusIcon} ${agent.name}`);
|
|
3715
|
+
lines.push(` Status: ${statusText}`);
|
|
3716
|
+
if (agent.currentTask) {
|
|
3717
|
+
lines.push(` Task: ${agent.currentTask}`);
|
|
3718
|
+
}
|
|
3719
|
+
if (agent.currentTicket) {
|
|
3720
|
+
lines.push(` Ticket: ${agent.currentTicket}`);
|
|
3721
|
+
}
|
|
3722
|
+
if (agent.pendingMessages > 0) {
|
|
3723
|
+
lines.push(` Pending messages: ${agent.pendingMessages}`);
|
|
3724
|
+
if (agent.messageDetails && agent.messageDetails.length > 0) {
|
|
3725
|
+
for (const msg of agent.messageDetails) {
|
|
3726
|
+
const dateStr = msg.date ? msg.date.toISOString().slice(0, 16).replace("T", " ") : "";
|
|
3727
|
+
lines.push(` - [${msg.type}] from ${msg.from}: ${msg.subject}`);
|
|
3728
|
+
if (dateStr) {
|
|
3729
|
+
lines.push(` ${dateStr}`);
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
}
|
|
3734
|
+
if (agent.lastActivity) {
|
|
3735
|
+
const activityStr = agent.lastActivity.toISOString().slice(0, 16).replace("T", " ");
|
|
3736
|
+
lines.push(` Last activity: ${activityStr}`);
|
|
3737
|
+
}
|
|
3738
|
+
lines.push("");
|
|
3739
|
+
}
|
|
3740
|
+
return lines.join("\n");
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3743
|
+
// src/cli/index.ts
|
|
3744
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
3745
|
+
var __dirname = dirname4(__filename);
|
|
3746
|
+
function getCoreAgentsPath() {
|
|
3747
|
+
return join11(__dirname, "..", "..", "agents");
|
|
3748
|
+
}
|
|
3749
|
+
var program = new Command();
|
|
3750
|
+
program.name("coreai").description("A configurable, team-ready AI agent orchestration platform").version(VERSION, "-v, --version", "output the current version");
|
|
3751
|
+
program.command("init").description("Initialize a new CoreAI project").option("-f, --force", "overwrite existing configuration").option("-n, --name <name>", "project name").option("-t, --type <type>", "project type (software, infrastructure, data, mobile)").option("--skip-dirs", "skip creating directory structure").action((options) => {
|
|
3752
|
+
const result = init({
|
|
3753
|
+
projectRoot: process.cwd(),
|
|
3754
|
+
force: options.force,
|
|
3755
|
+
name: options.name,
|
|
3756
|
+
type: options.type,
|
|
3757
|
+
skipDirs: options.skipDirs
|
|
3758
|
+
});
|
|
3759
|
+
console.log(formatInitResult(result));
|
|
3760
|
+
if (!result.success) {
|
|
3761
|
+
process.exit(1);
|
|
3762
|
+
}
|
|
3763
|
+
});
|
|
3764
|
+
program.command("build").description("Compile agent definitions to Claude-compatible markdown").option("-w, --watch", "watch for changes and rebuild").option("-o, --output <dir>", "output directory (default: .claude/agents)").option("--agents <agents>", "comma-separated list of agents to build").option("--init-knowledge-library", "initialize KnowledgeLibrary directories for agents").action(
|
|
3765
|
+
(options) => {
|
|
3766
|
+
if (options.watch) {
|
|
3767
|
+
console.log("Watch mode is not yet implemented");
|
|
3768
|
+
process.exit(1);
|
|
3769
|
+
}
|
|
3770
|
+
console.log("Building agents...\n");
|
|
3771
|
+
const result = build({
|
|
3772
|
+
projectRoot: process.cwd(),
|
|
3773
|
+
coreAgentsDir: getCoreAgentsPath(),
|
|
3774
|
+
outputDir: options.output,
|
|
3775
|
+
agents: options.agents?.split(",").map((a) => a.trim()),
|
|
3776
|
+
initKnowledgeLibrary: options.initKnowledgeLibrary
|
|
3777
|
+
});
|
|
3778
|
+
console.log(formatBuildResult(result));
|
|
3779
|
+
if (!result.success) {
|
|
3780
|
+
process.exit(1);
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
);
|
|
3784
|
+
program.command("sync").description("Sync shared context from remote sources").option("--force", "force refresh all cached content").option("--source <source>", "filter by source (github, confluence, notion)").option("--concurrency <n>", "max concurrent fetches", "5").action(async (options) => {
|
|
3785
|
+
try {
|
|
3786
|
+
console.log("Syncing shared context...\n");
|
|
3787
|
+
const result = await sync({
|
|
3788
|
+
projectRoot: process.cwd(),
|
|
3789
|
+
force: options.force,
|
|
3790
|
+
source: options.source,
|
|
3791
|
+
concurrency: parseInt(options.concurrency ?? "5", 10),
|
|
3792
|
+
continueOnError: true,
|
|
3793
|
+
onProgress: (completed, total, message) => {
|
|
3794
|
+
process.stdout.write(`\r${message}`);
|
|
3795
|
+
}
|
|
3796
|
+
});
|
|
3797
|
+
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
3798
|
+
console.log(formatSyncResult(result));
|
|
3799
|
+
if (!result.success) {
|
|
3800
|
+
process.exit(1);
|
|
3801
|
+
}
|
|
3802
|
+
} catch (error) {
|
|
3803
|
+
console.error(`Sync failed: ${error instanceof Error ? error.message : error}`);
|
|
3804
|
+
process.exit(1);
|
|
3805
|
+
}
|
|
3806
|
+
});
|
|
3807
|
+
program.command("validate").description("Validate configuration and project setup").option("--skip-agents", "skip agent validation").option("--skip-dirs", "skip directory validation").option("--json", "output as JSON").action((options) => {
|
|
3808
|
+
const result = validate({
|
|
3809
|
+
projectRoot: process.cwd(),
|
|
3810
|
+
coreAgentsDir: getCoreAgentsPath(),
|
|
3811
|
+
checkAgents: !options.skipAgents,
|
|
3812
|
+
checkDirs: !options.skipDirs
|
|
3813
|
+
});
|
|
3814
|
+
if (options.json) {
|
|
3815
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3816
|
+
} else {
|
|
3817
|
+
console.log(formatValidateResult(result));
|
|
3818
|
+
}
|
|
3819
|
+
if (!result.valid) {
|
|
3820
|
+
process.exit(1);
|
|
3821
|
+
}
|
|
3822
|
+
});
|
|
3823
|
+
var agents = program.command("agents").description("Manage agent definitions");
|
|
3824
|
+
agents.command("list").description("List available agents").option("--core", "show only core agents").option("--custom", "show only custom agents").action((options) => {
|
|
3825
|
+
try {
|
|
3826
|
+
const customAgentsDir = join11(process.cwd(), "coreai", "agents");
|
|
3827
|
+
const allAgents = loadAllAgents({
|
|
3828
|
+
coreAgentsDir: getCoreAgentsPath(),
|
|
3829
|
+
customAgentsDir
|
|
3830
|
+
});
|
|
3831
|
+
if (allAgents.size === 0) {
|
|
3832
|
+
console.log("No agents found.");
|
|
3833
|
+
return;
|
|
3834
|
+
}
|
|
3835
|
+
const agents2 = Array.from(allAgents.entries()).filter(([, meta]) => {
|
|
3836
|
+
if (options.core && meta.source !== "core") return false;
|
|
3837
|
+
if (options.custom && meta.source !== "custom" && meta.source !== "override") return false;
|
|
3838
|
+
return true;
|
|
3839
|
+
});
|
|
3840
|
+
if (agents2.length === 0) {
|
|
3841
|
+
console.log("No matching agents found.");
|
|
3842
|
+
return;
|
|
3843
|
+
}
|
|
3844
|
+
console.log("Available agents:\n");
|
|
3845
|
+
const coreAgents = agents2.filter(([, m]) => m.source === "core");
|
|
3846
|
+
const customAgents = agents2.filter(([, m]) => m.source === "custom");
|
|
3847
|
+
const overrideAgents = agents2.filter(([, m]) => m.source === "override");
|
|
3848
|
+
if (coreAgents.length > 0 && !options.custom) {
|
|
3849
|
+
console.log("Core agents:");
|
|
3850
|
+
for (const [role, meta] of coreAgents) {
|
|
3851
|
+
console.log(` ${role} (${meta.definition.type})`);
|
|
3852
|
+
console.log(` ${meta.definition.display_name}`);
|
|
3853
|
+
}
|
|
3854
|
+
console.log();
|
|
3855
|
+
}
|
|
3856
|
+
if (customAgents.length > 0 && !options.core) {
|
|
3857
|
+
console.log("Custom agents:");
|
|
3858
|
+
for (const [role, meta] of customAgents) {
|
|
3859
|
+
console.log(` ${role} (${meta.definition.type})`);
|
|
3860
|
+
console.log(` ${meta.definition.display_name}`);
|
|
3861
|
+
}
|
|
3862
|
+
console.log();
|
|
3863
|
+
}
|
|
3864
|
+
if (overrideAgents.length > 0 && !options.core) {
|
|
3865
|
+
console.log("Override agents:");
|
|
3866
|
+
for (const [role, meta] of overrideAgents) {
|
|
3867
|
+
console.log(` ${role} (${meta.definition.type})`);
|
|
3868
|
+
console.log(` ${meta.definition.display_name} [overrides core]`);
|
|
3869
|
+
}
|
|
3870
|
+
console.log();
|
|
3871
|
+
}
|
|
3872
|
+
console.log(`Total: ${agents2.length} agent(s)`);
|
|
3873
|
+
} catch (error) {
|
|
3874
|
+
console.error(`Failed to list agents: ${error instanceof Error ? error.message : error}`);
|
|
3875
|
+
process.exit(1);
|
|
3876
|
+
}
|
|
3877
|
+
});
|
|
3878
|
+
agents.command("show <name>").description("Show details for a specific agent").option("--markdown", "output as compiled markdown").option("--json", "output as JSON").action((name, options) => {
|
|
3879
|
+
try {
|
|
3880
|
+
const customAgentsDir = join11(process.cwd(), "coreai", "agents");
|
|
3881
|
+
const allAgents = loadAllAgents({
|
|
3882
|
+
coreAgentsDir: getCoreAgentsPath(),
|
|
3883
|
+
customAgentsDir
|
|
3884
|
+
});
|
|
3885
|
+
const agentMeta = allAgents.get(name);
|
|
3886
|
+
if (!agentMeta) {
|
|
3887
|
+
console.error(`Agent not found: ${name}`);
|
|
3888
|
+
console.log("\nAvailable agents:");
|
|
3889
|
+
for (const role of allAgents.keys()) {
|
|
3890
|
+
console.log(` - ${role}`);
|
|
3891
|
+
}
|
|
3892
|
+
process.exit(1);
|
|
3893
|
+
}
|
|
3894
|
+
const agent = agentMeta.definition;
|
|
3895
|
+
if (options.json) {
|
|
3896
|
+
console.log(JSON.stringify(agent, null, 2));
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
if (options.markdown) {
|
|
3900
|
+
console.log(generateAgentMarkdown(agent));
|
|
3901
|
+
return;
|
|
3902
|
+
}
|
|
3903
|
+
console.log(`
|
|
3904
|
+
${agent.display_name}`);
|
|
3905
|
+
console.log("=".repeat(agent.display_name.length));
|
|
3906
|
+
console.log();
|
|
3907
|
+
console.log(`Role: ${agent.role}`);
|
|
3908
|
+
console.log(`Type: ${agent.type}`);
|
|
3909
|
+
console.log(`Source: ${agentMeta.source}`);
|
|
3910
|
+
console.log();
|
|
3911
|
+
console.log("Description:");
|
|
3912
|
+
console.log(` ${agent.description}`);
|
|
3913
|
+
if (agent.responsibilities && agent.responsibilities.length > 0) {
|
|
3914
|
+
console.log();
|
|
3915
|
+
console.log("Responsibilities:");
|
|
3916
|
+
for (const r of agent.responsibilities) {
|
|
3917
|
+
console.log(` - ${r}`);
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
if (agent.expertise?.primary && agent.expertise.primary.length > 0) {
|
|
3921
|
+
console.log();
|
|
3922
|
+
console.log("Expertise:");
|
|
3923
|
+
for (const e of agent.expertise.primary) {
|
|
3924
|
+
console.log(` - ${e}`);
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
if (agent.skills && agent.skills.length > 0) {
|
|
3928
|
+
console.log();
|
|
3929
|
+
console.log("Skills:");
|
|
3930
|
+
for (const s of agent.skills) {
|
|
3931
|
+
console.log(` - ${s}`);
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
if (agent.behaviors?.workflow) {
|
|
3935
|
+
console.log();
|
|
3936
|
+
console.log(`Workflow: ${agent.behaviors.workflow}`);
|
|
3937
|
+
}
|
|
3938
|
+
console.log();
|
|
3939
|
+
console.log(`File: ${agentMeta.filePath}`);
|
|
3940
|
+
} catch (error) {
|
|
3941
|
+
console.error(`Failed to show agent: ${error instanceof Error ? error.message : error}`);
|
|
3942
|
+
process.exit(1);
|
|
3943
|
+
}
|
|
3944
|
+
});
|
|
3945
|
+
var cacheCmd = program.command("cache").description("Manage local cache");
|
|
3946
|
+
cacheCmd.command("clear").description("Clear the local cache").option("--expired", "only clear expired entries").option("--json", "output as JSON").action(async (options) => {
|
|
3947
|
+
try {
|
|
3948
|
+
const result = options.expired ? await cacheClearExpired({ projectRoot: process.cwd() }) : await cacheClear({ projectRoot: process.cwd() });
|
|
3949
|
+
if (options.json) {
|
|
3950
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3951
|
+
return;
|
|
3952
|
+
}
|
|
3953
|
+
if (!result.success) {
|
|
3954
|
+
console.error(`Failed to clear cache: ${result.error}`);
|
|
3955
|
+
process.exit(1);
|
|
3956
|
+
}
|
|
3957
|
+
if (result.cleared === 0) {
|
|
3958
|
+
console.log("Cache is already empty.");
|
|
3959
|
+
} else {
|
|
3960
|
+
const word = result.cleared === 1 ? "entry" : "entries";
|
|
3961
|
+
console.log(`Cleared ${result.cleared} cache ${word}.`);
|
|
3962
|
+
}
|
|
3963
|
+
} catch (error) {
|
|
3964
|
+
const msg = error instanceof Error ? error.message : error;
|
|
3965
|
+
console.error(`Failed to clear cache: ${msg}`);
|
|
3966
|
+
process.exit(1);
|
|
3967
|
+
}
|
|
3968
|
+
});
|
|
3969
|
+
cacheCmd.command("status").description("Show cache status").option("--json", "output as JSON").action(async (options) => {
|
|
3970
|
+
try {
|
|
3971
|
+
const result = await cacheStatus({ projectRoot: process.cwd() });
|
|
3972
|
+
if (options.json) {
|
|
3973
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3974
|
+
return;
|
|
3975
|
+
}
|
|
3976
|
+
console.log(formatCacheStatus(result));
|
|
3977
|
+
} catch (error) {
|
|
3978
|
+
const msg = error instanceof Error ? error.message : error;
|
|
3979
|
+
console.error(`Failed to get cache status: ${msg}`);
|
|
3980
|
+
process.exit(1);
|
|
3981
|
+
}
|
|
3982
|
+
});
|
|
3983
|
+
program.command("status").description("Show agent states and pending messages").option("-a, --agent <name>", "show status for a specific agent").option("-d, --detailed", "show detailed message information").option("--init", "initialize KnowledgeLibrary if not exists").option("--json", "output as JSON").action((options) => {
|
|
3984
|
+
const result = status({
|
|
3985
|
+
projectRoot: process.cwd(),
|
|
3986
|
+
agent: options.agent,
|
|
3987
|
+
detailed: options.detailed,
|
|
3988
|
+
init: options.init
|
|
3989
|
+
});
|
|
3990
|
+
if (options.json) {
|
|
3991
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3992
|
+
return;
|
|
3993
|
+
}
|
|
3994
|
+
console.log(formatStatusResult(result));
|
|
3995
|
+
if (!result.success) {
|
|
3996
|
+
process.exit(1);
|
|
3997
|
+
}
|
|
3998
|
+
});
|
|
3999
|
+
var skillsCmd = program.command("skills").description("Manage Claude skills");
|
|
4000
|
+
skillsCmd.command("generate").description("Generate Claude skills from templates").option("-o, --output <dir>", "output directory (default: .claude/commands)").option("--skills <skills>", "comma-separated list of skills to generate").option("--no-core", "exclude core skills").option("--no-optional", "exclude optional skills").option("--overwrite", "overwrite existing skill files").option("--json", "output as JSON").action(
|
|
4001
|
+
(options) => {
|
|
4002
|
+
console.log("Generating skills...\n");
|
|
4003
|
+
const result = skillsGenerate({
|
|
4004
|
+
projectRoot: process.cwd(),
|
|
4005
|
+
outputDir: options.output,
|
|
4006
|
+
skills: options.skills?.split(",").map((s) => s.trim()),
|
|
4007
|
+
includeCoreSkills: options.core !== false,
|
|
4008
|
+
includeOptionalSkills: options.optional !== false,
|
|
4009
|
+
overwrite: options.overwrite
|
|
4010
|
+
});
|
|
4011
|
+
if (options.json) {
|
|
4012
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4013
|
+
return;
|
|
4014
|
+
}
|
|
4015
|
+
console.log(formatSkillsGenerateResult(result));
|
|
4016
|
+
if (!result.success) {
|
|
4017
|
+
process.exit(1);
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
);
|
|
4021
|
+
skillsCmd.command("list").description("List available skills").option("--no-core", "exclude core skills").option("--no-optional", "exclude optional skills").option("--json", "output as JSON").action((options) => {
|
|
4022
|
+
const result = skillsList({
|
|
4023
|
+
projectRoot: process.cwd(),
|
|
4024
|
+
includeCoreSkills: options.core !== false,
|
|
4025
|
+
includeOptionalSkills: options.optional !== false
|
|
4026
|
+
});
|
|
4027
|
+
if (options.json) {
|
|
4028
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4029
|
+
return;
|
|
4030
|
+
}
|
|
4031
|
+
console.log(formatSkillsListResult(result));
|
|
4032
|
+
if (!result.success) {
|
|
4033
|
+
process.exit(1);
|
|
4034
|
+
}
|
|
4035
|
+
});
|
|
4036
|
+
program.parse();
|
|
4037
|
+
//# sourceMappingURL=index.js.map
|