@kuznai/inception-engine 0.8.0 → 0.10.1

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,18 +1,10 @@
1
1
  import { lstat, readFile, rm, unlink, writeFile } from "node:fs/promises";
2
- import path from "node:path";
3
2
  import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
4
3
  import { logger } from "../logger.js";
5
- import { lookupDeployment, unregisterDeployment } from "./ownership.js";
4
+ import { compileAgentRuleReverts, compileMcpServerReverts, } from "./adapters/index.js";
5
+ import { lookupDeployment, unregisterDeployment, } from "./ownership.js";
6
6
  import { resolveAgentSkillPath } from "./resolve.js";
7
- function resolveTargetTemplate(template, home) {
8
- const appdata = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
9
- const xdgRaw = process.env.XDG_CONFIG_HOME;
10
- const xdgConfig = xdgRaw && path.isAbsolute(xdgRaw) ? xdgRaw : path.join(home, ".config");
11
- return template
12
- .replace("{home}", home)
13
- .replace("{appdata}", appdata)
14
- .replace("{xdg_config}", xdgConfig);
15
- }
7
+ import { resolveTargetTemplate } from "./runtime-paths.js";
16
8
  function buildSkillDirReverts(manifest, home, agentFilter) {
17
9
  const actions = [];
18
10
  for (const skill of manifest.skills) {
@@ -75,6 +67,8 @@ export function planRevert(manifest, detectedAgents, home) {
75
67
  ...buildSkillDirReverts(manifest, home, detectedAgents),
76
68
  ...buildFileWriteReverts(manifest, home, detectedAgents),
77
69
  ...buildConfigPatchReverts(manifest, home, detectedAgents),
70
+ ...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, detectedAgents, home)),
71
+ ...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, detectedAgents, home)),
78
72
  ];
79
73
  }
80
74
  export function planRevertAll(manifest, home) {
@@ -82,6 +76,8 @@ export function planRevertAll(manifest, home) {
82
76
  ...buildSkillDirReverts(manifest, home, null),
83
77
  ...buildFileWriteReverts(manifest, home, null),
84
78
  ...buildConfigPatchReverts(manifest, home, null),
79
+ ...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, null, home)),
80
+ ...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, null, home)),
85
81
  ];
86
82
  }
87
83
  function recordOutcome(result, action, counts, failed) {
@@ -109,12 +105,18 @@ async function readJsonConfig(filePath) {
109
105
  }
110
106
  return parsed;
111
107
  }
