@kuznai/inception-engine 0.19.0 → 0.20.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 +48 -22
- package/dist/{config → src/config}/agents.js +87 -14
- package/dist/{core → src/core}/adapters/agent-definitions.d.ts +0 -12
- package/dist/src/core/adapters/agent-definitions.js +162 -0
- package/dist/{core → src/core}/adapters/frontmatter.d.ts +10 -0
- package/dist/{core → src/core}/adapters/frontmatter.js +23 -6
- package/dist/{core → src/core}/adapters/mcp.js +17 -20
- package/dist/{core → src/core}/adapters/permissions.js +16 -20
- package/dist/{core → src/core}/adapters/rules.js +26 -50
- package/dist/src/core/capabilities.d.ts +47 -0
- package/dist/src/core/capabilities.js +280 -0
- package/dist/{core → src/core}/deploy.js +70 -41
- package/dist/{core → src/core}/init.js +18 -24
- package/dist/src/core/merge-patch.d.ts +4 -0
- package/dist/src/core/merge-patch.js +46 -0
- package/dist/{core → src/core}/ownership.js +13 -0
- package/dist/{core → src/core}/preflight.js +141 -29
- package/dist/{core → src/core}/revert.js +86 -26
- package/dist/{core → src/core}/validation.js +20 -0
- package/dist/{schemas → src/schemas}/manifest.d.ts +20 -0
- package/dist/{schemas → src/schemas}/manifest.js +11 -0
- package/dist/{schemas → src/schemas}/registry.d.ts +36 -0
- package/dist/{schemas → src/schemas}/registry.js +12 -4
- package/dist/{types.d.ts → src/types.d.ts} +13 -0
- package/dist/test/helpers/fs.d.ts +2 -0
- package/dist/test/helpers/fs.js +17 -0
- package/dist/test/helpers/path.d.ts +10 -0
- package/dist/test/helpers/path.js +15 -0
- package/dist/test/helpers/skill-dir-scenarios.d.ts +7 -0
- package/dist/test/helpers/skill-dir-scenarios.js +206 -0
- package/dist/test/helpers/skill-dir.d.ts +7 -0
- package/dist/test/helpers/skill-dir.js +46 -0
- package/dist/test/os/cross-platform/cross-platform.test.d.ts +1 -0
- package/dist/test/os/cross-platform/cross-platform.test.js +328 -0
- package/dist/test/os/posix/skill-dir.test.d.ts +1 -0
- package/dist/test/os/posix/skill-dir.test.js +19 -0
- package/dist/test/os/windows/revert-integration.test.d.ts +1 -0
- package/dist/test/os/windows/revert-integration.test.js +72 -0
- package/dist/test/os/windows/skill-dir.test.d.ts +1 -0
- package/dist/test/os/windows/skill-dir.test.js +19 -0
- package/dist/test/unit/adapters.test.d.ts +1 -0
- package/dist/test/unit/adapters.test.js +947 -0
- package/dist/test/unit/capabilities.test.d.ts +1 -0
- package/dist/test/unit/capabilities.test.js +71 -0
- package/dist/test/unit/cli.test.d.ts +1 -0
- package/dist/test/unit/cli.test.js +834 -0
- package/dist/test/unit/deploy.test.d.ts +1 -0
- package/dist/test/unit/deploy.test.js +2481 -0
- package/dist/test/unit/detect.test.d.ts +1 -0
- package/dist/test/unit/detect.test.js +192 -0
- package/dist/test/unit/enterprise.test.d.ts +1 -0
- package/dist/test/unit/enterprise.test.js +106 -0
- package/dist/test/unit/frontmatter.test.d.ts +1 -0
- package/dist/test/unit/frontmatter.test.js +102 -0
- package/dist/test/unit/gemini-north-star.test.d.ts +1 -0
- package/dist/test/unit/gemini-north-star.test.js +243 -0
- package/dist/test/unit/manifest.test.d.ts +1 -0
- package/dist/test/unit/manifest.test.js +737 -0
- package/dist/test/unit/migration-golden.test.d.ts +1 -0
- package/dist/test/unit/migration-golden.test.js +197 -0
- package/dist/test/unit/ownership.test.d.ts +1 -0
- package/dist/test/unit/ownership.test.js +587 -0
- package/dist/test/unit/preflight.test.d.ts +1 -0
- package/dist/test/unit/preflight.test.js +501 -0
- package/dist/test/unit/resolve.test.d.ts +1 -0
- package/dist/test/unit/resolve.test.js +119 -0
- package/dist/test/unit/revert.test.d.ts +1 -0
- package/dist/test/unit/revert.test.js +1004 -0
- package/dist/test/unit/toml.test.d.ts +1 -0
- package/dist/test/unit/toml.test.js +55 -0
- package/package.json +1 -1
- package/dist/core/adapters/agent-definitions.js +0 -91
- /package/dist/{config → src/config}/agents.d.ts +0 -0
- /package/dist/{config → src/config}/manifest.d.ts +0 -0
- /package/dist/{config → src/config}/manifest.js +0 -0
- /package/dist/{core → src/core}/adapters/index.d.ts +0 -0
- /package/dist/{core → src/core}/adapters/index.js +0 -0
- /package/dist/{core → src/core}/adapters/mcp.d.ts +0 -0
- /package/dist/{core → src/core}/adapters/permissions.d.ts +0 -0
- /package/dist/{core → src/core}/adapters/rules.d.ts +0 -0
- /package/dist/{core → src/core}/adapters/toml.d.ts +0 -0
- /package/dist/{core → src/core}/adapters/toml.js +0 -0
- /package/dist/{core → src/core}/deploy.d.ts +0 -0
- /package/dist/{core → src/core}/detect.d.ts +0 -0
- /package/dist/{core → src/core}/detect.js +0 -0
- /package/dist/{core → src/core}/init.d.ts +0 -0
- /package/dist/{core → src/core}/ownership.d.ts +0 -0
- /package/dist/{core → src/core}/preflight.d.ts +0 -0
- /package/dist/{core → src/core}/resolve.d.ts +0 -0
- /package/dist/{core → src/core}/resolve.js +0 -0
- /package/dist/{core → src/core}/revert.d.ts +0 -0
- /package/dist/{core → src/core}/runtime-paths.d.ts +0 -0
- /package/dist/{core → src/core}/runtime-paths.js +0 -0
- /package/dist/{core → src/core}/validation.d.ts +0 -0
- /package/dist/{errors.d.ts → src/errors.d.ts} +0 -0
- /package/dist/{errors.js → src/errors.js} +0 -0
- /package/dist/{formatters.d.ts → src/formatters.d.ts} +0 -0
- /package/dist/{formatters.js → src/formatters.js} +0 -0
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/{index.js → src/index.js} +0 -0
- /package/dist/{logger.d.ts → src/logger.d.ts} +0 -0
- /package/dist/{logger.js → src/logger.js} +0 -0
- /package/dist/{schemas → src/schemas}/errors.d.ts +0 -0
- /package/dist/{schemas → src/schemas}/errors.js +0 -0
- /package/dist/{types.js → src/types.js} +0 -0
|
@@ -5,13 +5,12 @@ import { AGENT_REGISTRY, AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
|
5
5
|
import { UserError } from "../errors.js";
|
|
6
6
|
import { logger } from "../logger.js";
|
|
7
7
|
import { compileAdapterActions } from "./adapters/index.js";
|
|
8
|
+
import { planCapabilityForDeploy } from "./capabilities.js";
|
|
9
|
+
import { applyMergePatch, computeUndoPatch, isPlainObject, } from "./merge-patch.js";
|
|
8
10
|
import { lookupDeployment, registerDeployment, verifyDeployment, } from "./ownership.js";
|
|
9
11
|
import { getDeployMethod, resolveAgentSkillPath } from "./resolve.js";
|
|
10
12
|
import { resolveTargetTemplate } from "./runtime-paths.js";
|
|
11
13
|
import { sourceAccessError, validateSkillDefinitionFile, validateSourceFile, validateSourcePath, } from "./validation.js";
|
|
12
|
-
function isPlainObject(v) {
|
|
13
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
14
|
-
}
|
|
15
14
|
async function readJsonConfigFile(filePath) {
|
|
16
15
|
let rawContent;
|
|
17
16
|
try {
|
|
@@ -35,34 +34,6 @@ async function readJsonConfigFile(filePath) {
|
|
|
35
34
|
}
|
|
36
35
|
return parsed;
|
|
37
36
|
}
|
|
38
|
-
function computeUndoPatch(original, patch) {
|
|
39
|
-
const undoPatch = {};
|
|
40
|
-
for (const key of Object.keys(patch)) {
|
|
41
|
-
const patchVal = patch[key];
|
|
42
|
-
if (isPlainObject(patchVal) && isPlainObject(original[key])) {
|
|
43
|
-
undoPatch[key] = computeUndoPatch(original[key], patchVal);
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
undoPatch[key] = key in original ? original[key] : null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return undoPatch;
|
|
50
|
-
}
|
|
51
|
-
function applyMergePatch(original, patch) {
|
|
52
|
-
const patched = { ...original };
|
|
53
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
54
|
-
if (value === null) {
|
|
55
|
-
delete patched[key];
|
|
56
|
-
}
|
|
57
|
-
else if (isPlainObject(value) && isPlainObject(patched[key])) {
|
|
58
|
-
patched[key] = applyMergePatch(patched[key], value);
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
patched[key] = value;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return patched;
|
|
65
|
-
}
|
|
66
37
|
function detectCollisions(actions) {
|
|
67
38
|
const seen = new Map();
|
|
68
39
|
const warnings = [];
|
|
@@ -197,22 +168,39 @@ function checkAntigravityPathCollisions(manifest) {
|
|
|
197
168
|
}
|
|
198
169
|
return warnings;
|
|
199
170
|
}
|
|
171
|
+
function assertNoAntigravityPathCollisions(manifest) {
|
|
172
|
+
const collisions = checkAntigravityPathCollisions(manifest);
|
|
173
|
+
if (collisions.length === 0)
|
|
174
|
+
return;
|
|
175
|
+
throw new UserError("MANIFEST_INVALID", collisions.map((warning) => warning.message).join("; "));
|
|
176
|
+
}
|
|
200
177
|
async function planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home) {
|
|
201
178
|
const method = getDeployMethod();
|
|
202
179
|
const actions = [];
|
|
203
|
-
|
|
180
|
+
const warnings = [];
|
|
181
|
+
async function planSkillEntry(skill) {
|
|
204
182
|
const targetAgents = skill.agents.filter((agentId) => detectedAgents.includes(agentId));
|
|
205
183
|
if (targetAgents.length === 0)
|
|
206
|
-
|
|
184
|
+
return;
|
|
207
185
|
const source = path.resolve(sourceDir, skill.path);
|
|
208
186
|
await validateSourcePath(source, skill.path, resolvedSourceDir, realRoot);
|
|
209
187
|
await validateSkillContract(source, skill.path);
|
|
210
188
|
for (const agentId of targetAgents) {
|
|
189
|
+
const plan = planCapabilityForDeploy({
|
|
190
|
+
agentId,
|
|
191
|
+
capability: "skills",
|
|
192
|
+
entryName: skill.name,
|
|
193
|
+
targetAgentIds: targetAgents,
|
|
194
|
+
});
|
|
195
|
+
if (plan.outcome === "warn") {
|
|
196
|
+
warnings.push(plan.warning);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (plan.outcome === "native" || plan.outcome === "redundant")
|
|
200
|
+
continue;
|
|
211
201
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
212
202
|
if (!agent)
|
|
213
203
|
continue;
|
|
214
|
-
if (!agent.skills)
|
|
215
|
-
continue;
|
|
216
204
|
actions.push({
|
|
217
205
|
kind: "skill-dir",
|
|
218
206
|
skill: skill.name,
|
|
@@ -220,11 +208,14 @@ async function planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realR
|
|
|
220
208
|
source,
|
|
221
209
|
target: resolveAgentSkillPath(agent, skill.name, home),
|
|
222
210
|
method,
|
|
223
|
-
confidence:
|
|
211
|
+
confidence: plan.confidence ?? "provisional",
|
|
224
212
|
});
|
|
225
213
|
}
|
|
226
214
|
}
|
|
227
|
-
|
|
215
|
+
for (const skill of manifest.skills) {
|
|
216
|
+
await planSkillEntry(skill);
|
|
217
|
+
}
|
|
218
|
+
return { actions, warnings };
|
|
228
219
|
}
|
|
229
220
|
async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace) {
|
|
230
221
|
const actions = [];
|
|
@@ -273,6 +264,7 @@ function planConfigPatchActions(manifest, detectedAgents, home, repo, workspace)
|
|
|
273
264
|
return actions;
|
|
274
265
|
}
|
|
275
266
|
export async function planDeploy(manifest, sourceDir, detectedAgents, home, repo, workspace) {
|
|
267
|
+
assertNoAntigravityPathCollisions(manifest);
|
|
276
268
|
const resolvedSourceDir = path.resolve(sourceDir);
|
|
277
269
|
let realRoot;
|
|
278
270
|
try {
|
|
@@ -282,14 +274,16 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home, repo
|
|
|
282
274
|
realRoot = resolvedSourceDir;
|
|
283
275
|
}
|
|
284
276
|
const repoDir = repo ?? realRoot;
|
|
277
|
+
const skillPlan = await planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home);
|
|
285
278
|
const actions = [
|
|
286
|
-
...
|
|
279
|
+
...skillPlan.actions,
|
|
287
280
|
...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, workspace)),
|
|
288
281
|
...planConfigPatchActions(manifest, detectedAgents, home, repoDir, workspace),
|
|
289
282
|
];
|
|
290
283
|
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, manifest.agentDefinitions ?? [], workspace);
|
|
291
284
|
actions.push(...adapterResult.actions);
|
|
292
285
|
const warnings = [
|
|
286
|
+
...skillPlan.warnings,
|
|
293
287
|
...detectAmbiguities(detectedAgents, manifest),
|
|
294
288
|
...checkAntigravityPathCollisions(manifest),
|
|
295
289
|
...detectCollisions(actions),
|
|
@@ -495,6 +489,7 @@ async function deployFileWrite(action, dryRun, verbose, home, planned, deps) {
|
|
|
495
489
|
source: action.source,
|
|
496
490
|
skill: action.skill,
|
|
497
491
|
agent: action.agent,
|
|
492
|
+
migratedFrom: action.migratedFrom,
|
|
498
493
|
}, deps.registry));
|
|
499
494
|
logger.ok(label);
|
|
500
495
|
if (verbose) {
|
|
@@ -605,6 +600,11 @@ async function deployTomlPatch(action, dryRun, verbose, home, planned, deps) {
|
|
|
605
600
|
}
|
|
606
601
|
async function deployFrontmatterEmit(action, dryRun, verbose, home, planned, deps) {
|
|
607
602
|
const label = `${action.skill} -> ${action.agent}`;
|
|
603
|
+
if (!isPlainObject(action.frontmatter)) {
|
|
604
|
+
const msg = `Frontmatter patch for skill "${action.skill}" must be a plain object`;
|
|
605
|
+
logger.fail(label, msg);
|
|
606
|
+
return { error: msg };
|
|
607
|
+
}
|
|
608
608
|
if (dryRun) {
|
|
609
609
|
planned.push({
|
|
610
610
|
verb: "emit-frontmatter",
|
|
@@ -617,12 +617,41 @@ async function deployFrontmatterEmit(action, dryRun, verbose, home, planned, dep
|
|
|
617
617
|
return { error: null };
|
|
618
618
|
}
|
|
619
619
|
try {
|
|
620
|
-
await (
|
|
621
|
-
|
|
620
|
+
const existingEntry = await lookupDeployment(home, action.target, deps.registry);
|
|
621
|
+
if (existingEntry &&
|
|
622
|
+
(existingEntry.kind !== "frontmatter-emit" ||
|
|
623
|
+
existingEntry.skill !== action.skill ||
|
|
624
|
+
existingEntry.agent !== action.agent)) {
|
|
625
|
+
throw new Error(`Frontmatter target "${action.target}" is already patched by skill "${existingEntry.skill}" for agent "${existingEntry.agent}" — refusing to double-patch`);
|
|
626
|
+
}
|
|
627
|
+
const frontmatterAdapter = await import("./adapters/frontmatter.js");
|
|
628
|
+
const existing = await frontmatterAdapter.readFrontmatterDocumentFile(action.target);
|
|
629
|
+
const undoPatch = computeUndoPatch(existing.attributes, action.frontmatter);
|
|
630
|
+
const patchedFrontmatter = applyMergePatch(existing.attributes, action.frontmatter);
|
|
631
|
+
const content = frontmatterAdapter.buildMarkdownDocument(patchedFrontmatter, existing.body, { hasFrontmatter: true });
|
|
632
|
+
await replaceFileAtomically(action.target, deps, (tempPath, fileOps) => fileOps.writeFile(tempPath, content, "utf-8"), async () => {
|
|
633
|
+
try {
|
|
634
|
+
await lstat(action.target);
|
|
635
|
+
}
|
|
636
|
+
catch {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
const backupPath = `${action.target}.inception-backup`;
|
|
640
|
+
await (deps.fileOps ?? defaultDeployFileOps).rm(backupPath, {
|
|
641
|
+
recursive: true,
|
|
642
|
+
force: true,
|
|
643
|
+
});
|
|
644
|
+
await (deps.fileOps ?? defaultDeployFileOps).rename(action.target, backupPath);
|
|
645
|
+
return backupPath;
|
|
646
|
+
}, () => registerDeployment(home, action.target, {
|
|
622
647
|
kind: "frontmatter-emit",
|
|
648
|
+
patch: action.frontmatter,
|
|
649
|
+
undoPatch,
|
|
650
|
+
created: !existing.exists,
|
|
651
|
+
hadFrontmatter: existing.hasFrontmatter,
|
|
623
652
|
skill: action.skill,
|
|
624
653
|
agent: action.agent,
|
|
625
|
-
}, deps.registry);
|
|
654
|
+
}, deps.registry));
|
|
626
655
|
logger.ok(label);
|
|
627
656
|
if (verbose) {
|
|
628
657
|
logger.detail(`emit-frontmatter: ${action.target}`);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { access, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { AGENT_REGISTRY
|
|
3
|
+
import { AGENT_REGISTRY } from "../config/agents.js";
|
|
4
4
|
import { dryRunPrefix, logger } from "../logger.js";
|
|
5
5
|
import { AGENT_IDS, AgentDefinitionEntrySchema, ConfigEntrySchema, FileEntrySchema, McpServerEntrySchema, } from "../schemas/manifest.js";
|
|
6
6
|
import { parseFrontmatterDocument } from "./adapters/frontmatter.js";
|
|
7
|
+
import { shouldInitIncludeAgent } from "./capabilities.js";
|
|
7
8
|
const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
8
9
|
// Ordered list: first match wins. Catch-all is applied at call site.
|
|
9
10
|
// Derived from the registry: group agents by the filename of their global
|
|
@@ -14,13 +15,11 @@ const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
|
14
15
|
const AGENT_RULES_FILE_PATTERNS = (() => {
|
|
15
16
|
const filenameToAgents = new Map();
|
|
16
17
|
for (const agent of AGENT_REGISTRY) {
|
|
18
|
+
if (!shouldInitIncludeAgent(agent.id, "agentRules", "global"))
|
|
19
|
+
continue;
|
|
17
20
|
const support = agent.agentRulesSupport;
|
|
18
21
|
if (support?.status !== "supported")
|
|
19
22
|
continue;
|
|
20
|
-
// Skip riders that require the primary — they cannot be listed independently
|
|
21
|
-
if (support.surfaceKind?.kind === "shared-via" &&
|
|
22
|
-
support.surfaceKind.requiresPrimary)
|
|
23
|
-
continue;
|
|
24
23
|
const filename = support.path.posix[support.path.posix.length - 1]?.toLowerCase();
|
|
25
24
|
if (!filename)
|
|
26
25
|
continue;
|
|
@@ -47,10 +46,11 @@ const AGENT_RULES_SUBDIRS = ["rules", "instructions", ".github"];
|
|
|
47
46
|
// Conventional subdirectories that contain agent definition files.
|
|
48
47
|
// Derived from the registry: for each agent with a supported agentDefinitions
|
|
49
48
|
// surface, extract the directory prefix from its posix path template.
|
|
49
|
+
// Includes the legacy ".github/agents" path for backward compatibility.
|
|
50
50
|
const AGENT_DEFINITION_SUBDIRS = (() => {
|
|
51
51
|
const subdirs = new Set();
|
|
52
52
|
for (const agent of AGENT_REGISTRY) {
|
|
53
|
-
const support = agent.
|
|
53
|
+
const support = agent.agentDefinitionsRepoSupport;
|
|
54
54
|
if (support?.status !== "supported")
|
|
55
55
|
continue;
|
|
56
56
|
const tmpl = support.path.posix;
|
|
@@ -62,6 +62,7 @@ const AGENT_DEFINITION_SUBDIRS = (() => {
|
|
|
62
62
|
if (dirSegs.length > 0)
|
|
63
63
|
subdirs.add(dirSegs.join("/"));
|
|
64
64
|
}
|
|
65
|
+
subdirs.add(".github/agents");
|
|
65
66
|
return Array.from(subdirs);
|
|
66
67
|
})();
|
|
67
68
|
async function hasAntigravityMcpFrontmatter(absPath) {
|
|
@@ -114,6 +115,7 @@ function resolveSkillName(relPath, name, namesSeen) {
|
|
|
114
115
|
return null;
|
|
115
116
|
}
|
|
116
117
|
function buildSkills(found, agents) {
|
|
118
|
+
const initAgents = agents.filter((agentId) => shouldInitIncludeAgent(agentId, "skills"));
|
|
117
119
|
const namesSeen = new Set();
|
|
118
120
|
const skills = [];
|
|
119
121
|
for (const { relPath, name } of found) {
|
|
@@ -121,7 +123,7 @@ function buildSkills(found, agents) {
|
|
|
121
123
|
if (skillName === null)
|
|
122
124
|
continue;
|
|
123
125
|
namesSeen.add(skillName);
|
|
124
|
-
skills.push({ name: skillName, path: relPath, agents });
|
|
126
|
+
skills.push({ name: skillName, path: relPath, agents: initAgents });
|
|
125
127
|
}
|
|
126
128
|
return skills;
|
|
127
129
|
}
|
|
@@ -215,7 +217,7 @@ function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
|
|
|
215
217
|
}
|
|
216
218
|
/**
|
|
217
219
|
* Derives the name for an agent definition entry from its file name.
|
|
218
|
-
* For GitHub Copilot's `{name}.agent.md` naming convention, strips the
|
|
220
|
+
* For GitHub Copilot's legacy `{name}.agent.md` naming convention, strips the
|
|
219
221
|
* `.agent` infix in addition to the `.md` extension.
|
|
220
222
|
*/
|
|
221
223
|
function deriveAgentDefinitionName(relPath, fileName) {
|
|
@@ -236,12 +238,15 @@ function deriveAgentDefinitionName(relPath, fileName) {
|
|
|
236
238
|
/**
|
|
237
239
|
* Maps a known agent-definition subdirectory to the agent IDs that own it.
|
|
238
240
|
* Derived from the registry by matching each agent's agentDefinitionsSupport
|
|
239
|
-
* path prefix.
|
|
241
|
+
* path prefix. Includes legacy mapping for ".github/agents".
|
|
242
|
+
* Returns null when the subdir is not recognized.
|
|
240
243
|
*/
|
|
241
244
|
function agentsForDefinitionSubdir(subdir) {
|
|
245
|
+
if (subdir === ".github/agents")
|
|
246
|
+
return ["github-copilot"];
|
|
242
247
|
const matching = [];
|
|
243
248
|
for (const agent of AGENT_REGISTRY) {
|
|
244
|
-
const support = agent.
|
|
249
|
+
const support = agent.agentDefinitionsRepoSupport;
|
|
245
250
|
if (support?.status !== "supported")
|
|
246
251
|
continue;
|
|
247
252
|
const tmpl = support.path.posix;
|
|
@@ -325,8 +330,7 @@ async function findAgentDefinitionCandidates(baseDir, skillDirRelPaths, agentRul
|
|
|
325
330
|
function buildAgentDefinitions(candidates, activeAgents, namesSeen) {
|
|
326
331
|
const definitions = [];
|
|
327
332
|
const localNamesSeen = new Set(namesSeen);
|
|
328
|
-
const definitionsCapableAgents = activeAgents.filter((id) =>
|
|
329
|
-
"unsupported");
|
|
333
|
+
const definitionsCapableAgents = activeAgents.filter((id) => shouldInitIncludeAgent(id, "agentDefinitions", "repo"));
|
|
330
334
|
for (const { relPath, name: rawName, suggestedAgents } of candidates) {
|
|
331
335
|
// Use suggested agents (from the dir that was scanned), intersected with
|
|
332
336
|
// active agents that support agentDefinitions. Fall back to all capable
|
|
@@ -351,7 +355,7 @@ function buildAgentDefinitions(candidates, activeAgents, namesSeen) {
|
|
|
351
355
|
}
|
|
352
356
|
}
|
|
353
357
|
localNamesSeen.add(name);
|
|
354
|
-
definitions.push({ name, path: relPath, agents });
|
|
358
|
+
definitions.push({ name, path: relPath, agents, scope: "repo" });
|
|
355
359
|
}
|
|
356
360
|
return definitions;
|
|
357
361
|
}
|
|
@@ -548,17 +552,7 @@ function logVerboseManifest(skills, agentRules, mcpServers, files, configs, agen
|
|
|
548
552
|
export async function runInit(options) {
|
|
549
553
|
const { directory, dryRun, force, verbose } = options;
|
|
550
554
|
const agents = options.agents ?? [...AGENT_IDS];
|
|
551
|
-
const agentRulesCapableAgents = agents.filter((id) =>
|
|
552
|
-
const support = AGENT_REGISTRY_BY_ID[id].agentRulesSupport;
|
|
553
|
-
if (!support || support.status === "unsupported")
|
|
554
|
-
return false;
|
|
555
|
-
// Exclude riders that require the primary — they cannot operate independently
|
|
556
|
-
if (support.status === "supported" &&
|
|
557
|
-
support.surfaceKind?.kind === "shared-via" &&
|
|
558
|
-
support.surfaceKind.requiresPrimary)
|
|
559
|
-
return false;
|
|
560
|
-
return true;
|
|
561
|
-
});
|
|
555
|
+
const agentRulesCapableAgents = agents.filter((id) => shouldInitIncludeAgent(id, "agentRules", "global"));
|
|
562
556
|
const manifestPath = path.join(directory, "inception.json");
|
|
563
557
|
if (!dryRun && (await manifestExists(manifestPath)) && !force) {
|
|
564
558
|
logger.error(`Error: ${manifestPath} already exists. Use --force to overwrite.`);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function isPlainObject(v: unknown): v is Record<string, unknown>;
|
|
2
|
+
export declare function computeUndoPatch(original: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown>;
|
|
3
|
+
export declare function applyMergePatch(original: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown>;
|
|
4
|
+
export declare function applyUndoPatch(current: Record<string, unknown>, undoPatch: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export function isPlainObject(v) {
|
|
2
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3
|
+
}
|
|
4
|
+
export function computeUndoPatch(original, patch) {
|
|
5
|
+
const undoPatch = {};
|
|
6
|
+
for (const key of Object.keys(patch)) {
|
|
7
|
+
const patchVal = patch[key];
|
|
8
|
+
if (isPlainObject(patchVal) && isPlainObject(original[key])) {
|
|
9
|
+
undoPatch[key] = computeUndoPatch(original[key], patchVal);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
undoPatch[key] = key in original ? original[key] : null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return undoPatch;
|
|
16
|
+
}
|
|
17
|
+
export function applyMergePatch(original, patch) {
|
|
18
|
+
const patched = { ...original };
|
|
19
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
20
|
+
if (value === null) {
|
|
21
|
+
delete patched[key];
|
|
22
|
+
}
|
|
23
|
+
else if (isPlainObject(value) && isPlainObject(patched[key])) {
|
|
24
|
+
patched[key] = applyMergePatch(patched[key], value);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
patched[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return patched;
|
|
31
|
+
}
|
|
32
|
+
export function applyUndoPatch(current, undoPatch) {
|
|
33
|
+
const restored = { ...current };
|
|
34
|
+
for (const [key, originalValue] of Object.entries(undoPatch)) {
|
|
35
|
+
if (originalValue === null) {
|
|
36
|
+
delete restored[key];
|
|
37
|
+
}
|
|
38
|
+
else if (isPlainObject(originalValue) && isPlainObject(restored[key])) {
|
|
39
|
+
restored[key] = applyUndoPatch(restored[key], originalValue);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
restored[key] = originalValue;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return restored;
|
|
46
|
+
}
|
|
@@ -40,10 +40,23 @@ async function setFilePermissions(filePath) {
|
|
|
40
40
|
function emptyRegistry() {
|
|
41
41
|
return { version: 1, deployments: {} };
|
|
42
42
|
}
|
|
43
|
+
function surfaceIdForEntry(entry) {
|
|
44
|
+
return entry.surfaceId ?? `${entry.kind}:${entry.agent}:${entry.skill}`;
|
|
45
|
+
}
|
|
43
46
|
export async function registerDeployment(home, targetPath, entry, persistence = defaultRegistryPersistence) {
|
|
44
47
|
const registry = await persistence.load(home);
|
|
48
|
+
const surfaceId = surfaceIdForEntry(entry);
|
|
49
|
+
for (const migratedTarget of entry.migratedFrom ?? []) {
|
|
50
|
+
if (migratedTarget === targetPath)
|
|
51
|
+
continue;
|
|
52
|
+
const existing = registry.deployments[migratedTarget];
|
|
53
|
+
if (existing?.surfaceId === surfaceId) {
|
|
54
|
+
delete registry.deployments[migratedTarget];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
45
57
|
registry.deployments[targetPath] = {
|
|
46
58
|
...entry,
|
|
59
|
+
surfaceId,
|
|
47
60
|
deployed: new Date().toISOString(),
|
|
48
61
|
};
|
|
49
62
|
await persistence.save(home, registry);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
4
|
+
import { describeCapabilityConfidence, planCapabilityForDeploy, } from "./capabilities.js";
|
|
4
5
|
import { resolveRuntimePaths } from "./runtime-paths.js";
|
|
5
6
|
const BUDGET_WARN_BYTES = 50 * 1024; // 50 KB
|
|
6
7
|
async function detectEnterpriseManagement(agentId, home) {
|
|
@@ -36,6 +37,39 @@ async function detectEnterpriseManagement(agentId, home) {
|
|
|
36
37
|
}
|
|
37
38
|
return null;
|
|
38
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Reads ~/.gemini/settings.json and warns when `instructionFilename` is set
|
|
42
|
+
* to a value other than "GEMINI.md". If the override is in effect, inception-
|
|
43
|
+
* engine's agentRules deployment to GEMINI.md will be silently ignored by the
|
|
44
|
+
* agent at runtime.
|
|
45
|
+
*/
|
|
46
|
+
async function detectGeminiCustomInstructionFilename(home) {
|
|
47
|
+
const settingsPath = path.join(home, ".gemini", "settings.json");
|
|
48
|
+
try {
|
|
49
|
+
const raw = await readFile(settingsPath, "utf8");
|
|
50
|
+
const parsed = JSON.parse(raw);
|
|
51
|
+
if (typeof parsed === "object" &&
|
|
52
|
+
parsed !== null &&
|
|
53
|
+
typeof parsed.instructionFilename ===
|
|
54
|
+
"string") {
|
|
55
|
+
const configured = parsed
|
|
56
|
+
.instructionFilename;
|
|
57
|
+
if (configured !== "GEMINI.md") {
|
|
58
|
+
return {
|
|
59
|
+
kind: "config-authority",
|
|
60
|
+
message: `Agent "gemini-cli": settings.json sets instructionFilename to "${configured}" but inception-engine deploys agentRules to "GEMINI.md". The deployed rules file may not be loaded by the agent. Remove the override or align the deploy target.`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const code = err.code;
|
|
67
|
+
if (code !== "ENOENT" && code !== "EACCES" && code !== "EPERM") {
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
39
73
|
function groupRulesByPath(rulesForAgent) {
|
|
40
74
|
const entriesByPath = new Map();
|
|
41
75
|
for (const entry of rulesForAgent ?? []) {
|
|
@@ -129,39 +163,117 @@ async function detectInstructionBudgetRisk(detectedAgents, manifest, sourceDir)
|
|
|
129
163
|
}
|
|
130
164
|
return warnings;
|
|
131
165
|
}
|
|
166
|
+
function pushCapabilityWarning(acc, kind, message) {
|
|
167
|
+
if (acc.seen.has(`${kind}:${message}`))
|
|
168
|
+
return;
|
|
169
|
+
acc.seen.add(`${kind}:${message}`);
|
|
170
|
+
acc.warnings.push({ kind, message });
|
|
171
|
+
}
|
|
172
|
+
function collectCapabilityWarningsForAgent(acc, agentId, capability, entryName, targetAgents, scope) {
|
|
173
|
+
const plan = planCapabilityForDeploy({
|
|
174
|
+
agentId,
|
|
175
|
+
capability,
|
|
176
|
+
entryName,
|
|
177
|
+
targetAgentIds: targetAgents,
|
|
178
|
+
scope,
|
|
179
|
+
});
|
|
180
|
+
if (plan.outcome === "warn") {
|
|
181
|
+
pushCapabilityWarning(acc, "info", plan.warning.message);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// Evaluate undocumented/planned surfaces warning
|
|
185
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
186
|
+
if (agent?.unsupportedSurfaces) {
|
|
187
|
+
for (const surface of agent.unsupportedSurfaces) {
|
|
188
|
+
if (surface.status === "planned") {
|
|
189
|
+
pushCapabilityWarning(acc, "info", `Agent "${agentId}": ${surface.reason ?? surface.plannedSurface}`);
|
|
190
|
+
}
|
|
191
|
+
else if (surface.status === "unsupported") {
|
|
192
|
+
pushCapabilityWarning(acc, "config-authority", `Agent "${agentId}": ${surface.reason ?? surface.schemaLabel}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (capability === "skills")
|
|
197
|
+
return;
|
|
198
|
+
const confidence = describeCapabilityConfidence(agentId, capability, scope);
|
|
199
|
+
if (confidence.message) {
|
|
200
|
+
pushCapabilityWarning(acc, "config-authority", confidence.message);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function collectCapabilityWarningsForTargets(acc, targetAgents, capability, entryName, scope) {
|
|
204
|
+
for (const agentId of targetAgents) {
|
|
205
|
+
collectCapabilityWarningsForAgent(acc, agentId, capability, entryName, targetAgents, scope);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function collectManifestCapabilityWarnings(manifest, detectedAgents) {
|
|
209
|
+
const acc = {
|
|
210
|
+
warnings: [],
|
|
211
|
+
seen: new Set(),
|
|
212
|
+
};
|
|
213
|
+
for (const entry of manifest.skills) {
|
|
214
|
+
collectCapabilityWarningsForTargets(acc, entry.agents.filter((agentId) => detectedAgents.includes(agentId)), "skills", entry.name);
|
|
215
|
+
}
|
|
216
|
+
for (const entry of manifest.mcpServers ?? []) {
|
|
217
|
+
collectCapabilityWarningsForTargets(acc, entry.agents.filter((agentId) => detectedAgents.includes(agentId)), "mcpServers", entry.name, entry.scope);
|
|
218
|
+
}
|
|
219
|
+
for (const entry of manifest.agentRules ?? []) {
|
|
220
|
+
collectCapabilityWarningsForTargets(acc, entry.agents.filter((agentId) => detectedAgents.includes(agentId)), "agentRules", entry.name, entry.scope);
|
|
221
|
+
}
|
|
222
|
+
for (const entry of manifest.permissions ?? []) {
|
|
223
|
+
collectCapabilityWarningsForTargets(acc, entry.agents.filter((agentId) => detectedAgents.includes(agentId)), "permissions", entry.name);
|
|
224
|
+
}
|
|
225
|
+
for (const entry of manifest.agentDefinitions ?? []) {
|
|
226
|
+
collectCapabilityWarningsForTargets(acc, entry.agents.filter((agentId) => detectedAgents.includes(agentId)), "agentDefinitions", entry.name, entry.scope);
|
|
227
|
+
}
|
|
228
|
+
return acc.warnings;
|
|
229
|
+
}
|
|
230
|
+
function detectCapabilityPlanningWarnings(manifest, detectedAgents) {
|
|
231
|
+
return collectManifestCapabilityWarnings(manifest, detectedAgents);
|
|
232
|
+
}
|
|
233
|
+
async function collectAgentWarnings(agentId, manifest, home) {
|
|
234
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
235
|
+
if (!agent)
|
|
236
|
+
return [];
|
|
237
|
+
const warnings = [];
|
|
238
|
+
const skillConfidence = describeCapabilityConfidence(agentId, "skills");
|
|
239
|
+
if (skillConfidence.confidence === "implementation-only" ||
|
|
240
|
+
skillConfidence.confidence === "provisional") {
|
|
241
|
+
warnings.push({
|
|
242
|
+
kind: "config-authority",
|
|
243
|
+
message: skillConfidence.message ??
|
|
244
|
+
`Agent "${agentId}" skill support is ${skillConfidence.confidence}.`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const enterpriseWarning = await detectEnterpriseManagement(agentId, home);
|
|
248
|
+
if (enterpriseWarning) {
|
|
249
|
+
warnings.push({
|
|
250
|
+
kind: "policy",
|
|
251
|
+
message: `Agent "${agentId}": ${enterpriseWarning}`,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
else if (agent.policyNote &&
|
|
255
|
+
!agent.policyNote.includes("Organization policies may override")) {
|
|
256
|
+
warnings.push({
|
|
257
|
+
kind: "policy",
|
|
258
|
+
message: `Agent "${agentId}": ${agent.policyNote}`,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
if (agentId === "gemini-cli") {
|
|
262
|
+
const hasGeminiRules = (manifest.agentRules ?? []).some((e) => e.agents.includes("gemini-cli"));
|
|
263
|
+
if (hasGeminiRules) {
|
|
264
|
+
const filenameWarning = await detectGeminiCustomInstructionFilename(home);
|
|
265
|
+
if (filenameWarning)
|
|
266
|
+
warnings.push(filenameWarning);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return warnings;
|
|
270
|
+
}
|
|
132
271
|
export async function runPreflight(options, manifest, home, detectedAgents) {
|
|
133
272
|
const warnings = [];
|
|
134
273
|
for (const agentId of detectedAgents) {
|
|
135
|
-
|
|
136
|
-
if (!agent)
|
|
137
|
-
continue;
|
|
138
|
-
if (agent.provenance.skills === "implementation-only") {
|
|
139
|
-
warnings.push({
|
|
140
|
-
kind: "config-authority",
|
|
141
|
-
message: `Agent "${agentId}" skill support is implementation-only: paths are derived from source inspection, not published documentation.`,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
else if (agent.provenance.skills === "provisional") {
|
|
145
|
-
warnings.push({
|
|
146
|
-
kind: "config-authority",
|
|
147
|
-
message: `Agent "${agentId}" skill support is provisional: behavior has not been independently verified.`,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
const enterpriseWarning = await detectEnterpriseManagement(agentId, home);
|
|
151
|
-
if (enterpriseWarning) {
|
|
152
|
-
warnings.push({
|
|
153
|
-
kind: "policy",
|
|
154
|
-
message: `Agent "${agentId}": ${enterpriseWarning}`,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
else if (agent.policyNote &&
|
|
158
|
-
!agent.policyNote.includes("Organization policies may override")) {
|
|
159
|
-
warnings.push({
|
|
160
|
-
kind: "policy",
|
|
161
|
-
message: `Agent "${agentId}": ${agent.policyNote}`,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
274
|
+
warnings.push(...(await collectAgentWarnings(agentId, manifest, home)));
|
|
164
275
|
}
|
|
276
|
+
warnings.push(...detectCapabilityPlanningWarnings(manifest, detectedAgents));
|
|
165
277
|
warnings.push(...detectInstructionPrecedence(detectedAgents, manifest));
|
|
166
278
|
warnings.push(...(await detectInstructionBudgetRisk(detectedAgents, manifest, options.directory)));
|
|
167
279
|
return warnings;
|