@kuznai/inception-engine 0.18.0 → 0.20.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.
Files changed (109) hide show
  1. package/README.md +72 -43
  2. package/dist/{config → src/config}/agents.js +174 -24
  3. package/dist/src/core/adapters/agent-definitions.d.ts +8 -0
  4. package/dist/src/core/adapters/agent-definitions.js +162 -0
  5. package/dist/{core → src/core}/adapters/frontmatter.d.ts +10 -0
  6. package/dist/{core → src/core}/adapters/frontmatter.js +23 -6
  7. package/dist/{core → src/core}/adapters/index.d.ts +1 -1
  8. package/dist/{core → src/core}/adapters/index.js +4 -4
  9. package/dist/{core → src/core}/adapters/mcp.d.ts +2 -2
  10. package/dist/{core → src/core}/adapters/mcp.js +21 -24
  11. package/dist/{core → src/core}/adapters/permissions.js +16 -20
  12. package/dist/{core → src/core}/adapters/rules.d.ts +2 -2
  13. package/dist/src/core/adapters/rules.js +126 -0
  14. package/dist/src/core/capabilities.d.ts +47 -0
  15. package/dist/src/core/capabilities.js +280 -0
  16. package/dist/{core → src/core}/deploy.d.ts +1 -1
  17. package/dist/{core → src/core}/deploy.js +273 -92
  18. package/dist/{core → src/core}/init.js +126 -52
  19. package/dist/src/core/merge-patch.d.ts +4 -0
  20. package/dist/src/core/merge-patch.js +46 -0
  21. package/dist/{core → src/core}/ownership.d.ts +2 -2
  22. package/dist/{core → src/core}/ownership.js +13 -0
  23. package/dist/{core → src/core}/preflight.d.ts +1 -1
  24. package/dist/src/core/preflight.js +280 -0
  25. package/dist/{core → src/core}/resolve.d.ts +1 -1
  26. package/dist/{core → src/core}/resolve.js +7 -4
  27. package/dist/{core → src/core}/revert.js +95 -33
  28. package/dist/{core → src/core}/runtime-paths.d.ts +2 -1
  29. package/dist/{core → src/core}/runtime-paths.js +30 -23
  30. package/dist/{core → src/core}/validation.d.ts +10 -1
  31. package/dist/{core → src/core}/validation.js +75 -0
  32. package/dist/src/formatters.d.ts +5 -0
  33. package/dist/src/formatters.js +58 -0
  34. package/dist/{index.js → src/index.js} +8 -24
  35. package/dist/src/logger.js +62 -0
  36. package/dist/{schemas → src/schemas}/manifest.d.ts +22 -0
  37. package/dist/{schemas → src/schemas}/manifest.js +19 -5
  38. package/dist/{schemas → src/schemas}/registry.d.ts +74 -0
  39. package/dist/{schemas → src/schemas}/registry.js +18 -3
  40. package/dist/{types.d.ts → src/types.d.ts} +60 -0
  41. package/dist/test/helpers/fs.d.ts +2 -0
  42. package/dist/test/helpers/fs.js +17 -0
  43. package/dist/test/helpers/path.d.ts +10 -0
  44. package/dist/test/helpers/path.js +15 -0
  45. package/dist/test/helpers/skill-dir-scenarios.d.ts +7 -0
  46. package/dist/test/helpers/skill-dir-scenarios.js +206 -0
  47. package/dist/test/helpers/skill-dir.d.ts +7 -0
  48. package/dist/test/helpers/skill-dir.js +46 -0
  49. package/dist/test/os/cross-platform/cross-platform.test.d.ts +1 -0
  50. package/dist/test/os/cross-platform/cross-platform.test.js +328 -0
  51. package/dist/test/os/posix/skill-dir.test.d.ts +1 -0
  52. package/dist/test/os/posix/skill-dir.test.js +19 -0
  53. package/dist/test/os/windows/revert-integration.test.d.ts +1 -0
  54. package/dist/test/os/windows/revert-integration.test.js +72 -0
  55. package/dist/test/os/windows/skill-dir.test.d.ts +1 -0
  56. package/dist/test/os/windows/skill-dir.test.js +19 -0
  57. package/dist/test/unit/adapters.test.d.ts +1 -0
  58. package/dist/test/unit/adapters.test.js +947 -0
  59. package/dist/test/unit/capabilities.test.d.ts +1 -0
  60. package/dist/test/unit/capabilities.test.js +71 -0
  61. package/dist/test/unit/cli.test.d.ts +1 -0
  62. package/dist/test/unit/cli.test.js +834 -0
  63. package/dist/test/unit/deploy.test.d.ts +1 -0
  64. package/dist/test/unit/deploy.test.js +2481 -0
  65. package/dist/test/unit/detect.test.d.ts +1 -0
  66. package/dist/test/unit/detect.test.js +192 -0
  67. package/dist/test/unit/enterprise.test.d.ts +1 -0
  68. package/dist/test/unit/enterprise.test.js +106 -0
  69. package/dist/test/unit/frontmatter.test.d.ts +1 -0
  70. package/dist/test/unit/frontmatter.test.js +102 -0
  71. package/dist/test/unit/gemini-north-star.test.d.ts +1 -0
  72. package/dist/test/unit/gemini-north-star.test.js +243 -0
  73. package/dist/test/unit/manifest.test.d.ts +1 -0
  74. package/dist/test/unit/manifest.test.js +737 -0
  75. package/dist/test/unit/migration-golden.test.d.ts +1 -0
  76. package/dist/test/unit/migration-golden.test.js +197 -0
  77. package/dist/test/unit/ownership.test.d.ts +1 -0
  78. package/dist/test/unit/ownership.test.js +587 -0
  79. package/dist/test/unit/preflight.test.d.ts +1 -0
  80. package/dist/test/unit/preflight.test.js +501 -0
  81. package/dist/test/unit/resolve.test.d.ts +1 -0
  82. package/dist/test/unit/resolve.test.js +119 -0
  83. package/dist/test/unit/revert.test.d.ts +1 -0
  84. package/dist/test/unit/revert.test.js +1004 -0
  85. package/dist/test/unit/toml.test.d.ts +1 -0
  86. package/dist/test/unit/toml.test.js +55 -0
  87. package/package.json +1 -1
  88. package/dist/core/adapters/agent-definitions.d.ts +0 -20
  89. package/dist/core/adapters/agent-definitions.js +0 -90
  90. package/dist/core/adapters/rules.js +0 -103
  91. package/dist/core/preflight.js +0 -98
  92. package/dist/logger.js +0 -61
  93. /package/dist/{config → src/config}/agents.d.ts +0 -0
  94. /package/dist/{config → src/config}/manifest.d.ts +0 -0
  95. /package/dist/{config → src/config}/manifest.js +0 -0
  96. /package/dist/{core → src/core}/adapters/permissions.d.ts +0 -0
  97. /package/dist/{core → src/core}/adapters/toml.d.ts +0 -0
  98. /package/dist/{core → src/core}/adapters/toml.js +0 -0
  99. /package/dist/{core → src/core}/detect.d.ts +0 -0
  100. /package/dist/{core → src/core}/detect.js +0 -0
  101. /package/dist/{core → src/core}/init.d.ts +0 -0
  102. /package/dist/{core → src/core}/revert.d.ts +0 -0
  103. /package/dist/{errors.d.ts → src/errors.d.ts} +0 -0
  104. /package/dist/{errors.js → src/errors.js} +0 -0
  105. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  106. /package/dist/{logger.d.ts → src/logger.d.ts} +0 -0
  107. /package/dist/{schemas → src/schemas}/errors.d.ts +0 -0
  108. /package/dist/{schemas → src/schemas}/errors.js +0 -0
  109. /package/dist/{types.js → src/types.js} +0 -0
