@kuznai/inception-engine 0.25.0 → 1.0.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 +67 -3
- package/dist/src/core/adapters/agent-definitions.d.ts +2 -1
- package/dist/src/core/adapters/agent-definitions.js +13 -4
- package/dist/src/core/adapters/index.d.ts +2 -1
- package/dist/src/core/adapters/index.js +3 -3
- package/dist/src/core/adapters/rules.d.ts +2 -1
- package/dist/src/core/adapters/rules.js +12 -5
- package/dist/src/core/concurrency.d.ts +5 -0
- package/dist/src/core/concurrency.js +19 -0
- package/dist/src/core/deploy.js +157 -99
- package/dist/src/core/init.d.ts +1 -0
- package/dist/src/core/init.js +79 -28
- package/dist/src/core/preflight.js +6 -4
- package/dist/src/core/revert.js +12 -1
- package/dist/src/core/validation.d.ts +31 -3
- package/dist/src/core/validation.js +64 -10
- package/dist/src/schemas/manifest.js +1 -1
- package/dist/src/types.d.ts +1 -1
- package/dist/test/os/windows/deploy.test.js +12 -11
- package/dist/test/unit/adapters.test.js +84 -83
- package/dist/test/unit/cli.test.js +57 -1
- package/dist/test/unit/deploy.test.js +253 -0
- package/dist/test/unit/gemini-north-star.test.js +9 -8
- package/dist/test/unit/init.test.d.ts +1 -0
- package/dist/test/unit/init.test.js +14 -0
- package/dist/test/unit/manifest.test.js +25 -0
- package/dist/test/unit/preflight.test.js +29 -0
- package/dist/test/unit/revert.test.js +12 -11
- package/dist/test/unit/validation.test.d.ts +1 -0
- package/dist/test/unit/validation.test.js +102 -0
- package/package.json +4 -4
package/dist/src/core/revert.js
CHANGED
|
@@ -186,7 +186,18 @@ export async function executeRevert(actions, dryRun, verbose, home, deps = {}, s
|
|
|
186
186
|
registry: runRegistry,
|
|
187
187
|
};
|
|
188
188
|
if (!dryRun) {
|
|
189
|
-
|
|
189
|
+
try {
|
|
190
|
+
await runRegistry.preflight(home);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
194
|
+
return {
|
|
195
|
+
succeeded: 0,
|
|
196
|
+
skipped: 0,
|
|
197
|
+
failed: actions.map((action) => ({ action, error: message })),
|
|
198
|
+
planned,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
190
201
|
}
|
|
191
202
|
for (const action of actions) {
|
|
192
203
|
if (signal?.aborted)
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import type { AgentId } from "../schemas/manifest.ts";
|
|
2
2
|
export declare function sourceAccessError(err: unknown, sourcePath: string): string;
|
|
3
3
|
export declare function validateSourcePath(source: string, skillPath: string, resolvedSourceDir: string, realRoot: string): Promise<void>;
|
|
4
|
+
export type SourcePathValidator = (source: string, manifestPath: string, resolvedSourceDir: string) => Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Returns a memoizing source-path validator bound to a single `realRoot`.
|
|
7
|
+
*
|
|
8
|
+
* The cheap out-of-root string gate still runs on every call so per-entry
|
|
9
|
+
* `manifestPath` error messages remain accurate. The realpath resolution and
|
|
10
|
+
* the identity-based ancestor walk are memoized per resolved `source`, which
|
|
11
|
+
* eliminates repeated syscalls when the same source is referenced across
|
|
12
|
+
* multiple manifest sections or fans out to multiple agents within one
|
|
13
|
+
* `planDeploy` invocation.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createSourcePathValidator(realRoot: string): SourcePathValidator;
|
|
4
16
|
export declare function validateSourceFile(sourcePath: string, manifestPath: string): Promise<void>;
|
|
5
17
|
export declare function validateMcpServerConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
6
18
|
export declare function validatePermissionsConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
@@ -11,7 +23,23 @@ export declare function validateSkillDefinitionFile(sourcePath: string, manifest
|
|
|
11
23
|
body: string;
|
|
12
24
|
}>;
|
|
13
25
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
26
|
+
* Returns true when the agent requires its instruction-file source to expose
|
|
27
|
+
* structural YAML frontmatter. Used by adapters to decide whether to parse
|
|
28
|
+
* the source document before per-agent validation.
|
|
29
|
+
*/
|
|
30
|
+
export declare function instructionRequiresFrontmatter(agentId: AgentId): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Reads and parses an instruction file's YAML frontmatter once. Callers that
|
|
33
|
+
* fan out to multiple target agents should invoke this at most once per
|
|
34
|
+
* source file and reuse the returned attributes for each
|
|
35
|
+
* `validateInstructionAgentRequirements` call.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseInstructionDocument(sourcePath: string, manifestPath: string): Promise<{
|
|
38
|
+
attributes: Record<string, unknown>;
|
|
39
|
+
body: string;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Runs the per-agent structural checks against an already-parsed instruction
|
|
43
|
+
* document. Agents without frontmatter requirements are a no-op.
|
|
16
44
|
*/
|
|
17
|
-
export declare function
|
|
45
|
+
export declare function validateInstructionAgentRequirements(attributes: Record<string, unknown>, manifestPath: string, agentId: AgentId): void;
|
|
@@ -54,6 +54,47 @@ export async function validateSourcePath(source, skillPath, resolvedSourceDir, r
|
|
|
54
54
|
// Source doesn't exist yet — will be caught during execute
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Returns a memoizing source-path validator bound to a single `realRoot`.
|
|
59
|
+
*
|
|
60
|
+
* The cheap out-of-root string gate still runs on every call so per-entry
|
|
61
|
+
* `manifestPath` error messages remain accurate. The realpath resolution and
|
|
62
|
+
* the identity-based ancestor walk are memoized per resolved `source`, which
|
|
63
|
+
* eliminates repeated syscalls when the same source is referenced across
|
|
64
|
+
* multiple manifest sections or fans out to multiple agents within one
|
|
65
|
+
* `planDeploy` invocation.
|
|
66
|
+
*/
|
|
67
|
+
export function createSourcePathValidator(realRoot) {
|
|
68
|
+
const cache = new Map();
|
|
69
|
+
const resolveOutcome = async (source) => {
|
|
70
|
+
try {
|
|
71
|
+
const realSource = await realpath(source);
|
|
72
|
+
if (isSameOrDescendantPath(realSource, realRoot) ||
|
|
73
|
+
(await isWithinRootByIdentity(realSource, realRoot))) {
|
|
74
|
+
return { kind: "ok" };
|
|
75
|
+
}
|
|
76
|
+
return { kind: "escape", realSource };
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Source doesn't exist yet — will be caught during execute
|
|
80
|
+
return { kind: "ok" };
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
return async (source, manifestPath, resolvedSourceDir) => {
|
|
84
|
+
if (!source.startsWith(resolvedSourceDir + path.sep)) {
|
|
85
|
+
throw new UserError("DEPLOY_FAILED", `Skill path "${manifestPath}" resolves outside the repository root: ${source}`);
|
|
86
|
+
}
|
|
87
|
+
let pending = cache.get(source);
|
|
88
|
+
if (!pending) {
|
|
89
|
+
pending = resolveOutcome(source);
|
|
90
|
+
cache.set(source, pending);
|
|
91
|
+
}
|
|
92
|
+
const outcome = await pending;
|
|
93
|
+
if (outcome.kind === "escape") {
|
|
94
|
+
throw new UserError("DEPLOY_FAILED", `Skill path "${manifestPath}" resolves outside the repository root via symlink: ${source} -> ${outcome.realSource}`);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
57
98
|
export async function validateSourceFile(sourcePath, manifestPath) {
|
|
58
99
|
let stat;
|
|
59
100
|
try {
|
|
@@ -308,24 +349,37 @@ function validateAntigravityRequirements(attributes, manifestPath) {
|
|
|
308
349
|
}
|
|
309
350
|
}
|
|
310
351
|
/**
|
|
311
|
-
*
|
|
312
|
-
*
|
|
352
|
+
* Returns true when the agent requires its instruction-file source to expose
|
|
353
|
+
* structural YAML frontmatter. Used by adapters to decide whether to parse
|
|
354
|
+
* the source document before per-agent validation.
|
|
313
355
|
*/
|
|
314
|
-
export
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
356
|
+
export function instructionRequiresFrontmatter(agentId) {
|
|
357
|
+
return AGENT_REGISTRY_BY_ID[agentId]?.instructionFrontmatterRequired === true;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Reads and parses an instruction file's YAML frontmatter once. Callers that
|
|
361
|
+
* fan out to multiple target agents should invoke this at most once per
|
|
362
|
+
* source file and reuse the returned attributes for each
|
|
363
|
+
* `validateInstructionAgentRequirements` call.
|
|
364
|
+
*/
|
|
365
|
+
export async function parseInstructionDocument(sourcePath, manifestPath) {
|
|
319
366
|
try {
|
|
320
|
-
|
|
321
|
-
attributes = result.attributes;
|
|
367
|
+
return await validateSkillDefinitionFile(sourcePath, manifestPath);
|
|
322
368
|
}
|
|
323
369
|
catch (err) {
|
|
324
370
|
if (err instanceof UserError) {
|
|
325
|
-
throw new UserError("DEPLOY_FAILED", `Instruction file "${manifestPath}"
|
|
371
|
+
throw new UserError("DEPLOY_FAILED", `Instruction file "${manifestPath}" failed structural validation: ${err.message}`, { cause: err });
|
|
326
372
|
}
|
|
327
373
|
throw err;
|
|
328
374
|
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Runs the per-agent structural checks against an already-parsed instruction
|
|
378
|
+
* document. Agents without frontmatter requirements are a no-op.
|
|
379
|
+
*/
|
|
380
|
+
export function validateInstructionAgentRequirements(attributes, manifestPath, agentId) {
|
|
381
|
+
if (!instructionRequiresFrontmatter(agentId))
|
|
382
|
+
return;
|
|
329
383
|
if (agentId === "github-copilot") {
|
|
330
384
|
validateGithubCopilotRequirements(attributes, manifestPath);
|
|
331
385
|
}
|
|
@@ -55,7 +55,7 @@ const targetTemplateField = z
|
|
|
55
55
|
.string({ message: "target must be a non-empty string" })
|
|
56
56
|
.min(1, { message: "target must be a non-empty string" })
|
|
57
57
|
.refine((t) => TARGET_TEMPLATE_RE.test(t), {
|
|
58
|
-
message: "target must start with a known placeholder: {home}, {appdata}, {xdg_config}, or {
|
|
58
|
+
message: "target must start with a known placeholder: {home}, {appdata}, {xdg_config}, {repo}, or {workspace}",
|
|
59
59
|
})
|
|
60
60
|
.refine((t) => !t.split(/[\\/]+/).includes(".."), {
|
|
61
61
|
message: "target must not escape its placeholder root",
|
package/dist/src/types.d.ts
CHANGED
|
@@ -120,7 +120,7 @@ export interface AgentConfig {
|
|
|
120
120
|
unsupportedSurfaces?: AgentSurfaceSupport[];
|
|
121
121
|
/**
|
|
122
122
|
* When true, instruction files deployed to this agent must include valid
|
|
123
|
-
* YAML frontmatter. Drives
|
|
123
|
+
* YAML frontmatter. Drives instructionRequiresFrontmatter without
|
|
124
124
|
* hardcoded agent-ID checks.
|
|
125
125
|
*/
|
|
126
126
|
instructionFrontmatterRequired?: boolean;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
2
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { describe, it } from "node:test";
|
|
5
5
|
import { executeDeploy, planDeploy } from "../../../src/core/deploy.js";
|
|
6
|
-
import { lookupDeployment, registerDeployment, } from "../../../src/core/ownership.js";
|
|
6
|
+
import { defaultRegistryPersistence, lookupDeployment, registerDeployment, } from "../../../src/core/ownership.js";
|
|
7
7
|
import { UserError } from "../../../src/errors.js";
|
|
8
8
|
import { exists, makeTmpDir } from "../../helpers/fs.js";
|
|
9
9
|
import { createSkillSource, testSkillManifest, } from "../../helpers/skill-dir.js";
|
|
@@ -215,9 +215,16 @@ describe("atomic redeploy behavior (Windows)", {
|
|
|
215
215
|
const backupPath = `${target}.inception-backup`;
|
|
216
216
|
const { succeeded: firstSucceeded } = await executeDeploy(actions, false, false, home);
|
|
217
217
|
assert.equal(firstSucceeded, 1);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
// Simulate an unwritable registry via a failing RegistryPersistence
|
|
219
|
+
// instead of relying on chmod, which is not enforced for admin processes
|
|
220
|
+
// on Windows (e.g. GitHub Actions windows-latest runners).
|
|
221
|
+
const failingRegistry = {
|
|
222
|
+
load: (h) => defaultRegistryPersistence.load(h),
|
|
223
|
+
save: async () => {
|
|
224
|
+
throw Object.assign(new Error("EACCES: permission denied, open 'registry.json'"), { code: "EACCES" });
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
const { succeeded, failed } = await executeDeploy(actions, false, false, home, { registry: failingRegistry });
|
|
221
228
|
assert.equal(succeeded, 0);
|
|
222
229
|
assert.equal(failed.length, 1);
|
|
223
230
|
assert.ok(!(await exists(backupPath)));
|
|
@@ -225,12 +232,6 @@ describe("atomic redeploy behavior (Windows)", {
|
|
|
225
232
|
assert.ok(await exists(path.join(target, "SKILL.md")));
|
|
226
233
|
}
|
|
227
234
|
finally {
|
|
228
|
-
try {
|
|
229
|
-
await chmod(path.join(home, ".inception-engine", "registry.json"), 0o666);
|
|
230
|
-
}
|
|
231
|
-
catch {
|
|
232
|
-
// best effort
|
|
233
|
-
}
|
|
234
235
|
await rm(sourceDir, { recursive: true, force: true });
|
|
235
236
|
await rm(home, { recursive: true, force: true });
|
|
236
237
|
}
|