@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.
@@ -1,6 +1,8 @@
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";
5
+ import { parseFrontmatterDocument } from "./adapters/frontmatter.js";
4
6
  export function sourceAccessError(err, sourcePath) {
5
7
  const code = err.code;
6
8
  if (code === "ENOENT")
@@ -147,23 +149,6 @@ export function validateAgentRuleMarkdownPath(manifestPath, agentId) {
147
149
  throw new UserError("DEPLOY_FAILED", `agentRules entry "${manifestPath}" for agent "${agentId}" must point to a Markdown source file`);
148
150
  }
149
151
  }
150
- function trimMatchingQuotes(value) {
151
- if ((value.startsWith('"') && value.endsWith('"')) ||
152
- (value.startsWith("'") && value.endsWith("'"))) {
153
- return value.slice(1, -1).trim();
154
- }
155
- return value;
156
- }
157
- function parseSimpleFrontmatterValue(field, rawValue, manifestPath) {
158
- const value = trimMatchingQuotes(rawValue.trim());
159
- if (value.length === 0) {
160
- throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md frontmatter field "${field}" must be a non-empty string`);
161
- }
162
- if (rawValue.trim() === "|" || rawValue.trim() === ">") {
163
- throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md frontmatter field "${field}" must be a single-line string`);
164
- }
165
- return value;
166
- }
167
152
  export async function validateSkillDefinitionFile(sourcePath, manifestPath) {
168
153
  await validateSourceFile(sourcePath, `${manifestPath}/SKILL.md`);
169
154
  let raw;
@@ -181,27 +166,80 @@ export async function validateSkillDefinitionFile(sourcePath, manifestPath) {
181
166
  if (closingIndex === -1) {
182
167
  throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md is missing the closing --- frontmatter delimiter`);
183
168
  }
184
- let name = null;
185
- let description = null;
186
- for (const line of lines.slice(1, closingIndex)) {
187
- const trimmed = line.trim();
188
- if (trimmed.length === 0 || trimmed.startsWith("#"))
189
- continue;
190
- const match = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line);
191
- if (!match)
192
- continue;
193
- const [, key, rawValue] = match;
194
- if (key === "name") {
195
- name = parseSimpleFrontmatterValue("name", rawValue, manifestPath);
169
+ let attributes;
170
+ let body;
171
+ try {
172
+ const parsed = parseFrontmatterDocument(raw);
173
+ attributes = parsed.attributes;
174
+ body = parsed.body;
175
+ }
176
+ catch (err) {
177
+ throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md has malformed YAML frontmatter: ${err instanceof Error ? err.message : String(err)}`);
178
+ }
179
+ const validateField = (field) => {
180
+ const value = attributes[field];
181
+ if (value === undefined || value === null) {
182
+ throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md frontmatter must include a non-empty "${field}" field`);
183
+ }
184
+ if (typeof value !== "string" || value.trim().length === 0) {
185
+ throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md frontmatter field "${field}" must be a non-empty string`);
186
+ }
187
+ if (value.includes("\n") || value.includes("\r")) {
188
+ throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md frontmatter field "${field}" must be a single-line string`);
189
+ }
190
+ };
191
+ validateField("name");
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}"`);
196
216
  }
197
- else if (key === "description") {
198
- description = parseSimpleFrontmatterValue("description", rawValue, manifestPath);
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}`);
199
236
  }
237
+ throw err;
200
238
  }
201
- if (name === null) {
202
- throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md frontmatter must include a non-empty "name" field`);
239
+ if (agentId === "github-copilot") {
240
+ validateGithubCopilotRequirements(attributes, manifestPath);
203
241
  }
204
- if (description === null) {
205
- throw new UserError("DEPLOY_FAILED", `Skill "${manifestPath}" SKILL.md frontmatter must include a non-empty "description" field`);
242
+ else if (agentId === "antigravity") {
243
+ validateAntigravityRequirements(attributes, manifestPath);
206
244
  }
207
245
  }