@@ -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
@@ -83,16 +83,25 @@ export const McpServerEntrySchema = z.object({
83
83
  // Raw server descriptor passed verbatim to each agent adapter.
84
84
  // Adapters are responsible for validating the shape they need.
85
85
  config: z.record(z.string(), z.unknown()),
86
+ // Deployment scope: "global" (default) targets the agent's user-level MCP
87
+ // config file. "repo" and "workspace" target repo- or workspace-relative
88
+ // config files for agents that support them (e.g. GitHub Copilot's
89
+ // .vscode/mcp.json). For agents without scope-specific surfaces the
90
+ // adapter falls back to the global surface or emits a warning.
91
+ scope: z.enum(["global", "repo", "workspace"]).default("global"),
86
92
  });
87
93
  export const AgentRuleEntrySchema = z.object({
88
94
  name: nameField,
89
95
  agents: agentsField,
90
96
  // Relative path to the rules/instruction file within the source bundle.
97
+ // Must be a .md or .markdown file. Structural requirements (e.g. YAML
98
+ // frontmatter) are validated per agent requirements by the adapter.
91
99
  path: sourcePathField,
92
100
  // Deployment scope: "global" targets the agent's home-directory instruction
93
101
  // file (default), "repo" targets the project-root instruction file within the
94
- // deployed repository (e.g. {repo}/CLAUDE.md for claude-code).
95
- scope: z.enum(["global", "repo"]).default("global"),
102
+ // deployed repository (e.g. {repo}/CLAUDE.md for claude-code), and "workspace"
103
+ // targets the agent's workspace-local instruction surface.
104
+ scope: z.enum(["global", "repo", "workspace"]).default("global"),
96
105
  });
