@kuznai/inception-engine 0.15.0 → 0.17.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/README.md +127 -3
- package/dist/config/agents.js +97 -8
- package/dist/config/manifest.js +2 -0
- package/dist/core/adapters/agent-definitions.d.ts +20 -0
- package/dist/core/adapters/agent-definitions.js +90 -0
- package/dist/core/adapters/index.d.ts +5 -3
- package/dist/core/adapters/index.js +14 -2
- package/dist/core/adapters/mcp.js +10 -1
- package/dist/core/adapters/permissions.d.ts +8 -0
- package/dist/core/adapters/permissions.js +90 -0
- package/dist/core/adapters/rules.js +10 -1
- package/dist/core/deploy.js +5 -3
- package/dist/core/init.js +282 -20
- package/dist/core/resolve.js +4 -0
- package/dist/core/revert.js +7 -1
- package/dist/core/validation.d.ts +1 -0
- package/dist/core/validation.js +45 -0
- package/dist/schemas/manifest.d.ts +50 -0
- package/dist/schemas/manifest.js +18 -0
- package/dist/types.d.ts +28 -4
- package/package.json +1 -1
|
@@ -21,6 +21,13 @@ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDi
|
|
|
21
21
|
});
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
24
|
+
if (support.status === "planned") {
|
|
25
|
+
warnings.push({
|
|
26
|
+
kind: "confidence",
|
|
27
|
+
message: `agentRules: agent "${agentId}" rules support is planned via ${support.plannedSurface} — skipping "${entry.name}" until that surface is implemented`,
|
|
28
|
+
});
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
24
31
|
supportedTargets.push({
|
|
25
32
|
agentId,
|
|
26
33
|
confidence: agent.provenance.agentRules ?? "provisional",
|
|
@@ -56,7 +63,9 @@ export function compileAgentRuleReverts(entry, agentFilter, home, repo) {
|
|
|
56
63
|
continue;
|
|
57
64
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
58
65
|
const support = agent?.agentRulesSupport;
|
|
59
|
-
if (!support ||
|
|
66
|
+
if (!support ||
|
|
67
|
+
support.status === "unsupported" ||
|
|
68
|
+
support.status === "planned")
|
|
60
69
|
continue;
|
|
61
70
|
const target = resolvePlaceholders(support.path[platform], entry.name, home, repo);
|
|
62
71
|
actions.push({
|
package/dist/core/deploy.js
CHANGED
|
@@ -91,7 +91,7 @@ function detectAmbiguities(detectedAgents, manifest) {
|
|
|
91
91
|
if (bothAgents(entry.agents)) {
|
|
92
92
|
warnings.push({
|
|
93
93
|
kind: "ambiguity",
|
|
94
|
-
message: `Both "gemini-cli" and "antigravity" are listed in agentRules entry "${entry.name}". They
|
|
94
|
+
message: `Both "gemini-cli" and "antigravity" are listed in agentRules entry "${entry.name}". They write to distinct surfaces ("gemini-cli" → ~/.gemini/GEMINI.md, "antigravity" → {repo}/.agents/rules/${entry.name}.md) from the same source file — verify that deploying to both produces the intended behavior on each agent.`,
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -119,6 +119,8 @@ async function planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realR
|
|
|
119
119
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
120
120
|
if (!agent)
|
|
121
121
|
continue;
|
|
122
|
+
if (!agent.skills)
|
|
123
|
+
continue;
|
|
122
124
|
actions.push({
|
|
123
125
|
kind: "skill-dir",
|
|
124
126
|
skill: skill.name,
|
|
@@ -126,7 +128,7 @@ async function planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realR
|
|
|
126
128
|
source,
|
|
127
129
|
target: resolveAgentSkillPath(agent, skill.name, home),
|
|
128
130
|
method,
|
|
129
|
-
confidence: agent.provenance.skills,
|
|
131
|
+
confidence: agent.provenance.skills ?? "provisional",
|
|
130
132
|
});
|
|
131
133
|
}
|
|
132
134
|
}
|
|
@@ -192,7 +194,7 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
|
192
194
|
...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
193
195
|
...planConfigPatchActions(manifest, detectedAgents, home),
|
|
194
196
|
];
|
|
195
|
-
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home);
|
|
197
|
+
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, undefined, manifest.agentDefinitions ?? []);
|
|
196
198
|
actions.push(...adapterResult.actions);
|
|
197
199
|
const warnings = [
|
|
198
200
|
...detectAmbiguities(detectedAgents, manifest),
|
package/dist/core/init.js
CHANGED
|
@@ -2,7 +2,7 @@ import { access, readFile, readdir, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
4
4
|
import { dryRunPrefix, logger } from "../logger.js";
|
|
5
|
-
import { AGENT_IDS, McpServerEntrySchema } from "../schemas/manifest.js";
|
|
5
|
+
import { AGENT_IDS, AgentDefinitionEntrySchema, ConfigEntrySchema, FileEntrySchema, McpServerEntrySchema, } from "../schemas/manifest.js";
|
|
6
6
|
const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
7
7
|
// Ordered list: first match wins. Catch-all is applied at call site.
|
|
8
8
|
const AGENT_RULES_FILE_PATTERNS = [
|
|
@@ -27,6 +27,16 @@ const AGENT_RULES_SUBDIRS = [
|
|
|
27
27
|
".github",
|
|
28
28
|
".agents/rules",
|
|
29
29
|
];
|
|
30
|
+
// Conventional subdirectories that contain agent definition files.
|
|
31
|
+
// These are the agent-specific directories that each agent scans for
|
|
32
|
+
// subagent/persona definitions at runtime.
|
|
33
|
+
const AGENT_DEFINITION_SUBDIRS = [
|
|
34
|
+
".claude/agents",
|
|
35
|
+
".gemini/agents",
|
|
36
|
+
".agents/rules",
|
|
37
|
+
".opencode/agents",
|
|
38
|
+
".github/agents",
|
|
39
|
+
];
|
|
30
40
|
async function findSkillDirs(baseDir, dir, found) {
|
|
31
41
|
let entries;
|
|
32
42
|
try {
|
|
@@ -164,6 +174,117 @@ function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
|
|
|
164
174
|
}
|
|
165
175
|
return rules;
|
|
166
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Derives the name for an agent definition entry from its file name.
|
|
179
|
+
* For GitHub Copilot's `{name}.agent.md` naming convention, strips the
|
|
180
|
+
* `.agent` infix in addition to the `.md` extension.
|
|
181
|
+
*/
|
|
182
|
+
function deriveAgentDefinitionName(relPath, fileName) {
|
|
183
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
184
|
+
// Strip the extension to get the base name, then strip any trailing ".agent"
|
|
185
|
+
// suffix (GitHub Copilot convention: foo.agent.md → foo).
|
|
186
|
+
let baseName = path.basename(fileName, ext);
|
|
187
|
+
if (baseName.endsWith(".agent")) {
|
|
188
|
+
baseName = baseName.slice(0, -".agent".length);
|
|
189
|
+
}
|
|
190
|
+
const rawName = baseName.toLowerCase().replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
191
|
+
if (!SAFE_NAME_RE.test(rawName)) {
|
|
192
|
+
logger.warn("init", `Skipping "${relPath}": could not derive a valid agentDefinitions name`);
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
return rawName;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Maps a known agent-definition subdirectory to the agent IDs that own it.
|
|
199
|
+
* Returns null when the subdir is not agent-specific (fall back to all agents
|
|
200
|
+
* that support agentDefinitions).
|
|
201
|
+
*/
|
|
202
|
+
function agentsForDefinitionSubdir(subdir) {
|
|
203
|
+
switch (subdir) {
|
|
204
|
+
case ".claude/agents":
|
|
205
|
+
return ["claude-code"];
|
|
206
|
+
case ".gemini/agents":
|
|
207
|
+
return ["gemini-cli"];
|
|
208
|
+
case ".agents/rules":
|
|
209
|
+
return ["antigravity"];
|
|
210
|
+
case ".opencode/agents":
|
|
211
|
+
return ["opencode"];
|
|
212
|
+
case ".github/agents":
|
|
213
|
+
return ["github-copilot"];
|
|
214
|
+
default:
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function scanDefinitionSubdir(subdir, baseDir, seen, skillDirRelPaths, agentRulesRelPaths, candidates) {
|
|
219
|
+
const suggestedAgents = agentsForDefinitionSubdir(subdir) ?? [];
|
|
220
|
+
const dir = path.join(baseDir, subdir);
|
|
221
|
+
let entries;
|
|
222
|
+
try {
|
|
223
|
+
entries = await readdir(dir, { withFileTypes: true, encoding: "utf-8" });
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
for (const entry of entries) {
|
|
229
|
+
if (!entry.isFile())
|
|
230
|
+
continue;
|
|
231
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
232
|
+
if (ext !== ".md" && ext !== ".markdown")
|
|
233
|
+
continue;
|
|
234
|
+
const absPath = path.join(dir, entry.name);
|
|
235
|
+
const relPath = path.relative(baseDir, absPath).split(path.sep).join("/");
|
|
236
|
+
if (seen.has(relPath) ||
|
|
237
|
+
isInsideSkillDir(relPath, skillDirRelPaths) ||
|
|
238
|
+
agentRulesRelPaths.has(relPath))
|
|
239
|
+
continue;
|
|
240
|
+
const name = deriveAgentDefinitionName(relPath, entry.name);
|
|
241
|
+
if (name === null)
|
|
242
|
+
continue;
|
|
243
|
+
seen.add(relPath);
|
|
244
|
+
candidates.push({ relPath, name, suggestedAgents });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function findAgentDefinitionCandidates(baseDir, skillDirRelPaths, agentRulesRelPaths) {
|
|
248
|
+
const candidates = [];
|
|
249
|
+
const seen = new Set();
|
|
250
|
+
for (const subdir of AGENT_DEFINITION_SUBDIRS) {
|
|
251
|
+
await scanDefinitionSubdir(subdir, baseDir, seen, skillDirRelPaths, agentRulesRelPaths, candidates);
|
|
252
|
+
}
|
|
253
|
+
return candidates;
|
|
254
|
+
}
|
|
255
|
+
function buildAgentDefinitions(candidates, activeAgents, namesSeen) {
|
|
256
|
+
const definitions = [];
|
|
257
|
+
const localNamesSeen = new Set(namesSeen);
|
|
258
|
+
const definitionsCapableAgents = activeAgents.filter((id) => AGENT_REGISTRY_BY_ID[id].agentDefinitionsSupport?.status !==
|
|
259
|
+
"unsupported");
|
|
260
|
+
for (const { relPath, name: rawName, suggestedAgents } of candidates) {
|
|
261
|
+
// Use suggested agents (from the dir that was scanned), intersected with
|
|
262
|
+
// active agents that support agentDefinitions. Fall back to all capable
|
|
263
|
+
// agents if the intersection is empty.
|
|
264
|
+
const intersection = suggestedAgents.length > 0
|
|
265
|
+
? suggestedAgents.filter((a) => definitionsCapableAgents.includes(a))
|
|
266
|
+
: [];
|
|
267
|
+
const agents = intersection.length > 0 ? intersection : definitionsCapableAgents;
|
|
268
|
+
if (agents.length === 0)
|
|
269
|
+
continue;
|
|
270
|
+
// Resolve name collision
|
|
271
|
+
let name = rawName;
|
|
272
|
+
if (localNamesSeen.has(name)) {
|
|
273
|
+
const candidate = `${name}-agent`;
|
|
274
|
+
if (SAFE_NAME_RE.test(candidate)) {
|
|
275
|
+
logger.warn("init", `agentDefinitions name "${name}" collides with an existing name; using "${candidate}"`);
|
|
276
|
+
name = candidate;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
logger.warn("init", `Skipping "${relPath}": name "${name}" collides and fallback is invalid`);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
localNamesSeen.add(name);
|
|
284
|
+
definitions.push({ name, path: relPath, agents });
|
|
285
|
+
}
|
|
286
|
+
return definitions;
|
|
287
|
+
}
|
|
167
288
|
async function loadMcpServers(baseDir) {
|
|
168
289
|
const filePath = path.join(baseDir, "mcp-servers.json");
|
|
169
290
|
let raw;
|
|
@@ -197,14 +318,115 @@ async function loadMcpServers(baseDir) {
|
|
|
197
318
|
}
|
|
198
319
|
return results;
|
|
199
320
|
}
|
|
200
|
-
async function
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
321
|
+
async function loadFilesManifest(baseDir) {
|
|
322
|
+
const filePath = path.join(baseDir, "files-manifest.json");
|
|
323
|
+
let raw;
|
|
324
|
+
try {
|
|
325
|
+
raw = await readFile(filePath, "utf-8");
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
let parsed;
|
|
331
|
+
try {
|
|
332
|
+
parsed = JSON.parse(raw);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
logger.warn("init", "files-manifest.json: invalid JSON, skipping");
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
if (!Array.isArray(parsed)) {
|
|
339
|
+
logger.warn("init", "files-manifest.json: expected a JSON array, skipping");
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
const results = [];
|
|
343
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
344
|
+
const result = FileEntrySchema.safeParse(parsed[i]);
|
|
345
|
+
if (result.success) {
|
|
346
|
+
results.push(result.data);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
logger.warn("init", `files-manifest.json: entry[${i}] invalid, skipping`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return results;
|
|
353
|
+
}
|
|
354
|
+
async function loadConfigsManifest(baseDir) {
|
|
355
|
+
const filePath = path.join(baseDir, "configs-manifest.json");
|
|
356
|
+
let raw;
|
|
357
|
+
try {
|
|
358
|
+
raw = await readFile(filePath, "utf-8");
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
let parsed;
|
|
364
|
+
try {
|
|
365
|
+
parsed = JSON.parse(raw);
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
logger.warn("init", "configs-manifest.json: invalid JSON, skipping");
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
if (!Array.isArray(parsed)) {
|
|
372
|
+
logger.warn("init", "configs-manifest.json: expected a JSON array, skipping");
|
|
373
|
+
return [];
|
|
374
|
+
}
|
|
375
|
+
const results = [];
|
|
376
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
377
|
+
const result = ConfigEntrySchema.safeParse(parsed[i]);
|
|
378
|
+
if (result.success) {
|
|
379
|
+
results.push(result.data);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
logger.warn("init", `configs-manifest.json: entry[${i}] invalid, skipping`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return results;
|
|
386
|
+
}
|
|
387
|
+
async function loadAgentDefinitionsManifest(baseDir) {
|
|
388
|
+
const filePath = path.join(baseDir, "agent-definitions-manifest.json");
|
|
389
|
+
let raw;
|
|
390
|
+
try {
|
|
391
|
+
raw = await readFile(filePath, "utf-8");
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
let parsed;
|
|
397
|
+
try {
|
|
398
|
+
parsed = JSON.parse(raw);
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
logger.warn("init", "agent-definitions-manifest.json: invalid JSON, skipping");
|
|
402
|
+
return [];
|
|
403
|
+
}
|
|
404
|
+
if (!Array.isArray(parsed)) {
|
|
405
|
+
logger.warn("init", "agent-definitions-manifest.json: expected a JSON array, skipping");
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
const results = [];
|
|
409
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
410
|
+
const result = AgentDefinitionEntrySchema.safeParse(parsed[i]);
|
|
411
|
+
if (result.success) {
|
|
412
|
+
results.push(result.data);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
logger.warn("init", `agent-definitions-manifest.json: entry[${i}] invalid, skipping`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return results;
|
|
419
|
+
}
|
|
420
|
+
async function emitDirectoryHints(baseDir, filesLoaded, configsLoaded) {
|
|
421
|
+
for (const [dir, section, loaded, sidecar] of [
|
|
422
|
+
["files", "files", filesLoaded, "files-manifest.json"],
|
|
423
|
+
["configs", "configs", configsLoaded, "configs-manifest.json"],
|
|
204
424
|
]) {
|
|
205
425
|
try {
|
|
206
426
|
await access(path.join(baseDir, dir));
|
|
207
|
-
|
|
427
|
+
if (loaded === 0) {
|
|
428
|
+
logger.info(`Detected ${dir}/ directory — create ${sidecar} at the repo root to have init populate ${section} entries automatically.`);
|
|
429
|
+
}
|
|
208
430
|
}
|
|
209
431
|
catch {
|
|
210
432
|
// directory does not exist — silent
|
|
@@ -220,22 +442,38 @@ async function manifestExists(manifestPath) {
|
|
|
220
442
|
return false;
|
|
221
443
|
}
|
|
222
444
|
}
|
|
223
|
-
function
|
|
445
|
+
function logPathAgentEntries(label, entries) {
|
|
446
|
+
if (entries.length === 0)
|
|
447
|
+
return;
|
|
448
|
+
logger.detail(`${label}:`);
|
|
449
|
+
for (const e of entries) {
|
|
450
|
+
logger.detail(` ${e.name} → ${e.path} [${e.agents.join(", ")}]`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function logVerboseManifest(skills, agentRules, mcpServers, files, configs, agentDefinitions) {
|
|
224
454
|
for (const s of skills) {
|
|
225
455
|
logger.detail(`${s.name} → ${s.path}`);
|
|
226
456
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
for (const r of agentRules) {
|
|
230
|
-
logger.detail(` ${r.name} → ${r.path} [${r.agents.join(", ")}]`);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
457
|
+
logPathAgentEntries("agentRules", agentRules);
|
|
458
|
+
logPathAgentEntries("agentDefinitions", agentDefinitions);
|
|
233
459
|
if (mcpServers.length > 0) {
|
|
234
460
|
logger.detail("mcpServers:");
|
|
235
461
|
for (const m of mcpServers) {
|
|
236
462
|
logger.detail(` ${m.name} [${m.agents.join(", ")}]`);
|
|
237
463
|
}
|
|
238
464
|
}
|
|
465
|
+
if (files.length > 0) {
|
|
466
|
+
logger.detail("files:");
|
|
467
|
+
for (const f of files) {
|
|
468
|
+
logger.detail(` ${f.name} → ${f.target} [${f.agents.join(", ")}]`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (configs.length > 0) {
|
|
472
|
+
logger.detail("configs:");
|
|
473
|
+
for (const c of configs) {
|
|
474
|
+
logger.detail(` ${c.name} → ${c.target} [${c.agents.join(", ")}]`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
239
477
|
}
|
|
240
478
|
export async function runInit(options) {
|
|
241
479
|
const { directory, dryRun, force, verbose } = options;
|
|
@@ -256,27 +494,51 @@ export async function runInit(options) {
|
|
|
256
494
|
const skillDirRelPaths = new Set(found.map((f) => f.relPath));
|
|
257
495
|
const agentRulesCandidates = await findAgentRulesCandidates(directory, skillDirRelPaths);
|
|
258
496
|
const agentRules = buildAgentRules(agentRulesCandidates, agentRulesCapableAgents, skillNamesSeen);
|
|
497
|
+
const allNamesSeen = new Set([
|
|
498
|
+
...skillNamesSeen,
|
|
499
|
+
...agentRules.map((r) => r.name),
|
|
500
|
+
]);
|
|
501
|
+
const agentRulesRelPaths = new Set(agentRules.map((r) => r.path));
|
|
502
|
+
const agentDefinitionCandidates = await findAgentDefinitionCandidates(directory, skillDirRelPaths, agentRulesRelPaths);
|
|
503
|
+
const discoveredDefinitions = buildAgentDefinitions(agentDefinitionCandidates, agents, allNamesSeen);
|
|
504
|
+
// Sidecar file overrides take precedence over auto-discovery
|
|
505
|
+
const sidecarDefinitions = await loadAgentDefinitionsManifest(directory);
|
|
506
|
+
const agentDefinitions = sidecarDefinitions.length > 0 ? sidecarDefinitions : discoveredDefinitions;
|
|
259
507
|
const mcpServers = await loadMcpServers(directory);
|
|
508
|
+
const files = await loadFilesManifest(directory);
|
|
509
|
+
const configs = await loadConfigsManifest(directory);
|
|
260
510
|
const manifest = {
|
|
261
511
|
skills,
|
|
262
|
-
files
|
|
263
|
-
configs
|
|
512
|
+
files,
|
|
513
|
+
configs,
|
|
264
514
|
mcpServers,
|
|
265
515
|
agentRules,
|
|
516
|
+
agentDefinitions,
|
|
266
517
|
};
|
|
267
518
|
const json = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
519
|
+
function summarize() {
|
|
520
|
+
const parts = [
|
|
521
|
+
`${skills.length} skill(s)`,
|
|
522
|
+
`${agentRules.length} agentRule(s)`,
|
|
523
|
+
`${agentDefinitions.length} agentDefinition(s)`,
|
|
524
|
+
`${mcpServers.length} mcpServer(s)`,
|
|
525
|
+
`${files.length} file(s)`,
|
|
526
|
+
`${configs.length} config(s)`,
|
|
527
|
+
];
|
|
528
|
+
return parts.join(", ");
|
|
529
|
+
}
|
|
268
530
|
if (dryRun) {
|
|
269
|
-
logger.info(`${dryRunPrefix(true)}Would write ${manifestPath} with ${
|
|
531
|
+
logger.info(`${dryRunPrefix(true)}Would write ${manifestPath} with ${summarize()}:`);
|
|
270
532
|
logger.info("");
|
|
271
533
|
logger.info(json);
|
|
272
|
-
await emitDirectoryHints(directory);
|
|
534
|
+
await emitDirectoryHints(directory, files.length, configs.length);
|
|
273
535
|
return 0;
|
|
274
536
|
}
|
|
275
537
|
await writeFile(manifestPath, json, "utf-8");
|
|
276
|
-
logger.info(`Generated ${manifestPath} with ${
|
|
538
|
+
logger.info(`Generated ${manifestPath} with ${summarize()}.`);
|
|
277
539
|
if (verbose) {
|
|
278
|
-
logVerboseManifest(skills, agentRules, mcpServers);
|
|
540
|
+
logVerboseManifest(skills, agentRules, mcpServers, files, configs, agentDefinitions);
|
|
279
541
|
}
|
|
280
|
-
await emitDirectoryHints(directory);
|
|
542
|
+
await emitDirectoryHints(directory, files.length, configs.length);
|
|
281
543
|
return 0;
|
|
282
544
|
}
|
package/dist/core/resolve.js
CHANGED
|
@@ -85,6 +85,10 @@ export function getDeployMethod() {
|
|
|
85
85
|
return process.platform === "win32" ? "copy" : "symlink";
|
|
86
86
|
}
|
|
87
87
|
export function resolveAgentSkillPathFor(agent, skillName, home, platform) {
|
|
88
|
+
if (!agent.skills) {
|
|
89
|
+
throw new Error(`Agent "${agent.id}" does not have a skills deployment path. ` +
|
|
90
|
+
`Deploy skills via another agent target that covers this agent natively (e.g. claude-code for github-copilot).`);
|
|
91
|
+
}
|
|
88
92
|
return resolvePlaceholders(agent.skills[platform], skillName, home);
|
|
89
93
|
}
|
|
90
94
|
export function resolveAgentDetectPathFor(agent, home, platform) {
|
package/dist/core/revert.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { lstat, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
3
3
|
import { logger } from "../logger.js";
|
|
4
|
-
import { compileAgentRuleReverts, compileMcpServerReverts, } from "./adapters/index.js";
|
|
4
|
+
import { compileAgentDefinitionReverts, compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, } from "./adapters/index.js";
|
|
5
5
|
import { revertTomlMcpPatch } from "./adapters/toml.js";
|
|
6
6
|
import { lookupDeployment, unregisterDeployment, } from "./ownership.js";
|
|
7
7
|
import { resolveAgentSkillPath } from "./resolve.js";
|
|
@@ -15,6 +15,8 @@ function buildSkillDirReverts(manifest, home, agentFilter) {
|
|
|
15
15
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
16
16
|
if (!agent)
|
|
17
17
|
continue;
|
|
18
|
+
if (!agent.skills)
|
|
19
|
+
continue;
|
|
18
20
|
actions.push({
|
|
19
21
|
kind: "skill-dir",
|
|
20
22
|
skill: skill.name,
|
|
@@ -70,6 +72,8 @@ export function planRevert(manifest, detectedAgents, home, repo) {
|
|
|
70
72
|
...buildConfigPatchReverts(manifest, home, detectedAgents),
|
|
71
73
|
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, detectedAgents, home, repo)),
|
|
72
74
|
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, detectedAgents, home, repo)),
|
|
75
|
+
...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, detectedAgents, home)),
|
|
76
|
+
...(manifest.agentDefinitions ?? []).flatMap((e) => compileAgentDefinitionReverts(e, detectedAgents, home, repo)),
|
|
73
77
|
];
|
|
74
78
|
}
|
|
75
79
|
export function planRevertAll(manifest, home, repo) {
|
|
@@ -79,6 +83,8 @@ export function planRevertAll(manifest, home, repo) {
|
|
|
79
83
|
...buildConfigPatchReverts(manifest, home, null),
|
|
80
84
|
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, null, home, repo)),
|
|
81
85
|
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, null, home, repo)),
|
|
86
|
+
...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, null, home)),
|
|
87
|
+
...(manifest.agentDefinitions ?? []).flatMap((e) => compileAgentDefinitionReverts(e, null, home, repo)),
|
|
82
88
|
];
|
|
83
89
|
}
|
|
84
90
|
function recordOutcome(result, action, counts, failed) {
|
|
@@ -2,5 +2,6 @@ export declare function sourceAccessError(err: unknown, sourcePath: string): str
|
|
|
2
2
|
export declare function validateSourcePath(source: string, skillPath: string, resolvedSourceDir: string, realRoot: string): Promise<void>;
|
|
3
3
|
export declare function validateSourceFile(sourcePath: string, manifestPath: string): Promise<void>;
|
|
4
4
|
export declare function validateMcpServerConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
5
|
+
export declare function validatePermissionsConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
5
6
|
export declare function validateAgentRuleMarkdownPath(manifestPath: string, agentId: string): void;
|
|
6
7
|
export declare function validateSkillDefinitionFile(sourcePath: string, manifestPath: string): Promise<void>;
|
package/dist/core/validation.js
CHANGED
|
@@ -96,6 +96,51 @@ export function validateMcpServerConfigShape(config, entryName, agentId) {
|
|
|
96
96
|
throw new UserError("DEPLOY_FAILED", `mcpServers entry "${entryName}" for agent "${agentId}" must define "env" as an object of string values when present`);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
const CODEX_APPROVAL_POLICY_VALUES = [
|
|
100
|
+
"auto",
|
|
101
|
+
"manual",
|
|
102
|
+
"suggest",
|
|
103
|
+
"on-failure",
|
|
104
|
+
];
|
|
105
|
+
function rejectUnknownKeys(config, allowed, entryName, agentId) {
|
|
106
|
+
const unknownKeys = Object.keys(config).filter((k) => !allowed.includes(k));
|
|
107
|
+
if (unknownKeys.length > 0) {
|
|
108
|
+
throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "${agentId}" contains unrecognized keys: ${unknownKeys.join(", ")}. Only ${allowed.map((k) => `"${k}"`).join(", ")} is allowed.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function validateClaudeCodePermissions(config, entryName) {
|
|
112
|
+
rejectUnknownKeys(config, ["permissions"], entryName, "claude-code");
|
|
113
|
+
const perms = config.permissions;
|
|
114
|
+
if (perms === undefined)
|
|
115
|
+
return;
|
|
116
|
+
if (typeof perms !== "object" || perms === null || Array.isArray(perms)) {
|
|
117
|
+
throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "claude-code" must define "permissions" as an object`);
|
|
118
|
+
}
|
|
119
|
+
const permsObj = perms;
|
|
120
|
+
for (const key of ["allow", "deny"]) {
|
|
121
|
+
const val = permsObj[key];
|
|
122
|
+
if (val !== undefined &&
|
|
123
|
+
(!Array.isArray(val) || val.some((item) => typeof item !== "string"))) {
|
|
124
|
+
throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "claude-code" must define "permissions.${key}" as an array of strings when present`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function validateCodexPermissions(config, entryName) {
|
|
129
|
+
rejectUnknownKeys(config, ["approval_policy"], entryName, "codex");
|
|
130
|
+
const policy = config.approval_policy;
|
|
131
|
+
if (policy !== undefined &&
|
|
132
|
+
!CODEX_APPROVAL_POLICY_VALUES.includes(policy)) {
|
|
133
|
+
throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "codex" must define "approval_policy" as one of: ${CODEX_APPROVAL_POLICY_VALUES.join(", ")}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function validatePermissionsConfigShape(config, entryName, agentId) {
|
|
137
|
+
if (agentId === "claude-code") {
|
|
138
|
+
validateClaudeCodePermissions(config, entryName);
|
|
139
|
+
}
|
|
140
|
+
else if (agentId === "codex") {
|
|
141
|
+
validateCodexPermissions(config, entryName);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
99
144
|
export function validateAgentRuleMarkdownPath(manifestPath, agentId) {
|
|
100
145
|
const extension = path.extname(manifestPath).toLowerCase();
|
|
101
146
|
if (extension !== ".md" && extension !== ".markdown") {
|
|
@@ -72,6 +72,30 @@ export declare const AgentRuleEntrySchema: z.ZodObject<{
|
|
|
72
72
|
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
73
73
|
path: z.ZodString;
|
|
74
74
|
}, z.core.$strip>;
|
|
75
|
+
export declare const PermissionsEntrySchema: z.ZodObject<{
|
|
76
|
+
name: z.ZodString;
|
|
77
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
78
|
+
"claude-code": "claude-code";
|
|
79
|
+
codex: "codex";
|
|
80
|
+
"gemini-cli": "gemini-cli";
|
|
81
|
+
antigravity: "antigravity";
|
|
82
|
+
opencode: "opencode";
|
|
83
|
+
"github-copilot": "github-copilot";
|
|
84
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
85
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
86
|
+
}, z.core.$strip>;
|
|
87
|
+
export declare const AgentDefinitionEntrySchema: z.ZodObject<{
|
|
88
|
+
name: z.ZodString;
|
|
89
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
90
|
+
"claude-code": "claude-code";
|
|
91
|
+
codex: "codex";
|
|
92
|
+
"gemini-cli": "gemini-cli";
|
|
93
|
+
antigravity: "antigravity";
|
|
94
|
+
opencode: "opencode";
|
|
95
|
+
"github-copilot": "github-copilot";
|
|
96
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
97
|
+
path: z.ZodString;
|
|
98
|
+
}, z.core.$strip>;
|
|
75
99
|
export declare const ManifestSchema: z.ZodObject<{
|
|
76
100
|
skills: z.ZodArray<z.ZodObject<{
|
|
77
101
|
name: z.ZodString;
|
|
@@ -135,12 +159,38 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
135
159
|
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
136
160
|
path: z.ZodString;
|
|
137
161
|
}, z.core.$strip>>>;
|
|
162
|
+
permissions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
163
|
+
name: z.ZodString;
|
|
164
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
165
|
+
"claude-code": "claude-code";
|
|
166
|
+
codex: "codex";
|
|
167
|
+
"gemini-cli": "gemini-cli";
|
|
168
|
+
antigravity: "antigravity";
|
|
169
|
+
opencode: "opencode";
|
|
170
|
+
"github-copilot": "github-copilot";
|
|
171
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
172
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
173
|
+
}, z.core.$strip>>>;
|
|
174
|
+
agentDefinitions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
175
|
+
name: z.ZodString;
|
|
176
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
177
|
+
"claude-code": "claude-code";
|
|
178
|
+
codex: "codex";
|
|
179
|
+
"gemini-cli": "gemini-cli";
|
|
180
|
+
antigravity: "antigravity";
|
|
181
|
+
opencode: "opencode";
|
|
182
|
+
"github-copilot": "github-copilot";
|
|
183
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
184
|
+
path: z.ZodString;
|
|
185
|
+
}, z.core.$strip>>>;
|
|
138
186
|
}, z.core.$strip>;
|
|
139
187
|
export type SkillEntry = z.infer<typeof SkillEntrySchema>;
|
|
140
188
|
export type FileEntry = z.infer<typeof FileEntrySchema>;
|
|
141
189
|
export type ConfigEntry = z.infer<typeof ConfigEntrySchema>;
|
|
142
190
|
export type McpServerEntry = z.infer<typeof McpServerEntrySchema>;
|
|
143
191
|
export type AgentRuleEntry = z.infer<typeof AgentRuleEntrySchema>;
|
|
192
|
+
export type PermissionsEntry = z.infer<typeof PermissionsEntrySchema>;
|
|
193
|
+
export type AgentDefinitionEntry = z.infer<typeof AgentDefinitionEntrySchema>;
|
|
144
194
|
export type Manifest = z.infer<typeof ManifestSchema>;
|
|
145
195
|
export declare const AgentListSchema: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string[], string>>, z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
146
196
|
"claude-code": "claude-code";
|
package/dist/schemas/manifest.js
CHANGED
|
@@ -90,6 +90,22 @@ export const AgentRuleEntrySchema = z.object({
|
|
|
90
90
|
// Relative path to the rules/instruction file within the source bundle.
|
|
91
91
|
path: sourcePathField,
|
|
92
92
|
});
|
|
93
|
+
export const PermissionsEntrySchema = z.object({
|
|
94
|
+
name: nameField,
|
|
95
|
+
agents: agentsField,
|
|
96
|
+
// Raw permission config payload validated per agent by the permissions adapter.
|
|
97
|
+
// For claude-code: { permissions: { allow?: string[], deny?: string[] } }
|
|
98
|
+
// For codex: { approval_policy?: "auto" | "manual" | "suggest" | "on-failure" }
|
|
99
|
+
config: z.record(z.string(), z.unknown()),
|
|
100
|
+
});
|
|
101
|
+
export const AgentDefinitionEntrySchema = z.object({
|
|
102
|
+
name: nameField,
|
|
103
|
+
agents: agentsField,
|
|
104
|
+
// Relative path to the agent definition Markdown file within the source bundle.
|
|
105
|
+
// Must be a .md or .markdown file containing YAML frontmatter that describes
|
|
106
|
+
// the agent's persona, instructions, and any tool configuration.
|
|
107
|
+
path: sourcePathField,
|
|
108
|
+
});
|
|
93
109
|
export const ManifestSchema = z.object({
|
|
94
110
|
skills: z.array(SkillEntrySchema).superRefine((skills, ctx) => {
|
|
95
111
|
const seen = new Set();
|
|
@@ -108,6 +124,8 @@ export const ManifestSchema = z.object({
|
|
|
108
124
|
configs: z.array(ConfigEntrySchema).default([]),
|
|
109
125
|
mcpServers: z.array(McpServerEntrySchema).default([]),
|
|
110
126
|
agentRules: z.array(AgentRuleEntrySchema).default([]),
|
|
127
|
+
permissions: z.array(PermissionsEntrySchema).default([]),
|
|
128
|
+
agentDefinitions: z.array(AgentDefinitionEntrySchema).default([]),
|
|
111
129
|
});
|
|
112
130
|
// Parses the --agents CLI flag: comma-separated agent IDs → AgentId[]
|
|
113
131
|
export const AgentListSchema = z
|