108
+ function isPlainObject(v) {
109
+ return typeof v === "object" && v !== null && !Array.isArray(v);
110
+ }
112
111
  function applyUndoPatch(current, undoPatch) {
113
112
  const restored = { ...current };
114
113
  for (const [key, originalValue] of Object.entries(undoPatch)) {
115
114
  if (originalValue === null) {
116
115
  delete restored[key];
117
116
  }
117
+ else if (isPlainObject(originalValue) && isPlainObject(restored[key])) {
118
+ restored[key] = applyUndoPatch(restored[key], originalValue);
119
+ }
118
120
  else {
119
121
  restored[key] = originalValue;
120
122
  }
@@ -128,7 +130,7 @@ function lstatOutcome(err) {
128
130
  const msg = err instanceof Error ? err.message : String(err);
129
131
  return { outcome: "fail", error: msg };
130
132
  }
131
- export async function executeRevert(actions, dryRun, verbose, home) {
133
+ export async function executeRevert(actions, dryRun, verbose, home, deps = {}) {
132
134
  let succeeded = 0;
133
135
  let skipped = 0;
134
136
  const failed = [];
@@ -138,13 +140,13 @@ export async function executeRevert(actions, dryRun, verbose, home) {
138
140
  let result;
139
141
  switch (action.kind) {
140
142
  case "skill-dir":
141
- result = await executeRevertAction(action, dryRun, verbose, home, planned);
143
+ result = await executeRevertAction(action, dryRun, verbose, home, planned, deps);
142
144
  break;
143
145
  case "file-write":
144
- result = await revertFileWrite(action, dryRun, verbose, home, planned);
146
+ result = await revertFileWrite(action, dryRun, verbose, home, planned, deps);
145
147
  break;
146
148
  case "config-patch":
147
- result = await revertConfigPatch(action, dryRun, verbose, home, planned);
149
+ result = await revertConfigPatch(action, dryRun, verbose, home, planned, deps);
148
150
  break;
149
151
  default:
150
152
  throw new Error(`Unhandled revert action kind: ${action}`);
@@ -155,7 +157,7 @@ export async function executeRevert(actions, dryRun, verbose, home) {
155
157
  skipped = counts.skipped;
156
158
  return { succeeded, skipped, failed, planned };
157
159
  }
158
- async function executeRevertAction(action, dryRun, verbose, home, planned) {
160
+ async function executeRevertAction(action, dryRun, verbose, home, planned, deps) {
159
161
  const label = `${action.skill} -> ${action.agent}`;
160
162
  try {
161
163
  await lstat(action.target);
@@ -169,7 +171,7 @@ async function executeRevertAction(action, dryRun, verbose, home, planned) {
169
171
  logger.fail(label, result.error);
170
172
  return result;
171
173
  }
172
- const entry = await lookupDeployment(home, action.target);
174
+ const entry = await lookupDeployment(home, action.target, deps.registry);
173
175
  if (!entry || entry.skill !== action.skill || entry.agent !== action.agent) {
174
176
  logger.warn(label, `skipping: ${action.target} is not in the deployment registry — not managed by inception-engine`);
175
177
  return { outcome: "skip" };
@@ -194,7 +196,7 @@ async function executeRevertAction(action, dryRun, verbose, home, planned) {
194
196
  else {
195
197
  await rm(action.target, { recursive: true });
196
198
  }
197
- await unregisterDeployment(home, action.target);
199
+ await unregisterDeployment(home, action.target, deps.registry);
198
200
  logger.ok(label);
199
201
  if (verbose) {
200
202
  logger.detail(`removed: ${action.target}`);
@@ -212,7 +214,7 @@ async function executeRevertAction(action, dryRun, verbose, home, planned) {
212
214
  return { outcome: "fail", error: msg };
213
215
  }
214
216
  }
215
- async function revertFileWrite(action, dryRun, verbose, home, planned) {
217
+ async function revertFileWrite(action, dryRun, verbose, home, planned, deps) {
216
218
  const label = `${action.skill} -> ${action.agent}`;
217
219
  try {
218
220
  await lstat(action.target);
@@ -226,7 +228,7 @@ async function revertFileWrite(action, dryRun, verbose, home, planned) {
226
228
  logger.fail(label, result.error);
227
229
  return result;
228
230
  }
229
- const entry = await lookupDeployment(home, action.target);
231
+ const entry = await lookupDeployment(home, action.target, deps.registry);
230
232
  if (!entry || entry.skill !== action.skill || entry.agent !== action.agent) {
231
233
  logger.warn(label, `skipping: ${action.target} is not in the deployment registry — not managed by inception-engine`);
232
234
  return { outcome: "skip" };
@@ -246,7 +248,7 @@ async function revertFileWrite(action, dryRun, verbose, home, planned) {
246
248
  // removal window; handle the case where the file has since disappeared.
247
249
  await lstat(action.target);
248
250
  await unlink(action.target);
249
- await unregisterDeployment(home, action.target);
251
+ await unregisterDeployment(home, action.target, deps.registry);
250
252
  logger.ok(label);
251
253
  if (verbose) {
252
254
  logger.detail(`removed: ${action.target}`);
@@ -263,7 +265,7 @@ async function revertFileWrite(action, dryRun, verbose, home, planned) {
263
265
  return { outcome: "fail", error: msg };
264
266
  }
265
267
  }
266
- async function revertConfigPatch(action, dryRun, verbose, home, planned) {
268
+ async function revertConfigPatch(action, dryRun, verbose, home, planned, deps) {
267
269
  const label = `${action.skill} -> ${action.agent}`;
268
270
  try {
269
271
  await lstat(action.target);
@@ -277,7 +279,7 @@ async function revertConfigPatch(action, dryRun, verbose, home, planned) {
277
279
  logger.fail(label, result.error);
278
280
  return result;
279
281
  }
280
- const entry = await lookupDeployment(home, action.target);
282
+ const entry = await lookupDeployment(home, action.target, deps.registry);
281
283
  if (!entry ||
282
284
  entry.kind !== "config-patch" ||
283
285
  entry.skill !== action.skill ||
@@ -301,7 +303,7 @@ async function revertConfigPatch(action, dryRun, verbose, home, planned) {
301
303
  const current = await readJsonConfig(action.target);
302
304
  const restored = applyUndoPatch(current, configPatchEntry.undoPatch);
303
305
  await writeFile(action.target, `${JSON.stringify(restored, null, 2)}\n`, "utf-8");
304
- await unregisterDeployment(home, action.target);
306
+ await unregisterDeployment(home, action.target, deps.registry);
305
307
  logger.ok(label);
306
308
  if (verbose) {
307
309
  logger.detail(`unapplied patch from: ${action.target}`);
@@ -0,0 +1,8 @@
1
+ import path from "node:path";
2
+ export interface RuntimePaths {
3
+ appdata: string;
4
+ xdgConfig: string;
5
+ }
6
+ export declare function getPathApi(root: string): typeof path.posix | typeof path.win32;
7
+ export declare function resolveRuntimePaths(home: string): RuntimePaths;
8
+ export declare function resolveTargetTemplate(template: string, home: string): string;
@@ -0,0 +1,57 @@
1
+ import path from "node:path";
2
+ const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config)\}(?<suffix>(?:[\\/].*)?)$/;
3
+ export function getPathApi(root) {
4
+ if (root.includes("\\") || /^[a-zA-Z]:/.test(root)) {
5
+ return path.win32;
6
+ }
7
+ if (root.startsWith("/")) {
8
+ return path.posix;
9
+ }
10
+ return process.platform === "win32" ? path.win32 : path.posix;
11
+ }
12
+ function normalizePathForComparison(candidate, pathApi) {
13
+ const normalized = pathApi.normalize(candidate);
14
+ return pathApi === path.win32 ? normalized.toLowerCase() : normalized;
15
+ }
16
+ function isSameOrDescendantPath(candidate, root) {
17
+ const pathApi = getPathApi(root);
18
+ const normalizedCandidate = normalizePathForComparison(candidate, pathApi);
19
+ const normalizedRoot = normalizePathForComparison(root, pathApi);
20
+ return (normalizedCandidate === normalizedRoot ||
21
+ normalizedCandidate.startsWith(normalizedRoot + pathApi.sep));
22
+ }
23
+ export function resolveRuntimePaths(home) {
24
+ const appdataRaw = process.env.APPDATA;
25
+ const homePathApi = getPathApi(home);
26
+ const appdata = appdataRaw && getPathApi(appdataRaw).isAbsolute(appdataRaw)
27
+ ? appdataRaw
28
+ : homePathApi.join(home, "AppData", "Roaming");
29
+ const xdgRaw = process.env.XDG_CONFIG_HOME;
30
+ const xdgConfig = xdgRaw && getPathApi(xdgRaw).isAbsolute(xdgRaw)
31
+ ? xdgRaw
32
+ : homePathApi.join(home, ".config");
33
+ return { appdata, xdgConfig };
34
+ }
35
+ export function resolveTargetTemplate(template, home) {
36
+ const { appdata, xdgConfig } = resolveRuntimePaths(home);
37
+ const match = TARGET_TEMPLATE_RE.exec(template);
38
+ if (!match) {
39
+ throw new Error(`Invalid target template: ${template}`);
40
+ }
41
+ const baseByRoot = {
42
+ home,
43
+ appdata,
44
+ xdg_config: xdgConfig,
45
+ };
46
+ const root = match[1];
47
+ const suffix = match.groups?.suffix ?? "";
48
+ const segments = suffix.split(/[\\/]+/).filter(Boolean);
49
+ const pathApi = getPathApi(baseByRoot[root]);
50
+ const resolved = segments.length === 0
51
+ ? baseByRoot[root]
52
+ : pathApi.join(baseByRoot[root], ...segments);
53
+ if (!isSameOrDescendantPath(resolved, baseByRoot[root])) {
54
+ throw new Error(`Target template resolves outside its placeholder root: ${template}`);
55
+ }
56
+ return suffix === "" ? baseByRoot[root] : `${baseByRoot[root]}${suffix}`;
57
+ }
@@ -0,0 +1,3 @@
1
+ export declare function sourceAccessError(err: unknown, sourcePath: string): string;
2
+ export declare function validateSourcePath(source: string, skillPath: string, resolvedSourceDir: string, realRoot: string): Promise<void>;
3
+ export declare function validateSourceFile(sourcePath: string, manifestPath: string): Promise<void>;
@@ -0,0 +1,66 @@
1
+ import { lstat, realpath, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { UserError } from "../errors.js";
4
+ export function sourceAccessError(err, sourcePath) {
5
+ const code = err.code;
6
+ if (code === "ENOENT")
7
+ return `Source not found: ${sourcePath}`;
8
+ if (code === "EACCES" || code === "EPERM")
9
+ return `Permission denied accessing source: ${sourcePath}`;
10
+ const detail = err instanceof Error ? err.message : String(err);
11
+ return `Failed to access source ${sourcePath}: ${detail}`;
12
+ }
13
+ function normalizePathForComparison(candidate) {
14
+ const normalized = path.normalize(candidate);
15
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
16
+ }
17
+ function isSameOrDescendantPath(candidate, root) {
18
+ const normalizedCandidate = normalizePathForComparison(candidate);
19
+ const normalizedRoot = normalizePathForComparison(root);
20
+ return (normalizedCandidate === normalizedRoot ||
21
+ normalizedCandidate.startsWith(normalizedRoot + path.sep));
22
+ }
23
+ async function isSameFileSystemLocation(a, b) {
24
+ const [aStat, bStat] = await Promise.all([stat(a), stat(b)]);
25
+ return aStat.dev === bStat.dev && aStat.ino === bStat.ino;
26
+ }
27
+ async function isWithinRootByIdentity(candidate, root) {
28
+ let current = candidate;
29
+ while (true) {
30
+ if (await isSameFileSystemLocation(current, root))
31
+ return true;
32
+ const parent = path.dirname(current);
33
+ if (parent === current)
34
+ return false;
35
+ current = parent;
36
+ }
37
+ }
38
+ export async function validateSourcePath(source, skillPath, resolvedSourceDir, realRoot) {
39
+ if (!source.startsWith(resolvedSourceDir + path.sep)) {
40
+ throw new UserError("DEPLOY_FAILED", `Skill path "${skillPath}" resolves outside the repository root: ${source}`);
41
+ }
42
+ try {
43
+ const realSource = await realpath(source);
44
+ if (!(isSameOrDescendantPath(realSource, realRoot) ||
45
+ (await isWithinRootByIdentity(realSource, realRoot)))) {
46
+ throw new UserError("DEPLOY_FAILED", `Skill path "${skillPath}" resolves outside the repository root via symlink: ${source} -> ${realSource}`);
47
+ }
48
+ }
49
+ catch (err) {
50
+ if (err instanceof UserError)
51
+ throw err;
52
+ // Source doesn't exist yet — will be caught during execute
53
+ }
54
+ }
55
+ export async function validateSourceFile(sourcePath, manifestPath) {
56
+ let stat;
57
+ try {
58
+ stat = await lstat(sourcePath);
59
+ }
60
+ catch (err) {
61
+ throw new UserError("DEPLOY_FAILED", sourceAccessError(err, manifestPath));
62
+ }
63
+ if (!stat.isFile()) {
64
+ throw new UserError("DEPLOY_FAILED", `Source is not a file: ${manifestPath}`);
65
+ }
66
+ }
@@ -50,10 +50,28 @@ export declare const ConfigEntrySchema: z.ZodObject<{
50
50
  }, z.core.$strip>;
51
51
  export declare const McpServerEntrySchema: z.ZodObject<{
52
52
  name: z.ZodString;
53
- }, z.core.$loose>;
53
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
54
+ "claude-code": "claude-code";
55
+ codex: "codex";
56
+ "gemini-cli": "gemini-cli";
57
+ antigravity: "antigravity";
58
+ opencode: "opencode";
59
+ "github-copilot": "github-copilot";
60
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
61
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
62
+ }, z.core.$strip>;
54
63
  export declare const AgentRuleEntrySchema: z.ZodObject<{
55
64
  name: z.ZodString;
56
- }, z.core.$loose>;
65
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
66
+ "claude-code": "claude-code";
67
+ codex: "codex";
68
+ "gemini-cli": "gemini-cli";
69
+ antigravity: "antigravity";
70
+ opencode: "opencode";
71
+ "github-copilot": "github-copilot";
72
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
73
+ path: z.ZodString;
74
+ }, z.core.$strip>;
57
75
  export declare const ManifestSchema: z.ZodObject<{
58
76
  skills: z.ZodArray<z.ZodObject<{
59
77
  name: z.ZodString;
@@ -95,10 +113,28 @@ export declare const ManifestSchema: z.ZodObject<{
95
113
  }, z.core.$strip>>>;
96
114
  mcpServers: z.ZodDefault<z.ZodArray<z.ZodObject<{
97
115
  name: z.ZodString;
98
- }, z.core.$loose>>>;
116
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
117
+ "claude-code": "claude-code";
118
+ codex: "codex";
119
+ "gemini-cli": "gemini-cli";
120
+ antigravity: "antigravity";
121
+ opencode: "opencode";
122
+ "github-copilot": "github-copilot";
123
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
124
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
125
+ }, z.core.$strip>>>;
99
126
  agentRules: z.ZodDefault<z.ZodArray<z.ZodObject<{
100
127
  name: z.ZodString;
101
- }, z.core.$loose>>>;
128
+ agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
129
+ "claude-code": "claude-code";
130
+ codex: "codex";
131
+ "gemini-cli": "gemini-cli";
132
+ antigravity: "antigravity";
133
+ opencode: "opencode";
134
+ "github-copilot": "github-copilot";
135
+ }>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
136
+ path: z.ZodString;
137
+ }, z.core.$strip>>>;
102
138
  }, z.core.$strip>;
103
139
  export type SkillEntry = z.infer<typeof SkillEntrySchema>;
104
140
  export type FileEntry = z.infer<typeof FileEntrySchema>;
@@ -10,9 +10,10 @@ const AGENT_IDS = [
10
10
  ];
11
11
  export { AGENT_IDS };
12
12
  const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
13
- // Target templates must start with a known placeholder to prevent raw absolute
14
- // paths or directory traversal. e.g. "{home}/.claude/settings.json" is valid.
15
- const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config)\}/;
13
+ // Target templates must be rooted at a known placeholder and may only add
14
+ // descendant path segments beneath that root. e.g. "{home}/.claude/settings.json"
15
+ // is valid, while "{home}/../.ssh/config" is rejected.
16
+ const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config)\}(?:[\\/].*)?$/;
16
17
  // Standalone schema used for type derivation and single-ID validation (e.g. index.ts).
17
18
  export const AgentIdSchema = z.enum(AGENT_IDS);
18
19
  // Used inside SkillEntrySchema.agents so that enum failures embed the received
@@ -53,6 +54,9 @@ const targetTemplateField = z
53
54
  .min(1, { message: "target must be a non-empty string" })
54
55
  .refine((t) => TARGET_TEMPLATE_RE.test(t), {
55
56
  message: "target must start with a known placeholder: {home}, {appdata}, or {xdg_config}",
57
+ })
58
+ .refine((t) => !t.split(/[\\/]+/).includes(".."), {
59
+ message: "target must not escape its placeholder root",
56
60
  });
57
61
  export const SkillEntrySchema = z.object({
58
62
  name: nameField,
@@ -71,20 +75,19 @@ export const ConfigEntrySchema = z.object({
71
75
  patch: z.record(z.string(), z.unknown()),
72
76
  agents: agentsField,
73
77
  });
74
- export const McpServerEntrySchema = z
75
- .object({
76
- name: z
77
- .string({ message: "mcpServers entry name must be a non-empty string" })
78
- .min(1, { message: "mcpServers entry name must be a non-empty string" }),
79
- })
80
- .passthrough();
81
- export const AgentRuleEntrySchema = z
82
- .object({
83
- name: z
84
- .string({ message: "agentRules entry name must be a non-empty string" })
85
- .min(1, { message: "agentRules entry name must be a non-empty string" }),
86
- })
87
- .passthrough();
78
+ export const McpServerEntrySchema = z.object({
79
+ name: nameField,
80
+ agents: agentsField,
81
+ // Raw server descriptor passed verbatim to each agent adapter.
82
+ // Adapters are responsible for validating the shape they need.
83
+ config: z.record(z.string(), z.unknown()),
84
+ });
85
+ export const AgentRuleEntrySchema = z.object({
86
+ name: nameField,
87
+ agents: agentsField,
88
+ // Relative path to the rules/instruction file within the source bundle.
89
+ path: sourcePathField,
90
+ });
88
91
  export const ManifestSchema = z.object({
89
92
  skills: z.array(SkillEntrySchema).superRefine((skills, ctx) => {
90
93
  const seen = new Set();
package/dist/types.d.ts CHANGED
@@ -9,6 +9,8 @@ export interface AgentProvenance {
9
9
  skills: Confidence;
10
10
  detectPaths: Confidence;
11
11
  detectBinary: Confidence;
12
+ mcpConfig?: Confidence;
13
+ agentRules?: Confidence;
12
14
  }
13
15
  export interface AgentConfig {
14
16
  id: AgentId;
@@ -17,6 +19,8 @@ export interface AgentConfig {
17
19
  detectPaths: AgentPaths;
18
20
  detectBinary: string | null;
19
21
  provenance: AgentProvenance;
22
+ mcpConfigPath?: AgentPaths;
23
+ agentRulesPath?: AgentPaths;
20
24
  policyNote?: string;
21
25
  }
22
26
  export interface PlanWarning {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuznai/inception-engine",
3
- "version": "0.8.0",
3
+ "version": "0.10.1",
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",
@@ -38,11 +38,13 @@
38
38
  "typecheck": "tsc --noEmit",
39
39
  "fmt": "biome format --write .",
40
40
  "lint": "biome lint . --max-diagnostics none",
41
+ "lint:fix": "biome lint . --max-diagnostics none --write --unsafe",
41
42
  "predev": "node scripts/assert-direct-ts-node.mjs",
42
43
  "dev": "node src/index.ts",
43
44
  "pretest": "node scripts/assert-direct-ts-node.mjs",
44
- "test": "node --test test/*.test.ts",
45
- "prepublishOnly": "npm run typecheck && npm run lint && npm run build"
45
+ "test": "node --test --test-isolation=none test/**/*.test.ts",
46
+ "test:windows": "node --test --test-isolation=none test/unit/*.test.ts test/os/cross-platform/*.test.ts test/os/windows/*.test.ts",
47
+ "test:posix": "node --test --test-isolation=none test/unit/*.test.ts test/os/cross-platform/*.test.ts test/os/posix/*.test.ts"
46
48
  },
47
49
  "dependencies": {
48
50
  "zod": "^4.0.0"