@kuznai/inception-engine 0.16.0 → 0.18.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 +72 -6
- package/dist/config/agents.js +104 -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/frontmatter.d.ts +17 -5
- package/dist/core/adapters/frontmatter.js +36 -57
- package/dist/core/adapters/index.d.ts +4 -3
- package/dist/core/adapters/index.js +8 -2
- package/dist/core/adapters/mcp.js +10 -1
- package/dist/core/adapters/permissions.js +10 -1
- package/dist/core/adapters/rules.js +49 -16
- package/dist/core/deploy.js +5 -3
- package/dist/core/init.js +181 -11
- package/dist/core/preflight.d.ts +2 -2
- package/dist/core/preflight.js +71 -1
- package/dist/core/resolve.js +4 -0
- package/dist/core/revert.js +5 -1
- package/dist/core/validation.js +21 -38
- package/dist/schemas/manifest.d.ts +33 -0
- package/dist/schemas/manifest.js +13 -0
- package/dist/types.d.ts +27 -4
- package/package.json +2 -2
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type { AgentRuleEntry, McpServerEntry, PermissionsEntry } from "../../schemas/manifest.ts";
|
|
1
|
+
import type { AgentDefinitionEntry, AgentRuleEntry, McpServerEntry, PermissionsEntry } from "../../schemas/manifest.ts";
|
|
2
2
|
import type { AgentId, ConfigPatchDeployAction, FileWriteDeployAction, FrontmatterEmitDeployAction, PlanWarning, TomlPatchDeployAction } from "../../types.ts";
|
|
3
|
+
import { compileAgentDefinitionReverts } from "./agent-definitions.ts";
|
|
3
4
|
import { compileMcpServerReverts } from "./mcp.ts";
|
|
4
5
|
import { compilePermissionsReverts } from "./permissions.ts";
|
|
5
6
|
import { compileAgentRuleReverts } from "./rules.ts";
|
|
6
|
-
export { compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, };
|
|
7
|
+
export { compileAgentDefinitionReverts, compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, };
|
|
7
8
|
export type AdapterAction = ConfigPatchDeployAction | FileWriteDeployAction | TomlPatchDeployAction | FrontmatterEmitDeployAction;
|
|
8
9
|
export interface AdapterResult {
|
|
9
10
|
actions: AdapterAction[];
|
|
10
11
|
warnings: PlanWarning[];
|
|
11
12
|
}
|
|
12
|
-
export declare function compileAdapterActions(mcpServers: McpServerEntry[], agentRules: AgentRuleEntry[], permissions: PermissionsEntry[], sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string): Promise<AdapterResult>;
|
|
13
|
+
export declare function compileAdapterActions(mcpServers: McpServerEntry[], agentRules: AgentRuleEntry[], permissions: PermissionsEntry[], sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string, agentDefinitions?: AgentDefinitionEntry[]): Promise<AdapterResult>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { compileAgentDefinitionActions, compileAgentDefinitionReverts, } from "./agent-definitions.js";
|
|
1
2
|
import { compileMcpServerActions, compileMcpServerReverts } from "./mcp.js";
|
|
2
3
|
import { compilePermissionsActions, compilePermissionsReverts, } from "./permissions.js";
|
|
3
4
|
import { compileAgentRuleActions, compileAgentRuleReverts } from "./rules.js";
|
|
4
|
-
export { compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, };
|
|
5
|
-
export async function compileAdapterActions(mcpServers, agentRules, permissions, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo) {
|
|
5
|
+
export { compileAgentDefinitionReverts, compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, };
|
|
6
|
+
export async function compileAdapterActions(mcpServers, agentRules, permissions, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, agentDefinitions) {
|
|
6
7
|
const actions = [];
|
|
7
8
|
const warnings = [];
|
|
8
9
|
for (const entry of mcpServers) {
|
|
@@ -20,5 +21,10 @@ export async function compileAdapterActions(mcpServers, agentRules, permissions,
|
|
|
20
21
|
actions.push(...r.actions);
|
|
21
22
|
warnings.push(...r.warnings);
|
|
22
23
|
}
|
|
24
|
+
for (const entry of agentDefinitions ?? []) {
|
|
25
|
+
const r = await compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo);
|
|
26
|
+
actions.push(...r.actions);
|
|
27
|
+
warnings.push(...r.warnings);
|
|
28
|
+
}
|
|
23
29
|
return { actions, warnings };
|
|
24
30
|
}
|
|
@@ -62,6 +62,13 @@ export function compileMcpServerActions(entry, detectedAgents, home, repo) {
|
|
|
62
62
|
});
|
|
63
63
|
continue;
|
|
64
64
|
}
|
|
65
|
+
if (support.status === "planned") {
|
|
66
|
+
warnings.push({
|
|
67
|
+
kind: "confidence",
|
|
68
|
+
message: `mcpServers: agent "${agentId}" MCP support is planned via ${support.plannedSurface} — skipping "${entry.name}" until that surface is implemented`,
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
65
72
|
validateMcpServerConfigShape(entry.config, entry.name, agentId);
|
|
66
73
|
const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home, repo);
|
|
67
74
|
const resolvedTarget = path.resolve(rawTarget);
|
|
@@ -79,7 +86,9 @@ export function compileMcpServerReverts(entry, agentFilter, home, repo) {
|
|
|
79
86
|
continue;
|
|
80
87
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
81
88
|
const support = agent?.mcpSupport;
|
|
82
|
-
if (!support ||
|
|
89
|
+
if (!support ||
|
|
90
|
+
support.status === "unsupported" ||
|
|
91
|
+
support.status === "planned")
|
|
83
92
|
continue;
|
|
84
93
|
const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home, repo);
|
|
85
94
|
const target = path.resolve(rawTarget);
|
|
@@ -21,6 +21,13 @@ export function compilePermissionsActions(entry, detectedAgents, home) {
|
|
|
21
21
|
});
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
24
|
+
if (support.status === "planned") {
|
|
25
|
+
warnings.push({
|
|
26
|
+
kind: "confidence",
|
|
27
|
+
message: `permissions: agent "${agentId}" permissions support is planned via ${support.plannedSurface} — skipping "${entry.name}" until that surface is implemented`,
|
|
28
|
+
});
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
24
31
|
validatePermissionsConfigShape(entry.config, entry.name, agentId);
|
|
25
32
|
const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home);
|
|
26
33
|
const resolvedTarget = path.resolve(rawTarget);
|
|
@@ -56,7 +63,9 @@ export function compilePermissionsReverts(entry, agentFilter, home) {
|
|
|
56
63
|
continue;
|
|
57
64
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
58
65
|
const support = agent?.permissionsSupport;
|
|
59
|
-
if (!support ||
|
|
66
|
+
if (!support ||
|
|
67
|
+
support.status === "unsupported" ||
|
|
68
|
+
support.status === "planned")
|
|
60
69
|
continue;
|
|
61
70
|
const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home);
|
|
62
71
|
const target = path.resolve(rawTarget);
|
|
@@ -2,6 +2,40 @@ import path from "node:path";
|
|
|
2
2
|
import { AGENT_REGISTRY_BY_ID } from "../../config/agents.js";
|
|
3
3
|
import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
|
|
4
4
|
import { validateAgentRuleMarkdownPath, validateSourceFile, validateSourcePath, } from "../validation.js";
|
|
5
|
+
function resolveRulesSupport(agentId, scope) {
|
|
6
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
7
|
+
if (scope === "repo") {
|
|
8
|
+
return agent?.agentRulesRepoSupport ?? agent?.agentRulesSupport;
|
|
9
|
+
}
|
|
10
|
+
return agent?.agentRulesSupport;
|
|
11
|
+
}
|
|
12
|
+
function resolveAgentTarget(agentId, entry, home, repo, platform) {
|
|
13
|
+
const support = resolveRulesSupport(agentId, entry.scope);
|
|
14
|
+
if (!support || support.status === "unsupported") {
|
|
15
|
+
return {
|
|
16
|
+
kind: "confidence",
|
|
17
|
+
message: `agentRules: agent "${agentId}" uses ${support?.schemaLabel ?? "an unsupported instruction schema"} and ${support?.status === "unsupported" ? support.reason : "does not expose a supported rules adapter"} — skipping "${entry.name}"`,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (support.status === "planned") {
|
|
21
|
+
return {
|
|
22
|
+
kind: "confidence",
|
|
23
|
+
message: `agentRules: agent "${agentId}" rules support is planned via ${support.plannedSurface} — skipping "${entry.name}" until that surface is implemented`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (entry.scope === "repo" && !repo) {
|
|
27
|
+
return {
|
|
28
|
+
kind: "confidence",
|
|
29
|
+
message: `agentRules: scope "repo" requires a repository path but none was resolved — skipping "${entry.name}" for agent "${agentId}"`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
33
|
+
return {
|
|
34
|
+
agentId,
|
|
35
|
+
confidence: agent?.provenance.agentRules ?? "provisional",
|
|
36
|
+
target: resolvePlaceholders(support.path[platform], entry.name, home, repo),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
5
39
|
export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo) {
|
|
6
40
|
const actions = [];
|
|
7
41
|
const warnings = [];
|
|
@@ -12,26 +46,19 @@ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDi
|
|
|
12
46
|
return { actions, warnings };
|
|
13
47
|
}
|
|
14
48
|
for (const agentId of targetAgents) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
});
|
|
22
|
-
continue;
|
|
49
|
+
const result = resolveAgentTarget(agentId, entry, home, repo, platform);
|
|
50
|
+
if ("kind" in result) {
|
|
51
|
+
warnings.push(result);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
supportedTargets.push(result);
|
|
23
55
|
}
|
|
24
|
-
supportedTargets.push({
|
|
25
|
-
agentId,
|
|
26
|
-
confidence: agent.provenance.agentRules ?? "provisional",
|
|
27
|
-
target: resolvePlaceholders(support.path[platform], entry.name, home, repo),
|
|
28
|
-
});
|
|
29
56
|
}
|
|
30
57
|
if (supportedTargets.length === 0) {
|
|
31
58
|
return { actions, warnings };
|
|
32
59
|
}
|
|
33
60
|
// Validate the shared source file only when at least one target uses the
|
|
34
|
-
// current
|
|
61
|
+
// current rules adapter surface.
|
|
35
62
|
const source = path.resolve(sourceDir, entry.path);
|
|
36
63
|
await validateSourcePath(source, entry.path, resolvedSourceDir, realRoot);
|
|
37
64
|
await validateSourceFile(source, entry.path);
|
|
@@ -55,8 +82,14 @@ export function compileAgentRuleReverts(entry, agentFilter, home, repo) {
|
|
|
55
82
|
if (agentFilter && !agentFilter.includes(agentId))
|
|
56
83
|
continue;
|
|
57
84
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
58
|
-
const support =
|
|
59
|
-
|
|
85
|
+
const support = entry.scope === "repo"
|
|
86
|
+
? (agent?.agentRulesRepoSupport ?? agent?.agentRulesSupport)
|
|
87
|
+
: agent?.agentRulesSupport;
|
|
88
|
+
if (!support ||
|
|
89
|
+
support.status === "unsupported" ||
|
|
90
|
+
support.status === "planned")
|
|
91
|
+
continue;
|
|
92
|
+
if (entry.scope === "repo" && !repo)
|
|
60
93
|
continue;
|
|
61
94
|
const target = resolvePlaceholders(support.path[platform], entry.name, home, repo);
|
|
62
95
|
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, manifest.permissions ?? [], 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
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { access,
|
|
1
|
+
import { access, readdir, readFile, 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, ConfigEntrySchema, FileEntrySchema, 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 {
|
|
@@ -160,10 +170,121 @@ function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
|
|
|
160
170
|
}
|
|
161
171
|
}
|
|
162
172
|
namesSeen.add(name);
|
|
163
|
-
rules.push({ name, path: relPath, agents });
|
|
173
|
+
rules.push({ name, path: relPath, agents, scope: "global" });
|
|
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;
|
|
@@ -263,6 +384,39 @@ async function loadConfigsManifest(baseDir) {
|
|
|
263
384
|
}
|
|
264
385
|
return results;
|
|
265
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
|
+
}
|
|
266
420
|
async function emitDirectoryHints(baseDir, filesLoaded, configsLoaded) {
|
|
267
421
|
for (const [dir, section, loaded, sidecar] of [
|
|
268
422
|
["files", "files", filesLoaded, "files-manifest.json"],
|
|
@@ -288,16 +442,20 @@ async function manifestExists(manifestPath) {
|
|
|
288
442
|
return false;
|
|
289
443
|
}
|
|
290
444
|
}
|
|
291
|
-
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) {
|
|
292
454
|
for (const s of skills) {
|
|
293
455
|
logger.detail(`${s.name} → ${s.path}`);
|
|
294
456
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
for (const r of agentRules) {
|
|
298
|
-
logger.detail(` ${r.name} → ${r.path} [${r.agents.join(", ")}]`);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
457
|
+
logPathAgentEntries("agentRules", agentRules);
|
|
458
|
+
logPathAgentEntries("agentDefinitions", agentDefinitions);
|
|
301
459
|
if (mcpServers.length > 0) {
|
|
302
460
|
logger.detail("mcpServers:");
|
|
303
461
|
for (const m of mcpServers) {
|
|
@@ -336,6 +494,16 @@ export async function runInit(options) {
|
|
|
336
494
|
const skillDirRelPaths = new Set(found.map((f) => f.relPath));
|
|
337
495
|
const agentRulesCandidates = await findAgentRulesCandidates(directory, skillDirRelPaths);
|
|
338
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;
|
|
339
507
|
const mcpServers = await loadMcpServers(directory);
|
|
340
508
|
const files = await loadFilesManifest(directory);
|
|
341
509
|
const configs = await loadConfigsManifest(directory);
|
|
@@ -345,12 +513,14 @@ export async function runInit(options) {
|
|
|
345
513
|
configs,
|
|
346
514
|
mcpServers,
|
|
347
515
|
agentRules,
|
|
516
|
+
agentDefinitions,
|
|
348
517
|
};
|
|
349
518
|
const json = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
350
519
|
function summarize() {
|
|
351
520
|
const parts = [
|
|
352
521
|
`${skills.length} skill(s)`,
|
|
353
522
|
`${agentRules.length} agentRule(s)`,
|
|
523
|
+
`${agentDefinitions.length} agentDefinition(s)`,
|
|
354
524
|
`${mcpServers.length} mcpServer(s)`,
|
|
355
525
|
`${files.length} file(s)`,
|
|
356
526
|
`${configs.length} config(s)`,
|
|
@@ -367,7 +537,7 @@ export async function runInit(options) {
|
|
|
367
537
|
await writeFile(manifestPath, json, "utf-8");
|
|
368
538
|
logger.info(`Generated ${manifestPath} with ${summarize()}.`);
|
|
369
539
|
if (verbose) {
|
|
370
|
-
logVerboseManifest(skills, agentRules, mcpServers, files, configs);
|
|
540
|
+
logVerboseManifest(skills, agentRules, mcpServers, files, configs, agentDefinitions);
|
|
371
541
|
}
|
|
372
542
|
await emitDirectoryHints(directory, files.length, configs.length);
|
|
373
543
|
return 0;
|
package/dist/core/preflight.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentId, CliOptions, Manifest } from "../types.ts";
|
|
2
2
|
export interface PreflightWarning {
|
|
3
|
-
kind: "policy" | "config-authority" | "info";
|
|
3
|
+
kind: "policy" | "config-authority" | "info" | "precedence" | "budget";
|
|
4
4
|
message: string;
|
|
5
5
|
}
|
|
6
|
-
export declare function runPreflight(
|
|
6
|
+
export declare function runPreflight(options: CliOptions, manifest: Manifest, _home: string, detectedAgents: AgentId[]): Promise<PreflightWarning[]>;
|
package/dist/core/preflight.js
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
2
|
-
|
|
4
|
+
const BUDGET_WARN_BYTES = 50 * 1024; // 50 KB
|
|
5
|
+
function detectInstructionPrecedence(detectedAgents, manifest) {
|
|
6
|
+
const warnings = [];
|
|
7
|
+
for (const agentId of detectedAgents) {
|
|
8
|
+
const rulesForAgent = (manifest.agentRules ?? []).filter((e) => e.agents.includes(agentId));
|
|
9
|
+
const globalEntries = rulesForAgent.filter((e) => (e.scope ?? "global") === "global");
|
|
10
|
+
const repoEntries = rulesForAgent.filter((e) => e.scope === "repo");
|
|
11
|
+
if (globalEntries.length === 0 || repoEntries.length === 0)
|
|
12
|
+
continue;
|
|
13
|
+
const globalPaths = new Set(globalEntries.map((e) => e.path));
|
|
14
|
+
for (const entry of repoEntries) {
|
|
15
|
+
if (globalPaths.has(entry.path)) {
|
|
16
|
+
warnings.push({
|
|
17
|
+
kind: "precedence",
|
|
18
|
+
message: `Agent "${agentId}" has agentRules entry "${entry.name}" deployed to both global and repo scope from the same source path "${entry.path}". The file will be written to two distinct targets — verify this is intentional and not a copy-paste mistake.`,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const nonOverlapRepo = repoEntries.filter((e) => !globalPaths.has(e.path));
|
|
23
|
+
if (nonOverlapRepo.length > 0) {
|
|
24
|
+
const globalNames = globalEntries.map((e) => `"${e.name}"`).join(", ");
|
|
25
|
+
const repoNames = nonOverlapRepo.map((e) => `"${e.name}"`).join(", ");
|
|
26
|
+
warnings.push({
|
|
27
|
+
kind: "precedence",
|
|
28
|
+
message: `Agent "${agentId}" will have both global and repo instruction files active simultaneously: global [${globalNames}] and repo [${repoNames}]. Both will be loaded by the agent — ensure the content is intended to stack and does not conflict.`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return warnings;
|
|
33
|
+
}
|
|
34
|
+
async function detectInstructionBudgetRisk(detectedAgents, manifest, sourceDir) {
|
|
35
|
+
const warnings = [];
|
|
36
|
+
const checkedPaths = new Set();
|
|
37
|
+
async function checkEntry(entryPath, label, agentIds) {
|
|
38
|
+
if (!agentIds.some((id) => detectedAgents.includes(id)))
|
|
39
|
+
return;
|
|
40
|
+
const sourcePath = path.resolve(sourceDir, entryPath);
|
|
41
|
+
if (checkedPaths.has(sourcePath))
|
|
42
|
+
return;
|
|
43
|
+
checkedPaths.add(sourcePath);
|
|
44
|
+
try {
|
|
45
|
+
const fileStat = await stat(sourcePath);
|
|
46
|
+
if (fileStat.size > BUDGET_WARN_BYTES) {
|
|
47
|
+
const sizeKb = (fileStat.size / 1024).toFixed(1);
|
|
48
|
+
warnings.push({
|
|
49
|
+
kind: "budget",
|
|
50
|
+
message: `${label} source "${entryPath}" is ${sizeKb} KB — large instruction files risk crowding out code context in the agent's context window. Consider splitting into smaller focused files.`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
const code = err.code;
|
|
56
|
+
if (code !== "ENOENT" && code !== "EACCES" && code !== "EPERM")
|
|
57
|
+
throw err;
|
|
58
|
+
// Missing/unreadable files are silently skipped; compileAgentRuleActions
|
|
59
|
+
// will produce the proper UserError during action compilation.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const entry of manifest.agentRules ?? []) {
|
|
63
|
+
await checkEntry(entry.path, "agentRules", entry.agents);
|
|
64
|
+
}
|
|
65
|
+
for (const entry of manifest.agentDefinitions ?? []) {
|
|
66
|
+
await checkEntry(entry.path, "agentDefinitions", entry.agents);
|
|
67
|
+
}
|
|
68
|
+
return warnings;
|
|
69
|
+
}
|
|
70
|
+
export async function runPreflight(options, manifest, _home, detectedAgents) {
|
|
3
71
|
const warnings = [];
|
|
4
72
|
for (const agentId of detectedAgents) {
|
|
5
73
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
@@ -24,5 +92,7 @@ export async function runPreflight(_options, _manifest, _home, detectedAgents) {
|
|
|
24
92
|
});
|
|
25
93
|
}
|
|
26
94
|
}
|
|
95
|
+
warnings.push(...detectInstructionPrecedence(detectedAgents, manifest));
|
|
96
|
+
warnings.push(...(await detectInstructionBudgetRisk(detectedAgents, manifest, options.directory)));
|
|
27
97
|
return warnings;
|
|
28
98
|
}
|
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, compilePermissionsReverts, } 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,
|
|
@@ -71,6 +73,7 @@ export function planRevert(manifest, detectedAgents, home, repo) {
|
|
|
71
73
|
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, detectedAgents, home, repo)),
|
|
72
74
|
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, detectedAgents, home, repo)),
|
|
73
75
|
...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, detectedAgents, home)),
|
|
76
|
+
...(manifest.agentDefinitions ?? []).flatMap((e) => compileAgentDefinitionReverts(e, detectedAgents, home, repo)),
|
|
74
77
|
];
|
|
75
78
|
}
|
|
76
79
|
export function planRevertAll(manifest, home, repo) {
|
|
@@ -81,6 +84,7 @@ export function planRevertAll(manifest, home, repo) {
|
|
|
81
84
|
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, null, home, repo)),
|
|
82
85
|
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, null, home, repo)),
|
|
83
86
|
...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, null, home)),
|
|
87
|
+
...(manifest.agentDefinitions ?? []).flatMap((e) => compileAgentDefinitionReverts(e, null, home, repo)),
|
|
84
88
|
];
|
|
85
89
|
}
|
|
86
90
|
function recordOutcome(result, action, counts, failed) {
|