@kuznai/inception-engine 0.11.0 → 0.12.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 +7 -7
- package/dist/config/agents.js +74 -24
- package/dist/core/adapters/mcp.js +9 -5
- package/dist/core/adapters/rules.js +27 -13
- package/dist/core/deploy.js +2 -1
- package/dist/core/validation.d.ts +3 -0
- package/dist/core/validation.js +97 -1
- package/dist/types.d.ts +13 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ Managed skills overwrite their previous version. If a target exists but was not
|
|
|
49
49
|
| File write | All agents via manifest and CLI | All agents |
|
|
50
50
|
| Config patch (JSON merge) | All agents via manifest and CLI | All agents |
|
|
51
51
|
| MCP Servers | claude-code, gemini-cli; other agents are warned and skipped | claude-code, gemini-cli |
|
|
52
|
-
| Global Rules Files | claude-code, codex, gemini-cli, opencode; other agents are warned and skipped | claude-code, codex, gemini-cli, opencode |
|
|
52
|
+
| Global Rules Files | claude-code, codex, gemini-cli, opencode; github-copilot reads CLAUDE.md natively (deploy via claude-code); other agents are warned and skipped | claude-code, codex, gemini-cli, opencode |
|
|
53
53
|
| `init` manifest generation | Scans `SKILL.md` directories and writes starter `skills` entries | 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.
|
|
@@ -128,17 +128,17 @@ Each **mcpServer** entry registers an MCP server into the agent's config file by
|
|
|
128
128
|
|
|
129
129
|
- **name** - Unique identifier (same format as skill names); used as the server's key in the config
|
|
130
130
|
- **agents** - Array of agent IDs to register this server with
|
|
131
|
-
- **config** - Raw server descriptor object
|
|
131
|
+
- **config** - Raw server descriptor object. For the currently supported JSON-backed adapters, inception-engine requires at least one non-empty `command` or `url` field, validates `args` as an array of strings when present, and validates `env` as a string-to-string object when present. Additional keys are passed through verbatim.
|
|
132
132
|
|
|
133
|
-
MCP server registration is currently supported for `claude-code` (`~/.claude.json`) and `gemini-cli` (`~/.gemini/settings.json`). Other agents emit a warning and are skipped. Revert removes the registered server entry from the config file.
|
|
133
|
+
MCP server registration is currently supported for `claude-code` (`~/.claude.json`) and `gemini-cli` (`~/.gemini/settings.json`). Other agents emit a schema-aware warning and are skipped when their support would require a non-JSON adapter, such as Codex `config.toml`, Antigravity frontmatter in repo-scoped rules files, OpenCode `opencode.json`, or GitHub Copilot repo-scoped MCP surfaces. Revert removes the registered server entry from the config file.
|
|
134
134
|
|
|
135
135
|
Each **agentRules** entry deploys a Markdown instruction file to an agent's supported global rules file location:
|
|
136
136
|
|
|
137
137
|
- **name** - Unique identifier (same format as skill names)
|
|
138
|
-
- **path** - Relative path to the source Markdown file within the repo
|
|
138
|
+
- **path** - Relative path to the source Markdown file within the repo; supported global rules adapters require a `.md` or `.markdown` source path
|
|
139
139
|
- **agents** - Array of agent IDs to deploy this file to
|
|
140
140
|
|
|
141
|
-
Global rules-file deployment is currently supported for `claude-code` (`~/.claude/CLAUDE.md`), `codex` (`~/.codex/AGENTS.md`), `gemini-cli` (`~/.gemini/GEMINI.md`), and `opencode` (`~/.config/opencode/AGENTS.md`). Other agents emit
|
|
141
|
+
Global rules-file deployment is currently supported for `claude-code` (`~/.claude/CLAUDE.md`), `codex` (`~/.codex/AGENTS.md`), `gemini-cli` (`~/.gemini/GEMINI.md`), and `opencode` (`~/.config/opencode/AGENTS.md`). For `github-copilot`, no separate deployment is needed because Copilot reads `CLAUDE.md` natively — target it via the `claude-code` agentRules entry and it reaches Copilot automatically. Other agents emit schema-aware warnings and are skipped when their instruction surfaces are repo-scoped, frontmatter-driven, or otherwise not implemented as a single global Markdown file. Revert removes the deployed rules file.
|
|
142
142
|
|
|
143
143
|
## Creating Skills
|
|
144
144
|
|
|
@@ -155,7 +155,7 @@ description: What this skill does and when to use it
|
|
|
155
155
|
Instructions for the AI agent...
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
The `name` and `description` fields in the frontmatter are used by most agents. The description determines when the agent activates the skill. inception-engine
|
|
158
|
+
The `name` and `description` fields in the frontmatter are used by most agents. The description determines when the agent activates the skill. inception-engine now validates this minimum contract during deploy: `SKILL.md` must start with YAML frontmatter and include non-empty single-line `name` and `description` fields. It still does not attempt full YAML/schema validation beyond that targeted check.
|
|
159
159
|
|
|
160
160
|
## `init` Command
|
|
161
161
|
|
|
@@ -171,7 +171,7 @@ Current `init` behavior:
|
|
|
171
171
|
|
|
172
172
|
Current `init` limitations:
|
|
173
173
|
|
|
174
|
-
- It does not
|
|
174
|
+
- It does not reconcile generated manifest entries against `SKILL.md` frontmatter values
|
|
175
175
|
- It does not infer `files`, `configs`, `mcpServers`, or `agentRules`
|
|
176
176
|
- It does not reconcile generated output with the longer-term Claude-first portability direction
|
|
177
177
|
|
package/dist/config/agents.js
CHANGED
|
@@ -18,13 +18,21 @@ export const AGENT_REGISTRY = [
|
|
|
18
18
|
mcpConfig: "documented",
|
|
19
19
|
agentRules: "documented",
|
|
20
20
|
},
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
mcpSupport: {
|
|
22
|
+
status: "supported",
|
|
23
|
+
schemaLabel: "JSON mcpServers config",
|
|
24
|
+
path: {
|
|
25
|
+
posix: ["{home}", ".claude.json"],
|
|
26
|
+
windows: ["{home}", ".claude.json"],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
agentRulesSupport: {
|
|
30
|
+
status: "supported",
|
|
31
|
+
schemaLabel: "global Markdown rules file",
|
|
32
|
+
path: {
|
|
33
|
+
posix: ["{home}", ".claude", "CLAUDE.md"],
|
|
34
|
+
windows: ["{home}", ".claude", "CLAUDE.md"],
|
|
35
|
+
},
|
|
28
36
|
},
|
|
29
37
|
},
|
|
30
38
|
{
|
|
@@ -45,10 +53,18 @@ export const AGENT_REGISTRY = [
|
|
|
45
53
|
detectBinary: "documented",
|
|
46
54
|
agentRules: "documented",
|
|
47
55
|
},
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
mcpSupport: {
|
|
57
|
+
status: "unsupported",
|
|
58
|
+
schemaLabel: "config.toml MCP schema",
|
|
59
|
+
reason: "Codex stores MCP servers in config.toml, so JSON merge-patch deployment is not sufficient yet",
|
|
60
|
+
},
|
|
61
|
+
agentRulesSupport: {
|
|
62
|
+
status: "supported",
|
|
63
|
+
schemaLabel: "global Markdown rules file",
|
|
64
|
+
path: {
|
|
65
|
+
posix: ["{home}", ".codex", "AGENTS.md"],
|
|
66
|
+
windows: ["{home}", ".codex", "AGENTS.md"],
|
|
67
|
+
},
|
|
52
68
|
},
|
|
53
69
|
},
|
|
54
70
|
{
|
|
@@ -70,13 +86,21 @@ export const AGENT_REGISTRY = [
|
|
|
70
86
|
mcpConfig: "documented",
|
|
71
87
|
agentRules: "documented",
|
|
72
88
|
},
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
mcpSupport: {
|
|
90
|
+
status: "supported",
|
|
91
|
+
schemaLabel: "JSON mcpServers config",
|
|
92
|
+
path: {
|
|
93
|
+
posix: ["{home}", ".gemini", "settings.json"],
|
|
94
|
+
windows: ["{home}", ".gemini", "settings.json"],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
agentRulesSupport: {
|
|
98
|
+
status: "supported",
|
|
99
|
+
schemaLabel: "global Markdown rules file",
|
|
100
|
+
path: {
|
|
101
|
+
posix: ["{home}", ".gemini", "GEMINI.md"],
|
|
102
|
+
windows: ["{home}", ".gemini", "GEMINI.md"],
|
|
103
|
+
},
|
|
80
104
|
},
|
|
81
105
|
},
|
|
82
106
|
{
|
|
@@ -96,7 +120,16 @@ export const AGENT_REGISTRY = [
|
|
|
96
120
|
detectPaths: "implementation-only",
|
|
97
121
|
detectBinary: "provisional",
|
|
98
122
|
},
|
|
99
|
-
|
|
123
|
+
mcpSupport: {
|
|
124
|
+
status: "unsupported",
|
|
125
|
+
schemaLabel: "frontmatter-driven MCP rules",
|
|
126
|
+
reason: "Antigravity MCP support depends on frontmatter inside repo-scoped .agents/rules files, which this release does not translate or validate yet",
|
|
127
|
+
},
|
|
128
|
+
agentRulesSupport: {
|
|
129
|
+
status: "unsupported",
|
|
130
|
+
schemaLabel: "repo-scoped rules files",
|
|
131
|
+
reason: "Antigravity instructions live in repo-scoped .agents/rules files rather than one global Markdown target",
|
|
132
|
+
},
|
|
100
133
|
},
|
|
101
134
|
{
|
|
102
135
|
id: "opencode",
|
|
@@ -116,10 +149,18 @@ export const AGENT_REGISTRY = [
|
|
|
116
149
|
detectBinary: "documented",
|
|
117
150
|
agentRules: "documented",
|
|
118
151
|
},
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
152
|
+
mcpSupport: {
|
|
153
|
+
status: "unsupported",
|
|
154
|
+
schemaLabel: "opencode.json MCP schema",
|
|
155
|
+
reason: "OpenCode keeps MCP config in opencode.json under its own shape, so plain JSON merge-patch support is not implemented here",
|
|
156
|
+
},
|
|
157
|
+
agentRulesSupport: {
|
|
158
|
+
status: "supported",
|
|
159
|
+
schemaLabel: "global Markdown rules file",
|
|
160
|
+
path: {
|
|
161
|
+
posix: ["{xdg_config}", "opencode", "AGENTS.md"],
|
|
162
|
+
windows: ["{appdata}", "opencode", "AGENTS.md"],
|
|
163
|
+
},
|
|
123
164
|
},
|
|
124
165
|
},
|
|
125
166
|
{
|
|
@@ -139,7 +180,16 @@ export const AGENT_REGISTRY = [
|
|
|
139
180
|
detectPaths: "documented",
|
|
140
181
|
detectBinary: "documented",
|
|
141
182
|
},
|
|
142
|
-
|
|
183
|
+
mcpSupport: {
|
|
184
|
+
status: "unsupported",
|
|
185
|
+
schemaLabel: "repo-scoped MCP surfaces",
|
|
186
|
+
reason: "GitHub Copilot MCP support depends on repo-scoped files such as devcontainer or agent-frontmatter mappings, which are not translated here",
|
|
187
|
+
},
|
|
188
|
+
agentRulesSupport: {
|
|
189
|
+
status: "unsupported",
|
|
190
|
+
schemaLabel: "Claude-native shared instructions",
|
|
191
|
+
reason: 'GitHub Copilot reads CLAUDE.md natively, so deploy via the "claude-code" agentRules target instead of a separate rules surface',
|
|
192
|
+
},
|
|
143
193
|
policyNote: "Organization policies may override locally deployed skills. Verify with your GitHub org admin if deployed skills are not active.",
|
|
144
194
|
},
|
|
145
195
|
];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { AGENT_REGISTRY_BY_ID } from "../../config/agents.js";
|
|
3
3
|
import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
|
|
4
|
+
import { validateMcpServerConfigShape } from "../validation.js";
|
|
4
5
|
export function compileMcpServerActions(entry, detectedAgents, home) {
|
|
5
6
|
const actions = [];
|
|
6
7
|
const warnings = [];
|
|
@@ -9,14 +10,16 @@ export function compileMcpServerActions(entry, detectedAgents, home) {
|
|
|
9
10
|
if (!detectedAgents.includes(agentId))
|
|
10
11
|
continue;
|
|
11
12
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
12
|
-
|
|
13
|
+
const support = agent?.mcpSupport;
|
|
14
|
+
if (!support || support.status === "unsupported") {
|
|
13
15
|
warnings.push({
|
|
14
16
|
kind: "confidence",
|
|
15
|
-
message: `mcpServers: agent "${agentId}" does not
|
|
17
|
+
message: `mcpServers: agent "${agentId}" uses ${support?.schemaLabel ?? "an unsupported MCP schema"} and ${support?.reason ?? "does not expose a supported MCP adapter"} — skipping "${entry.name}"`,
|
|
16
18
|
});
|
|
17
19
|
continue;
|
|
18
20
|
}
|
|
19
|
-
|
|
21
|
+
validateMcpServerConfigShape(entry.config, entry.name, agentId);
|
|
22
|
+
const target = resolvePlaceholders(support.path[platform], "", home);
|
|
20
23
|
// Ensure no stray empty segments collapse the path unexpectedly
|
|
21
24
|
const resolvedTarget = path.resolve(target);
|
|
22
25
|
actions.push({
|
|
@@ -37,9 +40,10 @@ export function compileMcpServerReverts(entry, agentFilter, home) {
|
|
|
37
40
|
if (agentFilter && !agentFilter.includes(agentId))
|
|
38
41
|
continue;
|
|
39
42
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
40
|
-
|
|
43
|
+
const support = agent?.mcpSupport;
|
|
44
|
+
if (!support || support.status === "unsupported")
|
|
41
45
|
continue;
|
|
42
|
-
const target = path.resolve(resolvePlaceholders(
|
|
46
|
+
const target = path.resolve(resolvePlaceholders(support.path[platform], "", home));
|
|
43
47
|
actions.push({
|
|
44
48
|
kind: "config-patch",
|
|
45
49
|
skill: entry.name,
|
|
@@ -1,36 +1,49 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { AGENT_REGISTRY_BY_ID } from "../../config/agents.js";
|
|
3
3
|
import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
|
|
4
|
-
import { validateSourceFile, validateSourcePath } from "../validation.js";
|
|
4
|
+
import { validateAgentRuleMarkdownPath, validateSourceFile, validateSourcePath, } from "../validation.js";
|
|
5
5
|
export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home) {
|
|
6
6
|
const actions = [];
|
|
7
7
|
const warnings = [];
|
|
8
8
|
const platform = getPlatformKey();
|
|
9
9
|
const targetAgents = entry.agents.filter((agentId) => detectedAgents.includes(agentId));
|
|
10
|
+
const supportedTargets = [];
|
|
10
11
|
if (targetAgents.length === 0) {
|
|
11
12
|
return { actions, warnings };
|
|
12
13
|
}
|
|
13
|
-
// Validate the source file once (before iterating agents) since it is shared.
|
|
14
|
-
const source = path.resolve(sourceDir, entry.path);
|
|
15
|
-
await validateSourcePath(source, entry.path, resolvedSourceDir, realRoot);
|
|
16
|
-
await validateSourceFile(source, entry.path);
|
|
17
14
|
for (const agentId of targetAgents) {
|
|
18
15
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
19
|
-
|
|
16
|
+
const support = agent?.agentRulesSupport;
|
|
17
|
+
if (!support || support.status === "unsupported") {
|
|
20
18
|
warnings.push({
|
|
21
19
|
kind: "confidence",
|
|
22
|
-
message: `agentRules: agent "${agentId}" does not
|
|
20
|
+
message: `agentRules: agent "${agentId}" uses ${support?.schemaLabel ?? "an unsupported instruction schema"} and ${support?.reason ?? "does not expose a supported rules adapter"} — skipping "${entry.name}"`,
|
|
23
21
|
});
|
|
24
22
|
continue;
|
|
25
23
|
}
|
|
26
|
-
|
|
24
|
+
supportedTargets.push({
|
|
25
|
+
agentId,
|
|
26
|
+
confidence: agent.provenance.agentRules ?? "provisional",
|
|
27
|
+
target: resolvePlaceholders(support.path[platform], "", home),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (supportedTargets.length === 0) {
|
|
31
|
+
return { actions, warnings };
|
|
32
|
+
}
|
|
33
|
+
// Validate the shared source file only when at least one target uses the
|
|
34
|
+
// current global-Markdown adapter surface.
|
|
35
|
+
const source = path.resolve(sourceDir, entry.path);
|
|
36
|
+
await validateSourcePath(source, entry.path, resolvedSourceDir, realRoot);
|
|
37
|
+
await validateSourceFile(source, entry.path);
|
|
38
|
+
for (const target of supportedTargets) {
|
|
39
|
+
validateAgentRuleMarkdownPath(entry.path, target.agentId);
|
|
27
40
|
actions.push({
|
|
28
41
|
kind: "file-write",
|
|
29
42
|
skill: entry.name,
|
|
30
|
-
agent: agentId,
|
|
43
|
+
agent: target.agentId,
|
|
31
44
|
source,
|
|
32
|
-
target,
|
|
33
|
-
confidence:
|
|
45
|
+
target: target.target,
|
|
46
|
+
confidence: target.confidence,
|
|
34
47
|
});
|
|
35
48
|
}
|
|
36
49
|
return { actions, warnings };
|
|
@@ -42,9 +55,10 @@ export function compileAgentRuleReverts(entry, agentFilter, home) {
|
|
|
42
55
|
if (agentFilter && !agentFilter.includes(agentId))
|
|
43
56
|
continue;
|
|
44
57
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
45
|
-
|
|
58
|
+
const support = agent?.agentRulesSupport;
|
|
59
|
+
if (!support || support.status === "unsupported")
|
|
46
60
|
continue;
|
|
47
|
-
const target = resolvePlaceholders(
|
|
61
|
+
const target = resolvePlaceholders(support.path[platform], "", home);
|
|
48
62
|
actions.push({
|
|
49
63
|
kind: "file-write",
|
|
50
64
|
skill: entry.name,
|
package/dist/core/deploy.js
CHANGED
|
@@ -8,7 +8,7 @@ import { compileAdapterActions } from "./adapters/index.js";
|
|
|
8
8
|
import { lookupDeployment, registerDeployment, verifyDeployment, } from "./ownership.js";
|
|
9
9
|
import { getDeployMethod, resolveAgentSkillPath } from "./resolve.js";
|
|
10
10
|
import { resolveTargetTemplate } from "./runtime-paths.js";
|
|
11
|
-
import { sourceAccessError, validateSourceFile, validateSourcePath, } from "./validation.js";
|
|
11
|
+
import { sourceAccessError, validateSkillDefinitionFile, validateSourceFile, validateSourcePath, } from "./validation.js";
|
|
12
12
|
function isPlainObject(v) {
|
|
13
13
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
14
14
|
}
|
|
@@ -505,6 +505,7 @@ async function validateSkillContract(source, skillPath) {
|
|
|
505
505
|
}
|
|
506
506
|
throw new UserError("DEPLOY_FAILED", `Skill "${skillPath}" source is missing SKILL.md: ${source}`);
|
|
507
507
|
}
|
|
508
|
+
await validateSkillDefinitionFile(path.join(source, "SKILL.md"), skillPath);
|
|
508
509
|
}
|
|
509
510
|
async function assertTargetAbsent(targetPath) {
|
|
510
511
|
try {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export declare function sourceAccessError(err: unknown, sourcePath: string): string;
|
|
2
2
|
export declare function validateSourcePath(source: string, skillPath: string, resolvedSourceDir: string, realRoot: string): Promise<void>;
|
|
3
3
|
export declare function validateSourceFile(sourcePath: string, manifestPath: string): Promise<void>;
|
|
4
|
+
export declare function validateMcpServerConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
5
|
+
export declare function validateAgentRuleMarkdownPath(manifestPath: string, agentId: string): void;
|
|
6
|
+
export declare function validateSkillDefinitionFile(sourcePath: string, manifestPath: string): Promise<void>;
|
package/dist/core/validation.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { lstat, realpath, stat } from "node:fs/promises";
|
|
1
|
+
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { UserError } from "../errors.js";
|
|
4
4
|
export function sourceAccessError(err, sourcePath) {
|
|
@@ -64,3 +64,99 @@ export async function validateSourceFile(sourcePath, manifestPath) {
|
|
|
64
64
|
throw new UserError("DEPLOY_FAILED", `Source is not a file: ${manifestPath}`);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
function isRecordOfStrings(value) {
|
|
68
|
+
return (typeof value === "object" &&
|
|
69
|
+
value !== null &&
|
|
70
|
+
!Array.isArray(value) &&
|
|
71
|
+
Object.values(value).every((entry) => typeof entry === "string"));
|
|
72
|
+
}
|
|
73
|
+
function validateNonEmptyStringField(value, field, entryName, agentId) {
|
|
74
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
75
|
+
throw new UserError("DEPLOY_FAILED", `mcpServers entry "${entryName}" for agent "${agentId}" must define "${field}" as a non-empty string`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export function validateMcpServerConfigShape(config, entryName, agentId) {
|
|
79
|
+
const hasCommand = Object.hasOwn(config, "command");
|
|
80
|
+
const hasUrl = Object.hasOwn(config, "url");
|
|
81
|
+
if (!(hasCommand || hasUrl)) {
|
|
82
|
+
throw new UserError("DEPLOY_FAILED", `mcpServers entry "${entryName}" for agent "${agentId}" must define either a non-empty "command" or "url"`);
|
|
83
|
+
}
|
|
84
|
+
if (hasCommand) {
|
|
85
|
+
validateNonEmptyStringField(config.command, "command", entryName, agentId);
|
|
86
|
+
}
|
|
87
|
+
if (hasUrl) {
|
|
88
|
+
validateNonEmptyStringField(config.url, "url", entryName, agentId);
|
|
89
|
+
}
|
|
90
|
+
if (Object.hasOwn(config, "args") &&
|
|
91
|
+
(!Array.isArray(config.args) ||
|
|
92
|
+
config.args.some((arg) => typeof arg !== "string"))) {
|
|
93
|
+
throw new UserError("DEPLOY_FAILED", `mcpServers entry "${entryName}" for agent "${agentId}" must define "args" as an array of strings when present`);
|
|
94
|
+
}
|
|
95
|
+
if (Object.hasOwn(config, "env") && !isRecordOfStrings(config.env)) {
|
|
96
|
+
throw new UserError("DEPLOY_FAILED", `mcpServers entry "${entryName}" for agent "${agentId}" must define "env" as an object of string values when present`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export function validateAgentRuleMarkdownPath(manifestPath, agentId) {
|
|
100
|
+
const extension = path.extname(manifestPath).toLowerCase();
|
|
101
|
+
if (extension !== ".md" && extension !== ".markdown") {
|
|
102
|
+
throw new UserError("DEPLOY_FAILED", `agentRules entry "${manifestPath}" for agent "${agentId}" must point to a Markdown source file`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function trimMatchingQuotes(value) {
|
|
106
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
107
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
108
|
+
return value.slice(1, -1).trim();
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
function parseSimpleFrontmatterValue(field, rawValue, manifestPath) {
|
|
113
|
+
const value = trimMatchingQuotes(rawValue.trim());
|
|
114
|
+
if (value.length === 0) {
|
|
115
|
+
throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md frontmatter field "${field}" must be a non-empty string`);
|
|
116
|
+
}
|
|
117
|
+
if (rawValue.trim() === "|" || rawValue.trim() === ">") {
|
|
118
|
+
throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md frontmatter field "${field}" must be a single-line string`);
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
export async function validateSkillDefinitionFile(sourcePath, manifestPath) {
|
|
123
|
+
await validateSourceFile(sourcePath, `${manifestPath}/SKILL.md`);
|
|
124
|
+
let raw;
|
|
125
|
+
try {
|
|
126
|
+
raw = await readFile(sourcePath, "utf-8");
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
throw new UserError("DEPLOY_FAILED", sourceAccessError(err, `${manifestPath}/SKILL.md`));
|
|
130
|
+
}
|
|
131
|
+
const lines = raw.split(/\r?\n/);
|
|
132
|
+
if (lines[0]?.trim() !== "---") {
|
|
133
|
+
throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md must start with YAML frontmatter delimited by ---`);
|
|
134
|
+
}
|
|
135
|
+
const closingIndex = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
|
|
136
|
+
if (closingIndex === -1) {
|
|
137
|
+
throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md is missing the closing --- frontmatter delimiter`);
|
|
138
|
+
}
|
|
139
|
+
let name = null;
|
|
140
|
+
let description = null;
|
|
141
|
+
for (const line of lines.slice(1, closingIndex)) {
|
|
142
|
+
const trimmed = line.trim();
|
|
143
|
+
if (trimmed.length === 0 || trimmed.startsWith("#"))
|
|
144
|
+
continue;
|
|
145
|
+
const match = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line);
|
|
146
|
+
if (!match)
|
|
147
|
+
continue;
|
|
148
|
+
const [, key, rawValue] = match;
|
|
149
|
+
if (key === "name") {
|
|
150
|
+
name = parseSimpleFrontmatterValue("name", rawValue, manifestPath);
|
|
151
|
+
}
|
|
152
|
+
else if (key === "description") {
|
|
153
|
+
description = parseSimpleFrontmatterValue("description", rawValue, manifestPath);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (name === null) {
|
|
157
|
+
throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md frontmatter must include a non-empty "name" field`);
|
|
158
|
+
}
|
|
159
|
+
if (description === null) {
|
|
160
|
+
throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md frontmatter must include a non-empty "description" field`);
|
|
161
|
+
}
|
|
162
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,17 @@ export interface AgentPaths {
|
|
|
5
5
|
windows: string[];
|
|
6
6
|
}
|
|
7
7
|
export type Confidence = "documented" | "implementation-only" | "provisional";
|
|
8
|
+
export interface SupportedAgentSurface {
|
|
9
|
+
status: "supported";
|
|
10
|
+
path: AgentPaths;
|
|
11
|
+
schemaLabel: string;
|
|
12
|
+
}
|
|
13
|
+
export interface UnsupportedAgentSurface {
|
|
14
|
+
status: "unsupported";
|
|
15
|
+
schemaLabel: string;
|
|
16
|
+
reason: string;
|
|
17
|
+
}
|
|
18
|
+
export type AgentSurfaceSupport = SupportedAgentSurface | UnsupportedAgentSurface;
|
|
8
19
|
export interface AgentProvenance {
|
|
9
20
|
skills: Confidence;
|
|
10
21
|
detectPaths: Confidence;
|
|
@@ -19,8 +30,8 @@ export interface AgentConfig {
|
|
|
19
30
|
detectPaths: AgentPaths;
|
|
20
31
|
detectBinary: string | null;
|
|
21
32
|
provenance: AgentProvenance;
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
mcpSupport?: AgentSurfaceSupport;
|
|
34
|
+
agentRulesSupport?: AgentSurfaceSupport;
|
|
24
35
|
policyNote?: string;
|
|
25
36
|
}
|
|
26
37
|
export interface PlanWarning {
|