@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.
Files changed (109) hide show
  1. package/README.md +72 -43
  2. package/dist/{config → src/config}/agents.js +174 -24
  3. package/dist/src/core/adapters/agent-definitions.d.ts +8 -0
  4. package/dist/src/core/adapters/agent-definitions.js +162 -0
  5. package/dist/{core → src/core}/adapters/frontmatter.d.ts +10 -0
  6. package/dist/{core → src/core}/adapters/frontmatter.js +23 -6
  7. package/dist/{core → src/core}/adapters/index.d.ts +1 -1
  8. package/dist/{core → src/core}/adapters/index.js +4 -4
  9. package/dist/{core → src/core}/adapters/mcp.d.ts +2 -2
  10. package/dist/{core → src/core}/adapters/mcp.js +21 -24
  11. package/dist/{core → src/core}/adapters/permissions.js +16 -20
  12. package/dist/{core → src/core}/adapters/rules.d.ts +2 -2
  13. package/dist/src/core/adapters/rules.js +126 -0
  14. package/dist/src/core/capabilities.d.ts +47 -0
  15. package/dist/src/core/capabilities.js +280 -0
  16. package/dist/{core → src/core}/deploy.d.ts +1 -1
  17. package/dist/{core → src/core}/deploy.js +273 -92
  18. package/dist/{core → src/core}/init.js +126 -52
  19. package/dist/src/core/merge-patch.d.ts +4 -0
  20. package/dist/src/core/merge-patch.js +46 -0
  21. package/dist/{core → src/core}/ownership.d.ts +2 -2
  22. package/dist/{core → src/core}/ownership.js +13 -0
  23. package/dist/{core → src/core}/preflight.d.ts +1 -1
  24. package/dist/src/core/preflight.js +280 -0
  25. package/dist/{core → src/core}/resolve.d.ts +1 -1
  26. package/dist/{core → src/core}/resolve.js +7 -4
  27. package/dist/{core → src/core}/revert.js +95 -33
  28. package/dist/{core → src/core}/runtime-paths.d.ts +2 -1
  29. package/dist/{core → src/core}/runtime-paths.js +30 -23
  30. package/dist/{core → src/core}/validation.d.ts +10 -1
  31. package/dist/{core → src/core}/validation.js +75 -0
  32. package/dist/src/formatters.d.ts +5 -0
  33. package/dist/src/formatters.js +58 -0
  34. package/dist/{index.js → src/index.js} +8 -24
  35. package/dist/src/logger.js +62 -0
  36. package/dist/{schemas → src/schemas}/manifest.d.ts +22 -0
  37. package/dist/{schemas → src/schemas}/manifest.js +19 -5
  38. package/dist/{schemas → src/schemas}/registry.d.ts +74 -0
  39. package/dist/{schemas → src/schemas}/registry.js +18 -3
  40. package/dist/{types.d.ts → src/types.d.ts} +60 -0
  41. package/dist/test/helpers/fs.d.ts +2 -0
  42. package/dist/test/helpers/fs.js +17 -0
  43. package/dist/test/helpers/path.d.ts +10 -0
  44. package/dist/test/helpers/path.js +15 -0
  45. package/dist/test/helpers/skill-dir-scenarios.d.ts +7 -0
  46. package/dist/test/helpers/skill-dir-scenarios.js +206 -0
  47. package/dist/test/helpers/skill-dir.d.ts +7 -0
  48. package/dist/test/helpers/skill-dir.js +46 -0
  49. package/dist/test/os/cross-platform/cross-platform.test.d.ts +1 -0
  50. package/dist/test/os/cross-platform/cross-platform.test.js +328 -0
  51. package/dist/test/os/posix/skill-dir.test.d.ts +1 -0
  52. package/dist/test/os/posix/skill-dir.test.js +19 -0
  53. package/dist/test/os/windows/revert-integration.test.d.ts +1 -0
  54. package/dist/test/os/windows/revert-integration.test.js +72 -0
  55. package/dist/test/os/windows/skill-dir.test.d.ts +1 -0
  56. package/dist/test/os/windows/skill-dir.test.js +19 -0
  57. package/dist/test/unit/adapters.test.d.ts +1 -0
  58. package/dist/test/unit/adapters.test.js +947 -0
  59. package/dist/test/unit/capabilities.test.d.ts +1 -0
  60. package/dist/test/unit/capabilities.test.js +71 -0
  61. package/dist/test/unit/cli.test.d.ts +1 -0
  62. package/dist/test/unit/cli.test.js +834 -0
  63. package/dist/test/unit/deploy.test.d.ts +1 -0
  64. package/dist/test/unit/deploy.test.js +2481 -0
  65. package/dist/test/unit/detect.test.d.ts +1 -0
  66. package/dist/test/unit/detect.test.js +192 -0
  67. package/dist/test/unit/enterprise.test.d.ts +1 -0
  68. package/dist/test/unit/enterprise.test.js +106 -0
  69. package/dist/test/unit/frontmatter.test.d.ts +1 -0
  70. package/dist/test/unit/frontmatter.test.js +102 -0
  71. package/dist/test/unit/gemini-north-star.test.d.ts +1 -0
  72. package/dist/test/unit/gemini-north-star.test.js +243 -0
  73. package/dist/test/unit/manifest.test.d.ts +1 -0
  74. package/dist/test/unit/manifest.test.js +737 -0
  75. package/dist/test/unit/migration-golden.test.d.ts +1 -0
  76. package/dist/test/unit/migration-golden.test.js +197 -0
  77. package/dist/test/unit/ownership.test.d.ts +1 -0
  78. package/dist/test/unit/ownership.test.js +587 -0
  79. package/dist/test/unit/preflight.test.d.ts +1 -0
  80. package/dist/test/unit/preflight.test.js +501 -0
  81. package/dist/test/unit/resolve.test.d.ts +1 -0
  82. package/dist/test/unit/resolve.test.js +119 -0
  83. package/dist/test/unit/revert.test.d.ts +1 -0
  84. package/dist/test/unit/revert.test.js +1004 -0
  85. package/dist/test/unit/toml.test.d.ts +1 -0
  86. package/dist/test/unit/toml.test.js +55 -0
  87. package/package.json +1 -1
  88. package/dist/core/adapters/agent-definitions.d.ts +0 -20
  89. package/dist/core/adapters/agent-definitions.js +0 -90
  90. package/dist/core/adapters/rules.js +0 -103
  91. package/dist/core/preflight.js +0 -98
  92. package/dist/logger.js +0 -61
  93. /package/dist/{config → src/config}/agents.d.ts +0 -0
  94. /package/dist/{config → src/config}/manifest.d.ts +0 -0
  95. /package/dist/{config → src/config}/manifest.js +0 -0
  96. /package/dist/{core → src/core}/adapters/permissions.d.ts +0 -0
  97. /package/dist/{core → src/core}/adapters/toml.d.ts +0 -0
  98. /package/dist/{core → src/core}/adapters/toml.js +0 -0
  99. /package/dist/{core → src/core}/detect.d.ts +0 -0
  100. /package/dist/{core → src/core}/detect.js +0 -0
  101. /package/dist/{core → src/core}/init.d.ts +0 -0
  102. /package/dist/{core → src/core}/revert.d.ts +0 -0
  103. /package/dist/{errors.d.ts → src/errors.d.ts} +0 -0
  104. /package/dist/{errors.js → src/errors.js} +0 -0
  105. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  106. /package/dist/{logger.d.ts → src/logger.d.ts} +0 -0
  107. /package/dist/{schemas → src/schemas}/errors.d.ts +0 -0
  108. /package/dist/{schemas → src/schemas}/errors.js +0 -0
  109. /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
