@evo-dev/core 0.0.1-alpha

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 (51) hide show
  1. package/assets/agents/review/code-reviewer/examples.md +19 -0
  2. package/assets/agents/review/code-reviewer/manifest.json +10 -0
  3. package/assets/agents/review/code-reviewer/prompt.md +59 -0
  4. package/assets/agents/review/code-reviewer/verification.md +11 -0
  5. package/assets/skills/coding/engineering-discipline/SKILL.md +63 -0
  6. package/assets/skills/coding/engineering-discipline/anti-patterns.md +21 -0
  7. package/assets/skills/coding/engineering-discipline/examples.md +19 -0
  8. package/assets/skills/coding/engineering-discipline/manifest.json +10 -0
  9. package/assets/skills/coding/engineering-discipline/verification.md +11 -0
  10. package/assets/workflows/rd-bug-fix/WORKFLOW.json +45 -0
  11. package/assets/workflows/rd-code-review/WORKFLOW.json +45 -0
  12. package/assets/workflows/rd-docs-update/WORKFLOW.json +45 -0
  13. package/assets/workflows/rd-feature-implementation/WORKFLOW.json +45 -0
  14. package/assets/workflows/rd-refactor/WORKFLOW.json +45 -0
  15. package/assets/workflows/rd-release-readiness/WORKFLOW.json +49 -0
  16. package/assets/workflows/rd-security-boundary-review/WORKFLOW.json +45 -0
  17. package/assets/workflows/rd-test-generation/WORKFLOW.json +45 -0
  18. package/dist/assets/index.js +209 -0
  19. package/dist/config/index.js +601 -0
  20. package/dist/index.js +4879 -0
  21. package/dist/plugins/index.js +265 -0
  22. package/package.json +30 -0
  23. package/src/.gitkeep +0 -0
  24. package/src/agents/index.ts +561 -0
  25. package/src/assets/errors.ts +21 -0
  26. package/src/assets/index.ts +18 -0
  27. package/src/assets/manifest.ts +109 -0
  28. package/src/assets/scanner.ts +189 -0
  29. package/src/config/errors.ts +21 -0
  30. package/src/config/index.ts +26 -0
  31. package/src/config/paths.ts +43 -0
  32. package/src/config/registry.ts +84 -0
  33. package/src/config/settings.ts +212 -0
  34. package/src/config/state.ts +130 -0
  35. package/src/config/store.ts +166 -0
  36. package/src/daemon/index.ts +414 -0
  37. package/src/hooks/index.ts +1023 -0
  38. package/src/index.ts +14 -0
  39. package/src/learning/index.ts +714 -0
  40. package/src/observability/index.ts +272 -0
  41. package/src/pack/index.ts +779 -0
  42. package/src/plugins/capabilities.ts +347 -0
  43. package/src/plugins/index.ts +41 -0
  44. package/src/plugins/registry.ts +60 -0
  45. package/src/plugins/types.ts +123 -0
  46. package/src/project/index.ts +507 -0
  47. package/src/protected-zones/index.ts +137 -0
  48. package/src/sync/index.ts +7 -0
  49. package/src/sync/orchestrator.ts +298 -0
  50. package/src/task/index.ts +840 -0
  51. package/src/workflow/index.ts +137 -0
