@kuznai/inception-engine 0.13.0 → 0.14.1
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/dist/core/deploy.js +56 -122
- package/dist/core/init.js +127 -8
- package/package.json +1 -1
package/dist/core/deploy.js
CHANGED
|
@@ -4,9 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
5
5
|
import { UserError } from "../errors.js";
|
|
6
6
|
import { logger } from "../logger.js";
|
|
7
|
-
import { writeFrontmatterFile } from "./adapters/frontmatter.js";
|
|
8
7
|
import { compileAdapterActions } from "./adapters/index.js";
|
|
9
|
-
import { applyTomlMcpPatch } from "./adapters/toml.js";
|
|
10
8
|
import { lookupDeployment, registerDeployment, verifyDeployment, } from "./ownership.js";
|
|
11
9
|
import { getDeployMethod, resolveAgentSkillPath } from "./resolve.js";
|
|
12
10
|
import { resolveTargetTemplate } from "./runtime-paths.js";
|
|
@@ -82,18 +80,26 @@ function detectCollisions(actions) {
|
|
|
82
80
|
}
|
|
83
81
|
return warnings;
|
|
84
82
|
}
|
|
85
|
-
function detectAmbiguities(detectedAgents,
|
|
83
|
+
function detectAmbiguities(detectedAgents, manifest) {
|
|
86
84
|
const warnings = [];
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (
|
|
85
|
+
if (!(detectedAgents.includes("gemini-cli") &&
|
|
86
|
+
detectedAgents.includes("antigravity"))) {
|
|
87
|
+
return warnings;
|
|
88
|
+
}
|
|
89
|
+
const bothAgents = (agents) => agents.includes("gemini-cli") && agents.includes("antigravity");
|
|
90
|
+
for (const entry of manifest.agentRules ?? []) {
|
|
91
|
+
if (bothAgents(entry.agents)) {
|
|
92
|
+
warnings.push({
|
|
93
|
+
kind: "ambiguity",
|
|
94
|
+
message: `Both "gemini-cli" and "antigravity" are listed in agentRules entry "${entry.name}". They share a GEMINI.md-backed instruction shared surface — verify that deploying to both does not create conflicting behavior.`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const entry of manifest.mcpServers ?? []) {
|
|
99
|
+
if (bothAgents(entry.agents)) {
|
|
94
100
|
warnings.push({
|
|
95
101
|
kind: "ambiguity",
|
|
96
|
-
message:
|
|
102
|
+
message: `Both "gemini-cli" and "antigravity" are listed in mcpServers entry "${entry.name}". "gemini-cli" writes to ~/.gemini/settings.json as a shared surface — verify that deploying to both does not produce conflicting MCP server behavior.`,
|
|
97
103
|
});
|
|
98
104
|
}
|
|
99
105
|
}
|
|
@@ -151,7 +157,7 @@ async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, real
|
|
|
151
157
|
}
|
|
152
158
|
return actions;
|
|
153
159
|
}
|
|
154
|
-
function planConfigPatchActions(manifest, detectedAgents, home
|
|
160
|
+
function planConfigPatchActions(manifest, detectedAgents, home) {
|
|
155
161
|
const actions = [];
|
|
156
162
|
for (const configEntry of manifest.configs ?? []) {
|
|
157
163
|
for (const agentId of configEntry.agents) {
|
|
@@ -164,7 +170,7 @@ function planConfigPatchActions(manifest, detectedAgents, home, repo) {
|
|
|
164
170
|
kind: "config-patch",
|
|
165
171
|
skill: configEntry.name,
|
|
166
172
|
agent: agentId,
|
|
167
|
-
target: resolveTargetTemplate(configEntry.target, home
|
|
173
|
+
target: resolveTargetTemplate(configEntry.target, home),
|
|
168
174
|
patch: configEntry.patch,
|
|
169
175
|
confidence: agent.provenance.skills,
|
|
170
176
|
});
|
|
@@ -184,44 +190,56 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
|
184
190
|
const actions = [
|
|
185
191
|
...(await planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
186
192
|
...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
187
|
-
...planConfigPatchActions(manifest, detectedAgents, home
|
|
193
|
+
...planConfigPatchActions(manifest, detectedAgents, home),
|
|
188
194
|
];
|
|
189
|
-
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home
|
|
195
|
+
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home);
|
|
190
196
|
actions.push(...adapterResult.actions);
|
|
191
197
|
const warnings = [
|
|
192
|
-
...detectAmbiguities(detectedAgents,
|
|
198
|
+
...detectAmbiguities(detectedAgents, manifest),
|
|
193
199
|
...detectCollisions(actions),
|
|
194
200
|
...adapterResult.warnings,
|
|
195
201
|
];
|
|
196
202
|
return { actions, warnings };
|
|
197
203
|
}
|
|
198
|
-
async function dispatchDeployAction(action, dryRun, verbose, home, planned, deps) {
|
|
199
|
-
switch (action.kind) {
|
|
200
|
-
case "skill-dir":
|
|
201
|
-
return deploySkillDir(action, dryRun, verbose, home, planned, deps);
|
|
202
|
-
case "file-write":
|
|
203
|
-
return deployFileWrite(action, dryRun, verbose, home, planned, deps);
|
|
204
|
-
case "config-patch":
|
|
205
|
-
return deployConfigPatch(action, dryRun, verbose, home, planned, deps);
|
|
206
|
-
case "toml-patch":
|
|
207
|
-
return deployTomlPatch(action, dryRun, verbose, home, planned, deps);
|
|
208
|
-
case "frontmatter-emit":
|
|
209
|
-
return deployFrontmatterEmit(action, dryRun, verbose, home, planned, deps);
|
|
210
|
-
default:
|
|
211
|
-
throw new Error(`Unhandled deploy action kind: ${action.kind}`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
204
|
export async function executeDeploy(actions, dryRun, verbose, home, deps = {}) {
|
|
215
205
|
let succeeded = 0;
|
|
216
206
|
const failed = [];
|
|
217
207
|
const planned = [];
|
|
218
208
|
for (const action of actions) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
209
|
+
switch (action.kind) {
|
|
210
|
+
case "skill-dir": {
|
|
211
|
+
const result = await deploySkillDir(action, dryRun, verbose, home, planned, deps);
|
|
212
|
+
if (result.error === null) {
|
|
213
|
+
succeeded++;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
failed.push({ action, error: result.error });
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
case "file-write": {
|
|
221
|
+
const result = await deployFileWrite(action, dryRun, verbose, home, planned, deps);
|
|
222
|
+
if (result.error === null) {
|
|
223
|
+
succeeded++;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
failed.push({ action, error: result.error });
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "config-patch": {
|
|
231
|
+
const result = await deployConfigPatch(action, dryRun, verbose, home, planned, deps);
|
|
232
|
+
if (result.error === null) {
|
|
233
|
+
succeeded++;
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
failed.push({ action, error: result.error });
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
default: {
|
|
241
|
+
throw new Error(`Unhandled deploy action kind: ${action}`);
|
|
242
|
+
}
|
|
225
243
|
}
|
|
226
244
|
}
|
|
227
245
|
return { succeeded, failed, planned };
|
|
@@ -589,87 +607,3 @@ async function backupExisting(targetPath, verbose, home, expected, deps) {
|
|
|
589
607
|
await rename(targetPath, backupPath);
|
|
590
608
|
return backupPath;
|
|
591
609
|
}
|
|
592
|
-
async function deployTomlPatch(action, dryRun, verbose, home, planned, deps) {
|
|
593
|
-
const label = `${action.skill} -> ${action.agent}`;
|
|
594
|
-
if (dryRun) {
|
|
595
|
-
planned.push({
|
|
596
|
-
verb: "patch-toml",
|
|
597
|
-
kind: "toml-patch",
|
|
598
|
-
skill: action.skill,
|
|
599
|
-
agent: action.agent,
|
|
600
|
-
target: action.target,
|
|
601
|
-
confidence: action.confidence,
|
|
602
|
-
});
|
|
603
|
-
return { error: null };
|
|
604
|
-
}
|
|
605
|
-
try {
|
|
606
|
-
// Guard against double-patching by a different skill/agent.
|
|
607
|
-
const existingEntry = await lookupDeployment(home, action.target, deps.registry);
|
|
608
|
-
if (existingEntry &&
|
|
609
|
-
(existingEntry.skill !== action.skill ||
|
|
610
|
-
existingEntry.agent !== action.agent)) {
|
|
611
|
-
throw new Error(`Config "${action.target}" is already patched by skill "${existingEntry.skill}" for agent "${existingEntry.agent}" — refusing to double-patch`);
|
|
612
|
-
}
|
|
613
|
-
await applyTomlMcpPatch(action.target, action.skill, action.config);
|
|
614
|
-
await registerDeployment(home, action.target, {
|
|
615
|
-
kind: "config-patch",
|
|
616
|
-
patch: { mcpServers: { [action.skill]: action.config } },
|
|
617
|
-
undoPatch: { mcpServers: { [action.skill]: null } },
|
|
618
|
-
skill: action.skill,
|
|
619
|
-
agent: action.agent,
|
|
620
|
-
}, deps.registry);
|
|
621
|
-
logger.ok(label);
|
|
622
|
-
if (verbose) {
|
|
623
|
-
logger.detail(`patch-toml: wrote [mcpServers.${action.skill}] to ${action.target}`);
|
|
624
|
-
}
|
|
625
|
-
return { error: null };
|
|
626
|
-
}
|
|
627
|
-
catch (err) {
|
|
628
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
629
|
-
logger.fail(label, msg);
|
|
630
|
-
return { error: msg };
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
async function deployFrontmatterEmit(action, dryRun, verbose, home, planned, deps) {
|
|
634
|
-
const label = `${action.skill} -> ${action.agent}`;
|
|
635
|
-
if (dryRun) {
|
|
636
|
-
planned.push({
|
|
637
|
-
verb: "emit-frontmatter",
|
|
638
|
-
kind: "frontmatter-emit",
|
|
639
|
-
skill: action.skill,
|
|
640
|
-
agent: action.agent,
|
|
641
|
-
target: action.target,
|
|
642
|
-
frontmatter: action.frontmatter,
|
|
643
|
-
confidence: action.confidence,
|
|
644
|
-
});
|
|
645
|
-
return { error: null };
|
|
646
|
-
}
|
|
647
|
-
try {
|
|
648
|
-
// Guard against double-patching by a different skill/agent.
|
|
649
|
-
const existingEntry = await lookupDeployment(home, action.target, deps.registry);
|
|
650
|
-
if (existingEntry &&
|
|
651
|
-
(existingEntry.skill !== action.skill ||
|
|
652
|
-
existingEntry.agent !== action.agent)) {
|
|
653
|
-
throw new Error(`File "${action.target}" is already managed by skill "${existingEntry.skill}" for agent "${existingEntry.agent}" — refusing to overwrite`);
|
|
654
|
-
}
|
|
655
|
-
await writeFrontmatterFile(action.target, action.frontmatter, {
|
|
656
|
-
preserveBody: true,
|
|
657
|
-
});
|
|
658
|
-
await registerDeployment(home, action.target, {
|
|
659
|
-
kind: "file-write",
|
|
660
|
-
source: action.target,
|
|
661
|
-
skill: action.skill,
|
|
662
|
-
agent: action.agent,
|
|
663
|
-
}, deps.registry);
|
|
664
|
-
logger.ok(label);
|
|
665
|
-
if (verbose) {
|
|
666
|
-
logger.detail(`emit-frontmatter: wrote ${action.target}`);
|
|
667
|
-
}
|
|
668
|
-
return { error: null };
|
|
669
|
-
}
|
|
670
|
-
catch (err) {
|
|
671
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
672
|
-
logger.fail(label, msg);
|
|
673
|
-
return { error: msg };
|
|
674
|
-
}
|
|
675
|
-
}
|
package/dist/core/init.js
CHANGED
|
@@ -3,6 +3,29 @@ import path from "node:path";
|
|
|
3
3
|
import { dryRunPrefix, logger } from "../logger.js";
|
|
4
4
|
import { AGENT_IDS } from "../schemas/manifest.js";
|
|
5
5
|
const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
6
|
+
// Ordered list: first match wins. Catch-all is applied at call site.
|
|
7
|
+
const AGENT_RULES_FILE_PATTERNS = [
|
|
8
|
+
{
|
|
9
|
+
fileNames: ["claude.md", "claude-instructions.md"],
|
|
10
|
+
agents: ["claude-code"],
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
fileNames: ["agents.md", "agents-instructions.md"],
|
|
14
|
+
agents: ["codex", "opencode"],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
fileNames: ["gemini.md", "gemini-instructions.md"],
|
|
18
|
+
agents: ["gemini-cli", "antigravity"],
|
|
19
|
+
},
|
|
20
|
+
{ fileNames: ["copilot-instructions.md"], agents: ["github-copilot"] },
|
|
21
|
+
];
|
|
22
|
+
// Conventional subdirectory names to scan one level deep for .md files.
|
|
23
|
+
const AGENT_RULES_SUBDIRS = [
|
|
24
|
+
"rules",
|
|
25
|
+
"instructions",
|
|
26
|
+
".github",
|
|
27
|
+
".agents/rules",
|
|
28
|
+
];
|
|
6
29
|
async function findSkillDirs(baseDir, dir, found) {
|
|
7
30
|
let entries;
|
|
8
31
|
try {
|
|
@@ -52,6 +75,91 @@ function buildSkills(found, agents) {
|
|
|
52
75
|
}
|
|
53
76
|
return skills;
|
|
54
77
|
}
|
|
78
|
+
function defaultAgentsForFile(fileName, fallback) {
|
|
79
|
+
const lower = fileName.toLowerCase();
|
|
80
|
+
for (const { fileNames, agents } of AGENT_RULES_FILE_PATTERNS) {
|
|
81
|
+
if (fileNames.includes(lower))
|
|
82
|
+
return agents;
|
|
83
|
+
}
|
|
84
|
+
return fallback;
|
|
85
|
+
}
|
|
86
|
+
function isInsideSkillDir(relPath, skillDirRelPaths) {
|
|
87
|
+
const parentRelPath = path.dirname(relPath).split(path.sep).join("/");
|
|
88
|
+
if (skillDirRelPaths.has(parentRelPath))
|
|
89
|
+
return true;
|
|
90
|
+
return [...skillDirRelPaths].some((sp) => parentRelPath === sp || parentRelPath.startsWith(`${sp}/`));
|
|
91
|
+
}
|
|
92
|
+
function deriveAgentRulesName(relPath, fileName) {
|
|
93
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
94
|
+
const baseName = path.basename(fileName, ext);
|
|
95
|
+
const rawName = baseName.toLowerCase().replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
96
|
+
if (!SAFE_NAME_RE.test(rawName)) {
|
|
97
|
+
logger.warn("init", `Skipping "${relPath}": could not derive a valid agentRules name`);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return rawName;
|
|
101
|
+
}
|
|
102
|
+
async function scanDirForMarkdown(dir, baseDir, skillDirRelPaths, seen, candidates) {
|
|
103
|
+
let entries;
|
|
104
|
+
try {
|
|
105
|
+
entries = await readdir(dir, { withFileTypes: true, encoding: "utf-8" });
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
if (!entry.isFile())
|
|
112
|
+
continue;
|
|
113
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
114
|
+
if (ext !== ".md" && ext !== ".markdown")
|
|
115
|
+
continue;
|
|
116
|
+
const absPath = path.join(dir, entry.name);
|
|
117
|
+
const relPath = path.relative(baseDir, absPath).split(path.sep).join("/");
|
|
118
|
+
if (seen.has(relPath) || isInsideSkillDir(relPath, skillDirRelPaths))
|
|
119
|
+
continue;
|
|
120
|
+
const name = deriveAgentRulesName(relPath, entry.name);
|
|
121
|
+
if (name === null)
|
|
122
|
+
continue;
|
|
123
|
+
seen.add(relPath);
|
|
124
|
+
candidates.push({ relPath, name, defaultAgents: [] });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function findAgentRulesCandidates(baseDir, skillDirRelPaths) {
|
|
128
|
+
const candidates = [];
|
|
129
|
+
const seen = new Set();
|
|
130
|
+
await scanDirForMarkdown(baseDir, baseDir, skillDirRelPaths, seen, candidates);
|
|
131
|
+
for (const subdir of AGENT_RULES_SUBDIRS) {
|
|
132
|
+
await scanDirForMarkdown(path.join(baseDir, subdir), baseDir, skillDirRelPaths, seen, candidates);
|
|
133
|
+
}
|
|
134
|
+
return candidates;
|
|
135
|
+
}
|
|
136
|
+
function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
|
|
137
|
+
const rules = [];
|
|
138
|
+
const namesSeen = new Set(skillNamesSeen);
|
|
139
|
+
for (const { relPath, name: rawName } of candidates) {
|
|
140
|
+
const fileName = path.basename(relPath);
|
|
141
|
+
const defaultAgents = defaultAgentsForFile(fileName, activeAgents);
|
|
142
|
+
// Intersect with active agents; fall back to full active list if empty
|
|
143
|
+
const intersection = defaultAgents.filter((a) => activeAgents.includes(a));
|
|
144
|
+
const agents = intersection.length > 0 ? intersection : activeAgents;
|
|
145
|
+
// Resolve name collision with skill names
|
|
146
|
+
let name = rawName;
|
|
147
|
+
if (namesSeen.has(name)) {
|
|
148
|
+
const candidate = `${name}-rules`;
|
|
149
|
+
if (SAFE_NAME_RE.test(candidate)) {
|
|
150
|
+
logger.warn("init", `agentRules name "${name}" collides with a skill name; using "${candidate}"`);
|
|
151
|
+
name = candidate;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
logger.warn("init", `Skipping "${relPath}": name "${name}" collides with a skill name and fallback is invalid`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
namesSeen.add(name);
|
|
159
|
+
rules.push({ name, path: relPath, agents });
|
|
160
|
+
}
|
|
161
|
+
return rules;
|
|
162
|
+
}
|
|
55
163
|
async function manifestExists(manifestPath) {
|
|
56
164
|
try {
|
|
57
165
|
await access(manifestPath);
|
|
@@ -73,27 +181,38 @@ export async function runInit(options) {
|
|
|
73
181
|
await findSkillDirs(directory, directory, found);
|
|
74
182
|
if (found.length === 0) {
|
|
75
183
|
logger.info("No skill directories found (looking for directories containing SKILL.md).");
|
|
76
|
-
return 0;
|
|
77
184
|
}
|
|
78
185
|
const skills = buildSkills(found, agents);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const manifest = {
|
|
186
|
+
const skillNamesSeen = new Set(skills.map((s) => s.name));
|
|
187
|
+
const skillDirRelPaths = new Set(found.map((f) => f.relPath));
|
|
188
|
+
const agentRulesCandidates = await findAgentRulesCandidates(directory, skillDirRelPaths);
|
|
189
|
+
const agentRules = buildAgentRules(agentRulesCandidates, agents, skillNamesSeen);
|
|
190
|
+
const manifest = {
|
|
191
|
+
skills,
|
|
192
|
+
files: [],
|
|
193
|
+
configs: [],
|
|
194
|
+
mcpServers: [],
|
|
195
|
+
agentRules,
|
|
196
|
+
};
|
|
84
197
|
const json = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
85
198
|
if (dryRun) {
|
|
86
|
-
logger.info(`${dryRunPrefix(true)}Would write ${manifestPath} with ${skills.length} skill(s):`);
|
|
199
|
+
logger.info(`${dryRunPrefix(true)}Would write ${manifestPath} with ${skills.length} skill(s) and ${agentRules.length} agentRule(s):`);
|
|
87
200
|
logger.info("");
|
|
88
201
|
logger.info(json);
|
|
89
202
|
return 0;
|
|
90
203
|
}
|
|
91
204
|
await writeFile(manifestPath, json, "utf-8");
|
|
92
|
-
logger.info(`Generated ${manifestPath} with ${skills.length} skill(s).`);
|
|
205
|
+
logger.info(`Generated ${manifestPath} with ${skills.length} skill(s) and ${agentRules.length} agentRule(s).`);
|
|
93
206
|
if (verbose) {
|
|
94
207
|
for (const s of skills) {
|
|
95
208
|
logger.detail(`${s.name} → ${s.path}`);
|
|
96
209
|
}
|
|
210
|
+
if (agentRules.length > 0) {
|
|
211
|
+
logger.detail("agentRules:");
|
|
212
|
+
for (const r of agentRules) {
|
|
213
|
+
logger.detail(` ${r.name} → ${r.path} [${r.agents.join(", ")}]`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
97
216
|
}
|
|
98
217
|
return 0;
|
|
99
218
|
}
|