97
106
  export const PermissionsEntrySchema = z.object({
98
107
  name: nameField,
@@ -100,15 +109,20 @@ export const PermissionsEntrySchema = z.object({
100
109
  // Raw permission config payload validated per agent by the permissions adapter.
101
110
  // For claude-code: { permissions: { allow?: string[], deny?: string[] } }
102
111
  // For codex: { approval_policy?: "auto" | "manual" | "suggest" | "on-failure" }
112
+ // For opencode: { permissions: { allow?: string[], ask?: string[], deny?: string[] } }
103
113
  config: z.record(z.string(), z.unknown()),
104
114
  });
105
115
  export const AgentDefinitionEntrySchema = z.object({
106
116
  name: nameField,
107
117
  agents: agentsField,
108
118
  // Relative path to the agent definition Markdown file within the source bundle.
109
- // Must be a .md or .markdown file. Content and frontmatter structure are not
110
- // validated by inception-engine format requirements are agent-specific.
119
+ // Must be a .md or .markdown file. Structural requirements (e.g. YAML
120
+ // frontmatter) are validated per agent requirements by the adapter.
111
121
  path: sourcePathField,
122
+ // Deployment scope: "global" targets the agent's home-directory definition
123
+ // directory (if supported), "repo" targets the project-root definition directory
124
+ // within the deployed repository (default).
125
+ scope: z.enum(["global", "repo", "workspace"]).default("repo"),
112
126
  });
113
127
  export const ManifestSchema = z.object({
114
128
  skills: z.array(SkillEntrySchema).superRefine((skills, ctx) => {
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
2
  declare const SkillDirRegistryEntrySchema: z.ZodObject<{
3
+ surfaceId: z.ZodOptional<z.ZodString>;
4
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
3
5
  kind: z.ZodLiteral<"skill-dir">;
4
6
  source: z.ZodString;
5
7
  skill: z.ZodString;
@@ -18,6 +20,8 @@ declare const SkillDirRegistryEntrySchema: z.ZodObject<{
18
20
  deployed: z.ZodString;
19
21
  }, z.core.$strip>;
20
22
  declare const FileWriteRegistryEntrySchema: z.ZodObject<{
23
+ surfaceId: z.ZodOptional<z.ZodString>;
24
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
21
25
  kind: z.ZodLiteral<"file-write">;
22
26
  source: z.ZodString;
23
27
  skill: z.ZodString;
@@ -32,6 +36,8 @@ declare const FileWriteRegistryEntrySchema: z.ZodObject<{
32
36
  deployed: z.ZodString;
33
37
  }, z.core.$strip>;
34
38
  declare const ConfigPatchRegistryEntrySchema: z.ZodObject<{
39
+ surfaceId: z.ZodOptional<z.ZodString>;
40
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
35
41
  kind: z.ZodLiteral<"config-patch">;
36
42
  patch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
37
43
  undoPatch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -46,7 +52,28 @@ declare const ConfigPatchRegistryEntrySchema: z.ZodObject<{
46
52
  }>;
47
53
  deployed: z.ZodString;
48
54
  }, z.core.$strip>;
55
+ declare const FrontmatterEmitRegistryEntrySchema: z.ZodObject<{
56
+ surfaceId: z.ZodOptional<z.ZodString>;
57
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
58
+ kind: z.ZodLiteral<"frontmatter-emit">;
59
+ patch: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
60
+ undoPatch: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
61
+ created: z.ZodOptional<z.ZodBoolean>;
62
+ hadFrontmatter: z.ZodOptional<z.ZodBoolean>;
63
+ skill: z.ZodString;
64
+ agent: z.ZodEnum<{
65
+ "claude-code": "claude-code";
66
+ codex: "codex";
67
+ "gemini-cli": "gemini-cli";
68
+ antigravity: "antigravity";
69
+ opencode: "opencode";
70
+ "github-copilot": "github-copilot";
71
+ }>;
72
+ deployed: z.ZodString;
73
+ }, z.core.$strip>;
49
74
  export declare const RegistryEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
75
+ surfaceId: z.ZodOptional<z.ZodString>;
76
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
50
77
  kind: z.ZodLiteral<"skill-dir">;
51
78
  source: z.ZodString;
52
79
  skill: z.ZodString;
@@ -64,6 +91,8 @@ export declare const RegistryEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
64
91
  }>>;
65
92
  deployed: z.ZodString;
66
93
  }, z.core.$strip>, z.ZodObject<{
94
+ surfaceId: z.ZodOptional<z.ZodString>;
95
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
67
96
  kind: z.ZodLiteral<"file-write">;
68
97
  source: z.ZodString;
69
98
  skill: z.ZodString;
@@ -77,6 +106,8 @@ export declare const RegistryEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
77
106
  }>;
78
107
  deployed: z.ZodString;
79
108
  }, z.core.$strip>, z.ZodObject<{
109
+ surfaceId: z.ZodOptional<z.ZodString>;
110
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
80
111
  kind: z.ZodLiteral<"config-patch">;
81
112
  patch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
82
113
  undoPatch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -90,10 +121,30 @@ export declare const RegistryEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
90
121
  "github-copilot": "github-copilot";
91
122
  }>;
92
123
  deployed: z.ZodString;
124
+ }, z.core.$strip>, z.ZodObject<{
125
+ surfaceId: z.ZodOptional<z.ZodString>;
126
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
127
+ kind: z.ZodLiteral<"frontmatter-emit">;
128
+ patch: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
129
+ undoPatch: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
130
+ created: z.ZodOptional<z.ZodBoolean>;
131
+ hadFrontmatter: z.ZodOptional<z.ZodBoolean>;
132
+ skill: z.ZodString;
133
+ agent: z.ZodEnum<{
134
+ "claude-code": "claude-code";
135
+ codex: "codex";
136
+ "gemini-cli": "gemini-cli";
137
+ antigravity: "antigravity";
138
+ opencode: "opencode";
139
+ "github-copilot": "github-copilot";
140
+ }>;
141
+ deployed: z.ZodString;
93
142
  }, z.core.$strip>], "kind">;
94
143
  export declare const RegistrySchema: z.ZodObject<{
95
144
  version: z.ZodLiteral<1>;
96
145
  deployments: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
146
+ surfaceId: z.ZodOptional<z.ZodString>;
147
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
97
148
  kind: z.ZodLiteral<"skill-dir">;
98
149
  source: z.ZodString;
99
150
  skill: z.ZodString;
@@ -111,6 +162,8 @@ export declare const RegistrySchema: z.ZodObject<{
111
162
  }>>;
112
163
  deployed: z.ZodString;
113
164
  }, z.core.$strip>, z.ZodObject<{
165
+ surfaceId: z.ZodOptional<z.ZodString>;
166
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
114
167
  kind: z.ZodLiteral<"file-write">;
115
168
  source: z.ZodString;
116
169
  skill: z.ZodString;
@@ -124,6 +177,8 @@ export declare const RegistrySchema: z.ZodObject<{
124
177
  }>;
125
178
  deployed: z.ZodString;
126
179
  }, z.core.$strip>, z.ZodObject<{
180
+ surfaceId: z.ZodOptional<z.ZodString>;
181
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
127
182
  kind: z.ZodLiteral<"config-patch">;
128
183
  patch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
129
184
  undoPatch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -137,11 +192,30 @@ export declare const RegistrySchema: z.ZodObject<{
137
192
  "github-copilot": "github-copilot";
138
193
  }>;
139
194
  deployed: z.ZodString;
195
+ }, z.core.$strip>, z.ZodObject<{
196
+ surfaceId: z.ZodOptional<z.ZodString>;
197
+ migratedFrom: z.ZodOptional<z.ZodArray<z.ZodString>>;
198
+ kind: z.ZodLiteral<"frontmatter-emit">;
199
+ patch: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
200
+ undoPatch: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
201
+ created: z.ZodOptional<z.ZodBoolean>;
202
+ hadFrontmatter: z.ZodOptional<z.ZodBoolean>;
203
+ skill: z.ZodString;
204
+ agent: z.ZodEnum<{
205
+ "claude-code": "claude-code";
206
+ codex: "codex";
207
+ "gemini-cli": "gemini-cli";
208
+ antigravity: "antigravity";
209
+ opencode: "opencode";
210
+ "github-copilot": "github-copilot";
211
+ }>;
212
+ deployed: z.ZodString;
140
213
  }, z.core.$strip>], "kind">>;
141
214
  }, z.core.$strip>;
142
215
  export type SkillDirRegistryEntry = z.infer<typeof SkillDirRegistryEntrySchema>;
143
216
  export type FileWriteRegistryEntry = z.infer<typeof FileWriteRegistryEntrySchema>;
144
217
  export type ConfigPatchRegistryEntry = z.infer<typeof ConfigPatchRegistryEntrySchema>;
218
+ export type FrontmatterEmitRegistryEntry = z.infer<typeof FrontmatterEmitRegistryEntrySchema>;
145
219
  export type RegistryEntry = z.infer<typeof RegistryEntrySchema>;
146
220
  export type Registry = z.infer<typeof RegistrySchema>;
147
221
  export {};
@@ -1,6 +1,10 @@
1
1
  import { z } from "zod";
2
2
  import { AgentIdSchema } from "./manifest.js";
3
- const SkillDirRegistryEntrySchema = z.object({
3
+ const RegistryOwnershipMetadataSchema = z.object({
4
+ surfaceId: z.string().optional(),
5
+ migratedFrom: z.array(z.string()).optional(),
6
+ });
7
+ const SkillDirRegistryEntrySchema = RegistryOwnershipMetadataSchema.extend({
4
8
  kind: z.literal("skill-dir"),
5
9
  source: z.string(),
6
10
  skill: z.string(),
@@ -8,14 +12,14 @@ const SkillDirRegistryEntrySchema = z.object({
8
12
  method: z.enum(["symlink", "copy"]).optional(),
9
13
  deployed: z.string(),
10
14
  });
11
- const FileWriteRegistryEntrySchema = z.object({
15
+ const FileWriteRegistryEntrySchema = RegistryOwnershipMetadataSchema.extend({
12
16
  kind: z.literal("file-write"),
13
17
  source: z.string(),
14
18
  skill: z.string(),
15
19
  agent: AgentIdSchema,
16
20
  deployed: z.string(),
17
21
  });
18
- const ConfigPatchRegistryEntrySchema = z.object({
22
+ const ConfigPatchRegistryEntrySchema = RegistryOwnershipMetadataSchema.extend({
19
23
  kind: z.literal("config-patch"),
20
24
  patch: z.record(z.string(), z.unknown()),
21
25
  undoPatch: z.record(z.string(), z.unknown()),
@@ -23,10 +27,21 @@ const ConfigPatchRegistryEntrySchema = z.object({
23
27
  agent: AgentIdSchema,
24
28
  deployed: z.string(),
25
29
  });
30
+ const FrontmatterEmitRegistryEntrySchema = RegistryOwnershipMetadataSchema.extend({
31
+ kind: z.literal("frontmatter-emit"),
32
+ patch: z.record(z.string(), z.unknown()).optional(),
33
+ undoPatch: z.record(z.string(), z.unknown()).optional(),
34
+ created: z.boolean().optional(),
35
+ hadFrontmatter: z.boolean().optional(),
36
+ skill: z.string(),
37
+ agent: AgentIdSchema,
38
+ deployed: z.string(),
39
+ });
26
40
  export const RegistryEntrySchema = z.discriminatedUnion("kind", [
27
41
  SkillDirRegistryEntrySchema,
28
42
  FileWriteRegistryEntrySchema,
29
43
  ConfigPatchRegistryEntrySchema,
44
+ FrontmatterEmitRegistryEntrySchema,
30
45
  ]);
31
46
  export const RegistrySchema = z.object({
32
47
  version: z.literal(1),
@@ -5,8 +5,36 @@ export interface AgentPaths {
5
5
  windows: string[];
6
6
  }
7
7
  export type Confidence = "documented" | "implementation-only" | "provisional";
8
+ export type CapabilityKind = "skills" | "mcpServers" | "agentRules" | "permissions" | "agentDefinitions";
8
9
  export interface SupportedAgentSurface {
9
10
  status: "supported";
11
+ /**
12
+ * Declares how this agent relates to the surface:
13
+ * - "agent-specific" (default when absent): the agent owns this surface
14
+ * independently and deploy emits a normal action.
15
+ * - "native": the agent reads this surface autonomously without any deploy
16
+ * action from inception-engine; no action is emitted.
17
+ * - "shared-via": this agent rides another agent's deployment. When the
18
+ * primary agent (`via`) is also in the target list, deploy skips this
19
+ * agent's action (the primary writes the shared file). When the primary
20
+ * is absent, a guidance warning is emitted instead.
21
+ */
22
+ surfaceKind?: {
23
+ kind: "agent-specific";
24
+ } | {
25
+ kind: "native";
26
+ } | {
27
+ kind: "shared-via";
28
+ via: AgentId;
29
+ /**
30
+ * When true, this agent cannot deploy to the surface independently —
31
+ * the primary agent (`via`) must also be in the target list. If the
32
+ * primary is absent, a guidance warning is emitted and no action is
33
+ * generated. When false or absent, the agent can deploy to the surface
34
+ * on its own if the primary is not present.
35
+ */
36
+ requiresPrimary?: boolean;
37
+ };
10
38
  path: AgentPaths;
11
39
  schemaLabel: string;
12
40
  mcpPatchKey?: string;
@@ -50,15 +78,46 @@ export interface AgentConfig {
50
78
  * (e.g. GitHub Copilot reads `.claude/skills/` directly).
51
79
  */
52
80
  skills?: AgentPaths;
81
+ /**
82
+ * When `skills` is absent, this field explains why. Currently only
83
+ * "shared-via" is used — the agent reads skills from the `via` agent's
84
+ * deployment path natively and needs no separate skills deploy action.
85
+ */
86
+ skillsSurfaceKind?: {
87
+ kind: "shared-via";
88
+ via: AgentId;
89
+ };
53
90
  detectPaths: AgentPaths;
54
91
  detectBinary: string | null;
55
92
  provenance: AgentProvenance;
56
93
  mcpSupport?: AgentSurfaceSupport;
94
+ mcpRepoSupport?: AgentSurfaceSupport;
95
+ mcpWorkspaceSupport?: AgentSurfaceSupport;
57
96
  agentRulesSupport?: AgentSurfaceSupport;
58
97
  agentRulesRepoSupport?: AgentSurfaceSupport;
98
+ agentRulesWorkspaceSupport?: AgentSurfaceSupport;
59
99
  permissionsSupport?: AgentSurfaceSupport;
60
100
  agentDefinitionsSupport?: AgentSurfaceSupport;
101
+ agentDefinitionsRepoSupport?: AgentSurfaceSupport;
102
+ agentDefinitionsWorkspaceSupport?: AgentSurfaceSupport;
103
+ agentDefinitionsTomlSupport?: AgentSurfaceSupport;
104
+ agentDefinitionsTomlRepoSupport?: AgentSurfaceSupport;
61
105
  policyNote?: string;
106
+ /**
107
+ * A list of documented surfaces that are currently not safe to support directly.
108
+ * Preflight will emit warnings for these if the agent is targeted.
109
+ */
110
+ unsupportedSurfaces?: AgentSurfaceSupport[];
111
+ /**
112
+ * When true, instruction files deployed to this agent must include valid
113
+ * YAML frontmatter. Drives validateInstructionFileRequirements without
114
+ * hardcoded agent-ID checks.
115
+ */
116
+ instructionFrontmatterRequired?: boolean;
117
+ /**
118
+ * When true, preflight runs enterprise-policy detection for this agent.
119
+ */
120
+ enterprisePolicyDetection?: boolean;
62
121
  }
63
122
  export interface PlanWarning {
64
123
  kind: "confidence" | "collision" | "ambiguity";
@@ -80,6 +139,7 @@ export interface FileWriteDeployAction {
80
139
  source: string;
81
140
  target: string;
82
141
  confidence?: Confidence;
142
+ migratedFrom?: string[];
83
143
  }
84
144
  export interface ConfigPatchDeployAction {
85
145
  kind: "config-patch";
@@ -0,0 +1,2 @@
1
+ export declare function exists(p: string): Promise<boolean>;
2
+ export declare function makeTmpDir(prefix?: string): Promise<string>;
@@ -0,0 +1,17 @@
1
+ import { access, mkdir } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ export async function exists(p) {
5
+ try {
6
+ await access(p);
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ export async function makeTmpDir(prefix = "ie-test-dir") {
14
+ const dir = path.join(os.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
15
+ await mkdir(dir, { recursive: true });
16
+ return dir;
17
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Normalize path separators to forward slashes for cross-platform path assertions.
3
+ * Use this instead of hard-coding POSIX separators in expected strings.
4
+ */
5
+ export declare function normalizeSlashes(value: string): string;
6
+ /**
7
+ * Assert that a path ends with the given POSIX-style suffix, normalizing separators.
8
+ * Use instead of: assert.ok(normalizeSlashes(p).endsWith("some/suffix"))
9
+ */
10
+ export declare function assertPathEndsWith(actual: string, suffix: string, msg?: string): void;
@@ -0,0 +1,15 @@
1
+ import assert from "node:assert/strict";
2
+ /**
3
+ * Normalize path separators to forward slashes for cross-platform path assertions.
4
+ * Use this instead of hard-coding POSIX separators in expected strings.
5
+ */
6
+ export function normalizeSlashes(value) {
7
+ return value.replaceAll("\\", "/");
8
+ }
9
+ /**
10
+ * Assert that a path ends with the given POSIX-style suffix, normalizing separators.
11
+ * Use instead of: assert.ok(normalizeSlashes(p).endsWith("some/suffix"))
12
+ */
13
+ export function assertPathEndsWith(actual, suffix, msg) {
14
+ assert.ok(normalizeSlashes(actual).endsWith(suffix), msg ?? `Expected path "${actual}" to end with "${suffix}"`);
15
+ }
@@ -0,0 +1,7 @@
1
+ interface SkillDirScenarioOptions {
2
+ method: "symlink" | "copy";
3
+ assertManagedTarget(target: string, source: string, expectedSkillMd: string): Promise<void>;
4
+ }
5
+ export declare function registerSharedSkillDirDeployScenarios(options: SkillDirScenarioOptions): void;
6
+ export declare function registerSharedSkillDirRevertScenarios(options: SkillDirScenarioOptions): void;
7
+ export {};
@@ -0,0 +1,206 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { it } from "node:test";
5
+ import { executeDeploy, planDeploy } from "../../src/core/deploy.js";
6
+ import { lookupDeployment, registerDeployment, } from "../../src/core/ownership.js";
7
+ import { executeRevert, planRevert } from "../../src/core/revert.js";
8
+ import { exists, makeTmpDir } from "./fs.js";
9
+ import { createFailingRegistryPersistence, createSkillSource, testSkillManifest, } from "./skill-dir.js";
10
+ const FIRST_SKILL_MD = "---\nname: test\ndescription: First skill\n---\n# First";
11
+ const SECOND_SKILL_MD = "---\nname: test\ndescription: Second skill\n---\n# Second";
12
+ export function registerSharedSkillDirDeployScenarios(options) {
13
+ it("first deploy succeeds", async () => {
14
+ const sourceDir = await makeTmpDir("ie-skill-dir-source");
15
+ const home = await makeTmpDir("ie-skill-dir-home");
16
+ try {
17
+ const source = await createSkillSource(sourceDir, "skills/test-skill", FIRST_SKILL_MD);
18
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
19
+ const { succeeded, failed } = await executeDeploy(actions, false, false, home);
20
+ assert.equal(succeeded, 1);
21
+ assert.equal(failed.length, 0);
22
+ await options.assertManagedTarget(actions[0]?.target, source, FIRST_SKILL_MD);
23
+ const entry = await lookupDeployment(home, actions[0]?.target);
24
+ assert.ok(entry);
25
+ assert.equal(entry.kind, "skill-dir");
26
+ assert.equal(entry.method, options.method);
27
+ }
28
+ finally {
29
+ await rm(sourceDir, { recursive: true, force: true });
30
+ await rm(home, { recursive: true, force: true });
31
+ }
32
+ });
33
+ it("dry-run reports the planned skill-dir change only", async () => {
34
+ const sourceDir = await makeTmpDir("ie-skill-dir-source");
35
+ const home = await makeTmpDir("ie-skill-dir-home");
36
+ try {
37
+ await createSkillSource(sourceDir, "skills/test-skill", FIRST_SKILL_MD);
38
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
39
+ const { succeeded, failed, planned } = await executeDeploy(actions, true, false, home);
40
+ assert.equal(succeeded, 1);
41
+ assert.equal(failed.length, 0);
42
+ assert.equal(planned.length, 1);
43
+ assert.equal(planned[0]?.verb, options.method === "symlink" ? "create-symlink" : "copy-dir");
44
+ assert.ok(!(await exists(actions[0]?.target)));
45
+ }
46
+ finally {
47
+ await rm(sourceDir, { recursive: true, force: true });
48
+ await rm(home, { recursive: true, force: true });
49
+ }
50
+ });
51
+ it("refuses to overwrite an unmanaged target", async () => {
52
+ const sourceDir = await makeTmpDir("ie-skill-dir-source");
53
+ const home = await makeTmpDir("ie-skill-dir-home");
54
+ try {
55
+ await createSkillSource(sourceDir, "skills/test-skill", FIRST_SKILL_MD);
56
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
57
+ const target = actions[0]?.target;
58
+ await mkdir(target, { recursive: true });
59
+ await writeFile(path.join(target, "user.txt"), "user content");
60
+ const { succeeded, failed } = await executeDeploy(actions, false, false, home);
61
+ assert.equal(succeeded, 0);
62
+ assert.equal(failed.length, 1);
63
+ assert.match(failed[0]?.error, /not managed by inception-engine/);
64
+ assert.equal(await readFile(path.join(target, "user.txt"), "utf-8"), "user content");
65
+ }
66
+ finally {
67
+ await rm(sourceDir, { recursive: true, force: true });
68
+ await rm(home, { recursive: true, force: true });
69
+ }
70
+ });
71
+ it("refuses to overwrite a target with a mismatched registry entry", async () => {
72
+ const sourceDir = await makeTmpDir("ie-skill-dir-source");
73
+ const home = await makeTmpDir("ie-skill-dir-home");
74
+ try {
75
+ await createSkillSource(sourceDir, "skills/test-skill", FIRST_SKILL_MD);
76
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
77
+ const target = actions[0]?.target;
78
+ await mkdir(target, { recursive: true });
79
+ await writeFile(path.join(target, "SKILL.md"), "other");
80
+ await registerDeployment(home, target, {
81
+ kind: "skill-dir",
82
+ source: "/other/source",
83
+ skill: "other-skill",
84
+ agent: "codex",
85
+ method: options.method,
86
+ });
87
+ const { succeeded, failed } = await executeDeploy(actions, false, false, home);
88
+ assert.equal(succeeded, 0);
89
+ assert.equal(failed.length, 1);
90
+ assert.match(failed[0]?.error, /not managed by inception-engine/);
91
+ assert.equal(await readFile(path.join(target, "SKILL.md"), "utf-8"), "other");
92
+ }
93
+ finally {
94
+ await rm(sourceDir, { recursive: true, force: true });
95
+ await rm(home, { recursive: true, force: true });
96
+ }
97
+ });
98
+ it("managed redeploy succeeds", async () => {
99
+ const sourceDir = await makeTmpDir("ie-skill-dir-source");
100
+ const home = await makeTmpDir("ie-skill-dir-home");
101
+ try {
102
+ const source = await createSkillSource(sourceDir, "skills/test-skill", FIRST_SKILL_MD);
103
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
104
+ await executeDeploy(actions, false, false, home);
105
+ const { succeeded, failed } = await executeDeploy(actions, false, false, home);
106
+ assert.equal(succeeded, 1);
107
+ assert.equal(failed.length, 0);
108
+ await options.assertManagedTarget(actions[0]?.target, source, FIRST_SKILL_MD);
109
+ }
110
+ finally {
111
+ await rm(sourceDir, { recursive: true, force: true });
112
+ await rm(home, { recursive: true, force: true });
113
+ }
114
+ });
115
+ it("restores the prior managed target when registry persistence fails", async () => {
116
+ const sourceDir = await makeTmpDir("ie-skill-dir-source");
117
+ const home = await makeTmpDir("ie-skill-dir-home");
118
+ try {
119
+ const firstSource = await createSkillSource(sourceDir, "skills/test-skill", FIRST_SKILL_MD);
120
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
121
+ const firstAction = actions[0];
122
+ await executeDeploy([firstAction], false, false, home);
123
+ await writeFile(path.join(firstSource, "SKILL.md"), SECOND_SKILL_MD);
124
+ const failAction = {
125
+ ...firstAction,
126
+ };
127
+ const { succeeded, failed } = await executeDeploy([failAction], false, false, home, {
128
+ registry: createFailingRegistryPersistence(),
129
+ });
130
+ assert.equal(succeeded, 0);
131
+ assert.equal(failed.length, 1);
132
+ assert.match(failed[0]?.error, /simulated registry persistence failure/);
133
+ await options.assertManagedTarget(firstAction.target, firstSource, FIRST_SKILL_MD);
134
+ assert.ok(!(await exists(`${firstAction.target}.inception-backup`)));
135
+ }
136
+ finally {
137
+ await rm(sourceDir, { recursive: true, force: true });
138
+ await rm(home, { recursive: true, force: true });
139
+ }
140
+ });
141
+ }
142
+ export function registerSharedSkillDirRevertScenarios(options) {
143
+ it("reverts a managed skill-dir target", async () => {
144
+ const sourceDir = await makeTmpDir("ie-skill-dir-source");
145
+ const home = await makeTmpDir("ie-skill-dir-home");
146
+ try {
147
+ await createSkillSource(sourceDir, "skills/test-skill", FIRST_SKILL_MD);
148
+ const { actions } = await planDeploy(testSkillManifest, sourceDir, ["claude-code"], home);
149
+ await executeDeploy(actions, false, false, home);
150
+ const revertActions = planRevert(testSkillManifest, ["claude-code"], home);
151
+ const { succeeded, skipped, failed } = await executeRevert(revertActions, false, false, home);
152
+ assert.equal(succeeded, 1);
153
+ assert.equal(skipped, 0);
154
+ assert.equal(failed.length, 0);
155
+ assert.ok(!(await exists(actions[0]?.target)));
156
+ }
157
+ finally {
158
+ await rm(sourceDir, { recursive: true, force: true });
159
+ await rm(home, { recursive: true, force: true });
160
+ }
161
+ });
162
+ it("skips an unmanaged skill-dir target", async () => {
163
+ const home = await makeTmpDir("ie-skill-dir-home");
164
+ try {
165
+ const revertActions = planRevert(testSkillManifest, ["claude-code"], home);
166
+ const target = revertActions[0]?.target;
167
+ await mkdir(target, { recursive: true });
168
+ await writeFile(path.join(target, "SKILL.md"), FIRST_SKILL_MD);
169
+ const { succeeded, skipped, failed } = await executeRevert(revertActions, false, false, home);
170
+ assert.equal(succeeded, 0);
171
+ assert.equal(skipped, 1);
172
+ assert.equal(failed.length, 0);
173
+ assert.ok(await exists(target));
174
+ }
175
+ finally {
176
+ await rm(home, { recursive: true, force: true });
177
+ }
178
+ });
179
+ it("skips a target whose registry entry belongs to a different skill", async () => {
180
+ const sourceDir = await makeTmpDir("ie-skill-dir-source");
181
+ const home = await makeTmpDir("ie-skill-dir-home");
182
+ try {
183
+ const source = await createSkillSource(sourceDir, "skills/test-skill", FIRST_SKILL_MD);
184
+ const revertActions = planRevert(testSkillManifest, ["claude-code"], home);
185
+ const target = revertActions[0]?.target;
186
+ await mkdir(target, { recursive: true });
187
+ await writeFile(path.join(target, "SKILL.md"), FIRST_SKILL_MD);
188
+ await registerDeployment(home, target, {
189
+ kind: "skill-dir",
190
+ source,
191
+ skill: "different-skill",
192
+ agent: "codex",
193
+ method: options.method,
194
+ });
195
+ const { succeeded, skipped, failed } = await executeRevert(revertActions, false, false, home);
196
+ assert.equal(succeeded, 0);
197
+ assert.equal(skipped, 1);
198
+ assert.equal(failed.length, 0);
199
+ assert.ok(await exists(target));
200
+ }
201
+ finally {
202
+ await rm(sourceDir, { recursive: true, force: true });
203
+ await rm(home, { recursive: true, force: true });
204
+ }
205
+ });
206
+ }
@@ -0,0 +1,7 @@
1
+ import type { RegistryPersistence } from "../../src/core/ownership.ts";
2
+ import type { Manifest } from "../../src/types.ts";
3
+ export declare function createSkillSource(baseDir: string, skillPath?: string, skillBody?: string): Promise<string>;
4
+ export declare const testSkillManifest: Manifest;
5
+ export declare function createFailingRegistryPersistence(error?: Error): RegistryPersistence;
6
+ export declare function assertSymlinkTarget(target: string, source: string): Promise<void>;
7
+ export declare function assertCopyTarget(target: string, expectedSkillMd: string): Promise<void>;