@@ -0,0 +1,5 @@
1
+ import type { PlannedChange } from "./types.ts";
2
+ /**
3
+ * Groups and formats the dry-run plan by agent.
4
+ */
5
+ export declare function formatDryRunPlan(planned: PlannedChange[]): string;
@@ -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
- --dry-run Show what would be done without doing it
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
- "dry-run": { type: "boolean", default: false },
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
- renderDryRunPlan(planned);
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
- renderDryRunPlan(planned);
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
- const C = {
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
- process.stderr.write(` ${c("red", "✗", errTTY)} ${label}: ${msg}\n`);
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
- process.stdout.write(` ${c("green", "✓", tty)} ${label}\n`);
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
- process.stdout.write(` ${c("yellow", "-", tty)} ${label} ${note}\n`);
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
- process.stdout.write(` ${c("cyan", "○", tty)} ${label}\n`);
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
- process.stdout.write(` ${c("yellow", "!", tty)} ${label}: ${msg}\n`);
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 process.stdout.isTTY ? `\x1b[36m[dry-run]\x1b[0m ` : `[dry-run] `;
61
+ return styleText("cyan", "[plan] ");
61
62
  }
@@ -71,6 +71,11 @@ export declare const AgentRuleEntrySchema: z.ZodObject<{
71
71
  "github-copilot": "github-copilot";
72
72
  }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
73
73
  path: z.ZodString;
74
+ scope: z.ZodDefault<z.ZodEnum<{
75
+ global: "global";
76
+ repo: "repo";
77
+ workspace: "workspace";
78
+ }>>;
74
79
  }, z.core.$strip>;
75
80
  export declare const PermissionsEntrySchema: z.ZodObject<{
76
81
  name: z.ZodString;
@@ -158,6 +163,11 @@ export declare const ManifestSchema: z.ZodObject<{
158
163
  "github-copilot": "github-copilot";
159
164
  }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
160
165
  path: z.ZodString;
166
+ scope: z.ZodDefault<z.ZodEnum<{
167
+ global: "global";
168
+ repo: "repo";
169
+ workspace: "workspace";
170
+ }>>;
161
171
  }, z.core.$strip>>>;
162
172
  permissions: z.ZodDefault<z.ZodArray<z.ZodObject<{
163
173
  name: z.ZodString;
@@ -15,7 +15,7 @@ const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
15
15
  // is valid, while "{home}/../.ssh/config" is rejected.
16
16
  // {repo} resolves to the manifest directory at deploy time, enabling repo-local
17
17
  // targets (e.g. Antigravity's .agents/rules/ surface).
18
- const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config|repo)\}(?:[\\/].*)?$/;
18
+ const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config|repo|workspace)\}(?:[\\/].*)?$/;
19
19
  // Standalone schema used for type derivation and single-ID validation (e.g. index.ts).
20
20
  export const AgentIdSchema = z.enum(AGENT_IDS);
21
21
  // Used inside SkillEntrySchema.agents so that enum failures embed the received
@@ -88,7 +88,14 @@ export const AgentRuleEntrySchema = z.object({
88
88
  name: nameField,
89
89
  agents: agentsField,
90
90
  // Relative path to the rules/instruction file within the source bundle.
91
+ // Must be a .md or .markdown file. Structural requirements (e.g. YAML
92
+ // frontmatter) are validated per agent requirements by the adapter.
91
93
  path: sourcePathField,
94
+ // Deployment scope: "global" targets the agent's home-directory instruction
95
+ // file (default), "repo" targets the project-root instruction file within the
96
+ // deployed repository (e.g. {repo}/CLAUDE.md for claude-code), and "workspace"
97
+ // targets the agent's workspace-local instruction surface.
98
+ scope: z.enum(["global", "repo", "workspace"]).default("global"),
92
99
  });
