@kuznai/inception-engine 0.18.0 → 0.19.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 +27 -24
- package/dist/config/agents.js +87 -10
- package/dist/core/adapters/agent-definitions.d.ts +2 -2
- package/dist/core/adapters/agent-definitions.js +6 -5
- package/dist/core/adapters/index.d.ts +1 -1
- package/dist/core/adapters/index.js +4 -4
- package/dist/core/adapters/mcp.d.ts +2 -2
- package/dist/core/adapters/mcp.js +4 -4
- package/dist/core/adapters/rules.d.ts +2 -2
- package/dist/core/adapters/rules.js +61 -14
- package/dist/core/deploy.d.ts +1 -1
- package/dist/core/deploy.js +206 -54
- package/dist/core/init.js +127 -47
- package/dist/core/ownership.d.ts +2 -2
- package/dist/core/preflight.d.ts +1 -1
- package/dist/core/preflight.js +95 -25
- package/dist/core/resolve.d.ts +1 -1
- package/dist/core/resolve.js +7 -4
- package/dist/core/revert.js +9 -7
- package/dist/core/runtime-paths.d.ts +2 -1
- package/dist/core/runtime-paths.js +30 -23
- package/dist/core/validation.d.ts +10 -1
- package/dist/core/validation.js +55 -0
- package/dist/formatters.d.ts +5 -0
- package/dist/formatters.js +58 -0
- package/dist/index.js +8 -24
- package/dist/logger.js +22 -21
- package/dist/schemas/manifest.d.ts +2 -0
- package/dist/schemas/manifest.js +8 -5
- package/dist/schemas/registry.d.ts +38 -0
- package/dist/schemas/registry.js +7 -0
- package/dist/types.d.ts +47 -0
- package/package.json +1 -1
package/dist/core/preflight.js
CHANGED
|
@@ -1,33 +1,95 @@
|
|
|
1
|
-
import { stat } from "node:fs/promises";
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
4
|
+
import { resolveRuntimePaths } from "./runtime-paths.js";
|
|
4
5
|
const BUDGET_WARN_BYTES = 50 * 1024; // 50 KB
|
|
5
|
-
function
|
|
6
|
+
async function detectEnterpriseManagement(agentId, home) {
|
|
7
|
+
if (!AGENT_REGISTRY_BY_ID[agentId]?.enterprisePolicyDetection)
|
|
8
|
+
return null;
|
|
9
|
+
// Check for common GitHub Enterprise environment variables
|
|
10
|
+
if (process.env.GITHUB_ENTERPRISE_URL ||
|
|
11
|
+
process.env.GH_ENTERPRISE_TOKEN ||
|
|
12
|
+
process.env.GITHUB_TOKEN_TYPE === "enterprise") {
|
|
13
|
+
return "GitHub Enterprise environment variables detected. Enterprise policies may override local configurations.";
|
|
14
|
+
}
|
|
15
|
+
const { xdgConfig, localAppdata } = resolveRuntimePaths(home);
|
|
16
|
+
const configPath = process.platform === "win32"
|
|
17
|
+
? path.join(localAppdata, "github-copilot", "hosts.json")
|
|
18
|
+
: path.join(xdgConfig, "github-copilot", "hosts.json");
|
|
19
|
+
try {
|
|
20
|
+
const content = await readFile(configPath, "utf8");
|
|
21
|
+
const hosts = JSON.parse(content);
|
|
22
|
+
const hostNames = Object.keys(hosts);
|
|
23
|
+
const enterpriseHosts = hostNames.filter((h) => h !== "github.com" && h !== "localhost");
|
|
24
|
+
if (enterpriseHosts.length > 0) {
|
|
25
|
+
return `GitHub Copilot is authenticated against enterprise host(s): ${enterpriseHosts.join(", ")}. Enterprise policies may override local configurations.`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
const code = err.code;
|
|
30
|
+
if (code !== "ENOENT" &&
|
|
31
|
+
code !== "EACCES" &&
|
|
32
|
+
code !== "EPERM" &&
|
|
33
|
+
code !== undefined) {
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function groupRulesByPath(rulesForAgent) {
|
|
40
|
+
const entriesByPath = new Map();
|
|
41
|
+
for (const entry of rulesForAgent ?? []) {
|
|
42
|
+
const list = entriesByPath.get(entry.path) ?? [];
|
|
43
|
+
list.push({ name: entry.name, scope: entry.scope });
|
|
44
|
+
entriesByPath.set(entry.path, list);
|
|
45
|
+
}
|
|
46
|
+
return entriesByPath;
|
|
47
|
+
}
|
|
48
|
+
function detectScopeOverlaps(agentId, rulesForAgent) {
|
|
6
49
|
const warnings = [];
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
if (globalEntries.length === 0 || repoEntries.length === 0)
|
|
50
|
+
const entriesByPath = groupRulesByPath(rulesForAgent);
|
|
51
|
+
for (const [path, list] of entriesByPath) {
|
|
52
|
+
const scopes = new Set(list.map((l) => l.scope));
|
|
53
|
+
if (scopes.size < 2)
|
|
12
54
|
continue;
|
|
13
|
-
const
|
|
14
|
-
for (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
55
|
+
const scopeList = Array.from(scopes).sort();
|
|
56
|
+
for (let i = 0; i < scopeList.length; i++) {
|
|
57
|
+
for (let j = i + 1; j < scopeList.length; j++) {
|
|
58
|
+
const entryB = list.find((l) => l.scope === scopeList[j]);
|
|
59
|
+
if (entryB) {
|
|
60
|
+
warnings.push({
|
|
61
|
+
kind: "precedence",
|
|
62
|
+
message: `Agent "${agentId}" has agentRules entry "${entryB.name}" deployed to both ${scopeList[i]} and ${scopeList[j]} scope from the same source path "${path}". The file will be written to two distinct targets — verify this is intentional and not a copy-paste mistake.`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
20
65
|
}
|
|
21
66
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
67
|
+
}
|
|
68
|
+
return warnings;
|
|
69
|
+
}
|
|
70
|
+
function detectMultipleActiveInstructionScopes(agentId, rulesForAgent) {
|
|
71
|
+
const activeScopes = Array.from(new Set((rulesForAgent ?? []).map((e) => e.scope))).sort();
|
|
72
|
+
if (activeScopes.length <= 1)
|
|
73
|
+
return [];
|
|
74
|
+
const scopeDescriptions = activeScopes
|
|
75
|
+
.map((s) => {
|
|
76
|
+
const entries = (rulesForAgent ?? []).filter((e) => e.scope === s);
|
|
77
|
+
return `${s} [${entries.map((e) => `"${e.name}"`).join(", ")}]`;
|
|
78
|
+
})
|
|
79
|
+
.join(" and ");
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
kind: "precedence",
|
|
83
|
+
message: `Agent "${agentId}" will have multiple instruction files active simultaneously across ${activeScopes.length} scopes: ${scopeDescriptions}. All will be loaded by the agent — ensure the content is intended to stack and does not conflict.`,
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
function detectInstructionPrecedence(detectedAgents, manifest) {
|
|
88
|
+
const warnings = [];
|
|
89
|
+
for (const agentId of detectedAgents) {
|
|
90
|
+
const rulesForAgent = (manifest.agentRules ?? []).filter((e) => e.agents.includes(agentId));
|
|
91
|
+
warnings.push(...detectScopeOverlaps(agentId, rulesForAgent));
|
|
92
|
+
warnings.push(...detectMultipleActiveInstructionScopes(agentId, rulesForAgent));
|
|
31
93
|
}
|
|
32
94
|
return warnings;
|
|
33
95
|
}
|
|
@@ -67,7 +129,7 @@ async function detectInstructionBudgetRisk(detectedAgents, manifest, sourceDir)
|
|
|
67
129
|
}
|
|
68
130
|
return warnings;
|
|
69
131
|
}
|
|
70
|
-
export async function runPreflight(options, manifest,
|
|
132
|
+
export async function runPreflight(options, manifest, home, detectedAgents) {
|
|
71
133
|
const warnings = [];
|
|
72
134
|
for (const agentId of detectedAgents) {
|
|
73
135
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
@@ -85,7 +147,15 @@ export async function runPreflight(options, manifest, _home, detectedAgents) {
|
|
|
85
147
|
message: `Agent "${agentId}" skill support is provisional: behavior has not been independently verified.`,
|
|
86
148
|
});
|
|
87
149
|
}
|
|
88
|
-
|
|
150
|
+
const enterpriseWarning = await detectEnterpriseManagement(agentId, home);
|
|
151
|
+
if (enterpriseWarning) {
|
|
152
|
+
warnings.push({
|
|
153
|
+
kind: "policy",
|
|
154
|
+
message: `Agent "${agentId}": ${enterpriseWarning}`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else if (agent.policyNote &&
|
|
158
|
+
!agent.policyNote.includes("Organization policies may override")) {
|
|
89
159
|
warnings.push({
|
|
90
160
|
kind: "policy",
|
|
91
161
|
message: `Agent "${agentId}": ${agent.policyNote}`,
|
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, repo?: string): string;
|
|
12
|
+
export declare function resolvePlaceholders(segments: string[], skillName: string, home: string, repo?: string, workspace?: string): string;
|
package/dist/core/resolve.js
CHANGED
|
@@ -86,8 +86,10 @@ export function getDeployMethod() {
|
|
|
86
86
|
}
|
|
87
87
|
export function resolveAgentSkillPathFor(agent, skillName, home, platform) {
|
|
88
88
|
if (!agent.skills) {
|
|
89
|
-
|
|
90
|
-
`Deploy skills via
|
|
89
|
+
const hint = agent.skillsSurfaceKind?.kind === "shared-via"
|
|
90
|
+
? ` Deploy skills via the "${agent.skillsSurfaceKind.via}" target, which covers this agent natively.`
|
|
91
|
+
: "";
|
|
92
|
+
throw new Error(`Agent "${agent.id}" does not have a skills deployment path.${hint}`);
|
|
91
93
|
}
|
|
92
94
|
return resolvePlaceholders(agent.skills[platform], skillName, home);
|
|
93
95
|
}
|
|
@@ -100,14 +102,15 @@ export function resolveAgentSkillPath(agent, skillName, home) {
|
|
|
100
102
|
export function resolveAgentDetectPath(agent, home) {
|
|
101
103
|
return resolveAgentDetectPathFor(agent, home, getPlatformKey());
|
|
102
104
|
}
|
|
103
|
-
export function resolvePlaceholders(segments, skillName, home, repo) {
|
|
105
|
+
export function resolvePlaceholders(segments, skillName, home, repo, workspace) {
|
|
104
106
|
const { appdata, xdgConfig } = resolveRuntimePaths(home);
|
|
105
107
|
const resolved = segments.map((seg) => seg
|
|
106
108
|
.replace("{home}", home)
|
|
107
109
|
.replace("{name}", skillName)
|
|
108
110
|
.replace("{appdata}", appdata)
|
|
109
111
|
.replace("{xdg_config}", xdgConfig)
|
|
110
|
-
.replace("{repo}", repo ?? "")
|
|
112
|
+
.replace("{repo}", repo ?? "")
|
|
113
|
+
.replace("{workspace}", workspace ?? repo ?? ""));
|
|
111
114
|
const root = resolved.find((segment) => segment.length > 0) ?? home;
|
|
112
115
|
return getPathApi(root).join(...resolved);
|
|
113
116
|
}
|
package/dist/core/revert.js
CHANGED
|
@@ -138,11 +138,9 @@ function lstatOutcome(err) {
|
|
|
138
138
|
return { outcome: "fail", error: msg };
|
|
139
139
|
}
|
|
140
140
|
export async function executeRevert(actions, dryRun, verbose, home, deps = {}) {
|
|
141
|
-
let succeeded = 0;
|
|
142
|
-
let skipped = 0;
|
|
143
141
|
const failed = [];
|
|
144
142
|
const planned = [];
|
|
145
|
-
const counts = { succeeded, skipped };
|
|
143
|
+
const counts = { succeeded: 0, skipped: 0 };
|
|
146
144
|
for (const action of actions) {
|
|
147
145
|
let result;
|
|
148
146
|
switch (action.kind) {
|
|
@@ -168,14 +166,18 @@ export async function executeRevert(actions, dryRun, verbose, home, deps = {}) {
|
|
|
168
166
|
target: action.target,
|
|
169
167
|
}, dryRun, verbose, home, planned, deps);
|
|
170
168
|
break;
|
|
171
|
-
default:
|
|
169
|
+
default: {
|
|
172
170
|
throw new Error(`Unhandled revert action kind: ${action.kind}`);
|
|
171
|
+
}
|
|
173
172
|
}
|
|
174
173
|
recordOutcome(result, action, counts, failed);
|
|
175
174
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
return {
|
|
176
|
+
succeeded: counts.succeeded,
|
|
177
|
+
skipped: counts.skipped,
|
|
178
|
+
failed,
|
|
179
|
+
planned,
|
|
180
|
+
};
|
|
179
181
|
}
|
|
180
182
|
async function executeRevertAction(action, dryRun, verbose, home, planned, deps) {
|
|
181
183
|
const label = `${action.skill} -> ${action.agent}`;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
export interface RuntimePaths {
|
|
3
3
|
appdata: string;
|
|
4
|
+
localAppdata: string;
|
|
4
5
|
xdgConfig: string;
|
|
5
6
|
}
|
|
6
7
|
export declare function getPathApi(root: string): typeof path.posix | typeof path.win32;
|
|
7
8
|
export declare function resolveRuntimePaths(home: string): RuntimePaths;
|
|
8
|
-
export declare function resolveTargetTemplate(template: string, home: string, repo?: string): string;
|
|
9
|
+
export declare function resolveTargetTemplate(template: string, home: string, repo?: string, workspace?: string): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config|repo)\}(?<suffix>(?:[\\/].*)?)$/;
|
|
2
|
+
const TARGET_TEMPLATE_RE = /^\{(home|appdata|local_appdata|xdg_config|repo|workspace)\}(?<suffix>(?:[\\/].*)?)$/;
|
|
3
3
|
export function getPathApi(root) {
|
|
4
4
|
if (root.includes("\\") || /^[a-zA-Z]:/.test(root)) {
|
|
5
5
|
return path.win32;
|
|
@@ -22,49 +22,56 @@ function isSameOrDescendantPath(candidate, root) {
|
|
|
22
22
|
}
|
|
23
23
|
export function resolveRuntimePaths(home) {
|
|
24
24
|
const appdataRaw = process.env.APPDATA;
|
|
25
|
+
const localAppdataRaw = process.env.LOCALAPPDATA;
|
|
25
26
|
const homePathApi = getPathApi(home);
|
|
26
27
|
const appdata = appdataRaw && getPathApi(appdataRaw).isAbsolute(appdataRaw)
|
|
27
28
|
? appdataRaw
|
|
28
29
|
: homePathApi.join(home, "AppData", "Roaming");
|
|
30
|
+
const localAppdata = localAppdataRaw && getPathApi(localAppdataRaw).isAbsolute(localAppdataRaw)
|
|
31
|
+
? localAppdataRaw
|
|
32
|
+
: homePathApi.join(home, "AppData", "Local");
|
|
29
33
|
const xdgRaw = process.env.XDG_CONFIG_HOME;
|
|
30
34
|
const xdgConfig = xdgRaw && getPathApi(xdgRaw).isAbsolute(xdgRaw)
|
|
31
35
|
? xdgRaw
|
|
32
36
|
: homePathApi.join(home, ".config");
|
|
33
|
-
return { appdata, xdgConfig };
|
|
37
|
+
return { appdata, localAppdata, xdgConfig };
|
|
34
38
|
}
|
|
35
|
-
|
|
36
|
-
const
|
|
39
|
+
function resolveVfsPlaceholder(root, template, suffix, repo, workspace) {
|
|
40
|
+
const rootPath = root === "repo" ? repo : (workspace ?? repo);
|
|
41
|
+
if (!rootPath) {
|
|
42
|
+
throw new Error(`Target template uses {${root}} but no ${root} directory was provided: ${template}`);
|
|
43
|
+
}
|
|
44
|
+
const segments = suffix.split(/[\\/]+/).filter(Boolean);
|
|
45
|
+
const rootPathApi = getPathApi(rootPath);
|
|
46
|
+
const resolved = segments.length === 0 ? rootPath : rootPathApi.join(rootPath, ...segments);
|
|
47
|
+
if (!isSameOrDescendantPath(resolved, rootPath)) {
|
|
48
|
+
throw new Error(`Target template resolves outside its placeholder root: ${template}`);
|
|
49
|
+
}
|
|
50
|
+
return suffix === "" ? rootPath : `${rootPath}${suffix}`;
|
|
51
|
+
}
|
|
52
|
+
export function resolveTargetTemplate(template, home, repo, workspace) {
|
|
53
|
+
const { appdata, localAppdata, xdgConfig } = resolveRuntimePaths(home);
|
|
37
54
|
const match = TARGET_TEMPLATE_RE.exec(template);
|
|
38
55
|
if (!match) {
|
|
39
56
|
throw new Error(`Invalid target template: ${template}`);
|
|
40
57
|
}
|
|
41
58
|
const root = match[1];
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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}`;
|
|
59
|
+
const suffix = match.groups?.suffix ?? "";
|
|
60
|
+
if (root === "repo" || root === "workspace") {
|
|
61
|
+
return resolveVfsPlaceholder(root, template, suffix, repo, workspace);
|
|
54
62
|
}
|
|
55
63
|
const baseByRoot = {
|
|
56
64
|
home,
|
|
57
65
|
appdata,
|
|
66
|
+
local_appdata: localAppdata,
|
|
58
67
|
xdg_config: xdgConfig,
|
|
59
68
|
};
|
|
60
|
-
const
|
|
69
|
+
const base = baseByRoot[root];
|
|
61
70
|
const segments = suffix.split(/[\\/]+/).filter(Boolean);
|
|
62
|
-
const pathApi = getPathApi(
|
|
63
|
-
const resolved = segments.length === 0
|
|
64
|
-
|
|
65
|
-
: pathApi.join(baseByRoot[root], ...segments);
|
|
66
|
-
if (!isSameOrDescendantPath(resolved, baseByRoot[root])) {
|
|
71
|
+
const pathApi = getPathApi(base);
|
|
72
|
+
const resolved = segments.length === 0 ? base : pathApi.join(base, ...segments);
|
|
73
|
+
if (!isSameOrDescendantPath(resolved, base)) {
|
|
67
74
|
throw new Error(`Target template resolves outside its placeholder root: ${template}`);
|
|
68
75
|
}
|
|
69
|
-
return suffix === "" ?
|
|
76
|
+
return suffix === "" ? base : `${base}${suffix}`;
|
|
70
77
|
}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import type { AgentId } from "../schemas/manifest.ts";
|
|
1
2
|
export declare function sourceAccessError(err: unknown, sourcePath: string): string;
|
|
2
3
|
export declare function validateSourcePath(source: string, skillPath: string, resolvedSourceDir: string, realRoot: string): Promise<void>;
|
|
3
4
|
export declare function validateSourceFile(sourcePath: string, manifestPath: string): Promise<void>;
|
|
4
5
|
export declare function validateMcpServerConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
5
6
|
export declare function validatePermissionsConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
6
7
|
export declare function validateAgentRuleMarkdownPath(manifestPath: string, agentId: string): void;
|
|
7
|
-
export declare function validateSkillDefinitionFile(sourcePath: string, manifestPath: string): Promise<
|
|
8
|
+
export declare function validateSkillDefinitionFile(sourcePath: string, manifestPath: string): Promise<{
|
|
9
|
+
attributes: Record<string, unknown>;
|
|
10
|
+
body: string;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Validates that an instruction file (agentRules or agentDefinitions) meets
|
|
14
|
+
* the structural requirements of the target agent.
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateInstructionFileRequirements(sourcePath: string, manifestPath: string, agentId: AgentId): Promise<void>;
|
package/dist/core/validation.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
3
4
|
import { UserError } from "../errors.js";
|
|
4
5
|
import { parseFrontmatterDocument } from "./adapters/frontmatter.js";
|
|
5
6
|
export function sourceAccessError(err, sourcePath) {
|
|
@@ -166,9 +167,11 @@ export async function validateSkillDefinitionFile(sourcePath, manifestPath) {
|
|
|
166
167
|
throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md is missing the closing --- frontmatter delimiter`);
|
|
167
168
|
}
|
|
168
169
|
let attributes;
|
|
170
|
+
let body;
|
|
169
171
|
try {
|
|
170
172
|
const parsed = parseFrontmatterDocument(raw);
|
|
171
173
|
attributes = parsed.attributes;
|
|
174
|
+
body = parsed.body;
|
|
172
175
|
}
|
|
173
176
|
catch (err) {
|
|
174
177
|
throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md has malformed YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -187,4 +190,56 @@ export async function validateSkillDefinitionFile(sourcePath, manifestPath) {
|
|
|
187
190
|
};
|
|
188
191
|
validateField("name");
|
|
189
192
|
validateField("description");
|
|
193
|
+
return { attributes, body };
|
|
194
|
+
}
|
|
195
|
+
function validateGithubCopilotRequirements(attributes, manifestPath) {
|
|
196
|
+
const hasTools = Object.hasOwn(attributes, "tools");
|
|
197
|
+
const hasInstructions = Object.hasOwn(attributes, "instructions");
|
|
198
|
+
if (!(hasTools || hasInstructions)) {
|
|
199
|
+
throw new UserError("DEPLOY_FAILED", `Instruction file "${manifestPath}" for agent "github-copilot" must define "tools" or "instructions" in frontmatter`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function validateAntigravityRequirements(attributes, manifestPath) {
|
|
203
|
+
const mcpServers = attributes["mcp-servers"] ?? attributes.mcpServers;
|
|
204
|
+
if (mcpServers === undefined)
|
|
205
|
+
return;
|
|
206
|
+
if (typeof mcpServers !== "object" ||
|
|
207
|
+
mcpServers === null ||
|
|
208
|
+
Array.isArray(mcpServers)) {
|
|
209
|
+
throw new UserError("DEPLOY_FAILED", `Instruction file "${manifestPath}" for agent "antigravity" must define "mcp-servers" as an object`);
|
|
210
|
+
}
|
|
211
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
212
|
+
if (typeof config !== "object" ||
|
|
213
|
+
config === null ||
|
|
214
|
+
Array.isArray(config)) {
|
|
215
|
+
throw new UserError("DEPLOY_FAILED", `Instruction file "${manifestPath}" for agent "antigravity" has malformed MCP server config for "${name}"`);
|
|
216
|
+
}
|
|
217
|
+
validateMcpServerConfigShape(config, name, "antigravity");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Validates that an instruction file (agentRules or agentDefinitions) meets
|
|
222
|
+
* the structural requirements of the target agent.
|
|
223
|
+
*/
|
|
224
|
+
export async function validateInstructionFileRequirements(sourcePath, manifestPath, agentId) {
|
|
225
|
+
const requiresFrontmatter = AGENT_REGISTRY_BY_ID[agentId]?.instructionFrontmatterRequired === true;
|
|
226
|
+
if (!requiresFrontmatter)
|
|
227
|
+
return;
|
|
228
|
+
let attributes;
|
|
229
|
+
try {
|
|
230
|
+
const result = await validateSkillDefinitionFile(sourcePath, manifestPath);
|
|
231
|
+
attributes = result.attributes;
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
if (err instanceof UserError) {
|
|
235
|
+
throw new UserError("DEPLOY_FAILED", `Instruction file "${manifestPath}" for agent "${agentId}" failed structural validation: ${err.message}`);
|
|
236
|
+
}
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
if (agentId === "github-copilot") {
|
|
240
|
+
validateGithubCopilotRequirements(attributes, manifestPath);
|
|
241
|
+
}
|
|
242
|
+
else if (agentId === "antigravity") {
|
|
243
|
+
validateAntigravityRequirements(attributes, manifestPath);
|
|
244
|
+
}
|
|
190
245
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { styleText } from "node:util";
|
|
2
|
+
/**
|
|
3
|
+
* Groups and formats the dry-run plan by agent.
|
|
4
|
+
*/
|
|
5
|
+
export function formatDryRunPlan(planned) {
|
|
6
|
+
if (planned.length === 0)
|
|
7
|
+
return "";
|
|
8
|
+
const groups = new Map();
|
|
9
|
+
for (const change of planned) {
|
|
10
|
+
const list = groups.get(change.agent) ?? [];
|
|
11
|
+
list.push(change);
|
|
12
|
+
groups.set(change.agent, list);
|
|
13
|
+
}
|
|
14
|
+
const sortedAgents = Array.from(groups.keys()).sort();
|
|
15
|
+
const output = [];
|
|
16
|
+
for (const agent of sortedAgents) {
|
|
17
|
+
output.push(styleText(["bold", "yellow"], agent));
|
|
18
|
+
const changes = groups.get(agent);
|
|
19
|
+
if (!changes)
|
|
20
|
+
continue;
|
|
21
|
+
for (const change of changes) {
|
|
22
|
+
output.push(formatPlannedChange(change));
|
|
23
|
+
}
|
|
24
|
+
output.push("");
|
|
25
|
+
}
|
|
26
|
+
return output.join("\n");
|
|
27
|
+
}
|
|
28
|
+
function formatPlannedChange(change) {
|
|
29
|
+
const icon = styleText("cyan", "○");
|
|
30
|
+
const kind = styleText("dim", `[${change.kind}]`);
|
|
31
|
+
const lines = [
|
|
32
|
+
` ${icon} ${kind} ${change.verb} ${styleText("bold", change.skill)}`,
|
|
33
|
+
];
|
|
34
|
+
if (change.source !== undefined) {
|
|
35
|
+
lines.push(` source: ${styleText("dim", change.source)}`);
|
|
36
|
+
}
|
|
37
|
+
lines.push(` target: ${styleText("dim", change.target)}`);
|
|
38
|
+
const detail = formatChangeDetail(change);
|
|
39
|
+
if (detail) {
|
|
40
|
+
lines.push(` ${detail}`);
|
|
41
|
+
}
|
|
42
|
+
return lines.join("\n");
|
|
43
|
+
}
|
|
44
|
+
function formatChangeDetail(change) {
|
|
45
|
+
if (change.verb === "patch-config" && change.patch !== undefined) {
|
|
46
|
+
return `patch: ${styleText("dim", JSON.stringify(change.patch))}`;
|
|
47
|
+
}
|
|
48
|
+
if (change.verb === "unapply-patch" && change.patch !== undefined) {
|
|
49
|
+
return `undo: ${styleText("dim", JSON.stringify(change.patch))}`;
|
|
50
|
+
}
|
|
51
|
+
if (change.verb === "patch-toml" && change.patch !== undefined) {
|
|
52
|
+
return `patch: ${styleText("dim", JSON.stringify(change.patch))}`;
|
|
53
|
+
}
|
|
54
|
+
if (change.verb === "emit-frontmatter" && change.frontmatter !== undefined) {
|
|
55
|
+
return `frontmatter: ${styleText("dim", JSON.stringify(change.frontmatter))}`;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { runPreflight } from "./core/preflight.js";
|
|
|
10
10
|
import { resolveHome } from "./core/resolve.js";
|
|
11
11
|
import { executeRevert, planRevert, planRevertAll } from "./core/revert.js";
|
|
12
12
|
import { UserError } from "./errors.js";
|
|
13
|
+
import { formatDryRunPlan } from "./formatters.js";
|
|
13
14
|
import { dryRunPrefix, logger } from "./logger.js";
|
|
14
15
|
import { AgentListSchema } from "./schemas/manifest.js";
|
|
15
16
|
const USAGE = `
|
|
@@ -26,7 +27,7 @@ Commands:
|
|
|
26
27
|
init <directory> Scan a directory for skill folders and generate inception.json
|
|
27
28
|
|
|
28
29
|
Options:
|
|
29
|
-
--
|
|
30
|
+
--plan Show what would be done without doing it
|
|
30
31
|
--agents <list> Comma-separated list of agent IDs to target
|
|
31
32
|
--force (init only) Overwrite an existing inception.json
|
|
32
33
|
--verbose Show detailed output
|
|
@@ -55,7 +56,7 @@ function parseCLI(argv) {
|
|
|
55
56
|
args,
|
|
56
57
|
allowPositionals: true,
|
|
57
58
|
options: {
|
|
58
|
-
|
|
59
|
+
plan: { type: "boolean", default: false },
|
|
59
60
|
verbose: { type: "boolean", default: false },
|
|
60
61
|
debug: { type: "boolean", default: false },
|
|
61
62
|
help: { type: "boolean", default: false },
|
|
@@ -107,7 +108,7 @@ function parseCLI(argv) {
|
|
|
107
108
|
return {
|
|
108
109
|
command,
|
|
109
110
|
directory: path.resolve(rawDir),
|
|
110
|
-
dryRun: values["dry-run"],
|
|
111
|
+
dryRun: (values.plan || values["dry-run"]),
|
|
111
112
|
agents,
|
|
112
113
|
verbose: values.verbose,
|
|
113
114
|
debug: values.debug,
|
|
@@ -136,21 +137,6 @@ async function main() {
|
|
|
136
137
|
}
|
|
137
138
|
return runRevert(options, manifest, home);
|
|
138
139
|
}
|
|
139
|
-
function renderDryRunPlan(planned) {
|
|
140
|
-
for (const change of planned) {
|
|
141
|
-
logger.plan(`[${change.agent}] ${change.verb} ${change.skill}`);
|
|
142
|
-
if (change.source !== undefined) {
|
|
143
|
-
logger.detail(`source: ${change.source}`);
|
|
144
|
-
}
|
|
145
|
-
logger.detail(`target: ${change.target}`);
|
|
146
|
-
if (change.verb === "patch-config" && change.patch !== undefined) {
|
|
147
|
-
logger.detail(`patch: ${JSON.stringify(change.patch)}`);
|
|
148
|
-
}
|
|
149
|
-
else if (change.verb === "unapply-patch" && change.patch !== undefined) {
|
|
150
|
-
logger.detail(`undo: ${JSON.stringify(change.patch)}`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
140
|
async function runDeploy(options, manifest, home) {
|
|
155
141
|
let detectedAgents;
|
|
156
142
|
if (options.agents) {
|
|
@@ -187,9 +173,8 @@ async function runDeploy(options, manifest, home) {
|
|
|
187
173
|
const { succeeded, failed, planned } = await executeDeploy(actions, options.dryRun, options.verbose, home);
|
|
188
174
|
if (options.dryRun) {
|
|
189
175
|
logger.info("");
|
|
190
|
-
|
|
191
|
-
logger.info(
|
|
192
|
-
logger.info(`${planned.length} action(s) would be applied (dry-run)`);
|
|
176
|
+
logger.info(formatDryRunPlan(planned));
|
|
177
|
+
logger.info(`${planned.length} action(s) would be applied (plan)`);
|
|
193
178
|
return 0;
|
|
194
179
|
}
|
|
195
180
|
logger.info("");
|
|
@@ -212,9 +197,8 @@ async function runRevert(options, manifest, home) {
|
|
|
212
197
|
const { succeeded, skipped, failed, planned } = await executeRevert(actions, options.dryRun, options.verbose, home);
|
|
213
198
|
if (options.dryRun) {
|
|
214
199
|
logger.info("");
|
|
215
|
-
|
|
216
|
-
logger.info(
|
|
217
|
-
logger.info(`${planned.length} action(s) would be removed (dry-run)`);
|
|
200
|
+
logger.info(formatDryRunPlan(planned));
|
|
201
|
+
logger.info(`${planned.length} action(s) would be removed (plan)`);
|
|
218
202
|
return 0;
|
|
219
203
|
}
|
|
220
204
|
logger.info("");
|
package/dist/logger.js
CHANGED
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
-
|
|
3
|
-
red: "\x1b[31m",
|
|
4
|
-
green: "\x1b[32m",
|
|
5
|
-
yellow: "\x1b[33m",
|
|
6
|
-
cyan: "\x1b[36m",
|
|
7
|
-
reset: "\x1b[0m",
|
|
8
|
-
};
|
|
2
|
+
import { styleText } from "node:util";
|
|
9
3
|
export function createLogger() {
|
|
10
4
|
let silent = false;
|
|
11
|
-
const tty = Boolean(process.stdout.isTTY);
|
|
12
|
-
const errTTY = Boolean(process.stderr.isTTY);
|
|
13
|
-
const c = (col, text, onTTY) => onTTY ? `${C[col]}${text}${C.reset}` : text;
|
|
14
5
|
return {
|
|
15
6
|
fail(label, msg) {
|
|
16
|
-
if (!silent)
|
|
17
|
-
|
|
7
|
+
if (!silent) {
|
|
8
|
+
const icon = styleText("red", "✗");
|
|
9
|
+
process.stderr.write(` ${icon} ${label}: ${msg}\n`);
|
|
10
|
+
}
|
|
18
11
|
},
|
|
19
12
|
ok(label) {
|
|
20
|
-
if (!silent)
|
|
21
|
-
|
|
13
|
+
if (!silent) {
|
|
14
|
+
const icon = styleText("green", "✓");
|
|
15
|
+
process.stdout.write(` ${icon} ${label}\n`);
|
|
16
|
+
}
|
|
22
17
|
},
|
|
23
18
|
skip(label, note) {
|
|
24
|
-
if (!silent)
|
|
25
|
-
|
|
19
|
+
if (!silent) {
|
|
20
|
+
const icon = styleText("yellow", "-");
|
|
21
|
+
process.stdout.write(` ${icon} ${label} ${note}\n`);
|
|
22
|
+
}
|
|
26
23
|
},
|
|
27
24
|
plan(label) {
|
|
28
|
-
if (!silent)
|
|
29
|
-
|
|
25
|
+
if (!silent) {
|
|
26
|
+
const icon = styleText("cyan", "○");
|
|
27
|
+
process.stdout.write(` ${icon} ${label}\n`);
|
|
28
|
+
}
|
|
30
29
|
},
|
|
31
30
|
warn(label, msg) {
|
|
32
|
-
if (!silent)
|
|
33
|
-
|
|
31
|
+
if (!silent) {
|
|
32
|
+
const icon = styleText("yellow", "!");
|
|
33
|
+
process.stdout.write(` ${icon} ${label}: ${msg}\n`);
|
|
34
|
+
}
|
|
34
35
|
},
|
|
35
36
|
info(msg) {
|
|
36
37
|
if (!silent)
|
|
@@ -57,5 +58,5 @@ export const logger = createLogger();
|
|
|
57
58
|
export function dryRunPrefix(dryRun) {
|
|
58
59
|
if (!dryRun)
|
|
59
60
|
return "";
|
|
60
|
-
return
|
|
61
|
+
return styleText("cyan", "[plan] ");
|
|
61
62
|
}
|
|
@@ -74,6 +74,7 @@ export declare const AgentRuleEntrySchema: z.ZodObject<{
|
|
|
74
74
|
scope: z.ZodDefault<z.ZodEnum<{
|
|
75
75
|
global: "global";
|
|
76
76
|
repo: "repo";
|
|
77
|
+
workspace: "workspace";
|
|
77
78
|
}>>;
|
|
78
79
|
}, z.core.$strip>;
|
|
79
80
|
export declare const PermissionsEntrySchema: z.ZodObject<{
|
|
@@ -165,6 +166,7 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
165
166
|
scope: z.ZodDefault<z.ZodEnum<{
|
|
166
167
|
global: "global";
|
|
167
168
|
repo: "repo";
|
|
169
|
+
workspace: "workspace";
|
|
168
170
|
}>>;
|
|
169
171
|
}, z.core.$strip>>>;
|
|
170
172
|
permissions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|