@kuznai/inception-engine 0.18.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 +72 -43
- package/dist/{config → src/config}/agents.js +174 -24
- package/dist/src/core/adapters/agent-definitions.d.ts +8 -0
- 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/index.d.ts +1 -1
- package/dist/{core → src/core}/adapters/index.js +4 -4
- package/dist/{core → src/core}/adapters/mcp.d.ts +2 -2
- package/dist/{core → src/core}/adapters/mcp.js +21 -24
- package/dist/{core → src/core}/adapters/permissions.js +16 -20
- package/dist/{core → src/core}/adapters/rules.d.ts +2 -2
- package/dist/src/core/adapters/rules.js +126 -0
- package/dist/src/core/capabilities.d.ts +47 -0
- package/dist/src/core/capabilities.js +280 -0
- package/dist/{core → src/core}/deploy.d.ts +1 -1
- package/dist/{core → src/core}/deploy.js +273 -92
- package/dist/{core → src/core}/init.js +126 -52
- 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.d.ts +2 -2
- package/dist/{core → src/core}/ownership.js +13 -0
- package/dist/{core → src/core}/preflight.d.ts +1 -1
- package/dist/src/core/preflight.js +280 -0
- package/dist/{core → src/core}/resolve.d.ts +1 -1
- package/dist/{core → src/core}/resolve.js +7 -4
- package/dist/{core → src/core}/revert.js +95 -33
- package/dist/{core → src/core}/runtime-paths.d.ts +2 -1
- package/dist/{core → src/core}/runtime-paths.js +30 -23
- package/dist/{core → src/core}/validation.d.ts +10 -1
- package/dist/{core → src/core}/validation.js +75 -0
- package/dist/src/formatters.d.ts +5 -0
- package/dist/src/formatters.js +58 -0
- package/dist/{index.js → src/index.js} +8 -24
- package/dist/src/logger.js +62 -0
- package/dist/{schemas → src/schemas}/manifest.d.ts +22 -0
- package/dist/{schemas → src/schemas}/manifest.js +19 -5
- package/dist/{schemas → src/schemas}/registry.d.ts +74 -0
- package/dist/{schemas → src/schemas}/registry.js +18 -3
- package/dist/{types.d.ts → src/types.d.ts} +60 -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.d.ts +0 -20
- package/dist/core/adapters/agent-definitions.js +0 -90
- package/dist/core/adapters/rules.js +0 -103
- package/dist/core/preflight.js +0 -98
- package/dist/logger.js +0 -61
- /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/permissions.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}/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}/revert.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/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/{logger.d.ts → src/logger.d.ts} +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
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { constants } from "node:fs";
|
|
2
2
|
import { access, copyFile, cp, lstat, mkdir, readFile, realpath, rename, rm, symlink, unlink, writeFile, } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
4
|
+
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 = [];
|
|
@@ -80,47 +51,156 @@ function detectCollisions(actions) {
|
|
|
80
51
|
}
|
|
81
52
|
return warnings;
|
|
82
53
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Finds all (primary, rider) pairs among detected agents where the rider has
|
|
56
|
+
* at least one surface annotated as `shared-via` the primary and the primary
|
|
57
|
+
* is also detected. Used to drive ambiguity warnings without hardcoding agent
|
|
58
|
+
* IDs.
|
|
59
|
+
*/
|
|
60
|
+
function findSharedSurfacePairs(detectedAgents) {
|
|
61
|
+
const pairs = [];
|
|
62
|
+
for (const agent of AGENT_REGISTRY) {
|
|
63
|
+
if (!detectedAgents.includes(agent.id))
|
|
64
|
+
continue;
|
|
65
|
+
const surfaces = [
|
|
66
|
+
agent.agentRulesSupport,
|
|
67
|
+
agent.agentRulesRepoSupport,
|
|
68
|
+
agent.agentRulesWorkspaceSupport,
|
|
69
|
+
agent.mcpSupport,
|
|
70
|
+
agent.agentDefinitionsSupport,
|
|
71
|
+
];
|
|
72
|
+
for (const surface of surfaces) {
|
|
73
|
+
if (surface?.status === "supported" &&
|
|
74
|
+
surface.surfaceKind?.kind === "shared-via") {
|
|
75
|
+
const via = surface.surfaceKind.via;
|
|
76
|
+
if (detectedAgents.includes(via) &&
|
|
77
|
+
!pairs.some((p) => p.primary === via && p.rider === agent.id)) {
|
|
78
|
+
pairs.push({ primary: via, rider: agent.id });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
88
82
|
}
|
|
89
|
-
|
|
83
|
+
return pairs;
|
|
84
|
+
}
|
|
85
|
+
function resolveAgentRulesSupportForScope(agentId, scope) {
|
|
86
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
87
|
+
if (scope === "repo")
|
|
88
|
+
return agent?.agentRulesRepoSupport ?? agent?.agentRulesSupport;
|
|
89
|
+
if (scope === "workspace") {
|
|
90
|
+
return (agent?.agentRulesWorkspaceSupport ??
|
|
91
|
+
agent?.agentRulesRepoSupport ??
|
|
92
|
+
agent?.agentRulesSupport);
|
|
93
|
+
}
|
|
94
|
+
return agent?.agentRulesSupport;
|
|
95
|
+
}
|
|
96
|
+
function checkPairAgentRuleAmbiguities(manifest, primary, rider) {
|
|
97
|
+
const warnings = [];
|
|
90
98
|
for (const entry of manifest.agentRules ?? []) {
|
|
91
|
-
if (
|
|
99
|
+
if (!(entry.agents.includes(primary) && entry.agents.includes(rider)))
|
|
100
|
+
continue;
|
|
101
|
+
const primarySupport = resolveAgentRulesSupportForScope(primary, entry.scope);
|
|
102
|
+
const riderSupport = resolveAgentRulesSupportForScope(rider, entry.scope);
|
|
103
|
+
if (primarySupport?.status === "supported" &&
|
|
104
|
+
riderSupport?.status === "supported" &&
|
|
105
|
+
JSON.stringify(primarySupport.path) === JSON.stringify(riderSupport.path)) {
|
|
92
106
|
warnings.push({
|
|
93
107
|
kind: "ambiguity",
|
|
94
|
-
message: `Both "
|
|
108
|
+
message: `Both "${primary}" and "${rider}" are listed in agentRules entry "${entry.name}". Both target the same surface — listing both is redundant but harmless; deduplication ensures only one write action is emitted.`,
|
|
95
109
|
});
|
|
96
110
|
}
|
|
97
111
|
}
|
|
112
|
+
return warnings;
|
|
113
|
+
}
|
|
114
|
+
function checkPairMcpAmbiguities(manifest, primary, rider) {
|
|
115
|
+
const warnings = [];
|
|
116
|
+
const primaryMcp = AGENT_REGISTRY_BY_ID[primary]?.mcpSupport;
|
|
117
|
+
const riderMcp = AGENT_REGISTRY_BY_ID[rider]?.mcpSupport;
|
|
118
|
+
if (primaryMcp?.status !== "supported" || riderMcp?.status !== "supported")
|
|
119
|
+
return warnings;
|
|
120
|
+
for (const entry of manifest.mcpServers ?? []) {
|
|
121
|
+
if (!(entry.agents.includes(primary) && entry.agents.includes(rider)))
|
|
122
|
+
continue;
|
|
123
|
+
warnings.push({
|
|
124
|
+
kind: "ambiguity",
|
|
125
|
+
message: `Both "${primary}" and "${rider}" are listed in mcpServers entry "${entry.name}". "${primary}" writes to a shared MCP surface — verify that deploying to both does not produce conflicting MCP server behavior.`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return warnings;
|
|
129
|
+
}
|
|
130
|
+
function checkPairAgentDefinitionAmbiguities(manifest, primary, rider) {
|
|
131
|
+
const warnings = [];
|
|
132
|
+
for (const entry of manifest.agentDefinitions ?? []) {
|
|
133
|
+
if (!(entry.agents.includes(primary) && entry.agents.includes(rider)))
|
|
134
|
+
continue;
|
|
135
|
+
warnings.push({
|
|
136
|
+
kind: "ambiguity",
|
|
137
|
+
message: `Both "${primary}" and "${rider}" are listed in agentDefinitions entry "${entry.name}". They write to distinct surfaces — verify that this behavioral divergence is intended.`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return warnings;
|
|
141
|
+
}
|
|
142
|
+
function detectAmbiguities(detectedAgents, manifest) {
|
|
143
|
+
const pairs = findSharedSurfacePairs(detectedAgents);
|
|
144
|
+
if (pairs.length === 0)
|
|
145
|
+
return [];
|
|
146
|
+
const warnings = [];
|
|
147
|
+
for (const { primary, rider } of pairs) {
|
|
148
|
+
warnings.push(...checkPairAgentRuleAmbiguities(manifest, primary, rider));
|
|
149
|
+
warnings.push(...checkPairMcpAmbiguities(manifest, primary, rider));
|
|
150
|
+
warnings.push(...checkPairAgentDefinitionAmbiguities(manifest, primary, rider));
|
|
151
|
+
}
|
|
152
|
+
return warnings;
|
|
153
|
+
}
|
|
154
|
+
function checkAntigravityPathCollisions(manifest) {
|
|
155
|
+
const warnings = [];
|
|
156
|
+
const defNames = new Set((manifest.agentDefinitions ?? [])
|
|
157
|
+
.filter((e) => e.agents.includes("antigravity"))
|
|
158
|
+
.map((e) => e.name));
|
|
98
159
|
for (const entry of manifest.mcpServers ?? []) {
|
|
99
|
-
if (
|
|
160
|
+
if (!entry.agents.includes("antigravity"))
|
|
161
|
+
continue;
|
|
162
|
+
if (defNames.has(entry.name)) {
|
|
100
163
|
warnings.push({
|
|
101
|
-
kind: "
|
|
102
|
-
message: `
|
|
164
|
+
kind: "collision",
|
|
165
|
+
message: `agentDefinitions entry "${entry.name}" and mcpServers entry "${entry.name}" for agent "antigravity" both resolve to {repo}/.agents/rules/${entry.name}.md — one will silently overwrite the other; use different names or remove one entry`,
|
|
103
166
|
});
|
|
104
167
|
}
|
|
105
168
|
}
|
|
106
169
|
return warnings;
|
|
107
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
|
+
}
|
|
108
177
|
async function planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home) {
|
|
109
178
|
const method = getDeployMethod();
|
|
110
179
|
const actions = [];
|
|
111
|
-
|
|
180
|
+
const warnings = [];
|
|
181
|
+
async function planSkillEntry(skill) {
|
|
112
182
|
const targetAgents = skill.agents.filter((agentId) => detectedAgents.includes(agentId));
|
|
113
183
|
if (targetAgents.length === 0)
|
|
114
|
-
|
|
184
|
+
return;
|
|
115
185
|
const source = path.resolve(sourceDir, skill.path);
|
|
116
186
|
await validateSourcePath(source, skill.path, resolvedSourceDir, realRoot);
|
|
117
187
|
await validateSkillContract(source, skill.path);
|
|
118
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;
|
|
119
201
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
120
202
|
if (!agent)
|
|
121
203
|
continue;
|
|
122
|
-
if (!agent.skills)
|
|
123
|
-
continue;
|
|
124
204
|
actions.push({
|
|
125
205
|
kind: "skill-dir",
|
|
126
206
|
skill: skill.name,
|
|
@@ -128,13 +208,16 @@ async function planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realR
|
|
|
128
208
|
source,
|
|
129
209
|
target: resolveAgentSkillPath(agent, skill.name, home),
|
|
130
210
|
method,
|
|
131
|
-
confidence:
|
|
211
|
+
confidence: plan.confidence ?? "provisional",
|
|
132
212
|
});
|
|
133
213
|
}
|
|
134
214
|
}
|
|
135
|
-
|
|
215
|
+
for (const skill of manifest.skills) {
|
|
216
|
+
await planSkillEntry(skill);
|
|
217
|
+
}
|
|
218
|
+
return { actions, warnings };
|
|
136
219
|
}
|
|
137
|
-
async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home) {
|
|
220
|
+
async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace) {
|
|
138
221
|
const actions = [];
|
|
139
222
|
for (const fileEntry of manifest.files ?? []) {
|
|
140
223
|
const targetAgents = fileEntry.agents.filter((agentId) => detectedAgents.includes(agentId));
|
|
@@ -152,14 +235,14 @@ async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, real
|
|
|
152
235
|
skill: fileEntry.name,
|
|
153
236
|
agent: agentId,
|
|
154
237
|
source,
|
|
155
|
-
target: resolveTargetTemplate(fileEntry.target, home),
|
|
238
|
+
target: resolveTargetTemplate(fileEntry.target, home, repo, workspace),
|
|
156
239
|
confidence: agent.provenance.skills,
|
|
157
240
|
});
|
|
158
241
|
}
|
|
159
242
|
}
|
|
160
243
|
return actions;
|
|
161
244
|
}
|
|
162
|
-
function planConfigPatchActions(manifest, detectedAgents, home) {
|
|
245
|
+
function planConfigPatchActions(manifest, detectedAgents, home, repo, workspace) {
|
|
163
246
|
const actions = [];
|
|
164
247
|
for (const configEntry of manifest.configs ?? []) {
|
|
165
248
|
for (const agentId of configEntry.agents) {
|
|
@@ -172,7 +255,7 @@ function planConfigPatchActions(manifest, detectedAgents, home) {
|
|
|
172
255
|
kind: "config-patch",
|
|
173
256
|
skill: configEntry.name,
|
|
174
257
|
agent: agentId,
|
|
175
|
-
target: resolveTargetTemplate(configEntry.target, home),
|
|
258
|
+
target: resolveTargetTemplate(configEntry.target, home, repo, workspace),
|
|
176
259
|
patch: configEntry.patch,
|
|
177
260
|
confidence: agent.provenance.skills,
|
|
178
261
|
});
|
|
@@ -180,7 +263,8 @@ function planConfigPatchActions(manifest, detectedAgents, home) {
|
|
|
180
263
|
}
|
|
181
264
|
return actions;
|
|
182
265
|
}
|
|
183
|
-
export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
266
|
+
export async function planDeploy(manifest, sourceDir, detectedAgents, home, repo, workspace) {
|
|
267
|
+
assertNoAntigravityPathCollisions(manifest);
|
|
184
268
|
const resolvedSourceDir = path.resolve(sourceDir);
|
|
185
269
|
let realRoot;
|
|
186
270
|
try {
|
|
@@ -189,15 +273,19 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
|
189
273
|
catch {
|
|
190
274
|
realRoot = resolvedSourceDir;
|
|
191
275
|
}
|
|
276
|
+
const repoDir = repo ?? realRoot;
|
|
277
|
+
const skillPlan = await planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home);
|
|
192
278
|
const actions = [
|
|
193
|
-
...
|
|
194
|
-
...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
195
|
-
...planConfigPatchActions(manifest, detectedAgents, home),
|
|
279
|
+
...skillPlan.actions,
|
|
280
|
+
...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, workspace)),
|
|
281
|
+
...planConfigPatchActions(manifest, detectedAgents, home, repoDir, workspace),
|
|
196
282
|
];
|
|
197
|
-
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home,
|
|
283
|
+
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, manifest.agentDefinitions ?? [], workspace);
|
|
198
284
|
actions.push(...adapterResult.actions);
|
|
199
285
|
const warnings = [
|
|
286
|
+
...skillPlan.warnings,
|
|
200
287
|
...detectAmbiguities(detectedAgents, manifest),
|
|
288
|
+
...checkAntigravityPathCollisions(manifest),
|
|
201
289
|
...detectCollisions(actions),
|
|
202
290
|
...adapterResult.warnings,
|
|
203
291
|
];
|
|
@@ -208,44 +296,32 @@ export async function executeDeploy(actions, dryRun, verbose, home, deps = {}) {
|
|
|
208
296
|
const failed = [];
|
|
209
297
|
const planned = [];
|
|
210
298
|
for (const action of actions) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
else {
|
|
218
|
-
failed.push({ action, error: result.error });
|
|
219
|
-
}
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
case "file-write": {
|
|
223
|
-
const result = await deployFileWrite(action, dryRun, verbose, home, planned, deps);
|
|
224
|
-
if (result.error === null) {
|
|
225
|
-
succeeded++;
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
failed.push({ action, error: result.error });
|
|
229
|
-
}
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
case "config-patch": {
|
|
233
|
-
const result = await deployConfigPatch(action, dryRun, verbose, home, planned, deps);
|
|
234
|
-
if (result.error === null) {
|
|
235
|
-
succeeded++;
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
failed.push({ action, error: result.error });
|
|
239
|
-
}
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
default: {
|
|
243
|
-
throw new Error(`Unhandled deploy action kind: ${action}`);
|
|
244
|
-
}
|
|
299
|
+
const result = await dispatchDeployAction(action, dryRun, verbose, home, planned, deps);
|
|
300
|
+
if (result.error === null) {
|
|
301
|
+
succeeded++;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
failed.push({ action, error: result.error });
|
|
245
305
|
}
|
|
246
306
|
}
|
|
247
307
|
return { succeeded, failed, planned };
|
|
248
308
|
}
|
|
309
|
+
async function dispatchDeployAction(action, dryRun, verbose, home, planned, deps) {
|
|
310
|
+
switch (action.kind) {
|
|
311
|
+
case "skill-dir":
|
|
312
|
+
return deploySkillDir(action, dryRun, verbose, home, planned, deps);
|
|
313
|
+
case "file-write":
|
|
314
|
+
return deployFileWrite(action, dryRun, verbose, home, planned, deps);
|
|
315
|
+
case "config-patch":
|
|
316
|
+
return deployConfigPatch(action, dryRun, verbose, home, planned, deps);
|
|
317
|
+
case "toml-patch":
|
|
318
|
+
return deployTomlPatch(action, dryRun, verbose, home, planned, deps);
|
|
319
|
+
case "frontmatter-emit":
|
|
320
|
+
return deployFrontmatterEmit(action, dryRun, verbose, home, planned, deps);
|
|
321
|
+
default:
|
|
322
|
+
throw new Error(`Unhandled deploy action kind: ${action.kind}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
249
325
|
const defaultSkillDirOps = {
|
|
250
326
|
async createTarget(action) {
|
|
251
327
|
if (action.method === "symlink") {
|
|
@@ -413,6 +489,7 @@ async function deployFileWrite(action, dryRun, verbose, home, planned, deps) {
|
|
|
413
489
|
source: action.source,
|
|
414
490
|
skill: action.skill,
|
|
415
491
|
agent: action.agent,
|
|
492
|
+
migratedFrom: action.migratedFrom,
|
|
416
493
|
}, deps.registry));
|
|
417
494
|
logger.ok(label);
|
|
418
495
|
if (verbose) {
|
|
@@ -483,6 +560,110 @@ async function deployConfigPatch(action, dryRun, verbose, home, planned, deps) {
|
|
|
483
560
|
return { error: msg };
|
|
484
561
|
}
|
|
485
562
|
}
|
|
563
|
+
async function deployTomlPatch(action, dryRun, verbose, home, planned, deps) {
|
|
564
|
+
const label = `${action.skill} -> ${action.agent}`;
|
|
565
|
+
if (dryRun) {
|
|
566
|
+
planned.push({
|
|
567
|
+
verb: "patch-toml",
|
|
568
|
+
kind: "toml-patch",
|
|
569
|
+
skill: action.skill,
|
|
570
|
+
agent: action.agent,
|
|
571
|
+
target: action.target,
|
|
572
|
+
patch: action.config,
|
|
573
|
+
});
|
|
574
|
+
return { error: null };
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
const { previousValue } = await (deps.registry
|
|
578
|
+
? Promise.resolve({ previousValue: null })
|
|
579
|
+
: (await import("./adapters/toml.js")).applyTomlMcpPatch(action.target, action.skill, action.config));
|
|
580
|
+
// Note: To truly support custom deps here we'd need to refactor toml adapter to accept deps.
|
|
581
|
+
// For now we assume standard adapter for TOML.
|
|
582
|
+
await registerDeployment(home, action.target, {
|
|
583
|
+
kind: "config-patch",
|
|
584
|
+
patch: action.config,
|
|
585
|
+
undoPatch: { mcpServers: { [action.skill]: previousValue } },
|
|
586
|
+
skill: action.skill,
|
|
587
|
+
agent: action.agent,
|
|
588
|
+
}, deps.registry);
|
|
589
|
+
logger.ok(label);
|
|
590
|
+
if (verbose) {
|
|
591
|
+
logger.detail(`patch-toml: ${action.target}`);
|
|
592
|
+
}
|
|
593
|
+
return { error: null };
|
|
594
|
+
}
|
|
595
|
+
catch (err) {
|
|
596
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
597
|
+
logger.fail(label, msg);
|
|
598
|
+
return { error: msg };
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async function deployFrontmatterEmit(action, dryRun, verbose, home, planned, deps) {
|
|
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
|
+
if (dryRun) {
|
|
609
|
+
planned.push({
|
|
610
|
+
verb: "emit-frontmatter",
|
|
611
|
+
kind: "frontmatter-emit",
|
|
612
|
+
skill: action.skill,
|
|
613
|
+
agent: action.agent,
|
|
614
|
+
target: action.target,
|
|
615
|
+
frontmatter: action.frontmatter,
|
|
616
|
+
});
|
|
617
|
+
return { error: null };
|
|
618
|
+
}
|
|
619
|
+
try {
|
|
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, {
|
|
647
|
+
kind: "frontmatter-emit",
|
|
648
|
+
patch: action.frontmatter,
|
|
649
|
+
undoPatch,
|
|
650
|
+
created: !existing.exists,
|
|
651
|
+
hadFrontmatter: existing.hasFrontmatter,
|
|
652
|
+
skill: action.skill,
|
|
653
|
+
agent: action.agent,
|
|
654
|
+
}, deps.registry));
|
|
655
|
+
logger.ok(label);
|
|
656
|
+
if (verbose) {
|
|
657
|
+
logger.detail(`emit-frontmatter: ${action.target}`);
|
|
658
|
+
}
|
|
659
|
+
return { error: null };
|
|
660
|
+
}
|
|
661
|
+
catch (err) {
|
|
662
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
663
|
+
logger.fail(label, msg);
|
|
664
|
+
return { error: msg };
|
|
665
|
+
}
|
|
666
|
+
}
|
|
486
667
|
async function validateSkillContract(source, skillPath) {
|
|
487
668
|
let stat;
|
|
488
669
|
try {
|