@kuznai/inception-engine 0.12.0 → 0.14.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 +9 -4
- package/dist/config/agents.js +25 -12
- package/dist/core/adapters/frontmatter.d.ts +26 -0
- package/dist/core/adapters/frontmatter.js +120 -0
- package/dist/core/adapters/index.d.ts +3 -3
- package/dist/core/adapters/index.js +3 -3
- package/dist/core/adapters/mcp.d.ts +4 -4
- package/dist/core/adapters/mcp.js +78 -21
- package/dist/core/adapters/rules.d.ts +2 -2
- package/dist/core/adapters/rules.js +5 -5
- package/dist/core/adapters/toml.d.ts +19 -0
- package/dist/core/adapters/toml.js +96 -0
- package/dist/core/deploy.js +136 -46
- package/dist/core/init.js +127 -8
- package/dist/core/resolve.d.ts +1 -1
- package/dist/core/resolve.js +3 -2
- package/dist/core/revert.d.ts +2 -2
- package/dist/core/revert.js +68 -7
- package/dist/core/runtime-paths.d.ts +1 -1
- package/dist/core/runtime-paths.js +16 -3
- package/dist/schemas/manifest.js +4 -2
- package/dist/types.d.ts +34 -4
- package/package.json +3 -1
package/dist/core/deploy.js
CHANGED
|
@@ -4,10 +4,12 @@ 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";
|
|
7
8
|
import { compileAdapterActions } from "./adapters/index.js";
|
|
9
|
+
import { applyTomlMcpPatch } from "./adapters/toml.js";
|
|
8
10
|
import { lookupDeployment, registerDeployment, verifyDeployment, } from "./ownership.js";
|
|
9
11
|
import { getDeployMethod, resolveAgentSkillPath } from "./resolve.js";
|
|
10
|
-
import { resolveTargetTemplate } from "./runtime-paths.js";
|
|
12
|
+
import { getPathApi, resolveTargetTemplate } from "./runtime-paths.js";
|
|
11
13
|
import { sourceAccessError, validateSkillDefinitionFile, validateSourceFile, validateSourcePath, } from "./validation.js";
|
|
12
14
|
function isPlainObject(v) {
|
|
13
15
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -80,14 +82,30 @@ function detectCollisions(actions) {
|
|
|
80
82
|
}
|
|
81
83
|
return warnings;
|
|
82
84
|
}
|
|
83
|
-
function detectAmbiguities(detectedAgents) {
|
|
85
|
+
function detectAmbiguities(detectedAgents, actions, home) {
|
|
84
86
|
const warnings = [];
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
const hasGeminiCli = detectedAgents.includes("gemini-cli");
|
|
88
|
+
const hasAntigravity = detectedAgents.includes("antigravity");
|
|
89
|
+
if (hasGeminiCli && hasAntigravity) {
|
|
90
|
+
const homePathApi = getPathApi(home);
|
|
91
|
+
const sharedGeminiMd = homePathApi.join(home, ".gemini", "GEMINI.md");
|
|
92
|
+
const sharedSettings = homePathApi.join(home, ".gemini", "settings.json");
|
|
93
|
+
// Normalize paths for comparison to handle case-insensitivity on Windows
|
|
94
|
+
const normalize = (pathStr) => homePathApi === path.win32
|
|
95
|
+
? pathStr.toLowerCase()
|
|
96
|
+
: pathStr;
|
|
97
|
+
const normalizedGeminiMd = normalize(sharedGeminiMd);
|
|
98
|
+
const normalizedSettings = normalize(sharedSettings);
|
|
99
|
+
const targetsShared = actions.some((a) => {
|
|
100
|
+
const normalizedTarget = normalize(a.target);
|
|
101
|
+
return normalizedTarget === normalizedGeminiMd || normalizedTarget === normalizedSettings;
|
|
90
102
|
});
|
|
103
|
+
if (targetsShared) {
|
|
104
|
+
warnings.push({
|
|
105
|
+
kind: "ambiguity",
|
|
106
|
+
message: "Both 'gemini-cli' and 'antigravity' are active, and a deployment targets a shared surface (~/.gemini/GEMINI.md or settings.json). Because Antigravity treats GEMINI.md as an Agent Blueprint, changes intended for one runtime may unexpectedly affect the other.",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
91
109
|
}
|
|
92
110
|
return warnings;
|
|
93
111
|
}
|
|
@@ -143,7 +161,7 @@ async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, real
|
|
|
143
161
|
}
|
|
144
162
|
return actions;
|
|
145
163
|
}
|
|
146
|
-
function planConfigPatchActions(manifest, detectedAgents, home) {
|
|
164
|
+
function planConfigPatchActions(manifest, detectedAgents, home, repo) {
|
|
147
165
|
const actions = [];
|
|
148
166
|
for (const configEntry of manifest.configs ?? []) {
|
|
149
167
|
for (const agentId of configEntry.agents) {
|
|
@@ -156,7 +174,7 @@ function planConfigPatchActions(manifest, detectedAgents, home) {
|
|
|
156
174
|
kind: "config-patch",
|
|
157
175
|
skill: configEntry.name,
|
|
158
176
|
agent: agentId,
|
|
159
|
-
target: resolveTargetTemplate(configEntry.target, home),
|
|
177
|
+
target: resolveTargetTemplate(configEntry.target, home, repo),
|
|
160
178
|
patch: configEntry.patch,
|
|
161
179
|
confidence: agent.provenance.skills,
|
|
162
180
|
});
|
|
@@ -176,56 +194,44 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
|
176
194
|
const actions = [
|
|
177
195
|
...(await planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
178
196
|
...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
179
|
-
...planConfigPatchActions(manifest, detectedAgents, home),
|
|
197
|
+
...planConfigPatchActions(manifest, detectedAgents, home, resolvedSourceDir),
|
|
180
198
|
];
|
|
181
|
-
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home);
|
|
199
|
+
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, resolvedSourceDir);
|
|
182
200
|
actions.push(...adapterResult.actions);
|
|
183
201
|
const warnings = [
|
|
184
|
-
...detectAmbiguities(detectedAgents),
|
|
202
|
+
...detectAmbiguities(detectedAgents, actions, home),
|
|
185
203
|
...detectCollisions(actions),
|
|
186
204
|
...adapterResult.warnings,
|
|
187
205
|
];
|
|
188
206
|
return { actions, warnings };
|
|
189
207
|
}
|
|
208
|
+
async function dispatchDeployAction(action, dryRun, verbose, home, planned, deps) {
|
|
209
|
+
switch (action.kind) {
|
|
210
|
+
case "skill-dir":
|
|
211
|
+
return deploySkillDir(action, dryRun, verbose, home, planned, deps);
|
|
212
|
+
case "file-write":
|
|
213
|
+
return deployFileWrite(action, dryRun, verbose, home, planned, deps);
|
|
214
|
+
case "config-patch":
|
|
215
|
+
return deployConfigPatch(action, dryRun, verbose, home, planned, deps);
|
|
216
|
+
case "toml-patch":
|
|
217
|
+
return deployTomlPatch(action, dryRun, verbose, home, planned, deps);
|
|
218
|
+
case "frontmatter-emit":
|
|
219
|
+
return deployFrontmatterEmit(action, dryRun, verbose, home, planned, deps);
|
|
220
|
+
default:
|
|
221
|
+
throw new Error(`Unhandled deploy action kind: ${action.kind}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
190
224
|
export async function executeDeploy(actions, dryRun, verbose, home, deps = {}) {
|
|
191
225
|
let succeeded = 0;
|
|
192
226
|
const failed = [];
|
|
193
227
|
const planned = [];
|
|
194
228
|
for (const action of actions) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
else {
|
|
202
|
-
failed.push({ action, error: result.error });
|
|
203
|
-
}
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
case "file-write": {
|
|
207
|
-
const result = await deployFileWrite(action, dryRun, verbose, home, planned, deps);
|
|
208
|
-
if (result.error === null) {
|
|
209
|
-
succeeded++;
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
failed.push({ action, error: result.error });
|
|
213
|
-
}
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
216
|
-
case "config-patch": {
|
|
217
|
-
const result = await deployConfigPatch(action, dryRun, verbose, home, planned, deps);
|
|
218
|
-
if (result.error === null) {
|
|
219
|
-
succeeded++;
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
failed.push({ action, error: result.error });
|
|
223
|
-
}
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
default: {
|
|
227
|
-
throw new Error(`Unhandled deploy action kind: ${action}`);
|
|
228
|
-
}
|
|
229
|
+
const result = await dispatchDeployAction(action, dryRun, verbose, home, planned, deps);
|
|
230
|
+
if (result.error === null) {
|
|
231
|
+
succeeded++;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
failed.push({ action, error: result.error });
|
|
229
235
|
}
|
|
230
236
|
}
|
|
231
237
|
return { succeeded, failed, planned };
|
|
@@ -593,3 +599,87 @@ async function backupExisting(targetPath, verbose, home, expected, deps) {
|
|
|
593
599
|
await rename(targetPath, backupPath);
|
|
594
600
|
return backupPath;
|
|
595
601
|
}
|
|
602
|
+
async function deployTomlPatch(action, dryRun, verbose, home, planned, deps) {
|
|
603
|
+
const label = `${action.skill} -> ${action.agent}`;
|
|
604
|
+
if (dryRun) {
|
|
605
|
+
planned.push({
|
|
606
|
+
verb: "patch-toml",
|
|
607
|
+
kind: "toml-patch",
|
|
608
|
+
skill: action.skill,
|
|
609
|
+
agent: action.agent,
|
|
610
|
+
target: action.target,
|
|
611
|
+
confidence: action.confidence,
|
|
612
|
+
});
|
|
613
|
+
return { error: null };
|
|
614
|
+
}
|
|
615
|
+
try {
|
|
616
|
+
// Guard against double-patching by a different skill/agent.
|
|
617
|
+
const existingEntry = await lookupDeployment(home, action.target, deps.registry);
|
|
618
|
+
if (existingEntry &&
|
|
619
|
+
(existingEntry.skill !== action.skill ||
|
|
620
|
+
existingEntry.agent !== action.agent)) {
|
|
621
|
+
throw new Error(`Config "${action.target}" is already patched by skill "${existingEntry.skill}" for agent "${existingEntry.agent}" — refusing to double-patch`);
|
|
622
|
+
}
|
|
623
|
+
await applyTomlMcpPatch(action.target, action.skill, action.config);
|
|
624
|
+
await registerDeployment(home, action.target, {
|
|
625
|
+
kind: "config-patch",
|
|
626
|
+
patch: { mcpServers: { [action.skill]: action.config } },
|
|
627
|
+
undoPatch: { mcpServers: { [action.skill]: null } },
|
|
628
|
+
skill: action.skill,
|
|
629
|
+
agent: action.agent,
|
|
630
|
+
}, deps.registry);
|
|
631
|
+
logger.ok(label);
|
|
632
|
+
if (verbose) {
|
|
633
|
+
logger.detail(`patch-toml: wrote [mcpServers.${action.skill}] to ${action.target}`);
|
|
634
|
+
}
|
|
635
|
+
return { error: null };
|
|
636
|
+
}
|
|
637
|
+
catch (err) {
|
|
638
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
639
|
+
logger.fail(label, msg);
|
|
640
|
+
return { error: msg };
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
async function deployFrontmatterEmit(action, dryRun, verbose, home, planned, deps) {
|
|
644
|
+
const label = `${action.skill} -> ${action.agent}`;
|
|
645
|
+
if (dryRun) {
|
|
646
|
+
planned.push({
|
|
647
|
+
verb: "emit-frontmatter",
|
|
648
|
+
kind: "frontmatter-emit",
|
|
649
|
+
skill: action.skill,
|
|
650
|
+
agent: action.agent,
|
|
651
|
+
target: action.target,
|
|
652
|
+
frontmatter: action.frontmatter,
|
|
653
|
+
confidence: action.confidence,
|
|
654
|
+
});
|
|
655
|
+
return { error: null };
|
|
656
|
+
}
|
|
657
|
+
try {
|
|
658
|
+
// Guard against double-patching by a different skill/agent.
|
|
659
|
+
const existingEntry = await lookupDeployment(home, action.target, deps.registry);
|
|
660
|
+
if (existingEntry &&
|
|
661
|
+
(existingEntry.skill !== action.skill ||
|
|
662
|
+
existingEntry.agent !== action.agent)) {
|
|
663
|
+
throw new Error(`File "${action.target}" is already managed by skill "${existingEntry.skill}" for agent "${existingEntry.agent}" — refusing to overwrite`);
|
|
664
|
+
}
|
|
665
|
+
await writeFrontmatterFile(action.target, action.frontmatter, {
|
|
666
|
+
preserveBody: true,
|
|
667
|
+
});
|
|
668
|
+
await registerDeployment(home, action.target, {
|
|
669
|
+
kind: "file-write",
|
|
670
|
+
source: action.target,
|
|
671
|
+
skill: action.skill,
|
|
672
|
+
agent: action.agent,
|
|
673
|
+
}, deps.registry);
|
|
674
|
+
logger.ok(label);
|
|
675
|
+
if (verbose) {
|
|
676
|
+
logger.detail(`emit-frontmatter: wrote ${action.target}`);
|
|
677
|
+
}
|
|
678
|
+
return { error: null };
|
|
679
|
+
}
|
|
680
|
+
catch (err) {
|
|
681
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
682
|
+
logger.fail(label, msg);
|
|
683
|
+
return { error: msg };
|
|
684
|
+
}
|
|
685
|
+
}
|
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
|
}
|
package/dist/core/resolve.d.ts
CHANGED
|
@@ -9,4 +9,4 @@ export declare function resolveAgentSkillPathFor(agent: AgentConfig, skillName:
|
|
|
9
9
|
export declare function resolveAgentDetectPathFor(agent: AgentConfig, home: string, platform: "posix" | "windows"): string;
|
|
10
10
|
export declare function resolveAgentSkillPath(agent: AgentConfig, skillName: string, home: string): string;
|
|
11
11
|
export declare function resolveAgentDetectPath(agent: AgentConfig, home: string): string;
|
|
12
|
-
export declare function resolvePlaceholders(segments: string[], skillName: string, home: string): string;
|
|
12
|
+
export declare function resolvePlaceholders(segments: string[], skillName: string, home: string, repo?: string): string;
|
package/dist/core/resolve.js
CHANGED
|
@@ -96,13 +96,14 @@ export function resolveAgentSkillPath(agent, skillName, home) {
|
|
|
96
96
|
export function resolveAgentDetectPath(agent, home) {
|
|
97
97
|
return resolveAgentDetectPathFor(agent, home, getPlatformKey());
|
|
98
98
|
}
|
|
99
|
-
export function resolvePlaceholders(segments, skillName, home) {
|
|
99
|
+
export function resolvePlaceholders(segments, skillName, home, repo) {
|
|
100
100
|
const { appdata, xdgConfig } = resolveRuntimePaths(home);
|
|
101
101
|
const resolved = segments.map((seg) => seg
|
|
102
102
|
.replace("{home}", home)
|
|
103
103
|
.replace("{name}", skillName)
|
|
104
104
|
.replace("{appdata}", appdata)
|
|
105
|
-
.replace("{xdg_config}", xdgConfig)
|
|
105
|
+
.replace("{xdg_config}", xdgConfig)
|
|
106
|
+
.replace("{repo}", repo ?? ""));
|
|
106
107
|
const root = resolved.find((segment) => segment.length > 0) ?? home;
|
|
107
108
|
return getPathApi(root).join(...resolved);
|
|
108
109
|
}
|
package/dist/core/revert.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentId, Manifest, PlannedChange, RevertAction } from "../types.ts";
|
|
2
2
|
import { type RegistryPersistence } from "./ownership.ts";
|
|
3
|
-
export declare function planRevert(manifest: Manifest, detectedAgents: AgentId[], home: string): RevertAction[];
|
|
4
|
-
export declare function planRevertAll(manifest: Manifest, home: string): RevertAction[];
|
|
3
|
+
export declare function planRevert(manifest: Manifest, detectedAgents: AgentId[], home: string, repo?: string): RevertAction[];
|
|
4
|
+
export declare function planRevertAll(manifest: Manifest, home: string, repo?: string): RevertAction[];
|
|
5
5
|
export declare function executeRevert(actions: RevertAction[], dryRun: boolean, verbose: boolean, home: string, deps?: RevertDependencies): Promise<{
|
|
6
6
|
succeeded: number;
|
|
7
7
|
skipped: number;
|
package/dist/core/revert.js
CHANGED
|
@@ -2,6 +2,7 @@ import { lstat, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
3
3
|
import { logger } from "../logger.js";
|
|
4
4
|
import { compileAgentRuleReverts, compileMcpServerReverts, } from "./adapters/index.js";
|
|
5
|
+
import { revertTomlMcpPatch } from "./adapters/toml.js";
|
|
5
6
|
import { lookupDeployment, unregisterDeployment, } from "./ownership.js";
|
|
6
7
|
import { resolveAgentSkillPath } from "./resolve.js";
|
|
7
8
|
import { resolveTargetTemplate } from "./runtime-paths.js";
|
|
@@ -62,22 +63,22 @@ function buildConfigPatchReverts(manifest, home, agentFilter) {
|
|
|
62
63
|
}
|
|
63
64
|
return actions;
|
|
64
65
|
}
|
|
65
|
-
export function planRevert(manifest, detectedAgents, home) {
|
|
66
|
+
export function planRevert(manifest, detectedAgents, home, repo) {
|
|
66
67
|
return [
|
|
67
68
|
...buildSkillDirReverts(manifest, home, detectedAgents),
|
|
68
69
|
...buildFileWriteReverts(manifest, home, detectedAgents),
|
|
69
70
|
...buildConfigPatchReverts(manifest, home, detectedAgents),
|
|
70
|
-
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, detectedAgents, home)),
|
|
71
|
-
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, detectedAgents, home)),
|
|
71
|
+
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, detectedAgents, home, repo)),
|
|
72
|
+
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, detectedAgents, home, repo)),
|
|
72
73
|
];
|
|
73
74
|
}
|
|
74
|
-
export function planRevertAll(manifest, home) {
|
|
75
|
+
export function planRevertAll(manifest, home, repo) {
|
|
75
76
|
return [
|
|
76
77
|
...buildSkillDirReverts(manifest, home, null),
|
|
77
78
|
...buildFileWriteReverts(manifest, home, null),
|
|
78
79
|
...buildConfigPatchReverts(manifest, home, null),
|
|
79
|
-
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, null, home)),
|
|
80
|
-
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, null, home)),
|
|
80
|
+
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, null, home, repo)),
|
|
81
|
+
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, null, home, repo)),
|
|
81
82
|
];
|
|
82
83
|
}
|
|
83
84
|
function recordOutcome(result, action, counts, failed) {
|
|
@@ -148,8 +149,21 @@ export async function executeRevert(actions, dryRun, verbose, home, deps = {}) {
|
|
|
148
149
|
case "config-patch":
|
|
149
150
|
result = await revertConfigPatch(action, dryRun, verbose, home, planned, deps);
|
|
150
151
|
break;
|
|
152
|
+
case "toml-patch":
|
|
153
|
+
result = await revertTomlPatch(action, dryRun, verbose, home, planned, deps);
|
|
154
|
+
break;
|
|
155
|
+
case "frontmatter-emit":
|
|
156
|
+
// Frontmatter-emit files are fully managed by inception — revert
|
|
157
|
+
// deletes the file, same as a file-write revert.
|
|
158
|
+
result = await revertFileWrite({
|
|
159
|
+
kind: "file-write",
|
|
160
|
+
skill: action.skill,
|
|
161
|
+
agent: action.agent,
|
|
162
|
+
target: action.target,
|
|
163
|
+
}, dryRun, verbose, home, planned, deps);
|
|
164
|
+
break;
|
|
151
165
|
default:
|
|
152
|
-
throw new Error(`Unhandled revert action kind: ${action}`);
|
|
166
|
+
throw new Error(`Unhandled revert action kind: ${action.kind}`);
|
|
153
167
|
}
|
|
154
168
|
recordOutcome(result, action, counts, failed);
|
|
155
169
|
}
|
|
@@ -316,3 +330,50 @@ async function revertConfigPatch(action, dryRun, verbose, home, planned, deps) {
|
|
|
316
330
|
return { outcome: "fail", error: msg };
|
|
317
331
|
}
|
|
318
332
|
}
|
|
333
|
+
async function revertTomlPatch(action, dryRun, verbose, home, planned, deps) {
|
|
334
|
+
const label = `${action.skill} -> ${action.agent}`;
|
|
335
|
+
try {
|
|
336
|
+
await lstat(action.target);
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
const result = lstatOutcome(err);
|
|
340
|
+
if (result.outcome === "skip") {
|
|
341
|
+
logger.skip(label, "(not found, skipping)");
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
logger.fail(label, result.error);
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
const entry = await lookupDeployment(home, action.target, deps.registry);
|
|
348
|
+
if (!entry ||
|
|
349
|
+
entry.kind !== "config-patch" ||
|
|
350
|
+
entry.skill !== action.skill ||
|
|
351
|
+
entry.agent !== action.agent) {
|
|
352
|
+
logger.warn(label, `skipping: ${action.target} is not in the deployment registry — not managed by inception-engine`);
|
|
353
|
+
return { outcome: "skip" };
|
|
354
|
+
}
|
|
355
|
+
if (dryRun) {
|
|
356
|
+
planned.push({
|
|
357
|
+
verb: "unapply-patch",
|
|
358
|
+
kind: "toml-patch",
|
|
359
|
+
skill: action.skill,
|
|
360
|
+
agent: action.agent,
|
|
361
|
+
target: action.target,
|
|
362
|
+
});
|
|
363
|
+
return { outcome: "ok" };
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
await revertTomlMcpPatch(action.target, action.skill);
|
|
367
|
+
await unregisterDeployment(home, action.target, deps.registry);
|
|
368
|
+
logger.ok(label);
|
|
369
|
+
if (verbose) {
|
|
370
|
+
logger.detail(`removed [mcpServers.${action.skill}] from: ${action.target}`);
|
|
371
|
+
}
|
|
372
|
+
return { outcome: "ok" };
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
376
|
+
logger.fail(label, msg);
|
|
377
|
+
return { outcome: "fail", error: msg };
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -5,4 +5,4 @@ export interface RuntimePaths {
|
|
|
5
5
|
}
|
|
6
6
|
export declare function getPathApi(root: string): typeof path.posix | typeof path.win32;
|
|
7
7
|
export declare function resolveRuntimePaths(home: string): RuntimePaths;
|
|
8
|
-
export declare function resolveTargetTemplate(template: string, home: string): string;
|
|
8
|
+
export declare function resolveTargetTemplate(template: string, home: string, repo?: string): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config)\}(?<suffix>(?:[\\/].*)?)$/;
|
|
2
|
+
const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config|repo)\}(?<suffix>(?:[\\/].*)?)$/;
|
|
3
3
|
export function getPathApi(root) {
|
|
4
4
|
if (root.includes("\\") || /^[a-zA-Z]:/.test(root)) {
|
|
5
5
|
return path.win32;
|
|
@@ -32,18 +32,31 @@ export function resolveRuntimePaths(home) {
|
|
|
32
32
|
: homePathApi.join(home, ".config");
|
|
33
33
|
return { appdata, xdgConfig };
|
|
34
34
|
}
|
|
35
|
-
export function resolveTargetTemplate(template, home) {
|
|
35
|
+
export function resolveTargetTemplate(template, home, repo) {
|
|
36
36
|
const { appdata, xdgConfig } = resolveRuntimePaths(home);
|
|
37
37
|
const match = TARGET_TEMPLATE_RE.exec(template);
|
|
38
38
|
if (!match) {
|
|
39
39
|
throw new Error(`Invalid target template: ${template}`);
|
|
40
40
|
}
|
|
41
|
+
const root = match[1];
|
|
42
|
+
if (root === "repo") {
|
|
43
|
+
if (!repo) {
|
|
44
|
+
throw new Error(`Target template uses {repo} but no manifest directory was provided: ${template}`);
|
|
45
|
+
}
|
|
46
|
+
const suffix = match.groups?.suffix ?? "";
|
|
47
|
+
const segments = suffix.split(/[\\/]+/).filter(Boolean);
|
|
48
|
+
const repoPathApi = getPathApi(repo);
|
|
49
|
+
const resolved = segments.length === 0 ? repo : repoPathApi.join(repo, ...segments);
|
|
50
|
+
if (!isSameOrDescendantPath(resolved, repo)) {
|
|
51
|
+
throw new Error(`Target template resolves outside its placeholder root: ${template}`);
|
|
52
|
+
}
|
|
53
|
+
return suffix === "" ? repo : `${repo}${suffix}`;
|
|
54
|
+
}
|
|
41
55
|
const baseByRoot = {
|
|
42
56
|
home,
|
|
43
57
|
appdata,
|
|
44
58
|
xdg_config: xdgConfig,
|
|
45
59
|
};
|
|
46
|
-
const root = match[1];
|
|
47
60
|
const suffix = match.groups?.suffix ?? "";
|
|
48
61
|
const segments = suffix.split(/[\\/]+/).filter(Boolean);
|
|
49
62
|
const pathApi = getPathApi(baseByRoot[root]);
|
package/dist/schemas/manifest.js
CHANGED
|
@@ -13,7 +13,9 @@ const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
|
13
13
|
// Target templates must be rooted at a known placeholder and may only add
|
|
14
14
|
// descendant path segments beneath that root. e.g. "{home}/.claude/settings.json"
|
|
15
15
|
// is valid, while "{home}/../.ssh/config" is rejected.
|
|
16
|
-
|
|
16
|
+
// {repo} resolves to the manifest directory at deploy time, enabling repo-local
|
|
17
|
+
// targets (e.g. Antigravity's .agents/rules/ surface).
|
|
18
|
+
const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config|repo)\}(?:[\\/].*)?$/;
|
|
17
19
|
// Standalone schema used for type derivation and single-ID validation (e.g. index.ts).
|
|
18
20
|
export const AgentIdSchema = z.enum(AGENT_IDS);
|
|
19
21
|
// Used inside SkillEntrySchema.agents so that enum failures embed the received
|
|
@@ -53,7 +55,7 @@ const targetTemplateField = z
|
|
|
53
55
|
.string({ message: "target must be a non-empty string" })
|
|
54
56
|
.min(1, { message: "target must be a non-empty string" })
|
|
55
57
|
.refine((t) => TARGET_TEMPLATE_RE.test(t), {
|
|
56
|
-
message: "target must start with a known placeholder: {home}, {appdata}, or {
|
|
58
|
+
message: "target must start with a known placeholder: {home}, {appdata}, {xdg_config}, or {repo}",
|
|
57
59
|
})
|
|
58
60
|
.refine((t) => !t.split(/[\\/]+/).includes(".."), {
|
|
59
61
|
message: "target must not escape its placeholder root",
|