@kuznai/inception-engine 0.14.0 → 0.15.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 +23 -5
- package/dist/core/deploy.js +57 -133
- package/dist/core/init.js +80 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ GitHub Copilot is no longer treated as a separate instruction or skill target in
|
|
|
8
8
|
|
|
9
9
|
The broader portability layer is the roadmap direction, but this README focuses on what is working now.
|
|
10
10
|
|
|
11
|
-
`init` is available as a bootstrap command
|
|
11
|
+
`init` is available as a bootstrap command. It scans for directories containing `SKILL.md`, discovers agent-rules Markdown files using the Claude-first portability conventions, and reads `mcp-servers.json` from the repo root to populate `mcpServers`. `files` and `configs` remain empty — `init` emits guidance when it detects a `files/` or `configs/` directory.
|
|
12
12
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
@@ -50,7 +50,7 @@ Managed skills overwrite their previous version. If a target exists but was not
|
|
|
50
50
|
| Config patch (JSON merge) | All agents via manifest and CLI | All agents |
|
|
51
51
|
| MCP Servers | claude-code, gemini-cli, codex, antigravity, opencode; github-copilot repo-scoped surfaces are warned and skipped | claude-code, gemini-cli, codex, antigravity, opencode |
|
|
52
52
|
| Global/Repo Rules Files | All agents (antigravity uses repo-local `.agents/rules/`); github-copilot reads CLAUDE.md natively (deploy via claude-code) | All agents |
|
|
53
|
-
| `init` manifest generation | Scans `SKILL.md` directories and
|
|
53
|
+
| `init` manifest generation | Scans `SKILL.md` directories (`skills`), `.md` files with Claude-first agent mapping (`agentRules`), and `mcp-servers.json` (`mcpServers`); emits hints for `files/` and `configs/` directories | N/A |
|
|
54
54
|
|
|
55
55
|
Features that depend on agent-specific config surfaces are intentionally conservative: if a target path or schema is not implemented with enough confidence, inception-engine warns and skips it rather than guessing.
|
|
56
56
|
|
|
@@ -164,7 +164,7 @@ The `name` and `description` fields in the frontmatter are used by most agents.
|
|
|
164
164
|
|
|
165
165
|
## `init` Command
|
|
166
166
|
|
|
167
|
-
`init` is meant to bootstrap a repository that already has
|
|
167
|
+
`init` is meant to bootstrap a repository that already has skills and related manifest assets. It recursively scans the target directory, treats any directory containing `SKILL.md` as a skill, discovers supported instruction and MCP conventions, and writes a starter `inception.json`.
|
|
168
168
|
|
|
169
169
|
Current `init` behavior:
|
|
170
170
|
|
|
@@ -173,12 +173,30 @@ Current `init` behavior:
|
|
|
173
173
|
- Applies either the `--agents` list or all currently known agent IDs
|
|
174
174
|
- Refuses to overwrite an existing `inception.json` unless `--force` is provided
|
|
175
175
|
- Supports `--dry-run` so you can inspect the generated manifest before writing it
|
|
176
|
+
- Discovers agent-rules Markdown files in the root and conventional subdirectories (`rules/`, `instructions/`, `.github/`, `.agents/rules/`), mapping them to agents using Claude-first portability conventions: `copilot-instructions.md` maps to `claude-code` (Copilot reads `CLAUDE.md` natively), and the fallback for unrecognized files excludes agents whose agentRules surface is unsupported
|
|
177
|
+
- Reads `mcp-servers.json` from the repo root (if present) and generates `mcpServers` entries; invalid entries are warned and skipped
|
|
178
|
+
- Emits guidance when a `files/` or `configs/` directory is detected at the repo root
|
|
176
179
|
|
|
177
180
|
Current `init` limitations:
|
|
178
181
|
|
|
179
182
|
- It does not reconcile generated manifest entries against `SKILL.md` frontmatter values
|
|
180
|
-
-
|
|
181
|
-
|
|
183
|
+
- `files` and `configs` entries cannot be inferred automatically since deployment targets are system-specific; add them manually after `init`
|
|
184
|
+
|
|
185
|
+
### Repo Conventions Recognized by `init`
|
|
186
|
+
|
|
187
|
+
Place a `mcp-servers.json` file at the repo root to have `init` populate the `mcpServers` section automatically. The file must be a JSON array of MCP server entries using the same schema as the `mcpServers` field in `inception.json`:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
[
|
|
191
|
+
{
|
|
192
|
+
"name": "my-server",
|
|
193
|
+
"agents": ["claude-code", "gemini-cli"],
|
|
194
|
+
"config": { "command": "npx", "args": ["-y", "my-mcp-server"] }
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Invalid entries are warned and skipped; the rest are written into the generated manifest verbatim.
|
|
182
200
|
|
|
183
201
|
## CLI Reference
|
|
184
202
|
|
package/dist/core/deploy.js
CHANGED
|
@@ -4,12 +4,10 @@ import path from "node:path";
|
|
|
4
4
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
5
5
|
import { UserError } from "../errors.js";
|
|
6
6
|
import { logger } from "../logger.js";
|
|
7
|
-
import { writeFrontmatterFile } from "./adapters/frontmatter.js";
|
|
8
7
|
import { compileAdapterActions } from "./adapters/index.js";
|
|
9
|
-
import { applyTomlMcpPatch } from "./adapters/toml.js";
|
|
10
8
|
import { lookupDeployment, registerDeployment, verifyDeployment, } from "./ownership.js";
|
|
11
9
|
import { getDeployMethod, resolveAgentSkillPath } from "./resolve.js";
|
|
12
|
-
import {
|
|
10
|
+
import { resolveTargetTemplate } from "./runtime-paths.js";
|
|
13
11
|
import { sourceAccessError, validateSkillDefinitionFile, validateSourceFile, validateSourcePath, } from "./validation.js";
|
|
14
12
|
function isPlainObject(v) {
|
|
15
13
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -82,28 +80,26 @@ function detectCollisions(actions) {
|
|
|
82
80
|
}
|
|
83
81
|
return warnings;
|
|
84
82
|
}
|
|
85
|
-
function detectAmbiguities(detectedAgents,
|
|
83
|
+
function detectAmbiguities(detectedAgents, manifest) {
|
|
86
84
|
const warnings = [];
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
});
|
|
103
|
-
if (targetsShared) {
|
|
85
|
+
if (!(detectedAgents.includes("gemini-cli") &&
|
|
86
|
+
detectedAgents.includes("antigravity"))) {
|
|
87
|
+
return warnings;
|
|
88
|
+
}
|
|
89
|
+
const bothAgents = (agents) => agents.includes("gemini-cli") && agents.includes("antigravity");
|
|
90
|
+
for (const entry of manifest.agentRules ?? []) {
|
|
91
|
+
if (bothAgents(entry.agents)) {
|
|
92
|
+
warnings.push({
|
|
93
|
+
kind: "ambiguity",
|
|
94
|
+
message: `Both "gemini-cli" and "antigravity" are listed in agentRules entry "${entry.name}". They share a GEMINI.md-backed instruction shared surface — verify that deploying to both does not create conflicting behavior.`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const entry of manifest.mcpServers ?? []) {
|
|
99
|
+
if (bothAgents(entry.agents)) {
|
|
104
100
|
warnings.push({
|
|
105
101
|
kind: "ambiguity",
|
|
106
|
-
message:
|
|
102
|
+
message: `Both "gemini-cli" and "antigravity" are listed in mcpServers entry "${entry.name}". "gemini-cli" writes to ~/.gemini/settings.json as a shared surface — verify that deploying to both does not produce conflicting MCP server behavior.`,
|
|
107
103
|
});
|
|
108
104
|
}
|
|
109
105
|
}
|
|
@@ -161,7 +157,7 @@ async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, real
|
|
|
161
157
|
}
|
|
162
158
|
return actions;
|
|
163
159
|
}
|
|
164
|
-
function planConfigPatchActions(manifest, detectedAgents, home
|
|
160
|
+
function planConfigPatchActions(manifest, detectedAgents, home) {
|
|
165
161
|
const actions = [];
|
|
166
162
|
for (const configEntry of manifest.configs ?? []) {
|
|
167
163
|
for (const agentId of configEntry.agents) {
|
|
@@ -174,7 +170,7 @@ function planConfigPatchActions(manifest, detectedAgents, home, repo) {
|
|
|
174
170
|
kind: "config-patch",
|
|
175
171
|
skill: configEntry.name,
|
|
176
172
|
agent: agentId,
|
|
177
|
-
target: resolveTargetTemplate(configEntry.target, home
|
|
173
|
+
target: resolveTargetTemplate(configEntry.target, home),
|
|
178
174
|
patch: configEntry.patch,
|
|
179
175
|
confidence: agent.provenance.skills,
|
|
180
176
|
});
|
|
@@ -194,44 +190,56 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
|
194
190
|
const actions = [
|
|
195
191
|
...(await planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
196
192
|
...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
197
|
-
...planConfigPatchActions(manifest, detectedAgents, home
|
|
193
|
+
...planConfigPatchActions(manifest, detectedAgents, home),
|
|
198
194
|
];
|
|
199
|
-
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home
|
|
195
|
+
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home);
|
|
200
196
|
actions.push(...adapterResult.actions);
|
|
201
197
|
const warnings = [
|
|
202
|
-
...detectAmbiguities(detectedAgents,
|
|
198
|
+
...detectAmbiguities(detectedAgents, manifest),
|
|
203
199
|
...detectCollisions(actions),
|
|
204
200
|
...adapterResult.warnings,
|
|
205
201
|
];
|
|
206
202
|
return { actions, warnings };
|
|
207
203
|
}
|
|
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
|
-
}
|
|
224
204
|
export async function executeDeploy(actions, dryRun, verbose, home, deps = {}) {
|
|
225
205
|
let succeeded = 0;
|
|
226
206
|
const failed = [];
|
|
227
207
|
const planned = [];
|
|
228
208
|
for (const action of actions) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
209
|
+
switch (action.kind) {
|
|
210
|
+
case "skill-dir": {
|
|
211
|
+
const result = await deploySkillDir(action, dryRun, verbose, home, planned, deps);
|
|
212
|
+
if (result.error === null) {
|
|
213
|
+
succeeded++;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
failed.push({ action, error: result.error });
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
case "file-write": {
|
|
221
|
+
const result = await deployFileWrite(action, dryRun, verbose, home, planned, deps);
|
|
222
|
+
if (result.error === null) {
|
|
223
|
+
succeeded++;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
failed.push({ action, error: result.error });
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "config-patch": {
|
|
231
|
+
const result = await deployConfigPatch(action, dryRun, verbose, home, planned, deps);
|
|
232
|
+
if (result.error === null) {
|
|
233
|
+
succeeded++;
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
failed.push({ action, error: result.error });
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
default: {
|
|
241
|
+
throw new Error(`Unhandled deploy action kind: ${action}`);
|
|
242
|
+
}
|
|
235
243
|
}
|
|
236
244
|
}
|
|
237
245
|
return { succeeded, failed, planned };
|
|
@@ -599,87 +607,3 @@ async function backupExisting(targetPath, verbose, home, expected, deps) {
|
|
|
599
607
|
await rename(targetPath, backupPath);
|
|
600
608
|
return backupPath;
|
|
601
609
|
}
|
|
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
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { access, readdir, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
3
4
|
import { dryRunPrefix, logger } from "../logger.js";
|
|
4
|
-
import { AGENT_IDS } from "../schemas/manifest.js";
|
|
5
|
+
import { AGENT_IDS, McpServerEntrySchema } from "../schemas/manifest.js";
|
|
5
6
|
const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
6
7
|
// Ordered list: first match wins. Catch-all is applied at call site.
|
|
7
8
|
const AGENT_RULES_FILE_PATTERNS = [
|
|
@@ -17,7 +18,7 @@ const AGENT_RULES_FILE_PATTERNS = [
|
|
|
17
18
|
fileNames: ["gemini.md", "gemini-instructions.md"],
|
|
18
19
|
agents: ["gemini-cli", "antigravity"],
|
|
19
20
|
},
|
|
20
|
-
{ fileNames: ["copilot-instructions.md"], agents: ["
|
|
21
|
+
{ fileNames: ["copilot-instructions.md"], agents: ["claude-code"] },
|
|
21
22
|
];
|
|
22
23
|
// Conventional subdirectory names to scan one level deep for .md files.
|
|
23
24
|
const AGENT_RULES_SUBDIRS = [
|
|
@@ -142,6 +143,9 @@ function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
|
|
|
142
143
|
// Intersect with active agents; fall back to full active list if empty
|
|
143
144
|
const intersection = defaultAgents.filter((a) => activeAgents.includes(a));
|
|
144
145
|
const agents = intersection.length > 0 ? intersection : activeAgents;
|
|
146
|
+
// Skip if no capable agents remain (e.g. --agents github-copilot only)
|
|
147
|
+
if (agents.length === 0)
|
|
148
|
+
continue;
|
|
145
149
|
// Resolve name collision with skill names
|
|
146
150
|
let name = rawName;
|
|
147
151
|
if (namesSeen.has(name)) {
|
|
@@ -160,6 +164,53 @@ function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
|
|
|
160
164
|
}
|
|
161
165
|
return rules;
|
|
162
166
|
}
|
|
167
|
+
async function loadMcpServers(baseDir) {
|
|
168
|
+
const filePath = path.join(baseDir, "mcp-servers.json");
|
|
169
|
+
let raw;
|
|
170
|
+
try {
|
|
171
|
+
raw = await readFile(filePath, "utf-8");
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
let parsed;
|
|
177
|
+
try {
|
|
178
|
+
parsed = JSON.parse(raw);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
logger.warn("init", "mcp-servers.json: invalid JSON, skipping");
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
if (!Array.isArray(parsed)) {
|
|
185
|
+
logger.warn("init", "mcp-servers.json: expected a JSON array, skipping");
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
const results = [];
|
|
189
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
190
|
+
const result = McpServerEntrySchema.safeParse(parsed[i]);
|
|
191
|
+
if (result.success) {
|
|
192
|
+
results.push(result.data);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
logger.warn("init", `mcp-servers.json: entry[${i}] invalid, skipping`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return results;
|
|
199
|
+
}
|
|
200
|
+
async function emitDirectoryHints(baseDir) {
|
|
201
|
+
for (const [dir, section] of [
|
|
202
|
+
["files", "files"],
|
|
203
|
+
["configs", "configs"],
|
|
204
|
+
]) {
|
|
205
|
+
try {
|
|
206
|
+
await access(path.join(baseDir, dir));
|
|
207
|
+
logger.info(`Detected ${dir}/ directory — add ${section} entries manually to the generated manifest with target paths.`);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// directory does not exist — silent
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
163
214
|
async function manifestExists(manifestPath) {
|
|
164
215
|
try {
|
|
165
216
|
await access(manifestPath);
|
|
@@ -169,9 +220,27 @@ async function manifestExists(manifestPath) {
|
|
|
169
220
|
return false;
|
|
170
221
|
}
|
|
171
222
|
}
|
|
223
|
+
function logVerboseManifest(skills, agentRules, mcpServers) {
|
|
224
|
+
for (const s of skills) {
|
|
225
|
+
logger.detail(`${s.name} → ${s.path}`);
|
|
226
|
+
}
|
|
227
|
+
if (agentRules.length > 0) {
|
|
228
|
+
logger.detail("agentRules:");
|
|
229
|
+
for (const r of agentRules) {
|
|
230
|
+
logger.detail(` ${r.name} → ${r.path} [${r.agents.join(", ")}]`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (mcpServers.length > 0) {
|
|
234
|
+
logger.detail("mcpServers:");
|
|
235
|
+
for (const m of mcpServers) {
|
|
236
|
+
logger.detail(` ${m.name} [${m.agents.join(", ")}]`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
172
240
|
export async function runInit(options) {
|
|
173
241
|
const { directory, dryRun, force, verbose } = options;
|
|
174
242
|
const agents = options.agents ?? [...AGENT_IDS];
|
|
243
|
+
const agentRulesCapableAgents = agents.filter((id) => AGENT_REGISTRY_BY_ID[id].agentRulesSupport?.status !== "unsupported");
|
|
175
244
|
const manifestPath = path.join(directory, "inception.json");
|
|
176
245
|
if (!dryRun && (await manifestExists(manifestPath)) && !force) {
|
|
177
246
|
logger.error(`Error: ${manifestPath} already exists. Use --force to overwrite.`);
|
|
@@ -186,33 +255,28 @@ export async function runInit(options) {
|
|
|
186
255
|
const skillNamesSeen = new Set(skills.map((s) => s.name));
|
|
187
256
|
const skillDirRelPaths = new Set(found.map((f) => f.relPath));
|
|
188
257
|
const agentRulesCandidates = await findAgentRulesCandidates(directory, skillDirRelPaths);
|
|
189
|
-
const agentRules = buildAgentRules(agentRulesCandidates,
|
|
258
|
+
const agentRules = buildAgentRules(agentRulesCandidates, agentRulesCapableAgents, skillNamesSeen);
|
|
259
|
+
const mcpServers = await loadMcpServers(directory);
|
|
190
260
|
const manifest = {
|
|
191
261
|
skills,
|
|
192
262
|
files: [],
|
|
193
263
|
configs: [],
|
|
194
|
-
mcpServers
|
|
264
|
+
mcpServers,
|
|
195
265
|
agentRules,
|
|
196
266
|
};
|
|
197
267
|
const json = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
198
268
|
if (dryRun) {
|
|
199
|
-
logger.info(`${dryRunPrefix(true)}Would write ${manifestPath} with ${skills.length} skill(s)
|
|
269
|
+
logger.info(`${dryRunPrefix(true)}Would write ${manifestPath} with ${skills.length} skill(s), ${agentRules.length} agentRule(s), and ${mcpServers.length} mcpServer(s):`);
|
|
200
270
|
logger.info("");
|
|
201
271
|
logger.info(json);
|
|
272
|
+
await emitDirectoryHints(directory);
|
|
202
273
|
return 0;
|
|
203
274
|
}
|
|
204
275
|
await writeFile(manifestPath, json, "utf-8");
|
|
205
|
-
logger.info(`Generated ${manifestPath} with ${skills.length} skill(s)
|
|
276
|
+
logger.info(`Generated ${manifestPath} with ${skills.length} skill(s), ${agentRules.length} agentRule(s), and ${mcpServers.length} mcpServer(s).`);
|
|
206
277
|
if (verbose) {
|
|
207
|
-
|
|
208
|
-
logger.detail(`${s.name} → ${s.path}`);
|
|
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
|
-
}
|
|
278
|
+
logVerboseManifest(skills, agentRules, mcpServers);
|
|
216
279
|
}
|
|
280
|
+
await emitDirectoryHints(directory);
|
|
217
281
|
return 0;
|
|
218
282
|
}
|