@kuznai/inception-engine 0.17.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 +40 -13
- package/dist/config/agents.js +127 -5
- package/dist/core/adapters/agent-definitions.d.ts +2 -2
- package/dist/core/adapters/agent-definitions.js +6 -5
- package/dist/core/adapters/frontmatter.d.ts +17 -5
- package/dist/core/adapters/frontmatter.js +36 -57
- 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 +99 -28
- package/dist/core/deploy.d.ts +1 -1
- package/dist/core/deploy.js +206 -54
- package/dist/core/init.js +129 -49
- package/dist/core/ownership.d.ts +2 -2
- package/dist/core/preflight.d.ts +2 -2
- package/dist/core/preflight.js +142 -2
- 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 +73 -35
- 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 +10 -0
- package/dist/schemas/manifest.js +10 -3
- package/dist/schemas/registry.d.ts +38 -0
- package/dist/schemas/registry.js +7 -0
- package/dist/types.d.ts +48 -0
- package/package.json +2 -2
package/dist/core/init.js
CHANGED
|
@@ -1,42 +1,81 @@
|
|
|
1
|
-
import { access,
|
|
1
|
+
import { access, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
3
|
+
import { AGENT_REGISTRY, AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
4
4
|
import { dryRunPrefix, logger } from "../logger.js";
|
|
5
5
|
import { AGENT_IDS, AgentDefinitionEntrySchema, ConfigEntrySchema, FileEntrySchema, McpServerEntrySchema, } from "../schemas/manifest.js";
|
|
6
|
+
import { parseFrontmatterDocument } from "./adapters/frontmatter.js";
|
|
6
7
|
const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
7
8
|
// Ordered list: first match wins. Catch-all is applied at call site.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
// Derived from the registry: group agents by the filename of their global
|
|
10
|
+
// agentRulesSupport path. Agents with requiresPrimary (e.g. github-copilot)
|
|
11
|
+
// are excluded because they cannot be deployed independently. The
|
|
12
|
+
// copilot-instructions.md convention mapping is appended explicitly since it
|
|
13
|
+
// is a well-known filename convention, not derivable from a path template.
|
|
14
|
+
const AGENT_RULES_FILE_PATTERNS = (() => {
|
|
15
|
+
const filenameToAgents = new Map();
|
|
16
|
+
for (const agent of AGENT_REGISTRY) {
|
|
17
|
+
const support = agent.agentRulesSupport;
|
|
18
|
+
if (support?.status !== "supported")
|
|
19
|
+
continue;
|
|
20
|
+
// Skip riders that require the primary — they cannot be listed independently
|
|
21
|
+
if (support.surfaceKind?.kind === "shared-via" &&
|
|
22
|
+
support.surfaceKind.requiresPrimary)
|
|
23
|
+
continue;
|
|
24
|
+
const filename = support.path.posix[support.path.posix.length - 1]?.toLowerCase();
|
|
25
|
+
if (!filename)
|
|
26
|
+
continue;
|
|
27
|
+
const list = filenameToAgents.get(filename) ?? [];
|
|
28
|
+
list.push(agent.id);
|
|
29
|
+
filenameToAgents.set(filename, list);
|
|
30
|
+
}
|
|
31
|
+
return [
|
|
32
|
+
...Array.from(filenameToAgents.entries()).map(([filename, agents]) => ({
|
|
33
|
+
fileNames: [filename, filename.replace(".md", "-instructions.md")],
|
|
34
|
+
agents,
|
|
35
|
+
})),
|
|
36
|
+
// Convention mapping: copilot-instructions.md → claude-code. Copilot reads
|
|
37
|
+
// CLAUDE.md natively; this filename is a well-known convention that cannot
|
|
38
|
+
// be derived from any agent path template.
|
|
39
|
+
{
|
|
40
|
+
fileNames: ["copilot-instructions.md"],
|
|
41
|
+
agents: ["claude-code"],
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
})();
|
|
23
45
|
// Conventional subdirectory names to scan one level deep for .md files.
|
|
24
|
-
const AGENT_RULES_SUBDIRS = [
|
|
25
|
-
"rules",
|
|
26
|
-
"instructions",
|
|
27
|
-
".github",
|
|
28
|
-
".agents/rules",
|
|
29
|
-
];
|
|
46
|
+
const AGENT_RULES_SUBDIRS = ["rules", "instructions", ".github"];
|
|
30
47
|
// Conventional subdirectories that contain agent definition files.
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
const AGENT_DEFINITION_SUBDIRS =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
// Derived from the registry: for each agent with a supported agentDefinitions
|
|
49
|
+
// surface, extract the directory prefix from its posix path template.
|
|
50
|
+
const AGENT_DEFINITION_SUBDIRS = (() => {
|
|
51
|
+
const subdirs = new Set();
|
|
52
|
+
for (const agent of AGENT_REGISTRY) {
|
|
53
|
+
const support = agent.agentDefinitionsSupport;
|
|
54
|
+
if (support?.status !== "supported")
|
|
55
|
+
continue;
|
|
56
|
+
const tmpl = support.path.posix;
|
|
57
|
+
const repoIdx = tmpl.indexOf("{repo}");
|
|
58
|
+
const nameIdx = tmpl.findIndex((s) => s.includes("{name}"));
|
|
59
|
+
if (repoIdx === -1 || nameIdx === -1 || nameIdx <= repoIdx)
|
|
60
|
+
continue;
|
|
61
|
+
const dirSegs = tmpl.slice(repoIdx + 1, nameIdx);
|
|
62
|
+
if (dirSegs.length > 0)
|
|
63
|
+
subdirs.add(dirSegs.join("/"));
|
|
64
|
+
}
|
|
65
|
+
return Array.from(subdirs);
|
|
66
|
+
})();
|
|
67
|
+
async function hasAntigravityMcpFrontmatter(absPath) {
|
|
68
|
+
let raw;
|
|
69
|
+
try {
|
|
70
|
+
raw = await readFile(absPath, "utf-8");
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const { attributes } = parseFrontmatterDocument(raw);
|
|
76
|
+
return (Object.hasOwn(attributes, "mcp-servers") ||
|
|
77
|
+
Object.hasOwn(attributes, "mcpServers"));
|
|
78
|
+
}
|
|
40
79
|
async function findSkillDirs(baseDir, dir, found) {
|
|
41
80
|
let entries;
|
|
42
81
|
try {
|
|
@@ -170,7 +209,7 @@ function buildAgentRules(candidates, activeAgents, skillNamesSeen) {
|
|
|
170
209
|
}
|
|
171
210
|
}
|
|
172
211
|
namesSeen.add(name);
|
|
173
|
-
rules.push({ name, path: relPath, agents });
|
|
212
|
+
rules.push({ name, path: relPath, agents, scope: "global" });
|
|
174
213
|
}
|
|
175
214
|
return rules;
|
|
176
215
|
}
|
|
@@ -196,24 +235,53 @@ function deriveAgentDefinitionName(relPath, fileName) {
|
|
|
196
235
|
}
|
|
197
236
|
/**
|
|
198
237
|
* Maps a known agent-definition subdirectory to the agent IDs that own it.
|
|
199
|
-
*
|
|
200
|
-
*
|
|
238
|
+
* Derived from the registry by matching each agent's agentDefinitionsSupport
|
|
239
|
+
* path prefix. Returns null when the subdir is not recognized.
|
|
201
240
|
*/
|
|
202
241
|
function agentsForDefinitionSubdir(subdir) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
242
|
+
const matching = [];
|
|
243
|
+
for (const agent of AGENT_REGISTRY) {
|
|
244
|
+
const support = agent.agentDefinitionsSupport;
|
|
245
|
+
if (support?.status !== "supported")
|
|
246
|
+
continue;
|
|
247
|
+
const tmpl = support.path.posix;
|
|
248
|
+
const repoIdx = tmpl.indexOf("{repo}");
|
|
249
|
+
const nameIdx = tmpl.findIndex((s) => s.includes("{name}"));
|
|
250
|
+
if (repoIdx === -1 || nameIdx === -1 || nameIdx <= repoIdx)
|
|
251
|
+
continue;
|
|
252
|
+
const agentSubdir = tmpl.slice(repoIdx + 1, nameIdx).join("/");
|
|
253
|
+
if (agentSubdir === subdir)
|
|
254
|
+
matching.push(agent.id);
|
|
216
255
|
}
|
|
256
|
+
return matching.length > 0 ? matching : null;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Returns true when a file in a definition subdir contains MCP-specific
|
|
260
|
+
* frontmatter and should be excluded from agentDefinitions discovery. Detects
|
|
261
|
+
* this by checking whether any registered agent uses the same directory as
|
|
262
|
+
* both its definition surface and its MCP surface (currently: Antigravity's
|
|
263
|
+
* .agents/rules/).
|
|
264
|
+
*/
|
|
265
|
+
async function isSkippedMcpFile(subdir, absPath, relPath) {
|
|
266
|
+
const hasMcpSurfaceHere = AGENT_REGISTRY.some((agent) => {
|
|
267
|
+
const support = agent.mcpSupport;
|
|
268
|
+
if (support?.status !== "supported")
|
|
269
|
+
return false;
|
|
270
|
+
const tmpl = support.path.posix;
|
|
271
|
+
const repoIdx = tmpl.indexOf("{repo}");
|
|
272
|
+
const nameIdx = tmpl.findIndex((s) => s.includes("{name}"));
|
|
273
|
+
if (repoIdx === -1 || nameIdx === -1 || nameIdx <= repoIdx)
|
|
274
|
+
return false;
|
|
275
|
+
const prefix = tmpl.slice(repoIdx + 1, nameIdx).join("/");
|
|
276
|
+
return prefix === subdir;
|
|
277
|
+
});
|
|
278
|
+
if (!hasMcpSurfaceHere)
|
|
279
|
+
return false;
|
|
280
|
+
const isMcp = await hasAntigravityMcpFrontmatter(absPath);
|
|
281
|
+
if (isMcp) {
|
|
282
|
+
logger.warn("init", `Skipping "${relPath}" as agentDefinitions: frontmatter contains "mcp-servers" — file is an MCP surface, not an agent definition`);
|
|
283
|
+
}
|
|
284
|
+
return isMcp;
|
|
217
285
|
}
|
|
218
286
|
async function scanDefinitionSubdir(subdir, baseDir, seen, skillDirRelPaths, agentRulesRelPaths, candidates) {
|
|
219
287
|
const suggestedAgents = agentsForDefinitionSubdir(subdir) ?? [];
|
|
@@ -237,6 +305,8 @@ async function scanDefinitionSubdir(subdir, baseDir, seen, skillDirRelPaths, age
|
|
|
237
305
|
isInsideSkillDir(relPath, skillDirRelPaths) ||
|
|
238
306
|
agentRulesRelPaths.has(relPath))
|
|
239
307
|
continue;
|
|
308
|
+
if (await isSkippedMcpFile(subdir, absPath, relPath))
|
|
309
|
+
continue;
|
|
240
310
|
const name = deriveAgentDefinitionName(relPath, entry.name);
|
|
241
311
|
if (name === null)
|
|
242
312
|
continue;
|
|
@@ -478,7 +548,17 @@ function logVerboseManifest(skills, agentRules, mcpServers, files, configs, agen
|
|
|
478
548
|
export async function runInit(options) {
|
|
479
549
|
const { directory, dryRun, force, verbose } = options;
|
|
480
550
|
const agents = options.agents ?? [...AGENT_IDS];
|
|
481
|
-
const agentRulesCapableAgents = agents.filter((id) =>
|
|
551
|
+
const agentRulesCapableAgents = agents.filter((id) => {
|
|
552
|
+
const support = AGENT_REGISTRY_BY_ID[id].agentRulesSupport;
|
|
553
|
+
if (!support || support.status === "unsupported")
|
|
554
|
+
return false;
|
|
555
|
+
// Exclude riders that require the primary — they cannot operate independently
|
|
556
|
+
if (support.status === "supported" &&
|
|
557
|
+
support.surfaceKind?.kind === "shared-via" &&
|
|
558
|
+
support.surfaceKind.requiresPrimary)
|
|
559
|
+
return false;
|
|
560
|
+
return true;
|
|
561
|
+
});
|
|
482
562
|
const manifestPath = path.join(directory, "inception.json");
|
|
483
563
|
if (!dryRun && (await manifestExists(manifestPath)) && !force) {
|
|
484
564
|
logger.error(`Error: ${manifestPath} already exists. Use --force to overwrite.`);
|
package/dist/core/ownership.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ConfigPatchRegistryEntry, type FileWriteRegistryEntry, type Registry, type RegistryEntry, type SkillDirRegistryEntry } from "../schemas/registry.ts";
|
|
1
|
+
import { type ConfigPatchRegistryEntry, type FileWriteRegistryEntry, type FrontmatterEmitRegistryEntry, type Registry, type RegistryEntry, type SkillDirRegistryEntry } from "../schemas/registry.ts";
|
|
2
2
|
import type { AgentId } from "../types.ts";
|
|
3
3
|
export type { RegistryEntry } from "../schemas/registry.ts";
|
|
4
4
|
export interface RegistryPersistence {
|
|
@@ -22,7 +22,7 @@ export type VerifyExpected = {
|
|
|
22
22
|
};
|
|
23
23
|
export declare function registryPath(home: string): string;
|
|
24
24
|
export declare const defaultRegistryPersistence: RegistryPersistence;
|
|
25
|
-
export type RegisterEntry = Omit<SkillDirRegistryEntry, "deployed"> | Omit<FileWriteRegistryEntry, "deployed"> | Omit<ConfigPatchRegistryEntry, "deployed">;
|
|
25
|
+
export type RegisterEntry = Omit<SkillDirRegistryEntry, "deployed"> | Omit<FileWriteRegistryEntry, "deployed"> | Omit<ConfigPatchRegistryEntry, "deployed"> | Omit<FrontmatterEmitRegistryEntry, "deployed">;
|
|
26
26
|
export declare function registerDeployment(home: string, targetPath: string, entry: RegisterEntry, persistence?: RegistryPersistence): Promise<void>;
|
|
27
27
|
export declare function unregisterDeployment(home: string, targetPath: string, persistence?: RegistryPersistence): Promise<void>;
|
|
28
28
|
export declare function lookupDeployment(home: string, targetPath: string, persistence?: RegistryPersistence): Promise<RegistryEntry | null>;
|
package/dist/core/preflight.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentId, CliOptions, Manifest } from "../types.ts";
|
|
2
2
|
export interface PreflightWarning {
|
|
3
|
-
kind: "policy" | "config-authority" | "info";
|
|
3
|
+
kind: "policy" | "config-authority" | "info" | "precedence" | "budget";
|
|
4
4
|
message: string;
|
|
5
5
|
}
|
|
6
|
-
export declare function runPreflight(
|
|
6
|
+
export declare function runPreflight(options: CliOptions, manifest: Manifest, home: string, detectedAgents: AgentId[]): Promise<PreflightWarning[]>;
|
package/dist/core/preflight.js
CHANGED
|
@@ -1,5 +1,135 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
2
|
-
|
|
4
|
+
import { resolveRuntimePaths } from "./runtime-paths.js";
|
|
5
|
+
const BUDGET_WARN_BYTES = 50 * 1024; // 50 KB
|
|
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) {
|
|
49
|
+
const warnings = [];
|
|
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)
|
|
54
|
+
continue;
|
|
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
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
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));
|
|
93
|
+
}
|
|
94
|
+
return warnings;
|
|
95
|
+
}
|
|
96
|
+
async function detectInstructionBudgetRisk(detectedAgents, manifest, sourceDir) {
|
|
97
|
+
const warnings = [];
|
|
98
|
+
const checkedPaths = new Set();
|
|
99
|
+
async function checkEntry(entryPath, label, agentIds) {
|
|
100
|
+
if (!agentIds.some((id) => detectedAgents.includes(id)))
|
|
101
|
+
return;
|
|
102
|
+
const sourcePath = path.resolve(sourceDir, entryPath);
|
|
103
|
+
if (checkedPaths.has(sourcePath))
|
|
104
|
+
return;
|
|
105
|
+
checkedPaths.add(sourcePath);
|
|
106
|
+
try {
|
|
107
|
+
const fileStat = await stat(sourcePath);
|
|
108
|
+
if (fileStat.size > BUDGET_WARN_BYTES) {
|
|
109
|
+
const sizeKb = (fileStat.size / 1024).toFixed(1);
|
|
110
|
+
warnings.push({
|
|
111
|
+
kind: "budget",
|
|
112
|
+
message: `${label} source "${entryPath}" is ${sizeKb} KB — large instruction files risk crowding out code context in the agent's context window. Consider splitting into smaller focused files.`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
const code = err.code;
|
|
118
|
+
if (code !== "ENOENT" && code !== "EACCES" && code !== "EPERM")
|
|
119
|
+
throw err;
|
|
120
|
+
// Missing/unreadable files are silently skipped; compileAgentRuleActions
|
|
121
|
+
// will produce the proper UserError during action compilation.
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const entry of manifest.agentRules ?? []) {
|
|
125
|
+
await checkEntry(entry.path, "agentRules", entry.agents);
|
|
126
|
+
}
|
|
127
|
+
for (const entry of manifest.agentDefinitions ?? []) {
|
|
128
|
+
await checkEntry(entry.path, "agentDefinitions", entry.agents);
|
|
129
|
+
}
|
|
130
|
+
return warnings;
|
|
131
|
+
}
|
|
132
|
+
export async function runPreflight(options, manifest, home, detectedAgents) {
|
|
3
133
|
const warnings = [];
|
|
4
134
|
for (const agentId of detectedAgents) {
|
|
5
135
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
@@ -17,12 +147,22 @@ export async function runPreflight(_options, _manifest, _home, detectedAgents) {
|
|
|
17
147
|
message: `Agent "${agentId}" skill support is provisional: behavior has not been independently verified.`,
|
|
18
148
|
});
|
|
19
149
|
}
|
|
20
|
-
|
|
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")) {
|
|
21
159
|
warnings.push({
|
|
22
160
|
kind: "policy",
|
|
23
161
|
message: `Agent "${agentId}": ${agent.policyNote}`,
|
|
24
162
|
});
|
|
25
163
|
}
|
|
26
164
|
}
|
|
165
|
+
warnings.push(...detectInstructionPrecedence(detectedAgents, manifest));
|
|
166
|
+
warnings.push(...(await detectInstructionBudgetRisk(detectedAgents, manifest, options.directory)));
|
|
27
167
|
return warnings;
|
|
28
168
|
}
|
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>;
|