93
100
  export const PermissionsEntrySchema = z.object({
94
101
  name: nameField,
@@ -102,8 +109,8 @@ export const AgentDefinitionEntrySchema = z.object({
102
109
  name: nameField,
103
110
  agents: agentsField,
104
111
  // Relative path to the agent definition Markdown file within the source bundle.
105
- // Must be a .md or .markdown file containing YAML frontmatter that describes
106
- // the agent's persona, instructions, and any tool configuration.
112
+ // Must be a .md or .markdown file. Structural requirements (e.g. YAML
113
+ // frontmatter) are validated per agent requirements by the adapter.
107
114
  path: sourcePathField,
108
115
  });
109
116
  export const ManifestSchema = z.object({
@@ -46,6 +46,19 @@ declare const ConfigPatchRegistryEntrySchema: z.ZodObject<{
46
46
  }>;
47
47
  deployed: z.ZodString;
48
48
  }, z.core.$strip>;
49
+ declare const FrontmatterEmitRegistryEntrySchema: z.ZodObject<{
50
+ kind: z.ZodLiteral<"frontmatter-emit">;
51
+ skill: z.ZodString;
52
+ agent: z.ZodEnum<{
53
+ "claude-code": "claude-code";
54
+ codex: "codex";
55
+ "gemini-cli": "gemini-cli";
56
+ antigravity: "antigravity";
57
+ opencode: "opencode";
58
+ "github-copilot": "github-copilot";
59
+ }>;
60
+ deployed: z.ZodString;
61
+ }, z.core.$strip>;
49
62
  export declare const RegistryEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
50
63
  kind: z.ZodLiteral<"skill-dir">;
51
64
  source: z.ZodString;
@@ -90,6 +103,18 @@ export declare const RegistryEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
90
103
  "github-copilot": "github-copilot";
91
104
  }>;
92
105
  deployed: z.ZodString;
106
+ }, z.core.$strip>, z.ZodObject<{
107
+ kind: z.ZodLiteral<"frontmatter-emit">;
108
+ skill: z.ZodString;
109
+ agent: z.ZodEnum<{
110
+ "claude-code": "claude-code";
111
+ codex: "codex";
112
+ "gemini-cli": "gemini-cli";
113
+ antigravity: "antigravity";
114
+ opencode: "opencode";
115
+ "github-copilot": "github-copilot";
116
+ }>;
117
+ deployed: z.ZodString;
93
118
  }, z.core.$strip>], "kind">;
94
119
  export declare const RegistrySchema: z.ZodObject<{
95
120
  version: z.ZodLiteral<1>;
@@ -137,11 +162,24 @@ export declare const RegistrySchema: z.ZodObject<{
137
162
  "github-copilot": "github-copilot";
138
163
  }>;
139
164
  deployed: z.ZodString;
165
+ }, z.core.$strip>, z.ZodObject<{
166
+ kind: z.ZodLiteral<"frontmatter-emit">;
167
+ skill: z.ZodString;
168
+ agent: z.ZodEnum<{
169
+ "claude-code": "claude-code";
170
+ codex: "codex";
171
+ "gemini-cli": "gemini-cli";
172
+ antigravity: "antigravity";
173
+ opencode: "opencode";
174
+ "github-copilot": "github-copilot";
175
+ }>;
176
+ deployed: z.ZodString;
140
177
  }, z.core.$strip>], "kind">>;
141
178
  }, z.core.$strip>;
142
179
  export type SkillDirRegistryEntry = z.infer<typeof SkillDirRegistryEntrySchema>;
143
180
  export type FileWriteRegistryEntry = z.infer<typeof FileWriteRegistryEntrySchema>;
144
181
  export type ConfigPatchRegistryEntry = z.infer<typeof ConfigPatchRegistryEntrySchema>;
182
+ export type FrontmatterEmitRegistryEntry = z.infer<typeof FrontmatterEmitRegistryEntrySchema>;
145
183
  export type RegistryEntry = z.infer<typeof RegistryEntrySchema>;
146
184
  export type Registry = z.infer<typeof RegistrySchema>;
147
185
  export {};
@@ -23,10 +23,17 @@ const ConfigPatchRegistryEntrySchema = z.object({
23
23
  agent: AgentIdSchema,
24
24
  deployed: z.string(),
25
25
  });
26
+ const FrontmatterEmitRegistryEntrySchema = z.object({
27
+ kind: z.literal("frontmatter-emit"),
28
+ skill: z.string(),
29
+ agent: AgentIdSchema,
30
+ deployed: z.string(),
31
+ });
26
32
  export const RegistryEntrySchema = z.discriminatedUnion("kind", [
27
33
  SkillDirRegistryEntrySchema,
28
34
  FileWriteRegistryEntrySchema,
29
35
  ConfigPatchRegistryEntrySchema,
36
+ FrontmatterEmitRegistryEntrySchema,
30
37
  ]);
