@kuznai/inception-engine 0.13.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/dist/core/deploy.js +14 -4
- package/dist/core/init.js +127 -8
- package/package.json +1 -1
package/dist/core/deploy.js
CHANGED
|
@@ -9,7 +9,7 @@ import { compileAdapterActions } from "./adapters/index.js";
|
|
|
9
9
|
import { applyTomlMcpPatch } from "./adapters/toml.js";
|
|
10
10
|
import { lookupDeployment, registerDeployment, verifyDeployment, } from "./ownership.js";
|
|
11
11
|
import { getDeployMethod, resolveAgentSkillPath } from "./resolve.js";
|
|
12
|
-
import { resolveTargetTemplate } from "./runtime-paths.js";
|
|
12
|
+
import { getPathApi, resolveTargetTemplate } from "./runtime-paths.js";
|
|
13
13
|
import { sourceAccessError, validateSkillDefinitionFile, validateSourceFile, validateSourcePath, } from "./validation.js";
|
|
14
14
|
function isPlainObject(v) {
|
|
15
15
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -87,9 +87,19 @@ function detectAmbiguities(detectedAgents, actions, home) {
|
|
|
87
87
|
const hasGeminiCli = detectedAgents.includes("gemini-cli");
|
|
88
88
|
const hasAntigravity = detectedAgents.includes("antigravity");
|
|
89
89
|
if (hasGeminiCli && hasAntigravity) {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const
|
|
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;
|
|
102
|
+
});
|
|
93
103
|
if (targetsShared) {
|
|
94
104
|
warnings.push({
|
|
95
105
|
kind: "ambiguity",
|
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
|
}
|