@evo-dev/core 0.0.1-alpha → 0.0.1-alpha.2
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/assets/agents/review/code-reviewer/examples.md +1 -1
- package/assets/agents/review/code-reviewer/prompt.md +1 -1
- package/assets/agents/review/code-reviewer/verification.md +1 -1
- package/assets/skills/coding/knowledge-distillation/SKILL.md +249 -0
- package/assets/skills/coding/knowledge-distillation/manifest.json +10 -0
- package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +126 -0
- package/assets/workflows/rd-bug-fix/WORKFLOW.json +1 -1
- package/assets/workflows/rd-code-review/WORKFLOW.json +1 -1
- package/assets/workflows/rd-docs-update/WORKFLOW.json +1 -1
- package/assets/workflows/rd-feature-implementation/WORKFLOW.json +1 -1
- package/assets/workflows/rd-refactor/WORKFLOW.json +1 -1
- package/assets/workflows/rd-release-readiness/WORKFLOW.json +1 -1
- package/assets/workflows/rd-security-boundary-review/WORKFLOW.json +2 -2
- package/assets/workflows/rd-test-generation/WORKFLOW.json +1 -1
- package/dist/config/index.js +968 -39
- package/dist/index.js +10914 -1476
- package/dist/plugins/index.js +32 -32
- package/package.json +5 -1
- package/src/agents/index.ts +84 -49
- package/src/code-agent-traces/index.ts +521 -0
- package/src/config/index.ts +5 -0
- package/src/config/paths.ts +30 -0
- package/src/config/settings.ts +130 -0
- package/src/config/store.ts +152 -0
- package/src/daemon/index.ts +465 -3
- package/src/evolution/index.ts +2827 -0
- package/src/hooks/index.ts +543 -247
- package/src/index.ts +6 -0
- package/src/knowledge/index.ts +4784 -0
- package/src/pack/index.ts +13 -13
- package/src/plugins/capabilities.ts +40 -42
- package/src/plugins/index.ts +0 -1
- package/src/plugins/types.ts +4 -0
- package/src/protected-zones/index.ts +29 -11
- package/src/runtime-logs/index.ts +798 -0
- package/src/sync/orchestrator.ts +6 -0
- package/src/task/index.ts +3 -3
- package/src/team/index.ts +3069 -0
- package/src/team/mcp.ts +405 -0
- package/src/team/prompts.ts +141 -0
- package/src/workflow/index.ts +6 -6
package/src/config/settings.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { type HookSettings, createDefaultHookSettings, parseHookSettings } from "../hooks/index.ts";
|
|
2
3
|
import { EvoDevConfigError, describeType } from "./errors.ts";
|
|
4
|
+
import { resolveEvoDevPaths } from "./paths.ts";
|
|
3
5
|
|
|
4
6
|
export interface PluginSettings {
|
|
5
7
|
enabled: boolean;
|
|
@@ -7,6 +9,13 @@ export interface PluginSettings {
|
|
|
7
9
|
autoSyncAgents?: boolean;
|
|
8
10
|
}
|
|
9
11
|
|
|
12
|
+
export interface MemorySettings {
|
|
13
|
+
autoAccept: boolean;
|
|
14
|
+
runtimeInjection: boolean;
|
|
15
|
+
staleReview: boolean;
|
|
16
|
+
lexicalIndex: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
export interface EvoDevSettings {
|
|
11
20
|
version: 1;
|
|
12
21
|
platform: {
|
|
@@ -28,6 +37,8 @@ export interface EvoDevSettings {
|
|
|
28
37
|
lastRunAt: string | null;
|
|
29
38
|
};
|
|
30
39
|
hooks: HookSettings;
|
|
40
|
+
teamRuntime: TeamRuntimeSettings;
|
|
41
|
+
memory: MemorySettings;
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
export type SettingsInput = Partial<{
|
|
@@ -43,8 +54,18 @@ export type SettingsInput = Partial<{
|
|
|
43
54
|
}>;
|
|
44
55
|
doctor: Partial<EvoDevSettings["doctor"]>;
|
|
45
56
|
hooks: unknown;
|
|
57
|
+
teamRuntime: Partial<TeamRuntimeSettings>;
|
|
58
|
+
memory: Partial<MemorySettings>;
|
|
46
59
|
}>;
|
|
47
60
|
|
|
61
|
+
export interface TeamRuntimeSettings {
|
|
62
|
+
defaultRuntime: "codex" | "claude";
|
|
63
|
+
defaultModel: string | null;
|
|
64
|
+
defaultThinkingLevel: string | null;
|
|
65
|
+
recordTranscript: boolean;
|
|
66
|
+
displayMode: "normal" | "development";
|
|
67
|
+
}
|
|
68
|
+
|
|
48
69
|
export function createDefaultSettings(os: string = process.platform): EvoDevSettings {
|
|
49
70
|
return {
|
|
50
71
|
version: 1,
|
|
@@ -73,6 +94,27 @@ export function createDefaultSettings(os: string = process.platform): EvoDevSett
|
|
|
73
94
|
lastRunAt: null,
|
|
74
95
|
},
|
|
75
96
|
hooks: createDefaultHookSettings(),
|
|
97
|
+
teamRuntime: createDefaultTeamRuntimeSettings(),
|
|
98
|
+
memory: createDefaultMemorySettings(),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function createDefaultTeamRuntimeSettings(): TeamRuntimeSettings {
|
|
103
|
+
return {
|
|
104
|
+
defaultRuntime: "codex",
|
|
105
|
+
defaultModel: null,
|
|
106
|
+
defaultThinkingLevel: null,
|
|
107
|
+
recordTranscript: false,
|
|
108
|
+
displayMode: "normal",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createDefaultMemorySettings(): MemorySettings {
|
|
113
|
+
return {
|
|
114
|
+
autoAccept: true,
|
|
115
|
+
runtimeInjection: true,
|
|
116
|
+
staleReview: true,
|
|
117
|
+
lexicalIndex: true,
|
|
76
118
|
};
|
|
77
119
|
}
|
|
78
120
|
|
|
@@ -112,11 +154,29 @@ export function mergeSettings(
|
|
|
112
154
|
...existing.doctor,
|
|
113
155
|
},
|
|
114
156
|
hooks: existing.hooks ?? defaults.hooks,
|
|
157
|
+
teamRuntime: {
|
|
158
|
+
...defaults.teamRuntime,
|
|
159
|
+
...existing.teamRuntime,
|
|
160
|
+
},
|
|
161
|
+
memory: {
|
|
162
|
+
...defaults.memory,
|
|
163
|
+
...existing.memory,
|
|
164
|
+
},
|
|
115
165
|
};
|
|
116
166
|
|
|
117
167
|
return parseSettings(merged);
|
|
118
168
|
}
|
|
119
169
|
|
|
170
|
+
export async function readRuntimeInjectionSettings(homeDir?: string): Promise<MemorySettings> {
|
|
171
|
+
const paths = resolveEvoDevPaths(homeDir);
|
|
172
|
+
try {
|
|
173
|
+
return parseSettings(JSON.parse(await readFile(paths.settingsPath, "utf8"))).memory;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (isNotFoundError(error)) return createDefaultMemorySettings();
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
120
180
|
export function parseSettings(value: unknown): EvoDevSettings {
|
|
121
181
|
const root = expectRecord(value, "settings");
|
|
122
182
|
const version = root.version;
|
|
@@ -157,11 +217,75 @@ export function parseSettings(value: unknown): EvoDevSettings {
|
|
|
157
217
|
lastRunAt: expectNullableString(doctor.lastRunAt, "settings.doctor.lastRunAt"),
|
|
158
218
|
},
|
|
159
219
|
hooks: parseHookSettings(root.hooks),
|
|
220
|
+
teamRuntime: parseTeamRuntimeSettings(
|
|
221
|
+
root.teamRuntime ?? createDefaultTeamRuntimeSettings(),
|
|
222
|
+
"settings.teamRuntime",
|
|
223
|
+
),
|
|
224
|
+
memory: parseMemorySettings(root.memory ?? createDefaultMemorySettings(), "settings.memory"),
|
|
160
225
|
};
|
|
161
226
|
|
|
162
227
|
return parsed;
|
|
163
228
|
}
|
|
164
229
|
|
|
230
|
+
function parseMemorySettings(value: unknown, path: string): MemorySettings {
|
|
231
|
+
const input = expectRecord(value, path);
|
|
232
|
+
const defaults = createDefaultMemorySettings();
|
|
233
|
+
return {
|
|
234
|
+
autoAccept:
|
|
235
|
+
input.autoAccept === undefined
|
|
236
|
+
? defaults.autoAccept
|
|
237
|
+
: expectBoolean(input.autoAccept, `${path}.autoAccept`),
|
|
238
|
+
runtimeInjection:
|
|
239
|
+
input.runtimeInjection === undefined
|
|
240
|
+
? defaults.runtimeInjection
|
|
241
|
+
: expectBoolean(input.runtimeInjection, `${path}.runtimeInjection`),
|
|
242
|
+
staleReview:
|
|
243
|
+
input.staleReview === undefined
|
|
244
|
+
? defaults.staleReview
|
|
245
|
+
: expectBoolean(input.staleReview, `${path}.staleReview`),
|
|
246
|
+
lexicalIndex:
|
|
247
|
+
input.lexicalIndex === undefined
|
|
248
|
+
? defaults.lexicalIndex
|
|
249
|
+
: expectBoolean(input.lexicalIndex, `${path}.lexicalIndex`),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function parseTeamRuntimeSettings(value: unknown, path: string): TeamRuntimeSettings {
|
|
254
|
+
const input = expectRecord(value, path);
|
|
255
|
+
const defaults = createDefaultTeamRuntimeSettings();
|
|
256
|
+
const defaultRuntime = input.defaultRuntime ?? defaults.defaultRuntime;
|
|
257
|
+
if (defaultRuntime !== "codex" && defaultRuntime !== "claude") {
|
|
258
|
+
throw new EvoDevConfigError(`Invalid ${path}.defaultRuntime; expected codex or claude`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
defaultRuntime,
|
|
263
|
+
defaultModel:
|
|
264
|
+
input.defaultModel === undefined
|
|
265
|
+
? defaults.defaultModel
|
|
266
|
+
: expectNullableString(input.defaultModel, `${path}.defaultModel`),
|
|
267
|
+
defaultThinkingLevel:
|
|
268
|
+
input.defaultThinkingLevel === undefined
|
|
269
|
+
? defaults.defaultThinkingLevel
|
|
270
|
+
: expectNullableString(input.defaultThinkingLevel, `${path}.defaultThinkingLevel`),
|
|
271
|
+
recordTranscript:
|
|
272
|
+
input.recordTranscript === undefined
|
|
273
|
+
? defaults.recordTranscript
|
|
274
|
+
: expectBoolean(input.recordTranscript, `${path}.recordTranscript`),
|
|
275
|
+
displayMode: parseTeamRuntimeDisplayMode(input.displayMode, defaults.displayMode, path),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function parseTeamRuntimeDisplayMode(
|
|
280
|
+
value: unknown,
|
|
281
|
+
fallback: TeamRuntimeSettings["displayMode"],
|
|
282
|
+
path: string,
|
|
283
|
+
): TeamRuntimeSettings["displayMode"] {
|
|
284
|
+
if (value === undefined) return fallback;
|
|
285
|
+
if (value === "normal" || value === "development") return value;
|
|
286
|
+
throw new EvoDevConfigError(`Invalid ${path}.displayMode; expected normal or development`);
|
|
287
|
+
}
|
|
288
|
+
|
|
165
289
|
function parsePluginSettings(value: unknown, path: string): PluginSettings {
|
|
166
290
|
const input = expectRecord(value, path);
|
|
167
291
|
const parsed: PluginSettings = {
|
|
@@ -187,6 +311,12 @@ function expectRecord(value: unknown, path: string): Record<string, unknown> {
|
|
|
187
311
|
return value as Record<string, unknown>;
|
|
188
312
|
}
|
|
189
313
|
|
|
314
|
+
function isNotFoundError(error: unknown): boolean {
|
|
315
|
+
return (
|
|
316
|
+
error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT"
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
190
320
|
function expectString(value: unknown, path: string): string {
|
|
191
321
|
if (typeof value !== "string" || value.length === 0) {
|
|
192
322
|
throw new EvoDevConfigError(`Invalid ${path}; expected non-empty string`);
|
package/src/config/store.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
import { ensureOkfKnowledgeBase } from "../knowledge/index.ts";
|
|
3
4
|
import { EvoDevConfigError } from "./errors.ts";
|
|
4
5
|
import { type EvoDevPaths, resolveEvoDevPaths } from "./paths.ts";
|
|
5
6
|
import { type EvoDevRegistry, createDefaultRegistry, parseRegistry } from "./registry.ts";
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
export interface CoreConfigStore {
|
|
23
24
|
readonly paths: EvoDevPaths;
|
|
24
25
|
ensureBaseDirs(): Promise<void>;
|
|
26
|
+
ensureKnowledgeBase(): Promise<void>;
|
|
25
27
|
readSettings(): Promise<EvoDevSettings>;
|
|
26
28
|
writeSettings(settings: EvoDevSettings): Promise<void>;
|
|
27
29
|
mergeAndWriteSettings(input: SettingsInput): Promise<EvoDevSettings>;
|
|
@@ -40,6 +42,15 @@ export function createCoreConfigStore(homeDir?: string): CoreConfigStore {
|
|
|
40
42
|
paths,
|
|
41
43
|
async ensureBaseDirs() {
|
|
42
44
|
await mkdir(paths.stateDir, { recursive: true });
|
|
45
|
+
await mkdir(paths.logsDir, { recursive: true });
|
|
46
|
+
await mkdir(paths.knowledgeDir, { recursive: true });
|
|
47
|
+
await mkdir(paths.evosCasesDir, { recursive: true });
|
|
48
|
+
await mkdir(paths.roleAgentsDir, { recursive: true });
|
|
49
|
+
await mkdir(paths.teamsDir, { recursive: true });
|
|
50
|
+
await mkdir(paths.runsDir, { recursive: true });
|
|
51
|
+
},
|
|
52
|
+
async ensureKnowledgeBase() {
|
|
53
|
+
await ensureKnowledgeBaseFiles(paths);
|
|
43
54
|
},
|
|
44
55
|
async readSettings() {
|
|
45
56
|
return readJsonFile(paths.settingsPath, parseSettings);
|
|
@@ -81,6 +92,7 @@ export function createCoreConfigStore(homeDir?: string): CoreConfigStore {
|
|
|
81
92
|
export async function initializeCoreConfig(homeDir?: string): Promise<CoreConfigStore> {
|
|
82
93
|
const store = createCoreConfigStore(homeDir);
|
|
83
94
|
await store.ensureBaseDirs();
|
|
95
|
+
await store.ensureKnowledgeBase();
|
|
84
96
|
await writeIfMissing(store.paths.settingsPath, createDefaultSettings());
|
|
85
97
|
await writeIfMissing(store.paths.registryPath, createDefaultRegistry());
|
|
86
98
|
await writeIfMissing(store.paths.installStatePath, createDefaultInstallState());
|
|
@@ -88,6 +100,88 @@ export async function initializeCoreConfig(homeDir?: string): Promise<CoreConfig
|
|
|
88
100
|
return store;
|
|
89
101
|
}
|
|
90
102
|
|
|
103
|
+
export async function ensureKnowledgeBaseFiles(paths: EvoDevPaths): Promise<void> {
|
|
104
|
+
await mkdir(paths.knowledgeDir, { recursive: true });
|
|
105
|
+
await mkdir(paths.evosCasesDir, { recursive: true });
|
|
106
|
+
await ensureOkfKnowledgeBase(paths.homeDir);
|
|
107
|
+
await writeTextIfMissing(
|
|
108
|
+
`${paths.knowledgeDir}/README.md`,
|
|
109
|
+
[
|
|
110
|
+
"# EvoDev Knowledge",
|
|
111
|
+
"",
|
|
112
|
+
"Local-private knowledge base for user-accepted facts, decisions, architecture notes, and reusable domain context.",
|
|
113
|
+
"",
|
|
114
|
+
"EvoDev must not populate this directory from source code, prompts, command output, logs, or transcripts without an explicit consent flow.",
|
|
115
|
+
"",
|
|
116
|
+
].join("\n"),
|
|
117
|
+
);
|
|
118
|
+
await writeIndexIfMissingOrMigrate(paths.knowledgeIndexPath, "knowledge-index", {
|
|
119
|
+
version: 1,
|
|
120
|
+
kind: "knowledge-index",
|
|
121
|
+
roleTags: [],
|
|
122
|
+
entries: [],
|
|
123
|
+
});
|
|
124
|
+
await writeTextIfMissing(
|
|
125
|
+
`${paths.evosDir}/README.md`,
|
|
126
|
+
[
|
|
127
|
+
"# EvoDev Evos",
|
|
128
|
+
"",
|
|
129
|
+
"Local-private evolution case library for reviewed improvement cases and reusable process changes.",
|
|
130
|
+
"",
|
|
131
|
+
"Cases start empty. Future automation may propose candidates, but accepted evos require explicit review before they can influence workflows or routing.",
|
|
132
|
+
"",
|
|
133
|
+
].join("\n"),
|
|
134
|
+
);
|
|
135
|
+
await writeTextIfMissing(
|
|
136
|
+
`${paths.evosCasesDir}/README.md`,
|
|
137
|
+
[
|
|
138
|
+
"# Evolution Cases",
|
|
139
|
+
"",
|
|
140
|
+
"Store one reviewed evolution case per file. Do not store raw prompts, source dumps, secrets, transcripts, or raw command output here.",
|
|
141
|
+
"",
|
|
142
|
+
].join("\n"),
|
|
143
|
+
);
|
|
144
|
+
await writeIndexIfMissingOrMigrate(paths.evosIndexPath, "evos-index", {
|
|
145
|
+
version: 1,
|
|
146
|
+
kind: "evos-index",
|
|
147
|
+
roleTags: [],
|
|
148
|
+
cases: [],
|
|
149
|
+
});
|
|
150
|
+
await writeTextIfMissing(
|
|
151
|
+
`${paths.roleAgentsDir}/README.md`,
|
|
152
|
+
[
|
|
153
|
+
"# Role Agents",
|
|
154
|
+
"",
|
|
155
|
+
"Local-private role agent registry for EvoDev-managed agent roles and user-reviewed role extensions.",
|
|
156
|
+
"",
|
|
157
|
+
"Repository-specific role agents should be proposed first and written into a user repository only after explicit project opt-in.",
|
|
158
|
+
"",
|
|
159
|
+
].join("\n"),
|
|
160
|
+
);
|
|
161
|
+
await writeIndexIfMissingOrMigrate(paths.roleAgentsIndexPath, "role-agent-index", {
|
|
162
|
+
version: 1,
|
|
163
|
+
kind: "role-agent-index",
|
|
164
|
+
roles: [],
|
|
165
|
+
projectExtensions: [],
|
|
166
|
+
});
|
|
167
|
+
await writeTextIfMissing(
|
|
168
|
+
`${paths.teamsDir}/README.md`,
|
|
169
|
+
[
|
|
170
|
+
"# Agent Teams",
|
|
171
|
+
"",
|
|
172
|
+
"Local-private EvoHub team registry for reviewed role-agent team definitions.",
|
|
173
|
+
"",
|
|
174
|
+
"Teams may reference role agents and role-tagged knowledge, but they must not contain raw source, prompts, transcripts, secrets, or raw command output.",
|
|
175
|
+
"",
|
|
176
|
+
].join("\n"),
|
|
177
|
+
);
|
|
178
|
+
await writeIndexIfMissingOrMigrate(paths.teamsIndexPath, "agent-team-index", {
|
|
179
|
+
version: 1,
|
|
180
|
+
kind: "agent-team-index",
|
|
181
|
+
teams: [],
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
91
185
|
async function readJsonFile<T>(filePath: string, parse: (value: unknown) => T): Promise<T> {
|
|
92
186
|
let raw: string;
|
|
93
187
|
|
|
@@ -148,6 +242,60 @@ async function writeIfMissing(filePath: string, value: unknown): Promise<void> {
|
|
|
148
242
|
}
|
|
149
243
|
}
|
|
150
244
|
|
|
245
|
+
async function writeIndexIfMissingOrMigrate(
|
|
246
|
+
filePath: string,
|
|
247
|
+
kind: string,
|
|
248
|
+
defaults: Record<string, unknown>,
|
|
249
|
+
): Promise<void> {
|
|
250
|
+
let raw: string;
|
|
251
|
+
try {
|
|
252
|
+
raw = await readFile(filePath, "utf8");
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
255
|
+
await writeJsonFile(filePath, defaults);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
throw new EvoDevConfigError(
|
|
260
|
+
`Cannot inspect config file (${describeFileError(error)})`,
|
|
261
|
+
filePath,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let existing: unknown;
|
|
266
|
+
try {
|
|
267
|
+
existing = JSON.parse(raw);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
throw new EvoDevConfigError(
|
|
270
|
+
`Invalid bootstrap index JSON (${describeFileError(error)})`,
|
|
271
|
+
filePath,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!isRecord(existing) || existing.kind !== kind) return;
|
|
276
|
+
|
|
277
|
+
const migrated = { ...defaults, ...existing };
|
|
278
|
+
if (Object.keys(defaults).every((key) => key in existing)) return;
|
|
279
|
+
await writeJsonFile(filePath, migrated);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function writeTextIfMissing(filePath: string, value: string): Promise<void> {
|
|
283
|
+
try {
|
|
284
|
+
await readFile(filePath, "utf8");
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
287
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
288
|
+
await writeFile(filePath, value, "utf8");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
throw new EvoDevConfigError(
|
|
293
|
+
`Cannot inspect config file (${describeFileError(error)})`,
|
|
294
|
+
filePath,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
151
299
|
async function writeJsonFile(filePath: string, value: unknown): Promise<void> {
|
|
152
300
|
await mkdir(dirname(filePath), { recursive: true });
|
|
153
301
|
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
@@ -164,3 +312,7 @@ function describeFileError(error: unknown): string {
|
|
|
164
312
|
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
165
313
|
return error instanceof Error && "code" in error;
|
|
166
314
|
}
|
|
315
|
+
|
|
316
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
317
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
318
|
+
}
|