31
38
  export const RegistrySchema = z.object({
32
39
  version: z.literal(1),
package/dist/types.d.ts CHANGED
@@ -7,6 +7,33 @@ export interface AgentPaths {
7
7
  export type Confidence = "documented" | "implementation-only" | "provisional";
8
8
  export interface SupportedAgentSurface {
9
9
  status: "supported";
10
+ /**
11
+ * Declares how this agent relates to the surface:
12
+ * - "agent-specific" (default when absent): the agent owns this surface
13
+ * independently and deploy emits a normal action.
14
+ * - "native": the agent reads this surface autonomously without any deploy
15
+ * action from inception-engine; no action is emitted.
16
+ * - "shared-via": this agent rides another agent's deployment. When the
17
+ * primary agent (`via`) is also in the target list, deploy skips this
18
+ * agent's action (the primary writes the shared file). When the primary
19
+ * is absent, a guidance warning is emitted instead.
20
+ */
21
+ surfaceKind?: {
22
+ kind: "agent-specific";
23
+ } | {
24
+ kind: "native";
25
+ } | {
26
+ kind: "shared-via";
27
+ via: AgentId;
28
+ /**
29
+ * When true, this agent cannot deploy to the surface independently —
30
+ * the primary agent (`via`) must also be in the target list. If the
31
+ * primary is absent, a guidance warning is emitted and no action is
32
+ * generated. When false or absent, the agent can deploy to the surface
33
+ * on its own if the primary is not present.
34
+ */
35
+ requiresPrimary?: boolean;
36
+ };
10
37
  path: AgentPaths;
11
38
  schemaLabel: string;
12
39
  mcpPatchKey?: string;
@@ -50,14 +77,35 @@ export interface AgentConfig {
50
77
  * (e.g. GitHub Copilot reads `.claude/skills/` directly).
51
78
  */
52
79
  skills?: AgentPaths;
80
+ /**
81
+ * When `skills` is absent, this field explains why. Currently only
82
+ * "shared-via" is used — the agent reads skills from the `via` agent's
83
+ * deployment path natively and needs no separate skills deploy action.
84
+ */
85
+ skillsSurfaceKind?: {
86
+ kind: "shared-via";
87
+ via: AgentId;
88
+ };
53
89
  detectPaths: AgentPaths;
54
90
  detectBinary: string | null;
55
91
  provenance: AgentProvenance;
56
92
  mcpSupport?: AgentSurfaceSupport;
57
93
  agentRulesSupport?: AgentSurfaceSupport;
94
+ agentRulesRepoSupport?: AgentSurfaceSupport;
95
+ agentRulesWorkspaceSupport?: AgentSurfaceSupport;
58
96
  permissionsSupport?: AgentSurfaceSupport;
59
97
  agentDefinitionsSupport?: AgentSurfaceSupport;
60
98
  policyNote?: string;
99
+ /**
100
+ * When true, instruction files deployed to this agent must include valid
101
+ * YAML frontmatter. Drives validateInstructionFileRequirements without
102
+ * hardcoded agent-ID checks.
103
+ */
104
+ instructionFrontmatterRequired?: boolean;
105
+ /**
106
+ * When true, preflight runs enterprise-policy detection for this agent.
107
+ */
108
+ enterprisePolicyDetection?: boolean;
61
109
  }
62
110
  export interface PlanWarning {
63
111
  kind: "confidence" | "collision" | "ambiguity";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuznai/inception-engine",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Deploy AI agent skills from a git repo to user home directories",
5
5
  "license": "MIT",
6
6
  "author": "Damian Piątkowski",
@@ -47,8 +47,8 @@
47
47
  "test:posix": "node --test --test-isolation=none test/unit/*.test.ts test/os/cross-platform/*.test.ts test/os/posix/*.test.ts"
48
48
  },
49
49
  "dependencies": {
50
- "front-matter": "^4.0.2",
51
50
  "smol-toml": "^1.6.1",
51
+ "yaml": "^2.8.3",
52
52
  "zod": "^4.0.0"
53
53
  },
54
54
  "devDependencies": {