- function detectAmbiguities(detectedAgents, manifest) {
84
- const warnings = [];
85
- if (!(detectedAgents.includes("gemini-cli") &&
86
- detectedAgents.includes("antigravity"))) {
87
- return warnings;
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
- const bothAgents = (agents) => agents.includes("gemini-cli") && agents.includes("antigravity");
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 (bothAgents(entry.agents)) {
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 "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.`,
108
+ message: `Both "${primary}" and "${rider}" are listed in agentRules entry "${entry.name}". Both target the same surfacelisting 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 (bothAgents(entry.agents)) {
160
+ if (!entry.agents.includes("antigravity"))
161
+ continue;
162
+ if (defNames.has(entry.name)) {
100
163
  warnings.push({
101
- kind: "ambiguity",
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.`,
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
- for (const skill of manifest.skills) {
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
- continue;
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: agent.provenance.skills ?? "provisional",
211
+ confidence: plan.confidence ?? "provisional",
132
212
  });
133
213
  }
134
214
  }
135
- return actions;
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
- ...(await planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
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, undefined, manifest.agentDefinitions ?? []);
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
- switch (action.kind) {
212
- case "skill-dir": {
213
- const result = await deploySkillDir(action, dryRun, verbose, home, planned, deps);
214
- if (result.error === null) {
215
- succeeded++;
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 {