@kuznai/inception-engine 0.21.0 → 0.23.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 +83 -8
- package/dist/src/config/agents.js +94 -11
- package/dist/src/core/adapters/execution-config.d.ts +14 -0
- package/dist/src/core/adapters/execution-config.js +60 -0
- package/dist/src/core/adapters/hooks.d.ts +8 -0
- package/dist/src/core/adapters/hooks.js +86 -0
- package/dist/src/core/adapters/index.d.ts +5 -3
- package/dist/src/core/adapters/index.js +9 -5
- package/dist/src/core/adapters/mcp.js +15 -1
- package/dist/src/core/adapters/rules.js +50 -19
- package/dist/src/core/capabilities.d.ts +1 -1
- package/dist/src/core/capabilities.js +33 -2
- package/dist/src/core/deploy.js +1 -1
- package/dist/src/core/init.js +87 -23
- package/dist/src/core/preflight.js +28 -0
- package/dist/src/core/revert.js +5 -1
- package/dist/src/core/validation.d.ts +1 -0
- package/dist/src/core/validation.js +57 -0
- package/dist/src/schemas/manifest.d.ts +58 -0
- package/dist/src/schemas/manifest.js +49 -3
- package/dist/src/types.d.ts +10 -2
- package/dist/test/unit/adapters.test.js +280 -3
- package/dist/test/unit/deploy.test.js +31 -3
- package/dist/test/unit/init-fixture.test.js +85 -1
- package/dist/test/unit/manifest.test.js +72 -0
- package/dist/test/unit/preflight.test.js +91 -7
- package/dist/test/unit/revert.test.js +73 -0
- package/package.json +1 -1
|
@@ -2,6 +2,37 @@ import path from "node:path";
|
|
|
2
2
|
import { planCapabilityForDeploy, resolveCapabilitySurface, } from "../capabilities.js";
|
|
3
3
|
import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
|
|
4
4
|
import { validateAgentRuleMarkdownPath, validateInstructionFileRequirements, validateSourceFile, validateSourcePath, } from "../validation.js";
|
|
5
|
+
function requiresRepoPath(scope) {
|
|
6
|
+
return (scope === "repo" || scope === "copilot-repo" || scope === "copilot-scoped");
|
|
7
|
+
}
|
|
8
|
+
function deduplicateTargets(supportedTargets, scope) {
|
|
9
|
+
const seenTargetPaths = new Set();
|
|
10
|
+
const dedupedTargets = [];
|
|
11
|
+
for (const t of supportedTargets) {
|
|
12
|
+
const surface = resolveCapabilitySurface(t.agentId, "agentRules", scope);
|
|
13
|
+
if (surface.surfaceKind === "shared-via" &&
|
|
14
|
+
surface.sharedVia &&
|
|
15
|
+
supportedTargets.some((o) => o.agentId === surface.sharedVia && o.target === t.target)) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (!seenTargetPaths.has(t.target)) {
|
|
19
|
+
seenTargetPaths.add(t.target);
|
|
20
|
+
dedupedTargets.push(t);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return dedupedTargets;
|
|
24
|
+
}
|
|
25
|
+
function injectTargetDir(pathSegments, targetDir) {
|
|
26
|
+
if (!targetDir)
|
|
27
|
+
return pathSegments;
|
|
28
|
+
const placeholderIndex = pathSegments.findIndex((seg) => seg === "{repo}" || seg === "{workspace}");
|
|
29
|
+
if (placeholderIndex === -1)
|
|
30
|
+
return pathSegments;
|
|
31
|
+
const dirSegments = targetDir.split(/[\\/]+/).filter(Boolean);
|
|
32
|
+
const result = [...pathSegments];
|
|
33
|
+
result.splice(placeholderIndex + 1, 0, ...dirSegments);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
5
36
|
function resolveAgentTarget(agentId, entry, home, repo, platform, workspace, allTargetAgentIds) {
|
|
6
37
|
const plan = planCapabilityForDeploy({
|
|
7
38
|
agentId,
|
|
@@ -28,10 +59,17 @@ function resolveAgentTarget(agentId, entry, home, repo, platform, workspace, all
|
|
|
28
59
|
message: `agentRules: scope "workspace" requires a workspace or repository path but none was resolved — skipping "${entry.name}" for agent "${agentId}"`,
|
|
29
60
|
};
|
|
30
61
|
}
|
|
62
|
+
if ((entry.scope === "copilot-repo" || entry.scope === "copilot-scoped") &&
|
|
63
|
+
!repo) {
|
|
64
|
+
return {
|
|
65
|
+
kind: "confidence",
|
|
66
|
+
message: `agentRules: scope "${entry.scope}" requires a repository path but none was resolved — skipping "${entry.name}" for agent "${agentId}"`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
31
69
|
return {
|
|
32
70
|
agentId,
|
|
33
71
|
confidence: plan.confidence,
|
|
34
|
-
target: resolvePlaceholders(support?.path[platform] ?? [], entry.name, home, repo, workspace),
|
|
72
|
+
target: resolvePlaceholders(injectTargetDir(support?.path[platform] ?? [], entry.targetDir), entry.name, home, repo, workspace),
|
|
35
73
|
};
|
|
36
74
|
}
|
|
37
75
|
export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo, workspace) {
|
|
@@ -61,29 +99,22 @@ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDi
|
|
|
61
99
|
// Deduplicate: when a shared-via rider's primary is also in supportedTargets,
|
|
62
100
|
// skip the rider — the primary agent writes the shared surface. As a fallback,
|
|
63
101
|
// also dedup by resolved path so no target file is written twice.
|
|
64
|
-
const
|
|
65
|
-
const dedupedTargets = [];
|
|
66
|
-
for (const t of supportedTargets) {
|
|
67
|
-
const surface = resolveCapabilitySurface(t.agentId, "agentRules", entry.scope);
|
|
68
|
-
if (surface.surfaceKind === "shared-via" &&
|
|
69
|
-
surface.sharedVia &&
|
|
70
|
-
supportedTargets.some((o) => o.agentId === surface.sharedVia && o.target === t.target)) {
|
|
71
|
-
// Primary agent is present and writes the same target — skip rider.
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (!seenTargetPaths.has(t.target)) {
|
|
75
|
-
seenTargetPaths.add(t.target);
|
|
76
|
-
dedupedTargets.push(t);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
102
|
+
const dedupedTargets = deduplicateTargets(supportedTargets, entry.scope);
|
|
79
103
|
// Validate the shared source file only when at least one target uses the
|
|
80
104
|
// current rules adapter surface.
|
|
81
105
|
const source = path.resolve(sourceDir, entry.path);
|
|
82
106
|
await validateSourcePath(source, entry.path, resolvedSourceDir, realRoot);
|
|
83
107
|
await validateSourceFile(source, entry.path);
|
|
108
|
+
// Native Copilot instruction files (.github/copilot-instructions.md and
|
|
109
|
+
// .github/instructions/*.instructions.md) are plain markdown and do not
|
|
110
|
+
// require agent-definition-style frontmatter (tools/instructions keys).
|
|
111
|
+
// Skip the instructionFrontmatterRequired check for these scopes.
|
|
112
|
+
const skipFrontmatterValidation = entry.scope === "copilot-repo" || entry.scope === "copilot-scoped";
|
|
84
113
|
for (const target of dedupedTargets) {
|
|
85
114
|
validateAgentRuleMarkdownPath(entry.path, target.agentId);
|
|
86
|
-
|
|
115
|
+
if (!skipFrontmatterValidation) {
|
|
116
|
+
await validateInstructionFileRequirements(source, entry.path, target.agentId);
|
|
117
|
+
}
|
|
87
118
|
actions.push({
|
|
88
119
|
kind: "file-write",
|
|
89
120
|
skill: entry.name,
|
|
@@ -107,11 +138,11 @@ export function compileAgentRuleReverts(entry, agentFilter, home, repo, workspac
|
|
|
107
138
|
if (surface.supportStatus !== "supported" || !support) {
|
|
108
139
|
continue;
|
|
109
140
|
}
|
|
110
|
-
if (entry.scope
|
|
141
|
+
if (requiresRepoPath(entry.scope) && !repo)
|
|
111
142
|
continue;
|
|
112
143
|
if (entry.scope === "workspace" && !workspace && !repo)
|
|
113
144
|
continue;
|
|
114
|
-
const target = resolvePlaceholders(support.path[platform], entry.name, home, repo, workspace);
|
|
145
|
+
const target = resolvePlaceholders(injectTargetDir(support.path[platform], entry.targetDir), entry.name, home, repo, workspace);
|
|
115
146
|
if (seenRevertTargets.has(target))
|
|
116
147
|
continue;
|
|
117
148
|
seenRevertTargets.add(target);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentId, AgentSurfaceSupport, CapabilityKind, Confidence, PlanWarning, SupportedAgentSurface } from "../types.ts";
|
|
2
|
-
type CapabilityScope = "global" | "repo" | "workspace";
|
|
2
|
+
type CapabilityScope = "global" | "repo" | "workspace" | "devcontainer" | "copilot-repo" | "copilot-scoped";
|
|
3
3
|
export interface ResolvedCapabilitySurface {
|
|
4
4
|
agentId: AgentId;
|
|
5
5
|
capability: CapabilityKind;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
2
2
|
function resolveAgentRulesSupport(agentId, scope) {
|
|
3
3
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
4
|
+
if (scope === "copilot-repo") {
|
|
5
|
+
return agent?.agentRulesCopilotRepoSupport;
|
|
6
|
+
}
|
|
7
|
+
if (scope === "copilot-scoped") {
|
|
8
|
+
return agent?.agentRulesCopilotScopedSupport;
|
|
9
|
+
}
|
|
4
10
|
if (scope === "repo") {
|
|
5
11
|
return agent?.agentRulesRepoSupport ?? agent?.agentRulesSupport;
|
|
6
12
|
}
|
|
@@ -19,6 +25,11 @@ function resolveMcpSupport(agentId, scope) {
|
|
|
19
25
|
if (scope === "workspace") {
|
|
20
26
|
return (agent?.mcpWorkspaceSupport ?? agent?.mcpRepoSupport ?? agent?.mcpSupport);
|
|
21
27
|
}
|
|
28
|
+
if (scope === "devcontainer") {
|
|
29
|
+
return (agent?.mcpDevcontainerSupport ??
|
|
30
|
+
agent?.mcpRepoSupport ??
|
|
31
|
+
agent?.mcpSupport);
|
|
32
|
+
}
|
|
22
33
|
return agent?.mcpSupport;
|
|
23
34
|
}
|
|
24
35
|
function resolveAgentDefinitionsSupport(agentId, scope) {
|
|
@@ -45,6 +56,10 @@ function capabilityLabel(capability) {
|
|
|
45
56
|
return "permissions";
|
|
46
57
|
case "agentDefinitions":
|
|
47
58
|
return "agentDefinitions";
|
|
59
|
+
case "hooks":
|
|
60
|
+
return "hooks";
|
|
61
|
+
case "executionConfigs":
|
|
62
|
+
return "executionConfigs";
|
|
48
63
|
}
|
|
49
64
|
}
|
|
50
65
|
function capabilitySurfaceLabel(capability) {
|
|
@@ -59,6 +74,10 @@ function capabilitySurfaceLabel(capability) {
|
|
|
59
74
|
return "permissions";
|
|
60
75
|
case "agentDefinitions":
|
|
61
76
|
return "agent-definitions";
|
|
77
|
+
case "hooks":
|
|
78
|
+
return "hooks";
|
|
79
|
+
case "executionConfigs":
|
|
80
|
+
return "execution-config";
|
|
62
81
|
}
|
|
63
82
|
}
|
|
64
83
|
function resolveSkillsCapability(agentId) {
|
|
@@ -110,9 +129,21 @@ function getSurfaceRecordAndConfidence(agentId, capability, scope) {
|
|
|
110
129
|
confidence: agent.provenance.permissions,
|
|
111
130
|
};
|
|
112
131
|
}
|
|
132
|
+
if (capability === "agentDefinitions") {
|
|
133
|
+
return {
|
|
134
|
+
supportRecord: resolveAgentDefinitionsSupport(agentId, scope),
|
|
135
|
+
confidence: agent.provenance.agentDefinitions,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (capability === "hooks") {
|
|
139
|
+
return {
|
|
140
|
+
supportRecord: agent.hooksSupport,
|
|
141
|
+
confidence: agent.provenance.hooks,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
113
144
|
return {
|
|
114
|
-
supportRecord:
|
|
115
|
-
confidence: agent.provenance.
|
|
145
|
+
supportRecord: agent.executionConfigSupport,
|
|
146
|
+
confidence: agent.provenance.executionConfig,
|
|
116
147
|
};
|
|
117
148
|
}
|
|
118
149
|
function resolveUnsupportedSurface(agentId, capability, supportRecord, confidence) {
|
package/dist/src/core/deploy.js
CHANGED
|
@@ -280,7 +280,7 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home, repo
|
|
|
280
280
|
...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, workspace)),
|
|
281
281
|
...planConfigPatchActions(manifest, detectedAgents, home, repoDir, workspace),
|
|
282
282
|
];
|
|
283
|
-
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, manifest.agentDefinitions ?? [], workspace);
|
|
283
|
+
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repoDir, manifest.agentDefinitions ?? [], workspace, manifest.hooks ?? [], manifest.executionConfigs ?? []);
|
|
284
284
|
actions.push(...adapterResult.actions);
|
|
285
285
|
const warnings = [
|
|
286
286
|
...skillPlan.warnings,
|
package/dist/src/core/init.js
CHANGED
|
@@ -32,9 +32,10 @@ const AGENT_RULES_FILE_PATTERNS = (() => {
|
|
|
32
32
|
fileNames: [filename, filename.replace(".md", "-instructions.md")],
|
|
33
33
|
agents,
|
|
34
34
|
})),
|
|
35
|
-
// Convention mapping
|
|
36
|
-
//
|
|
37
|
-
//
|
|
35
|
+
// Convention mapping for copilot-instructions.md found outside .github/:
|
|
36
|
+
// map to claude-code since Copilot reads CLAUDE.md natively. Files at
|
|
37
|
+
// .github/copilot-instructions.md are handled separately as a native
|
|
38
|
+
// Copilot surface via the copilot-repo scope.
|
|
38
39
|
{
|
|
39
40
|
fileNames: ["copilot-instructions.md"],
|
|
40
41
|
agents: ["claude-code"],
|
|
@@ -183,18 +184,73 @@ async function findAgentRulesCandidates(baseDir, skillDirRelPaths) {
|
|
|
183
184
|
for (const subdir of AGENT_RULES_SUBDIRS) {
|
|
184
185
|
await scanDirForMarkdown(path.join(baseDir, subdir), baseDir, skillDirRelPaths, seen, candidates);
|
|
185
186
|
}
|
|
187
|
+
// Promote .github/copilot-instructions.md to the native copilot-repo scope.
|
|
188
|
+
// The general scanDirForMarkdown pass above picks it up via AGENT_RULES_SUBDIRS
|
|
189
|
+
// (".github"), so we just patch the candidate that was already added.
|
|
190
|
+
const copilotRepoRelPath = ".github/copilot-instructions.md";
|
|
191
|
+
const copilotRepoCandidate = candidates.find((c) => c.relPath === copilotRepoRelPath);
|
|
192
|
+
if (copilotRepoCandidate) {
|
|
193
|
+
copilotRepoCandidate.defaultAgents = ["github-copilot"];
|
|
194
|
+
copilotRepoCandidate.scope = "copilot-repo";
|
|
195
|
+
}
|
|
196
|
+
// Discover .github/instructions/*.instructions.md as copilot-scoped entries.
|
|
197
|
+
// These are not covered by the general scan (it only looks for .md/.markdown
|
|
198
|
+
// and the stem derivation would lose the .instructions suffix).
|
|
199
|
+
const instructionsDir = path.join(baseDir, ".github", "instructions");
|
|
200
|
+
let instrEntries;
|
|
201
|
+
try {
|
|
202
|
+
instrEntries = await readdir(instructionsDir, {
|
|
203
|
+
withFileTypes: true,
|
|
204
|
+
encoding: "utf-8",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
instrEntries = [];
|
|
209
|
+
}
|
|
210
|
+
for (const entry of instrEntries) {
|
|
211
|
+
if (!entry.isFile())
|
|
212
|
+
continue;
|
|
213
|
+
const lower = entry.name.toLowerCase();
|
|
214
|
+
if (!lower.endsWith(".instructions.md"))
|
|
215
|
+
continue;
|
|
216
|
+
const absPath = path.join(instructionsDir, entry.name);
|
|
217
|
+
const relPath = path.relative(baseDir, absPath).split(path.sep).join("/");
|
|
218
|
+
if (seen.has(relPath) || isInsideSkillDir(relPath, skillDirRelPaths))
|
|
219
|
+
continue;
|
|
220
|
+
// Derive name by stripping ".instructions.md" suffix
|
|
221
|
+
const baseStem = entry.name.slice(0, -".instructions.md".length);
|
|
222
|
+
const rawName = baseStem.toLowerCase().replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
223
|
+
if (!SAFE_NAME_RE.test(rawName)) {
|
|
224
|
+
logger.warn("init", `Skipping "${relPath}": could not derive a valid agentRules name`);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
seen.add(relPath);
|
|
228
|
+
candidates.push({
|
|
229
|
+
relPath,
|
|
230
|
+
name: rawName,
|
|
231
|
+
defaultAgents: ["github-copilot"],
|
|
232
|
+
scope: "copilot-scoped",
|
|
233
|
+
});
|
|
234
|
+
}
|
|
186
235
|
return candidates;
|
|
187
236
|
}
|
|
188
|
-
function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
|
|
237
|
+
function buildAgentRules(candidates, activeAgents, allAgents, skillNamesSeen) {
|
|
189
238
|
const rules = [];
|
|
190
239
|
const namesSeen = new Set(skillNamesSeen);
|
|
191
|
-
for (const { relPath, name: rawName } of candidates) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
240
|
+
for (const { relPath, name: rawName, defaultAgents: presetAgents, scope: presetScope, } of candidates) {
|
|
241
|
+
let agents;
|
|
242
|
+
if (presetAgents.length > 0) {
|
|
243
|
+
// Candidates with preset agents (e.g. copilot-repo, copilot-scoped) use
|
|
244
|
+
// those agents directly, intersected with the full agents list.
|
|
245
|
+
agents = presetAgents.filter((a) => allAgents.includes(a));
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// Derive from filename pattern and intersect with active (capable) agents.
|
|
249
|
+
const defaults = defaultAgentsForFile(path.basename(relPath), activeAgents);
|
|
250
|
+
const ix = defaults.filter((a) => activeAgents.includes(a));
|
|
251
|
+
agents = ix.length > 0 ? ix : activeAgents;
|
|
252
|
+
}
|
|
253
|
+
// Skip if excluded by --agents or no capable agents remain
|
|
198
254
|
if (agents.length === 0)
|
|
199
255
|
continue;
|
|
200
256
|
// Resolve name collision with skill names
|
|
@@ -211,7 +267,7 @@ function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
|
|
|
211
267
|
}
|
|
212
268
|
}
|
|
213
269
|
namesSeen.add(name);
|
|
214
|
-
rules.push({ name, path: relPath, agents, scope: "global" });
|
|
270
|
+
rules.push({ name, path: relPath, agents, scope: presetScope ?? "global" });
|
|
215
271
|
}
|
|
216
272
|
return rules;
|
|
217
273
|
}
|
|
@@ -269,16 +325,24 @@ function agentsForDefinitionSubdir(subdir) {
|
|
|
269
325
|
*/
|
|
270
326
|
async function isSkippedMcpFile(subdir, absPath, relPath) {
|
|
271
327
|
const hasMcpSurfaceHere = AGENT_REGISTRY.some((agent) => {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
328
|
+
const supports = [
|
|
329
|
+
agent.mcpSupport,
|
|
330
|
+
agent.mcpRepoSupport,
|
|
331
|
+
agent.mcpWorkspaceSupport,
|
|
332
|
+
];
|
|
333
|
+
for (const support of supports) {
|
|
334
|
+
if (support?.status !== "supported")
|
|
335
|
+
continue;
|
|
336
|
+
const tmpl = support.path.posix;
|
|
337
|
+
const repoIdx = tmpl.indexOf("{repo}");
|
|
338
|
+
const nameIdx = tmpl.findIndex((s) => s.includes("{name}"));
|
|
339
|
+
if (repoIdx === -1 || nameIdx === -1 || nameIdx <= repoIdx)
|
|
340
|
+
continue;
|
|
341
|
+
const prefix = tmpl.slice(repoIdx + 1, nameIdx).join("/");
|
|
342
|
+
if (prefix === subdir)
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
return false;
|
|
282
346
|
});
|
|
283
347
|
if (!hasMcpSurfaceHere)
|
|
284
348
|
return false;
|
|
@@ -568,7 +632,7 @@ export async function runInit(options) {
|
|
|
568
632
|
const skillNamesSeen = new Set(skills.map((s) => s.name));
|
|
569
633
|
const skillDirRelPaths = new Set(found.map((f) => f.relPath));
|
|
570
634
|
const agentRulesCandidates = await findAgentRulesCandidates(directory, skillDirRelPaths);
|
|
571
|
-
const agentRules = buildAgentRules(agentRulesCandidates, agentRulesCapableAgents, skillNamesSeen);
|
|
635
|
+
const agentRules = buildAgentRules(agentRulesCandidates, agentRulesCapableAgents, agents, skillNamesSeen);
|
|
572
636
|
const allNamesSeen = new Set([
|
|
573
637
|
...skillNamesSeen,
|
|
574
638
|
...agentRules.map((r) => r.name),
|
|
@@ -118,12 +118,37 @@ function detectMultipleActiveInstructionScopes(agentId, rulesForAgent) {
|
|
|
118
118
|
},
|
|
119
119
|
];
|
|
120
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Warns when github-copilot has both a shared-via CLAUDE.md agentRules entry
|
|
123
|
+
* (scope: "repo" or "global") AND a native Copilot instruction entry
|
|
124
|
+
* (scope: "copilot-repo" or "copilot-scoped"). GitHub Copilot merges all
|
|
125
|
+
* active instruction sources at runtime, so duplicate or conflicting rules
|
|
126
|
+
* across these surfaces may cause unexpected agent behavior.
|
|
127
|
+
*/
|
|
128
|
+
function detectCopilotInstructionPrecedence(rulesForAgent) {
|
|
129
|
+
const hasSharedVia = (rulesForAgent ?? []).some((e) => e.scope === "repo" || e.scope === "global");
|
|
130
|
+
const hasNative = (rulesForAgent ?? []).some((e) => e.scope === "copilot-repo" || e.scope === "copilot-scoped");
|
|
131
|
+
if (!(hasSharedVia && hasNative))
|
|
132
|
+
return [];
|
|
133
|
+
return [
|
|
134
|
+
{
|
|
135
|
+
kind: "precedence",
|
|
136
|
+
message: `Agent "github-copilot" will load both a CLAUDE.md-shared instruction file` +
|
|
137
|
+
` and a native Copilot instruction file (.github/copilot-instructions.md or` +
|
|
138
|
+
` .github/instructions/). GitHub Copilot merges all active instruction` +
|
|
139
|
+
` sources - ensure content is non-conflicting and does not duplicate rules.`,
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
}
|
|
121
143
|
function detectInstructionPrecedence(detectedAgents, manifest) {
|
|
122
144
|
const warnings = [];
|
|
123
145
|
for (const agentId of detectedAgents) {
|
|
124
146
|
const rulesForAgent = (manifest.agentRules ?? []).filter((e) => e.agents.includes(agentId));
|
|
125
147
|
warnings.push(...detectScopeOverlaps(agentId, rulesForAgent));
|
|
126
148
|
warnings.push(...detectMultipleActiveInstructionScopes(agentId, rulesForAgent));
|
|
149
|
+
if (agentId === "github-copilot") {
|
|
150
|
+
warnings.push(...detectCopilotInstructionPrecedence(rulesForAgent));
|
|
151
|
+
}
|
|
127
152
|
}
|
|
128
153
|
return warnings;
|
|
129
154
|
}
|
|
@@ -239,6 +264,9 @@ function collectManifestCapabilityWarnings(manifest, detectedAgents) {
|
|
|
239
264
|
for (const entry of manifest.agentDefinitions ?? []) {
|
|
240
265
|
collectCapabilityWarningsForTargets(acc, entry.agents.filter((agentId) => detectedAgents.includes(agentId)), "agentDefinitions", entry.name, entry.scope);
|
|
241
266
|
}
|
|
267
|
+
for (const entry of manifest.executionConfigs ?? []) {
|
|
268
|
+
collectCapabilityWarningsForTargets(acc, entry.agents.filter((agentId) => detectedAgents.includes(agentId)), "executionConfigs", entry.name);
|
|
269
|
+
}
|
|
242
270
|
return acc.warnings;
|
|
243
271
|
}
|
|
244
272
|
function detectCapabilityPlanningWarnings(manifest, detectedAgents) {
|
package/dist/src/core/revert.js
CHANGED
|
@@ -2,7 +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 * as frontmatterAdapter from "./adapters/frontmatter.js";
|
|
5
|
-
import { compileAgentDefinitionReverts, compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, } from "./adapters/index.js";
|
|
5
|
+
import { compileAgentDefinitionReverts, compileAgentRuleReverts, compileExecutionConfigReverts, compileHookReverts, compileMcpServerReverts, compilePermissionsReverts, } from "./adapters/index.js";
|
|
6
6
|
import { revertTomlMcpPatch } from "./adapters/toml.js";
|
|
7
7
|
import { applyUndoPatch } from "./merge-patch.js";
|
|
8
8
|
import { lookupDeployment, unregisterDeployment, } from "./ownership.js";
|
|
@@ -75,6 +75,8 @@ export function planRevert(manifest, detectedAgents, home, repo) {
|
|
|
75
75
|
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, detectedAgents, home, repo)),
|
|
76
76
|
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, detectedAgents, home, repo)),
|
|
77
77
|
...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, detectedAgents, home)),
|
|
78
|
+
...(manifest.hooks ?? []).flatMap((e) => compileHookReverts(e, detectedAgents, home)),
|
|
79
|
+
...(manifest.executionConfigs ?? []).flatMap((e) => compileExecutionConfigReverts(e, detectedAgents, home)),
|
|
78
80
|
...(manifest.agentDefinitions ?? []).flatMap((e) => compileAgentDefinitionReverts(e, detectedAgents, home, repo)),
|
|
79
81
|
];
|
|
80
82
|
}
|
|
@@ -86,6 +88,8 @@ export function planRevertAll(manifest, home, repo) {
|
|
|
86
88
|
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, null, home, repo)),
|
|
87
89
|
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, null, home, repo)),
|
|
88
90
|
...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, null, home)),
|
|
91
|
+
...(manifest.hooks ?? []).flatMap((e) => compileHookReverts(e, null, home)),
|
|
92
|
+
...(manifest.executionConfigs ?? []).flatMap((e) => compileExecutionConfigReverts(e, null, home)),
|
|
89
93
|
...(manifest.agentDefinitions ?? []).flatMap((e) => compileAgentDefinitionReverts(e, null, home, repo)),
|
|
90
94
|
];
|
|
91
95
|
}
|
|
@@ -4,6 +4,7 @@ export declare function validateSourcePath(source: string, skillPath: string, re
|
|
|
4
4
|
export declare function validateSourceFile(sourcePath: string, manifestPath: string): Promise<void>;
|
|
5
5
|
export declare function validateMcpServerConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
6
6
|
export declare function validatePermissionsConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
7
|
+
export declare function validateHookConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
7
8
|
export declare function validateAgentRuleMarkdownPath(manifestPath: string, agentId: string): void;
|
|
8
9
|
export declare function validateSkillDefinitionFile(sourcePath: string, manifestPath: string): Promise<{
|
|
9
10
|
attributes: Record<string, unknown>;
|
|
@@ -163,6 +163,63 @@ export function validatePermissionsConfigShape(config, entryName, agentId) {
|
|
|
163
163
|
validateOpenCodePermissions(config, entryName);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
|
+
function validateClaudeHookCommand(cmd, path) {
|
|
167
|
+
if (typeof cmd !== "object" || cmd === null || Array.isArray(cmd)) {
|
|
168
|
+
throw new UserError("DEPLOY_FAILED", `hooks entry ${path} must be an object`);
|
|
169
|
+
}
|
|
170
|
+
const cmdObj = cmd;
|
|
171
|
+
if (cmdObj.type !== "command") {
|
|
172
|
+
throw new UserError("DEPLOY_FAILED", `hooks entry ${path}.type must be "command"`);
|
|
173
|
+
}
|
|
174
|
+
if (typeof cmdObj.command !== "string") {
|
|
175
|
+
throw new UserError("DEPLOY_FAILED", `hooks entry ${path}.command must be a string`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function validateClaudeHookMatcher(matcher, path) {
|
|
179
|
+
if (typeof matcher !== "object" ||
|
|
180
|
+
matcher === null ||
|
|
181
|
+
Array.isArray(matcher)) {
|
|
182
|
+
throw new UserError("DEPLOY_FAILED", `hooks entry ${path} must be an object`);
|
|
183
|
+
}
|
|
184
|
+
const matcherObj = matcher;
|
|
185
|
+
if (matcherObj.matcher !== undefined &&
|
|
186
|
+
typeof matcherObj.matcher !== "string") {
|
|
187
|
+
throw new UserError("DEPLOY_FAILED", `hooks entry ${path}.matcher must be a string when present`);
|
|
188
|
+
}
|
|
189
|
+
const matcherHooks = matcherObj.hooks;
|
|
190
|
+
if (!Array.isArray(matcherHooks)) {
|
|
191
|
+
throw new UserError("DEPLOY_FAILED", `hooks entry ${path}.hooks must be an array`);
|
|
192
|
+
}
|
|
193
|
+
for (const [cmdIdx, cmd] of matcherHooks.entries()) {
|
|
194
|
+
validateClaudeHookCommand(cmd, `${path}.hooks[${cmdIdx}]`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function validateClaudeCodeHooks(config, entryName) {
|
|
198
|
+
const unknownKeys = Object.keys(config).filter((k) => k !== "hooks");
|
|
199
|
+
if (unknownKeys.length > 0) {
|
|
200
|
+
throw new UserError("DEPLOY_FAILED", `hooks entry "${entryName}" for agent "claude-code" contains unrecognized keys: ${unknownKeys.join(", ")}. Only "hooks" is allowed.`);
|
|
201
|
+
}
|
|
202
|
+
const hooks = config.hooks;
|
|
203
|
+
if (hooks === undefined)
|
|
204
|
+
return;
|
|
205
|
+
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) {
|
|
206
|
+
throw new UserError("DEPLOY_FAILED", `hooks entry "${entryName}" for agent "claude-code" must define "hooks" as an object`);
|
|
207
|
+
}
|
|
208
|
+
const hooksObj = hooks;
|
|
209
|
+
for (const [eventName, matchers] of Object.entries(hooksObj)) {
|
|
210
|
+
if (!Array.isArray(matchers)) {
|
|
211
|
+
throw new UserError("DEPLOY_FAILED", `hooks entry "${entryName}" for agent "claude-code": "hooks.${eventName}" must be an array`);
|
|
212
|
+
}
|
|
213
|
+
for (const [idx, matcher] of matchers.entries()) {
|
|
214
|
+
validateClaudeHookMatcher(matcher, `"${entryName}" for agent "claude-code": "hooks.${eventName}[${idx}]"`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
export function validateHookConfigShape(config, entryName, agentId) {
|
|
219
|
+
if (agentId === "claude-code") {
|
|
220
|
+
validateClaudeCodeHooks(config, entryName);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
166
223
|
export function validateAgentRuleMarkdownPath(manifestPath, agentId) {
|
|
167
224
|
const extension = path.extname(manifestPath).toLowerCase();
|
|
168
225
|
if (extension !== ".md" && extension !== ".markdown") {
|
|
@@ -63,6 +63,7 @@ export declare const McpServerEntrySchema: z.ZodObject<{
|
|
|
63
63
|
global: "global";
|
|
64
64
|
repo: "repo";
|
|
65
65
|
workspace: "workspace";
|
|
66
|
+
devcontainer: "devcontainer";
|
|
66
67
|
}>>;
|
|
67
68
|
}, z.core.$strip>;
|
|
68
69
|
export declare const AgentRuleEntrySchema: z.ZodObject<{
|
|
@@ -80,7 +81,10 @@ export declare const AgentRuleEntrySchema: z.ZodObject<{
|
|
|
80
81
|
global: "global";
|
|
81
82
|
repo: "repo";
|
|
82
83
|
workspace: "workspace";
|
|
84
|
+
"copilot-repo": "copilot-repo";
|
|
85
|
+
"copilot-scoped": "copilot-scoped";
|
|
83
86
|
}>>;
|
|
87
|
+
targetDir: z.ZodOptional<z.ZodString>;
|
|
84
88
|
}, z.core.$strip>;
|
|
85
89
|
export declare const PermissionsEntrySchema: z.ZodObject<{
|
|
86
90
|
name: z.ZodString;
|
|
@@ -94,6 +98,18 @@ export declare const PermissionsEntrySchema: z.ZodObject<{
|
|
|
94
98
|
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
95
99
|
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
96
100
|
}, z.core.$strip>;
|
|
101
|
+
export declare const ExecutionConfigEntrySchema: z.ZodObject<{
|
|
102
|
+
name: z.ZodString;
|
|
103
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
104
|
+
"claude-code": "claude-code";
|
|
105
|
+
codex: "codex";
|
|
106
|
+
"gemini-cli": "gemini-cli";
|
|
107
|
+
antigravity: "antigravity";
|
|
108
|
+
opencode: "opencode";
|
|
109
|
+
"github-copilot": "github-copilot";
|
|
110
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
111
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
112
|
+
}, z.core.$strip>;
|
|
97
113
|
export declare const AgentDefinitionEntrySchema: z.ZodObject<{
|
|
98
114
|
name: z.ZodString;
|
|
99
115
|
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
@@ -111,6 +127,18 @@ export declare const AgentDefinitionEntrySchema: z.ZodObject<{
|
|
|
111
127
|
workspace: "workspace";
|
|
112
128
|
}>>;
|
|
113
129
|
}, z.core.$strip>;
|
|
130
|
+
export declare const HookEntrySchema: z.ZodObject<{
|
|
131
|
+
name: z.ZodString;
|
|
132
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
133
|
+
"claude-code": "claude-code";
|
|
134
|
+
codex: "codex";
|
|
135
|
+
"gemini-cli": "gemini-cli";
|
|
136
|
+
antigravity: "antigravity";
|
|
137
|
+
opencode: "opencode";
|
|
138
|
+
"github-copilot": "github-copilot";
|
|
139
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
140
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
141
|
+
}, z.core.$strip>;
|
|
114
142
|
export declare const ManifestSchema: z.ZodObject<{
|
|
115
143
|
skills: z.ZodArray<z.ZodObject<{
|
|
116
144
|
name: z.ZodString;
|
|
@@ -165,6 +193,7 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
165
193
|
global: "global";
|
|
166
194
|
repo: "repo";
|
|
167
195
|
workspace: "workspace";
|
|
196
|
+
devcontainer: "devcontainer";
|
|
168
197
|
}>>;
|
|
169
198
|
}, z.core.$strip>>>;
|
|
170
199
|
agentRules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
@@ -182,7 +211,10 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
182
211
|
global: "global";
|
|
183
212
|
repo: "repo";
|
|
184
213
|
workspace: "workspace";
|
|
214
|
+
"copilot-repo": "copilot-repo";
|
|
215
|
+
"copilot-scoped": "copilot-scoped";
|
|
185
216
|
}>>;
|
|
217
|
+
targetDir: z.ZodOptional<z.ZodString>;
|
|
186
218
|
}, z.core.$strip>>>;
|
|
187
219
|
permissions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
188
220
|
name: z.ZodString;
|
|
@@ -213,6 +245,30 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
213
245
|
workspace: "workspace";
|
|
214
246
|
}>>;
|
|
215
247
|
}, z.core.$strip>>>;
|
|
248
|
+
hooks: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
249
|
+
name: z.ZodString;
|
|
250
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
251
|
+
"claude-code": "claude-code";
|
|
252
|
+
codex: "codex";
|
|
253
|
+
"gemini-cli": "gemini-cli";
|
|
254
|
+
antigravity: "antigravity";
|
|
255
|
+
opencode: "opencode";
|
|
256
|
+
"github-copilot": "github-copilot";
|
|
257
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
258
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
259
|
+
}, z.core.$strip>>>;
|
|
260
|
+
executionConfigs: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
261
|
+
name: z.ZodString;
|
|
262
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
263
|
+
"claude-code": "claude-code";
|
|
264
|
+
codex: "codex";
|
|
265
|
+
"gemini-cli": "gemini-cli";
|
|
266
|
+
antigravity: "antigravity";
|
|
267
|
+
opencode: "opencode";
|
|
268
|
+
"github-copilot": "github-copilot";
|
|
269
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
270
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
271
|
+
}, z.core.$strip>>>;
|
|
216
272
|
}, z.core.$strip>;
|
|
217
273
|
export type SkillEntry = z.infer<typeof SkillEntrySchema>;
|
|
218
274
|
export type FileEntry = z.infer<typeof FileEntrySchema>;
|
|
@@ -221,6 +277,8 @@ export type McpServerEntry = z.infer<typeof McpServerEntrySchema>;
|
|
|
221
277
|
export type AgentRuleEntry = z.infer<typeof AgentRuleEntrySchema>;
|
|
222
278
|
export type PermissionsEntry = z.infer<typeof PermissionsEntrySchema>;
|
|
223
279
|
export type AgentDefinitionEntry = z.infer<typeof AgentDefinitionEntrySchema>;
|
|
280
|
+
export type HookEntry = z.infer<typeof HookEntrySchema>;
|
|
281
|
+
export type ExecutionConfigEntry = z.infer<typeof ExecutionConfigEntrySchema>;
|
|
224
282
|
export type Manifest = z.infer<typeof ManifestSchema>;
|
|
225
283
|
export declare const AgentListSchema: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string[], string>>, z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
226
284
|
"claude-code": "claude-code";
|