@@ -0,0 +1,601 @@
1
+ // packages/core/src/config/errors.ts
2
+ class EvoDevConfigError extends Error {
3
+ filePath;
4
+ constructor(message, filePath) {
5
+ super(filePath ? `${message}: ${filePath}` : message);
6
+ this.filePath = filePath;
7
+ this.name = "EvoDevConfigError";
8
+ }
9
+ }
10
+ function describeType(value) {
11
+ if (value === null) {
12
+ return "null";
13
+ }
14
+ if (Array.isArray(value)) {
15
+ return "array";
16
+ }
17
+ return typeof value;
18
+ }
19
+ // packages/core/src/config/paths.ts
20
+ function resolveEvoDevPaths(homeDir = getHomeDir()) {
21
+ const normalizedHome = stripTrailingSlash(homeDir);
22
+ const rootDir = `${normalizedHome}/.evodev`;
23
+ const stateDir = `${rootDir}/state`;
24
+ return {
25
+ homeDir: normalizedHome,
26
+ rootDir,
27
+ settingsPath: `${rootDir}/settings.json`,
28
+ registryPath: `${rootDir}/registry.json`,
29
+ stateDir,
30
+ installStatePath: `${stateDir}/install.json`,
31
+ syncStatePath: `${stateDir}/sync.json`
32
+ };
33
+ }
34
+ function getHomeDir() {
35
+ const home = process.env.HOME;
36
+ if (home === undefined || home.trim() === "") {
37
+ throw new Error("Cannot resolve home directory: HOME is not set");
38
+ }
39
+ return home;
40
+ }
41
+ function stripTrailingSlash(path) {
42
+ if (path === "/") {
43
+ return path;
44
+ }
45
+ return path.replace(/\/+$/, "");
46
+ }
47
+ // packages/core/src/config/registry.ts
48
+ function createDefaultRegistry() {
49
+ return {
50
+ version: 1,
51
+ skills: {},
52
+ agents: {}
53
+ };
54
+ }
55
+ function parseRegistry(value) {
56
+ const root = expectRecord(value, "registry");
57
+ if (root.version !== 1) {
58
+ throw new EvoDevConfigError("Invalid registry.version; expected 1");
59
+ }
60
+ return {
61
+ version: 1,
62
+ skills: parseAssetMap(root.skills, "registry.skills"),
63
+ agents: parseAssetMap(root.agents, "registry.agents")
64
+ };
65
+ }
66
+ function parseAssetMap(value, path) {
67
+ const input = expectRecord(value, path);
68
+ const output = {};
69
+ for (const [assetId, asset] of Object.entries(input)) {
70
+ output[assetId] = parseRegisteredAsset(asset, `${path}.${assetId}`);
71
+ }
72
+ return output;
73
+ }
74
+ function parseRegisteredAsset(value, path) {
75
+ const input = expectRecord(value, path);
76
+ const source = input.source;
77
+ if (source !== "builtin" && source !== "user") {
78
+ throw new EvoDevConfigError(`Invalid ${path}.source; expected builtin or user`);
79
+ }
80
+ if (!Array.isArray(input.targets) || !input.targets.every((target) => typeof target === "string")) {
81
+ throw new EvoDevConfigError(`Invalid ${path}.targets; expected string array`);
82
+ }
83
+ return {
84
+ version: expectString(input.version, `${path}.version`),
85
+ source,
86
+ targets: [...input.targets]
87
+ };
88
+ }
89
+ function expectRecord(value, path) {
90
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
91
+ throw new EvoDevConfigError(`Invalid ${path}; expected object, got ${describeType(value)}`);
92
+ }
93
+ return value;
94
+ }
95
+ function expectString(value, path) {
96
+ if (typeof value !== "string" || value.length === 0) {
97
+ throw new EvoDevConfigError(`Invalid ${path}; expected non-empty string`);
98
+ }
99
+ return value;
100
+ }
101
+ // packages/core/src/task/index.ts
102
+ var FORBIDDEN_TASK_WRITE_SEGMENTS = new Set([".claude", ".codex"]);
103
+ var FORBIDDEN_PROJECT_ASSET_SEGMENTS = new Set(["packages", "src"]);
104
+ var FORBIDDEN_TASK_WRITE_FILES = new Set(["agents.md", "claude.md", "package.json", "readme.md"]);
105
+ var FORBIDDEN_RAW_KEYS = new Set([
106
+ "rawoutput",
107
+ "raw_output",
108
+ "stdout",
109
+ "stderr",
110
+ "source",
111
+ "sourcecontent",
112
+ "source_content",
113
+ "sourcetext",
114
+ "source_text",
115
+ "prompt",
116
+ "prompttext",
117
+ "prompt_text",
118
+ "transcript",
119
+ "transcripttext",
120
+ "transcript_text",
121
+ "secret",
122
+ "secretvalue",
123
+ "secret_value"
124
+ ]);
125
+ var ALLOWED_VERIFICATION_KEYS = new Set([
126
+ "acceptanceResults",
127
+ "antiCriteriaResults",
128
+ "commands",
129
+ "evidence",
130
+ "exitCode",
131
+ "id",
132
+ "status",
133
+ "summary",
134
+ "type"
135
+ ]);
136
+
137
+ // packages/core/src/hooks/index.ts
138
+ var CANONICAL_HOOK_EVENT_TYPES = [
139
+ "SessionStart",
140
+ "UserPromptSubmit",
141
+ "UserPromptExpansion",
142
+ "PreToolUse",
143
+ "PermissionRequest",
144
+ "PostToolUse",
145
+ "PostToolUseFailure",
146
+ "PostToolBatch",
147
+ "PermissionDenied",
148
+ "SubagentStart",
149
+ "Stop",
150
+ "StopFailure",
151
+ "TeammateIdle",
152
+ "SubagentStop",
153
+ "TaskCreated",
154
+ "TaskCompleted",
155
+ "PreCompact",
156
+ "PostCompact",
157
+ "SessionEnd",
158
+ "ConfigChange",
159
+ "CwdChanged",
160
+ "FileChanged",
161
+ "WorktreeCreate",
162
+ "WorktreeRemove"
163
+ ];
164
+ var DEFAULT_EVENT_SETTINGS = {
165
+ SessionStart: false,
166
+ UserPromptSubmit: false,
167
+ UserPromptExpansion: false,
168
+ PreToolUse: false,
169
+ PermissionRequest: false,
170
+ PostToolUse: false,
171
+ PostToolUseFailure: false,
172
+ PostToolBatch: false,
173
+ PermissionDenied: false,
174
+ SubagentStart: false,
175
+ Stop: false,
176
+ StopFailure: false,
177
+ TeammateIdle: false,
178
+ SubagentStop: false,
179
+ TaskCreated: false,
180
+ TaskCompleted: false,
181
+ PreCompact: false,
182
+ PostCompact: false,
183
+ SessionEnd: false,
184
+ ConfigChange: false,
185
+ CwdChanged: false,
186
+ FileChanged: false,
187
+ WorktreeCreate: false,
188
+ WorktreeRemove: false
189
+ };
190
+ function createDefaultHookSettings() {
191
+ return {
192
+ enabled: false,
193
+ targets: {
194
+ claude: {
195
+ enabled: false,
196
+ events: { ...DEFAULT_EVENT_SETTINGS }
197
+ },
198
+ codex: {
199
+ enabled: false,
200
+ events: { ...DEFAULT_EVENT_SETTINGS }
201
+ }
202
+ },
203
+ observability: {
204
+ metadataOnly: true,
205
+ rawPayloadStorage: false,
206
+ appendEvents: false
207
+ },
208
+ learning: {
209
+ emitCandidates: false,
210
+ writeMemory: false
211
+ }
212
+ };
213
+ }
214
+ function parseHookSettings(value) {
215
+ const defaults = createDefaultHookSettings();
216
+ if (value === undefined || value === null)
217
+ return defaults;
218
+ if (!isRecord(value))
219
+ throw new Error("Invalid hooks settings; expected object.");
220
+ return {
221
+ enabled: optionalBoolean(value.enabled, defaults.enabled, "hooks.enabled"),
222
+ targets: {
223
+ claude: parseHookTargetSettings(value.targets, defaults.targets.claude, "claude"),
224
+ codex: parseHookTargetSettings(value.targets, defaults.targets.codex, "codex")
225
+ },
226
+ observability: {
227
+ metadataOnly: true,
228
+ rawPayloadStorage: false,
229
+ appendEvents: false
230
+ },
231
+ learning: {
232
+ emitCandidates: false,
233
+ writeMemory: false
234
+ }
235
+ };
236
+ }
237
+ function parseHookTargetSettings(value, defaults, target) {
238
+ const targets = isRecord(value) ? value : {};
239
+ const targetSettings = isRecord(targets[target]) ? targets[target] : {};
240
+ const events = isRecord(targetSettings.events) ? targetSettings.events : {};
241
+ const parsedEvents = { ...defaults.events };
242
+ for (const eventType of CANONICAL_HOOK_EVENT_TYPES) {
243
+ parsedEvents[eventType] = optionalBoolean(events[eventType], defaults.events[eventType], `hooks.targets.${target}.events.${eventType}`);
244
+ }
245
+ return {
246
+ enabled: optionalBoolean(targetSettings.enabled, defaults.enabled, `hooks.targets.${target}.enabled`),
247
+ events: parsedEvents
248
+ };
249
+ }
250
+ function optionalBoolean(value, fallback, path) {
251
+ if (value === undefined)
252
+ return fallback;
253
+ if (typeof value !== "boolean")
254
+ throw new Error(`Invalid ${path}; expected boolean.`);
255
+ return value;
256
+ }
257
+ function isRecord(value) {
258
+ return typeof value === "object" && value !== null && !Array.isArray(value);
259
+ }
260
+
261
+ // packages/core/src/config/settings.ts
262
+ function createDefaultSettings(os = process.platform) {
263
+ return {
264
+ version: 1,
265
+ platform: {
266
+ os
267
+ },
268
+ plugins: {
269
+ claude: {
270
+ enabled: true,
271
+ autoSyncSkills: true,
272
+ autoSyncAgents: true
273
+ },
274
+ codex: {
275
+ enabled: false
276
+ }
277
+ },
278
+ assets: {
279
+ skills: {
280
+ enabled: true
281
+ },
282
+ agents: {
283
+ enabled: true
284
+ }
285
+ },
286
+ doctor: {
287
+ lastRunAt: null
288
+ },
289
+ hooks: createDefaultHookSettings()
290
+ };
291
+ }
292
+ function mergeSettings(existing, defaults = createDefaultSettings()) {
293
+ const merged = {
294
+ ...defaults,
295
+ ...existing,
296
+ platform: {
297
+ ...defaults.platform,
298
+ ...existing.platform
299
+ },
300
+ plugins: {
301
+ claude: {
302
+ ...defaults.plugins.claude,
303
+ ...existing.plugins?.claude
304
+ },
305
+ codex: {
306
+ ...defaults.plugins.codex,
307
+ ...existing.plugins?.codex
308
+ }
309
+ },
310
+ assets: {
311
+ skills: {
312
+ ...defaults.assets.skills,
313
+ ...existing.assets?.skills
314
+ },
315
+ agents: {
316
+ ...defaults.assets.agents,
317
+ ...existing.assets?.agents
318
+ }
319
+ },
320
+ doctor: {
321
+ ...defaults.doctor,
322
+ ...existing.doctor
323
+ },
324
+ hooks: existing.hooks ?? defaults.hooks
325
+ };
326
+ return parseSettings(merged);
327
+ }
328
+ function parseSettings(value) {
329
+ const root = expectRecord2(value, "settings");
330
+ const version = root.version;
331
+ if (version !== 1) {
332
+ throw new EvoDevConfigError("Invalid settings.version; expected 1");
333
+ }
334
+ const platform = expectRecord2(root.platform, "settings.platform");
335
+ const plugins = expectRecord2(root.plugins, "settings.plugins");
336
+ const assets = expectRecord2(root.assets, "settings.assets");
337
+ const doctor = expectRecord2(root.doctor, "settings.doctor");
338
+ const parsed = {
339
+ version: 1,
340
+ platform: {
341
+ os: expectString2(platform.os, "settings.platform.os")
342
+ },
343
+ plugins: {
344
+ claude: parsePluginSettings(plugins.claude, "settings.plugins.claude"),
345
+ codex: parsePluginSettings(plugins.codex, "settings.plugins.codex")
346
+ },
347
+ assets: {
348
+ skills: {
349
+ enabled: expectBoolean(expectRecord2(assets.skills, "settings.assets.skills").enabled, "settings.assets.skills.enabled")
350
+ },
351
+ agents: {
352
+ enabled: expectBoolean(expectRecord2(assets.agents, "settings.assets.agents").enabled, "settings.assets.agents.enabled")
353
+ }
354
+ },
355
+ doctor: {
356
+ lastRunAt: expectNullableString(doctor.lastRunAt, "settings.doctor.lastRunAt")
357
+ },
358
+ hooks: parseHookSettings(root.hooks)
359
+ };
360
+ return parsed;
361
+ }
362
+ function parsePluginSettings(value, path) {
363
+ const input = expectRecord2(value, path);
364
+ const parsed = {
365
+ enabled: expectBoolean(input.enabled, `${path}.enabled`)
366
+ };
367
+ if (input.autoSyncSkills !== undefined) {
368
+ parsed.autoSyncSkills = expectBoolean(input.autoSyncSkills, `${path}.autoSyncSkills`);
369
+ }
370
+ if (input.autoSyncAgents !== undefined) {
371
+ parsed.autoSyncAgents = expectBoolean(input.autoSyncAgents, `${path}.autoSyncAgents`);
372
+ }
373
+ return parsed;
374
+ }
375
+ function expectRecord2(value, path) {
376
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
377
+ throw new EvoDevConfigError(`Invalid ${path}; expected object, got ${describeType(value)}`);
378
+ }
379
+ return value;
380
+ }
381
+ function expectString2(value, path) {
382
+ if (typeof value !== "string" || value.length === 0) {
383
+ throw new EvoDevConfigError(`Invalid ${path}; expected non-empty string`);
384
+ }
385
+ return value;
386
+ }
387
+ function expectNullableString(value, path) {
388
+ if (value === null || typeof value === "string") {
389
+ return value;
390
+ }
391
+ throw new EvoDevConfigError(`Invalid ${path}; expected string or null`);
392
+ }
393
+ function expectBoolean(value, path) {
394
+ if (typeof value !== "boolean") {
395
+ throw new EvoDevConfigError(`Invalid ${path}; expected boolean`);
396
+ }
397
+ return value;
398
+ }
399
+ // packages/core/src/config/state.ts
400
+ function createDefaultInstallState() {
401
+ return {
402
+ version: 1,
403
+ initializedAt: null,
404
+ initializedBy: "evodev",
405
+ selectedPlugins: []
406
+ };
407
+ }
408
+ function createDefaultSyncState() {
409
+ return {
410
+ version: 1,
411
+ lastSyncAt: null,
412
+ targets: {}
413
+ };
414
+ }
415
+ function parseInstallState(value) {
416
+ const root = expectRecord3(value, "install state");
417
+ if (root.version !== 1) {
418
+ throw new EvoDevConfigError("Invalid install state.version; expected 1");
419
+ }
420
+ if (root.initializedBy !== "evodev") {
421
+ throw new EvoDevConfigError("Invalid install state.initializedBy; expected evodev");
422
+ }
423
+ if (!Array.isArray(root.selectedPlugins) || !root.selectedPlugins.every((plugin) => typeof plugin === "string")) {
424
+ throw new EvoDevConfigError("Invalid install state.selectedPlugins; expected string array");
425
+ }
426
+ return {
427
+ version: 1,
428
+ initializedAt: expectNullableString2(root.initializedAt, "install state.initializedAt"),
429
+ initializedBy: "evodev",
430
+ selectedPlugins: [...root.selectedPlugins]
431
+ };
432
+ }
433
+ function parseSyncState(value) {
434
+ const root = expectRecord3(value, "sync state");
435
+ if (root.version !== 1) {
436
+ throw new EvoDevConfigError("Invalid sync state.version; expected 1");
437
+ }
438
+ const targets = expectRecord3(root.targets, "sync state.targets");
439
+ const parsedTargets = {};
440
+ for (const [pluginId, state] of Object.entries(targets)) {
441
+ parsedTargets[pluginId] = parsePluginSyncState(state, `sync state.targets.${pluginId}`);
442
+ }
443
+ return {
444
+ version: 1,
445
+ lastSyncAt: expectNullableString2(root.lastSyncAt, "sync state.lastSyncAt"),
446
+ targets: parsedTargets
447
+ };
448
+ }
449
+ function parsePluginSyncState(value, path) {
450
+ const root = expectRecord3(value, path);
451
+ const status = root.status;
452
+ if (status !== "success" && status !== "partial" && status !== "failed" && status !== "never-run") {
453
+ throw new EvoDevConfigError(`Invalid ${path}.status; expected success, partial, failed, or never-run`);
454
+ }
455
+ return {
456
+ skills: expectNonNegativeInteger(root.skills, `${path}.skills`),
457
+ agents: expectNonNegativeInteger(root.agents, `${path}.agents`),
458
+ status
459
+ };
460
+ }
461
+ function expectRecord3(value, path) {
462
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
463
+ throw new EvoDevConfigError(`Invalid ${path}; expected object, got ${describeType(value)}`);
464
+ }
465
+ return value;
466
+ }
467
+ function expectNullableString2(value, path) {
468
+ if (value === null || typeof value === "string") {
469
+ return value;
470
+ }
471
+ throw new EvoDevConfigError(`Invalid ${path}; expected string or null`);
472
+ }
473
+ function expectNonNegativeInteger(value, path) {
474
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
475
+ throw new EvoDevConfigError(`Invalid ${path}; expected non-negative integer`);
476
+ }
477
+ return value;
478
+ }
479
+ // packages/core/src/config/store.ts
480
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
481
+ import { dirname } from "node:path";
482
+ function createCoreConfigStore(homeDir) {
483
+ const paths = resolveEvoDevPaths(homeDir);
484
+ return {
485
+ paths,
486
+ async ensureBaseDirs() {
487
+ await mkdir(paths.stateDir, { recursive: true });
488
+ },
489
+ async readSettings() {
490
+ return readJsonFile(paths.settingsPath, parseSettings);
491
+ },
492
+ async writeSettings(settings) {
493
+ await writeJsonFile(paths.settingsPath, parseSettings(settings));
494
+ },
495
+ async mergeAndWriteSettings(input) {
496
+ const current = await readJsonFileOrDefault(paths.settingsPath, parseSettings, createDefaultSettings());
497
+ const merged = mergeSettings(input, current);
498
+ await writeJsonFile(paths.settingsPath, merged);
499
+ return merged;
500
+ },
501
+ async readRegistry() {
502
+ return readJsonFile(paths.registryPath, parseRegistry);
503
+ },
504
+ async writeRegistry(registry) {
505
+ await writeJsonFile(paths.registryPath, parseRegistry(registry));
506
+ },
507
+ async readInstallState() {
508
+ return readJsonFile(paths.installStatePath, parseInstallState);
509
+ },
510
+ async writeInstallState(state) {
511
+ await writeJsonFile(paths.installStatePath, parseInstallState(state));
512
+ },
513
+ async readSyncState() {
514
+ return readJsonFile(paths.syncStatePath, parseSyncState);
515
+ },
516
+ async writeSyncState(state) {
517
+ await writeJsonFile(paths.syncStatePath, parseSyncState(state));
518
+ }
519
+ };
520
+ }
521
+ async function initializeCoreConfig(homeDir) {
522
+ const store = createCoreConfigStore(homeDir);
523
+ await store.ensureBaseDirs();
524
+ await writeIfMissing(store.paths.settingsPath, createDefaultSettings());
525
+ await writeIfMissing(store.paths.registryPath, createDefaultRegistry());
526
+ await writeIfMissing(store.paths.installStatePath, createDefaultInstallState());
527
+ await writeIfMissing(store.paths.syncStatePath, createDefaultSyncState());
528
+ return store;
529
+ }
530
+ async function readJsonFile(filePath, parse) {
531
+ let raw;
532
+ try {
533
+ raw = await readFile(filePath, "utf8");
534
+ } catch (error) {
535
+ throw new EvoDevConfigError(`Cannot read config file (${describeFileError(error)})`, filePath);
536
+ }
537
+ let json;
538
+ try {
539
+ json = JSON.parse(raw);
540
+ } catch (error) {
541
+ throw new EvoDevConfigError(`Invalid JSON (${describeFileError(error)})`, filePath);
542
+ }
543
+ try {
544
+ return parse(json);
545
+ } catch (error) {
546
+ if (error instanceof EvoDevConfigError) {
547
+ throw new EvoDevConfigError(error.message, filePath);
548
+ }
549
+ throw error;
550
+ }
551
+ }
552
+ async function readJsonFileOrDefault(filePath, parse, fallback) {
553
+ try {
554
+ return await readJsonFile(filePath, parse);
555
+ } catch (error) {
556
+ if (error instanceof EvoDevConfigError && error.message.includes("ENOENT")) {
557
+ return fallback;
558
+ }
559
+ throw error;
560
+ }
561
+ }
562
+ async function writeIfMissing(filePath, value) {
563
+ try {
564
+ await readFile(filePath, "utf8");
565
+ } catch (error) {
566
+ if (isNodeError(error) && error.code === "ENOENT") {
567
+ await writeJsonFile(filePath, value);
568
+ return;
569
+ }
570
+ throw new EvoDevConfigError(`Cannot inspect config file (${describeFileError(error)})`, filePath);
571
+ }
572
+ }
573
+ async function writeJsonFile(filePath, value) {
574
+ await mkdir(dirname(filePath), { recursive: true });
575
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}
576
+ `, "utf8");
577
+ }
578
+ function describeFileError(error) {
579
+ if (error instanceof Error) {
580
+ return error.message;
581
+ }
582
+ return String(error);
583
+ }
584
+ function isNodeError(error) {
585
+ return error instanceof Error && "code" in error;
586
+ }
587
+ export {
588
+ resolveEvoDevPaths,
589
+ parseSyncState,
590
+ parseSettings,
591
+ parseRegistry,
592
+ parseInstallState,
593
+ mergeSettings,
594
+ initializeCoreConfig,
595
+ createDefaultSyncState,
596
+ createDefaultSettings,
597
+ createDefaultRegistry,
598
+ createDefaultInstallState,
599
+ createCoreConfigStore,
600
+ EvoDevConfigError
601
+ };