@evo-dev/core 0.0.1-alpha.1 → 0.0.1-alpha.3
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/skills/coding/knowledge-distillation/SKILL.md +112 -111
- package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +11 -7
- package/assets/team/agents/code-reviewer.md +48 -0
- package/assets/team/agents/docs-maintainer.md +51 -0
- package/assets/team/agents/implementation-engineer.md +51 -0
- package/assets/team/agents/product-scope-analyst.md +58 -0
- package/assets/team/agents/release-engineer.md +55 -0
- package/assets/team/agents/security-boundary-reviewer.md +50 -0
- package/assets/team/agents/solution-architect.md +51 -0
- package/assets/team/agents/verification-engineer.md +51 -0
- package/assets/team/team.md +102 -0
- package/dist/config/index.js +774 -34
- package/dist/index.js +7331 -1573
- package/package.json +5 -1
- package/src/agents/index.ts +56 -0
- package/src/code-agent-traces/index.ts +521 -0
- package/src/config/index.ts +3 -0
- package/src/config/paths.ts +1 -1
- package/src/config/settings.ts +78 -0
- package/src/config/store.ts +2 -0
- package/src/daemon/index.ts +98 -9
- package/src/evolution/index.ts +494 -23
- package/src/hooks/index.ts +315 -36
- package/src/index.ts +2 -0
- package/src/knowledge/index.ts +4784 -0
- package/src/runtime-logs/index.ts +490 -16
- package/src/team/index.ts +1429 -185
- package/src/team/mcp.ts +9 -5
- package/src/team/prompts.ts +141 -0
package/dist/config/index.js
CHANGED
|
@@ -26,7 +26,7 @@ function resolveEvoDevPaths(homeDir = getHomeDir()) {
|
|
|
26
26
|
const evosDir = `${rootDir}/evos`;
|
|
27
27
|
const roleAgentsDir = `${rootDir}/agents/roles`;
|
|
28
28
|
const teamsDir = `${rootDir}/teams`;
|
|
29
|
-
const runsDir = `${
|
|
29
|
+
const runsDir = `${teamsDir}/runs`;
|
|
30
30
|
return {
|
|
31
31
|
homeDir: normalizedHome,
|
|
32
32
|
rootDir,
|
|
@@ -116,6 +116,601 @@ function expectString(value, path) {
|
|
|
116
116
|
}
|
|
117
117
|
return value;
|
|
118
118
|
}
|
|
119
|
+
// packages/core/src/config/settings.ts
|
|
120
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
121
|
+
|
|
122
|
+
// packages/core/src/runtime-logs/index.ts
|
|
123
|
+
var SAFE_EXECUTION_METADATA_KEYS = new Set([
|
|
124
|
+
"phase",
|
|
125
|
+
"runtimeSurface",
|
|
126
|
+
"stdinBytes",
|
|
127
|
+
"durationMs",
|
|
128
|
+
"enabled",
|
|
129
|
+
"stateWriteCount",
|
|
130
|
+
"warningCount",
|
|
131
|
+
"commandClass",
|
|
132
|
+
"exitCode",
|
|
133
|
+
"status",
|
|
134
|
+
"redactionCount",
|
|
135
|
+
"redactionLabels",
|
|
136
|
+
"normalizedEventType"
|
|
137
|
+
]);
|
|
138
|
+
var SAFE_REDACTION_LABELS = new Set([
|
|
139
|
+
"raw-command",
|
|
140
|
+
"raw-command-output",
|
|
141
|
+
"raw-prompt",
|
|
142
|
+
"sensitive-command",
|
|
143
|
+
"sensitive-text"
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
// packages/core/src/knowledge/index.ts
|
|
147
|
+
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
148
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
149
|
+
var RESERVED_OKF_FILENAMES = new Set(["index.md", "log.md"]);
|
|
150
|
+
var FORBIDDEN_OKF_TEXT = /\b(secret|token|password|passwd|api[_-]?key|apikey|credential|credentials|secret[\s_-]*token(?:[\s_-]*repro)?|raw[\s_-]*(?:log|logs|output|source|prompt)(?:[\s_-]*repro)?|shell[\s_-]*history|command[\s_-]*history)\b/i;
|
|
151
|
+
var PRIVATE_OR_INTERNAL_URL = /https?:\/\/\S*(?:internal|private|corp|localhost|127\.0\.0\.1)\S*/i;
|
|
152
|
+
var OKF_PRIVACY_FLAG_KEYS = [
|
|
153
|
+
"rawPromptsStored",
|
|
154
|
+
"rawLogsStored",
|
|
155
|
+
"sourceDumpsStored",
|
|
156
|
+
"rawCommandOutputStored",
|
|
157
|
+
"secretsStored",
|
|
158
|
+
"internalLinksStored"
|
|
159
|
+
];
|
|
160
|
+
var OKF_QUERY_PRIVACY_FLAG_KEYS = [
|
|
161
|
+
...OKF_PRIVACY_FLAG_KEYS,
|
|
162
|
+
"rawOutputStored",
|
|
163
|
+
"sourceContentStored"
|
|
164
|
+
];
|
|
165
|
+
var ACTIVE_OKF_REVIEW_STATES = ["accepted", "auto-accepted"];
|
|
166
|
+
var OKF_REVIEW_STATES = [
|
|
167
|
+
"auto-accepted",
|
|
168
|
+
"accepted",
|
|
169
|
+
"needs-human",
|
|
170
|
+
"rejected",
|
|
171
|
+
"deferred",
|
|
172
|
+
"stale",
|
|
173
|
+
"deprecated",
|
|
174
|
+
"revoked",
|
|
175
|
+
"superseded",
|
|
176
|
+
"auto-stored/unreviewed"
|
|
177
|
+
];
|
|
178
|
+
var OKF_LIFECYCLE_STATUSES = [
|
|
179
|
+
"active",
|
|
180
|
+
"stale",
|
|
181
|
+
"deprecated",
|
|
182
|
+
"revoked",
|
|
183
|
+
"superseded"
|
|
184
|
+
];
|
|
185
|
+
var BEHAVIOR_CHANGE_KINDS = new Set([
|
|
186
|
+
"skill-improvement",
|
|
187
|
+
"role-agent-suggestion",
|
|
188
|
+
"team-suggestion",
|
|
189
|
+
"workflow-improvement",
|
|
190
|
+
"task-split-improvement",
|
|
191
|
+
"tool-use-improvement",
|
|
192
|
+
"repo-asset-suggestion"
|
|
193
|
+
]);
|
|
194
|
+
function resolveOkfKnowledgePaths(homeDir) {
|
|
195
|
+
const paths = resolveEvoDevPaths(homeDir);
|
|
196
|
+
return {
|
|
197
|
+
knowledgeDir: paths.knowledgeDir,
|
|
198
|
+
okfDir: join(paths.knowledgeDir, "okf"),
|
|
199
|
+
indexesDir: join(paths.knowledgeDir, "indexes"),
|
|
200
|
+
tmpDir: join(paths.knowledgeDir, "tmp")
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
async function ensureOkfKnowledgeBase(homeDir) {
|
|
204
|
+
const paths = resolveOkfKnowledgePaths(homeDir);
|
|
205
|
+
await ensureLocalKnowledgeGitRepository(paths.knowledgeDir);
|
|
206
|
+
await mkdir(paths.indexesDir, { recursive: true });
|
|
207
|
+
await mkdir(paths.tmpDir, { recursive: true });
|
|
208
|
+
const directories = [
|
|
209
|
+
{
|
|
210
|
+
path: "",
|
|
211
|
+
title: "EvoDev Knowledge",
|
|
212
|
+
description: "User-local active engineering knowledge."
|
|
213
|
+
},
|
|
214
|
+
{ path: "concepts", title: "Core Concepts", description: "Canonical core knowledge." },
|
|
215
|
+
{ path: "concepts/rules", title: "Rules", description: "Normative engineering rules." },
|
|
216
|
+
{
|
|
217
|
+
path: "concepts/decisions",
|
|
218
|
+
title: "Decisions",
|
|
219
|
+
description: "Durable engineering decisions."
|
|
220
|
+
},
|
|
221
|
+
{ path: "concepts/patterns", title: "Patterns", description: "Reusable engineering patterns." },
|
|
222
|
+
{ path: "concepts/warnings", title: "Warnings", description: "Risks and anti-patterns." },
|
|
223
|
+
{ path: "concepts/checklists", title: "Checklists", description: "Verification checklists." },
|
|
224
|
+
{
|
|
225
|
+
path: "concepts/verification",
|
|
226
|
+
title: "Verification",
|
|
227
|
+
description: "Repeatable verification patterns."
|
|
228
|
+
},
|
|
229
|
+
{ path: "concepts/evos", title: "Evolution Cases", description: "Reviewed evolution cases." },
|
|
230
|
+
{ path: "concepts/glossary", title: "Glossary", description: "Terms and taxonomy notes." },
|
|
231
|
+
{ path: "repos", title: "Repositories", description: "Repository attention overlays." },
|
|
232
|
+
{ path: "roles", title: "Roles", description: "Role attention overlays." },
|
|
233
|
+
{ path: "workflows", title: "Workflows", description: "Workflow attention overlays." },
|
|
234
|
+
{ path: "references", title: "References", description: "Cited references." }
|
|
235
|
+
];
|
|
236
|
+
for (const directory of directories) {
|
|
237
|
+
await ensureOkfDirectory(paths.okfDir, directory.path, directory.title, directory.description);
|
|
238
|
+
}
|
|
239
|
+
await rebuildOkfKnowledgeIndexes({ homeDir });
|
|
240
|
+
}
|
|
241
|
+
async function rebuildOkfKnowledgeIndexes(input) {
|
|
242
|
+
const paths = resolveOkfKnowledgePaths(input.homeDir);
|
|
243
|
+
await mkdir(paths.indexesDir, { recursive: true });
|
|
244
|
+
const concepts = (await listOkfKnowledgeConcepts({ homeDir: input.homeDir })).filter(isActiveOkfConcept);
|
|
245
|
+
const conceptSummaries = concepts.map((concept) => ({
|
|
246
|
+
id: concept.id,
|
|
247
|
+
type: concept.type,
|
|
248
|
+
title: concept.title,
|
|
249
|
+
description: concept.description,
|
|
250
|
+
sourceLink: concept.sourceLink,
|
|
251
|
+
stableKey: concept.stableKey,
|
|
252
|
+
reviewState: concept.reviewState,
|
|
253
|
+
tags: concept.tags,
|
|
254
|
+
repoTags: concept.repoTags,
|
|
255
|
+
roleTags: concept.roleTags,
|
|
256
|
+
workflowTags: concept.workflowTags,
|
|
257
|
+
pathScopes: concept.pathScopes
|
|
258
|
+
}));
|
|
259
|
+
const pathsWritten = [
|
|
260
|
+
join(paths.indexesDir, "index.json"),
|
|
261
|
+
join(paths.indexesDir, "concepts.json"),
|
|
262
|
+
join(paths.indexesDir, "repos.json"),
|
|
263
|
+
join(paths.indexesDir, "roles.json"),
|
|
264
|
+
join(paths.indexesDir, "workflows.json")
|
|
265
|
+
];
|
|
266
|
+
await writeJson(pathsWritten[0], {
|
|
267
|
+
schemaVersion: 1,
|
|
268
|
+
kind: "okf-knowledge-index",
|
|
269
|
+
conceptCount: concepts.length,
|
|
270
|
+
updatedAt: new Date().toISOString()
|
|
271
|
+
}, { overwrite: true });
|
|
272
|
+
await writeJson(pathsWritten[1], {
|
|
273
|
+
schemaVersion: 1,
|
|
274
|
+
kind: "okf-concepts-index",
|
|
275
|
+
concepts: conceptSummaries
|
|
276
|
+
}, { overwrite: true });
|
|
277
|
+
await writeJson(pathsWritten[2], {
|
|
278
|
+
schemaVersion: 1,
|
|
279
|
+
kind: "okf-repos-index",
|
|
280
|
+
repos: groupConceptsByTag(concepts, "repoTags")
|
|
281
|
+
}, { overwrite: true });
|
|
282
|
+
await writeJson(pathsWritten[3], {
|
|
283
|
+
schemaVersion: 1,
|
|
284
|
+
kind: "okf-roles-index",
|
|
285
|
+
roles: groupConceptsByTag(concepts, "roleTags")
|
|
286
|
+
}, { overwrite: true });
|
|
287
|
+
await writeJson(pathsWritten[4], {
|
|
288
|
+
schemaVersion: 1,
|
|
289
|
+
kind: "okf-workflows-index",
|
|
290
|
+
workflows: groupConceptsByTag(concepts, "workflowTags")
|
|
291
|
+
}, { overwrite: true });
|
|
292
|
+
return pathsWritten;
|
|
293
|
+
}
|
|
294
|
+
async function listOkfKnowledgeConcepts(input) {
|
|
295
|
+
const okfDir = resolveOkfKnowledgePaths(input.homeDir).okfDir;
|
|
296
|
+
if (!await pathExists(okfDir))
|
|
297
|
+
return [];
|
|
298
|
+
const files = await listMarkdownFiles(okfDir);
|
|
299
|
+
const concepts = [];
|
|
300
|
+
for (const file of files) {
|
|
301
|
+
if (RESERVED_OKF_FILENAMES.has(file.name))
|
|
302
|
+
continue;
|
|
303
|
+
const content = await readFile(file.path, "utf8");
|
|
304
|
+
const parsed = parseOkfConceptFile(okfDir, file.path, content);
|
|
305
|
+
if (parsed !== null)
|
|
306
|
+
concepts.push(parsed);
|
|
307
|
+
}
|
|
308
|
+
return concepts.filter((concept) => matchesConceptFilters(concept, input)).sort((left, right) => left.id.localeCompare(right.id));
|
|
309
|
+
}
|
|
310
|
+
async function ensureLocalKnowledgeGitRepository(knowledgeDir) {
|
|
311
|
+
await mkdir(knowledgeDir, { recursive: true });
|
|
312
|
+
const gitDir = join(knowledgeDir, ".git");
|
|
313
|
+
if (await pathExists(gitDir))
|
|
314
|
+
return;
|
|
315
|
+
await mkdir(join(gitDir, "objects", "info"), { recursive: true });
|
|
316
|
+
await mkdir(join(gitDir, "objects", "pack"), { recursive: true });
|
|
317
|
+
await mkdir(join(gitDir, "refs", "heads"), { recursive: true });
|
|
318
|
+
await mkdir(join(gitDir, "refs", "tags"), { recursive: true });
|
|
319
|
+
await mkdir(join(gitDir, "info"), { recursive: true });
|
|
320
|
+
await writeTextIfMissing(join(gitDir, "HEAD"), `ref: refs/heads/main
|
|
321
|
+
`);
|
|
322
|
+
await writeTextIfMissing(join(gitDir, "config"), [
|
|
323
|
+
"[core]",
|
|
324
|
+
"\trepositoryformatversion = 0",
|
|
325
|
+
"\tfilemode = true",
|
|
326
|
+
"\tbare = false",
|
|
327
|
+
"\tlogallrefupdates = true",
|
|
328
|
+
""
|
|
329
|
+
].join(`
|
|
330
|
+
`));
|
|
331
|
+
await writeTextIfMissing(join(gitDir, "info", "exclude"), [
|
|
332
|
+
"# EvoDev user-local knowledge git repository.",
|
|
333
|
+
"# No remote is configured by default.",
|
|
334
|
+
""
|
|
335
|
+
].join(`
|
|
336
|
+
`));
|
|
337
|
+
}
|
|
338
|
+
async function writeTextIfMissing(path, value) {
|
|
339
|
+
if (await pathExists(path))
|
|
340
|
+
return;
|
|
341
|
+
await mkdir(dirname(path), { recursive: true });
|
|
342
|
+
await writeFile(path, value, { encoding: "utf8", flag: "wx" });
|
|
343
|
+
}
|
|
344
|
+
function sanitizeOkfText(value) {
|
|
345
|
+
return value.replace(new RegExp(PRIVATE_OR_INTERNAL_URL.source, "gi"), "[redacted]").replace(new RegExp(FORBIDDEN_OKF_TEXT.source, "gi"), "[redacted]").replace(/\s+/gu, " ").trim().slice(0, 800);
|
|
346
|
+
}
|
|
347
|
+
async function ensureOkfDirectory(okfDir, relativeDir, title, description) {
|
|
348
|
+
const dir = relativeDir === "" || relativeDir === "." ? okfDir : resolveOkfTargetPath(okfDir, relativeDir);
|
|
349
|
+
await mkdir(dir, { recursive: true });
|
|
350
|
+
const isRoot = dir === okfDir;
|
|
351
|
+
const indexPath = join(dir, "index.md");
|
|
352
|
+
if (!await pathExists(indexPath)) {
|
|
353
|
+
await writeFile(indexPath, isRoot ? [
|
|
354
|
+
"---",
|
|
355
|
+
`okf_version: ${yamlString("0.1")}`,
|
|
356
|
+
`title: ${yamlString(title)}`,
|
|
357
|
+
`description: ${yamlString(description)}`,
|
|
358
|
+
"---",
|
|
359
|
+
"",
|
|
360
|
+
`# ${title}`,
|
|
361
|
+
"",
|
|
362
|
+
description,
|
|
363
|
+
""
|
|
364
|
+
].join(`
|
|
365
|
+
`) : [`# ${title}`, "", description, ""].join(`
|
|
366
|
+
`), "utf8");
|
|
367
|
+
}
|
|
368
|
+
const logPath = join(dir, "log.md");
|
|
369
|
+
if (!await pathExists(logPath)) {
|
|
370
|
+
await writeFile(logPath, [
|
|
371
|
+
"# Directory Update Log",
|
|
372
|
+
"",
|
|
373
|
+
`## ${todayIsoDate()}`,
|
|
374
|
+
"* **Initialization**: Created OKF directory.",
|
|
375
|
+
""
|
|
376
|
+
].join(`
|
|
377
|
+
`), "utf8");
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function parseOkfConceptFile(okfDir, filePath, content) {
|
|
381
|
+
const parsed = extractFrontmatter(content);
|
|
382
|
+
if (parsed === null)
|
|
383
|
+
return null;
|
|
384
|
+
const rel = toOkfRelativePath(okfDir, filePath);
|
|
385
|
+
const id = rel.replace(/\.md$/, "");
|
|
386
|
+
const frontmatter = parsed.frontmatter;
|
|
387
|
+
const stableKey = readIndentedYamlScalar(frontmatter, "stableKey") ?? `legacy:${rel.replace(/\.md$/, "")}`;
|
|
388
|
+
const reviewState = parseOkfReviewState(readIndentedYamlScalar(frontmatter, "reviewState") ?? "auto-stored/unreviewed");
|
|
389
|
+
const type = readYamlScalar(frontmatter, "type") ?? "Unknown";
|
|
390
|
+
const title = readYamlScalar(frontmatter, "title") ?? titleFromSlug(id.split("/").at(-1) ?? id);
|
|
391
|
+
const description = readYamlScalar(frontmatter, "description") ?? "";
|
|
392
|
+
const tags = readYamlList(frontmatter, "tags");
|
|
393
|
+
const repoTags = readYamlList(frontmatter, "repoTags");
|
|
394
|
+
const roleTags = readYamlList(frontmatter, "roleTags");
|
|
395
|
+
const workflowTags = readYamlList(frontmatter, "workflowTags");
|
|
396
|
+
const pathScopes = readYamlList(frontmatter, "pathScopes");
|
|
397
|
+
const lifecycleParsed = parseOkfLifecycle(frontmatter, {
|
|
398
|
+
type,
|
|
399
|
+
path: rel,
|
|
400
|
+
tags,
|
|
401
|
+
title,
|
|
402
|
+
reviewState,
|
|
403
|
+
createdAt: readYamlScalar(frontmatter, "timestamp") ?? undefined
|
|
404
|
+
});
|
|
405
|
+
return {
|
|
406
|
+
id,
|
|
407
|
+
path: filePath,
|
|
408
|
+
sourceLink: `/${rel}`,
|
|
409
|
+
type,
|
|
410
|
+
stableKey,
|
|
411
|
+
reviewState,
|
|
412
|
+
lifecycle: lifecycleParsed.lifecycle,
|
|
413
|
+
lifecyclePersisted: lifecycleParsed.persisted,
|
|
414
|
+
title,
|
|
415
|
+
description,
|
|
416
|
+
tags,
|
|
417
|
+
repoTags,
|
|
418
|
+
roleTags,
|
|
419
|
+
workflowTags,
|
|
420
|
+
pathScopes,
|
|
421
|
+
body: parsed.body
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function matchesConceptFilters(concept, input) {
|
|
425
|
+
if (input.projectKey !== undefined && concept.repoTags.length > 0 && !concept.repoTags.includes(sanitizeSlug(input.projectKey)) && !concept.tags.includes(`repo:${sanitizeSlug(input.projectKey)}`)) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
if (input.roleId !== undefined && concept.roleTags.length > 0 && !concept.roleTags.includes(sanitizeSlug(input.roleId)) && !concept.tags.includes(`role:${sanitizeSlug(input.roleId)}`)) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
if (input.workflowId !== undefined && concept.workflowTags.length > 0 && !concept.workflowTags.includes(sanitizeSlug(input.workflowId)) && !concept.tags.includes(`workflow:${sanitizeSlug(input.workflowId)}`)) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
if (input.paths !== undefined && input.paths.length > 0 && concept.pathScopes.length > 0) {
|
|
435
|
+
return input.paths.some((path) => concept.pathScopes.some((scope) => path.startsWith(scope) || scope.startsWith(path)));
|
|
436
|
+
}
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
function isActiveOkfConcept(concept) {
|
|
440
|
+
return resolveOkfConceptQueryEligibility(concept, {
|
|
441
|
+
includeStale: false,
|
|
442
|
+
now: new Date
|
|
443
|
+
}).include;
|
|
444
|
+
}
|
|
445
|
+
function isActiveOkfReviewState(reviewState) {
|
|
446
|
+
return ACTIVE_OKF_REVIEW_STATES.includes(reviewState);
|
|
447
|
+
}
|
|
448
|
+
function resolveOkfConceptQueryEligibility(concept, input) {
|
|
449
|
+
if (concept.lifecycle.status === "deprecated" || concept.lifecycle.status === "revoked" || concept.lifecycle.status === "superseded" || concept.reviewState === "deprecated" || concept.reviewState === "revoked" || concept.reviewState === "superseded") {
|
|
450
|
+
return { include: false, stale: false, scoreAdjustment: 0, reason: "inactive" };
|
|
451
|
+
}
|
|
452
|
+
const staleByStatus = concept.lifecycle.status === "stale" || concept.reviewState === "stale";
|
|
453
|
+
const staleByDate = isLifecycleDateDue(concept.lifecycle.staleAfter, input.now);
|
|
454
|
+
const stale = staleByStatus || staleByDate;
|
|
455
|
+
if (stale) {
|
|
456
|
+
return {
|
|
457
|
+
include: input.includeStale,
|
|
458
|
+
stale: input.includeStale,
|
|
459
|
+
scoreAdjustment: input.includeStale ? -250 : 0,
|
|
460
|
+
reason: staleByStatus ? "status=stale" : `staleAfter=${concept.lifecycle.staleAfter}`
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
include: isActiveOkfReviewState(concept.reviewState) && concept.lifecycle.status === "active",
|
|
465
|
+
stale: false,
|
|
466
|
+
scoreAdjustment: 0,
|
|
467
|
+
reason: "active"
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function isLifecycleDateDue(value, now) {
|
|
471
|
+
const normalized = normalizeIsoDateString(value);
|
|
472
|
+
return normalized !== null && Date.parse(normalized) <= now.getTime();
|
|
473
|
+
}
|
|
474
|
+
function createDefaultOkfLifecycle(input) {
|
|
475
|
+
const createdAt = normalizeIsoDateString(input.createdAt) ?? "1970-01-01T00:00:00.000Z";
|
|
476
|
+
const policy = resolveOkfLifecyclePolicy(input);
|
|
477
|
+
return {
|
|
478
|
+
status: lifecycleStatusFromReviewState(input.reviewState),
|
|
479
|
+
createdAt,
|
|
480
|
+
lastVerifiedAt: createdAt,
|
|
481
|
+
reviewAfter: addDaysIso(createdAt, policy.reviewAfterDays),
|
|
482
|
+
staleAfter: addDaysIso(createdAt, policy.staleAfterDays),
|
|
483
|
+
supersedes: [],
|
|
484
|
+
supersededBy: null,
|
|
485
|
+
revokedAt: null,
|
|
486
|
+
revokedReason: null
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function parseOkfLifecycle(frontmatter, input) {
|
|
490
|
+
const defaults = createDefaultOkfLifecycle(input);
|
|
491
|
+
const block = extractNestedYamlBlock(frontmatter, "evodev", "lifecycle");
|
|
492
|
+
if (block === null)
|
|
493
|
+
return { lifecycle: defaults, persisted: false };
|
|
494
|
+
const status = readIndentedYamlScalar(block, "status");
|
|
495
|
+
const createdAt = readIndentedYamlScalar(block, "createdAt");
|
|
496
|
+
const lastVerifiedAt = readIndentedYamlScalar(block, "lastVerifiedAt");
|
|
497
|
+
const reviewAfter = readIndentedYamlScalar(block, "reviewAfter");
|
|
498
|
+
const staleAfter = readIndentedYamlScalar(block, "staleAfter");
|
|
499
|
+
const revokedAt = readIndentedYamlScalar(block, "revokedAt");
|
|
500
|
+
return {
|
|
501
|
+
persisted: true,
|
|
502
|
+
lifecycle: {
|
|
503
|
+
status: isOkfLifecycleStatus(status) ? status : defaults.status,
|
|
504
|
+
createdAt: normalizeIsoDateString(createdAt) ?? defaults.createdAt,
|
|
505
|
+
lastVerifiedAt: normalizeIsoDateString(lastVerifiedAt) ?? defaults.lastVerifiedAt,
|
|
506
|
+
reviewAfter: normalizeIsoDateString(reviewAfter) ?? defaults.reviewAfter,
|
|
507
|
+
staleAfter: normalizeIsoDateString(staleAfter) ?? defaults.staleAfter,
|
|
508
|
+
supersedes: readYamlList(block, "supersedes"),
|
|
509
|
+
supersededBy: readNullableLifecycleString(readIndentedYamlScalar(block, "supersededBy")),
|
|
510
|
+
revokedAt: readNullableLifecycleString(revokedAt) === null ? null : normalizeIsoDateString(revokedAt),
|
|
511
|
+
revokedReason: readNullableLifecycleString(readIndentedYamlScalar(block, "revokedReason"))
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function lifecycleStatusFromReviewState(reviewState) {
|
|
516
|
+
if (reviewState === "stale")
|
|
517
|
+
return "stale";
|
|
518
|
+
if (reviewState === "deprecated")
|
|
519
|
+
return "deprecated";
|
|
520
|
+
if (reviewState === "revoked")
|
|
521
|
+
return "revoked";
|
|
522
|
+
if (reviewState === "superseded")
|
|
523
|
+
return "superseded";
|
|
524
|
+
return "active";
|
|
525
|
+
}
|
|
526
|
+
function resolveOkfLifecyclePolicy(input) {
|
|
527
|
+
const comparable = `${input.type} ${input.path} ${input.tags.join(" ")} ${input.title}`.toLowerCase();
|
|
528
|
+
if (/\bverification\b/u.test(comparable))
|
|
529
|
+
return { reviewAfterDays: 120, staleAfterDays: 240 };
|
|
530
|
+
if (/\bworkflow\b/u.test(comparable))
|
|
531
|
+
return { reviewAfterDays: 60, staleAfterDays: 120 };
|
|
532
|
+
return { reviewAfterDays: 90, staleAfterDays: 180 };
|
|
533
|
+
}
|
|
534
|
+
function isOkfLifecycleStatus(value) {
|
|
535
|
+
return OKF_LIFECYCLE_STATUSES.includes(value);
|
|
536
|
+
}
|
|
537
|
+
function normalizeIsoDateString(value) {
|
|
538
|
+
if (value === undefined || value === null || value === "" || value === "null")
|
|
539
|
+
return null;
|
|
540
|
+
const time = Date.parse(value);
|
|
541
|
+
if (!Number.isFinite(time))
|
|
542
|
+
return null;
|
|
543
|
+
return new Date(time).toISOString();
|
|
544
|
+
}
|
|
545
|
+
function addDaysIso(value, days) {
|
|
546
|
+
const date = new Date(value);
|
|
547
|
+
date.setUTCDate(date.getUTCDate() + days);
|
|
548
|
+
return date.toISOString();
|
|
549
|
+
}
|
|
550
|
+
function readNullableLifecycleString(value) {
|
|
551
|
+
if (value === null)
|
|
552
|
+
return null;
|
|
553
|
+
const normalized = sanitizeOkfText(value);
|
|
554
|
+
return normalized === "" || normalized === "null" ? null : normalized;
|
|
555
|
+
}
|
|
556
|
+
function yamlString(value) {
|
|
557
|
+
return JSON.stringify(value);
|
|
558
|
+
}
|
|
559
|
+
function extractFrontmatter(content) {
|
|
560
|
+
if (!content.startsWith(`---
|
|
561
|
+
`))
|
|
562
|
+
return null;
|
|
563
|
+
const end = content.indexOf(`
|
|
564
|
+
---`, 4);
|
|
565
|
+
if (end < 0)
|
|
566
|
+
return null;
|
|
567
|
+
const frontmatter = content.slice(4, end).trim();
|
|
568
|
+
const body = content.slice(end + 4).replace(/^\n/u, "");
|
|
569
|
+
return { frontmatter, body };
|
|
570
|
+
}
|
|
571
|
+
function extractNestedYamlBlock(frontmatter, rootKey, childKey) {
|
|
572
|
+
const lines = frontmatter.split(`
|
|
573
|
+
`);
|
|
574
|
+
const rootIndex = lines.findIndex((line) => line.trim() === `${rootKey}:`);
|
|
575
|
+
if (rootIndex < 0)
|
|
576
|
+
return null;
|
|
577
|
+
const rootEnd = findYamlBlockEnd(lines, rootIndex, 0);
|
|
578
|
+
const childIndex = lines.findIndex((line, index) => index > rootIndex && index < rootEnd && line.startsWith(" ") && line.trim() === `${childKey}:`);
|
|
579
|
+
if (childIndex < 0)
|
|
580
|
+
return null;
|
|
581
|
+
const childEnd = findYamlBlockEnd(lines, childIndex, 2);
|
|
582
|
+
return lines.slice(childIndex + 1, childEnd).join(`
|
|
583
|
+
`);
|
|
584
|
+
}
|
|
585
|
+
function findYamlBlockEnd(lines, startIndex, parentIndent) {
|
|
586
|
+
for (let index = startIndex + 1;index < lines.length; index += 1) {
|
|
587
|
+
const line = lines[index] ?? "";
|
|
588
|
+
if (line.trim() === "")
|
|
589
|
+
continue;
|
|
590
|
+
const indent = line.length - line.trimStart().length;
|
|
591
|
+
if (indent <= parentIndent)
|
|
592
|
+
return index;
|
|
593
|
+
}
|
|
594
|
+
return lines.length;
|
|
595
|
+
}
|
|
596
|
+
function readYamlScalar(frontmatter, key) {
|
|
597
|
+
const match = frontmatter.match(new RegExp(`^${escapeRegExp(key)}:\\s*(.+?)\\s*$`, "m"));
|
|
598
|
+
if (match?.[1] === undefined)
|
|
599
|
+
return null;
|
|
600
|
+
return parseYamlValue(match[1]);
|
|
601
|
+
}
|
|
602
|
+
function readIndentedYamlScalar(frontmatter, key) {
|
|
603
|
+
const match = frontmatter.match(new RegExp(`^\\s*${escapeRegExp(key)}:\\s*(.+?)\\s*$`, "m"));
|
|
604
|
+
if (match?.[1] === undefined)
|
|
605
|
+
return null;
|
|
606
|
+
return parseYamlValue(match[1]);
|
|
607
|
+
}
|
|
608
|
+
function parseOkfReviewState(value) {
|
|
609
|
+
return OKF_REVIEW_STATES.includes(value) ? value : "auto-stored/unreviewed";
|
|
610
|
+
}
|
|
611
|
+
function readYamlList(frontmatter, key) {
|
|
612
|
+
const inline = frontmatter.match(new RegExp(`^\\s*${escapeRegExp(key)}:\\s*\\[(.*?)\\]\\s*$`, "m"));
|
|
613
|
+
if (inline?.[1] !== undefined) {
|
|
614
|
+
return inline[1].split(",").map((item) => parseYamlValue(item.trim())).filter((item) => item !== "");
|
|
615
|
+
}
|
|
616
|
+
const lines = frontmatter.split(`
|
|
617
|
+
`);
|
|
618
|
+
const values = [];
|
|
619
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
620
|
+
const line = lines[index] ?? "";
|
|
621
|
+
if (!line.trim().startsWith(`${key}:`))
|
|
622
|
+
continue;
|
|
623
|
+
for (let next = index + 1;next < lines.length; next += 1) {
|
|
624
|
+
const candidate = lines[next] ?? "";
|
|
625
|
+
if (!/^\s+-\s+/u.test(candidate))
|
|
626
|
+
break;
|
|
627
|
+
values.push(parseYamlValue(candidate.replace(/^\s+-\s+/u, "")));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return [...new Set(values.filter((value) => value !== ""))];
|
|
631
|
+
}
|
|
632
|
+
function parseYamlValue(value) {
|
|
633
|
+
const trimmed = value.trim();
|
|
634
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
635
|
+
try {
|
|
636
|
+
return JSON.parse(trimmed);
|
|
637
|
+
} catch {
|
|
638
|
+
return trimmed.slice(1, -1);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return trimmed.replace(/^['"]|['"]$/gu, "");
|
|
642
|
+
}
|
|
643
|
+
async function listMarkdownFiles(root) {
|
|
644
|
+
if (!await pathExists(root))
|
|
645
|
+
return [];
|
|
646
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
647
|
+
const files = [];
|
|
648
|
+
for (const entry of entries) {
|
|
649
|
+
const path = join(root, entry.name);
|
|
650
|
+
if (entry.isDirectory()) {
|
|
651
|
+
files.push(...await listMarkdownFiles(path));
|
|
652
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
653
|
+
files.push({ path, name: entry.name });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return files;
|
|
657
|
+
}
|
|
658
|
+
function groupConceptsByTag(concepts, field) {
|
|
659
|
+
const groups = new Map;
|
|
660
|
+
for (const concept of concepts) {
|
|
661
|
+
for (const id of concept[field]) {
|
|
662
|
+
groups.set(id, [
|
|
663
|
+
...groups.get(id) ?? [],
|
|
664
|
+
{ id: concept.id, title: concept.title, sourceLink: concept.sourceLink }
|
|
665
|
+
]);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return [...groups.entries()].map(([id, groupedConcepts]) => ({ id, concepts: groupedConcepts }));
|
|
669
|
+
}
|
|
670
|
+
function resolveOkfTargetPath(okfDir, targetPath) {
|
|
671
|
+
const clean = targetPath.replace(/^\/+/u, "");
|
|
672
|
+
if (clean.split("/").some((segment) => segment === ".." || segment === "." || segment === "")) {
|
|
673
|
+
throw new Error(`Unsafe OKF target path: ${targetPath}`);
|
|
674
|
+
}
|
|
675
|
+
const resolved = join(okfDir, clean);
|
|
676
|
+
const rel = relative(okfDir, resolved);
|
|
677
|
+
if (rel.startsWith("..") || rel === "")
|
|
678
|
+
throw new Error(`Unsafe OKF target path: ${targetPath}`);
|
|
679
|
+
return resolved;
|
|
680
|
+
}
|
|
681
|
+
function toOkfRelativePath(okfDir, path) {
|
|
682
|
+
return relative(okfDir, path).replace(/\\/gu, "/");
|
|
683
|
+
}
|
|
684
|
+
function sanitizeSlug(value) {
|
|
685
|
+
const slug = value.trim().toLowerCase().replace(/[^a-z0-9._/-]+/gu, "-").replace(/\/+/gu, "/").replace(/^-+|-+$/gu, "");
|
|
686
|
+
return slug === "" ? "unknown" : slug;
|
|
687
|
+
}
|
|
688
|
+
function titleFromSlug(value) {
|
|
689
|
+
return value.replace(/\.md$/u, "").split(/[/-]/u).filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
690
|
+
}
|
|
691
|
+
function escapeRegExp(value) {
|
|
692
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
693
|
+
}
|
|
694
|
+
function todayIsoDate() {
|
|
695
|
+
return new Date().toISOString().slice(0, 10);
|
|
696
|
+
}
|
|
697
|
+
async function writeJson(path, value, options = {}) {
|
|
698
|
+
await mkdir(dirname(path), { recursive: true });
|
|
699
|
+
const flag = options.overwrite === true ? "w" : "wx";
|
|
700
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}
|
|
701
|
+
`, { encoding: "utf8", flag });
|
|
702
|
+
}
|
|
703
|
+
async function pathExists(path) {
|
|
704
|
+
try {
|
|
705
|
+
await stat(path);
|
|
706
|
+
return true;
|
|
707
|
+
} catch (error) {
|
|
708
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
709
|
+
return false;
|
|
710
|
+
throw error;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
119
714
|
// packages/core/src/evolution/index.ts
|
|
120
715
|
var PROCESS_LOCK_STALE_MS = 5 * 60 * 1000;
|
|
121
716
|
var FORBIDDEN_RAW_KEYS = new Set([
|
|
@@ -189,6 +784,88 @@ var ALLOWED_VERIFICATION_KEYS = new Set([
|
|
|
189
784
|
"type"
|
|
190
785
|
]);
|
|
191
786
|
|
|
787
|
+
// packages/core/src/team/prompts.ts
|
|
788
|
+
var TEAM_ROLE_STARTUP_PROMPT_TEMPLATE = [
|
|
789
|
+
"You are an EvoDev managed role agent.",
|
|
790
|
+
"Team run: {{runId}}",
|
|
791
|
+
"Repository: {{repoRoot}}",
|
|
792
|
+
"Role id: {{roleId}}",
|
|
793
|
+
"Role name: {{roleName}}",
|
|
794
|
+
"Runtime: {{runtime}}",
|
|
795
|
+
"Native Code Agent binding: {{nativeAgentBinding}}",
|
|
796
|
+
"Model: {{model}}",
|
|
797
|
+
"Thinking level: {{thinkingLevel}}",
|
|
798
|
+
"Write mode: {{writeMode}}",
|
|
799
|
+
"Transcript recording: {{transcriptRecording}}",
|
|
800
|
+
"",
|
|
801
|
+
"Current known agents:",
|
|
802
|
+
"{{roster}}",
|
|
803
|
+
"",
|
|
804
|
+
"EvoDev team runtime control contract:",
|
|
805
|
+
"This run is already inside the EvoDev-managed team runtime.",
|
|
806
|
+
"For EvoDev role-agent lifecycle, use the current EvoDev run control plane.",
|
|
807
|
+
"Unprefixed user requests such as 'start the team', 'execute team', 'create agents', or 'spawn roles' mean: use the current EvoDev run control plane.",
|
|
808
|
+
"Do not answer those requests with only a role plan when a role agent should be created.",
|
|
809
|
+
"",
|
|
810
|
+
"Use Teams MCP as the primary control plane:",
|
|
811
|
+
"- list_agents: discover current EvoDev role agents.",
|
|
812
|
+
"- spawn_role: create or reuse exactly one EvoDev role agent.",
|
|
813
|
+
"- send_message: communicate through the EvoDev broker.",
|
|
814
|
+
"- stop_role: stop a role agent when allowed.",
|
|
815
|
+
"If Teams MCP is unavailable, fall back to the EvoDev CLI from this repository:",
|
|
816
|
+
"- evodev team spawn --role <roleId>",
|
|
817
|
+
"- evodev team send --to <roleId> --message <text>",
|
|
818
|
+
"- evodev team status",
|
|
819
|
+
"Teams MCP defaults are inherited from the environment:",
|
|
820
|
+
"EVODEV_TEAM_RUN_ID={{runId}}",
|
|
821
|
+
"EVODEV_TEAM_ROLE_ID={{roleId}}",
|
|
822
|
+
"When calling Teams MCP tools, let the MCP server-bound environment identify this run and role.",
|
|
823
|
+
"Do not operate tmux directly for role lifecycle; let EvoDev create and track panes.",
|
|
824
|
+
"Any server-bound role may request role lifecycle changes; EvoDev records and tracks panes but does not use role policy to stop execution.",
|
|
825
|
+
"{{roleGuidance}}",
|
|
826
|
+
"{{nativeAgentInstruction}}",
|
|
827
|
+
"Team messages are durably queued and delivered at hook safe points, not pasted into a live prompt. cc values are audit context; they are not an instruction for main to immediately forward or act.",
|
|
828
|
+
"{{scopedContext}}",
|
|
829
|
+
"",
|
|
830
|
+
"{{rolePrompt}}"
|
|
831
|
+
].join(`
|
|
832
|
+
`);
|
|
833
|
+
var MAIN_ROLE_GUIDANCE = [
|
|
834
|
+
"As main, you are the planner and delegator for complex work, not a standby worker.",
|
|
835
|
+
"On each user prompt or hook-delivered inbox message, decide whether to answer directly, ask for clarification, or use team execution.",
|
|
836
|
+
"Use team execution only when role separation improves correctness, coverage, safety, or latency.",
|
|
837
|
+
"When team execution is needed, create an upfront task plan with required role ids, role-specific assignments, dependencies, and runnable batches.",
|
|
838
|
+
"Spawn or reuse all roles needed for the first runnable batch and send each role a self-contained task message.",
|
|
839
|
+
"After all currently runnable tasks are delegated, finish your current turn immediately; do not call sleep, wait idly, poll list_agents/status, or keep the turn alive to watch progress.",
|
|
840
|
+
"Use list_agents only for one-time roster discovery when the current roster is genuinely unknown, never as a progress check.",
|
|
841
|
+
"Resume coordination only when the user sends new input or a role message is delivered by hooks; then decide whether to adjust tasks, send supplemental instructions, spawn dependent roles, synthesize completed results, or ask the user.",
|
|
842
|
+
"While delegated role work is pending, do not perform concrete implementation, testing, package research, or review work yourself."
|
|
843
|
+
].join(" ");
|
|
844
|
+
|
|
845
|
+
// packages/core/src/team/index.ts
|
|
846
|
+
var TEAM_INTERNAL_WAKE_SIGNAL = [
|
|
847
|
+
"[EvoDev internal wake signal]",
|
|
848
|
+
"No user request is included in this message. Continue only from EvoDev team inbox messages injected by hooks."
|
|
849
|
+
].join(`
|
|
850
|
+
`);
|
|
851
|
+
var BUILT_IN_TEAM_DEFINITION = {
|
|
852
|
+
version: 1,
|
|
853
|
+
name: "builtin-minimal-team",
|
|
854
|
+
description: "Built-in minimal EvoDev team fallback.",
|
|
855
|
+
agents: {
|
|
856
|
+
executor: "builtin:executor",
|
|
857
|
+
reviewer: "builtin:reviewer",
|
|
858
|
+
tester: "builtin:tester"
|
|
859
|
+
},
|
|
860
|
+
body: [
|
|
861
|
+
"# Built-in Minimal Team",
|
|
862
|
+
"",
|
|
863
|
+
"Use role agents only when delegation improves correctness, coverage, safety, or latency.",
|
|
864
|
+
"Spawn roles on demand and send self-contained assignments through Teams MCP."
|
|
865
|
+
].join(`
|
|
866
|
+
`)
|
|
867
|
+
};
|
|
868
|
+
|
|
192
869
|
// packages/core/src/hooks/index.ts
|
|
193
870
|
var CANONICAL_HOOK_EVENT_TYPES = [
|
|
194
871
|
"SessionStart",
|
|
@@ -242,6 +919,20 @@ var DEFAULT_EVENT_SETTINGS = {
|
|
|
242
919
|
WorktreeCreate: true,
|
|
243
920
|
WorktreeRemove: true
|
|
244
921
|
};
|
|
922
|
+
var TEAM_MESSAGE_DELIVERY_EVENTS = new Set([
|
|
923
|
+
"SessionStart",
|
|
924
|
+
"UserPromptSubmit",
|
|
925
|
+
"PostToolUse",
|
|
926
|
+
"PostToolUseFailure",
|
|
927
|
+
"Stop",
|
|
928
|
+
"TeammateIdle",
|
|
929
|
+
"SubagentStop",
|
|
930
|
+
"TaskCompleted"
|
|
931
|
+
]);
|
|
932
|
+
var CODEX_STOP_EVENTS_WITHOUT_ADDITIONAL_CONTEXT = new Set([
|
|
933
|
+
"Stop",
|
|
934
|
+
"SubagentStop"
|
|
935
|
+
]);
|
|
245
936
|
function createDefaultHookSettings() {
|
|
246
937
|
return {
|
|
247
938
|
enabled: true,
|
|
@@ -256,8 +947,8 @@ function createDefaultHookSettings() {
|
|
|
256
947
|
}
|
|
257
948
|
},
|
|
258
949
|
observability: {
|
|
259
|
-
metadataOnly:
|
|
260
|
-
rawPayloadStorage:
|
|
950
|
+
metadataOnly: true,
|
|
951
|
+
rawPayloadStorage: false,
|
|
261
952
|
appendEvents: false
|
|
262
953
|
},
|
|
263
954
|
learning: {
|
|
@@ -282,8 +973,8 @@ function parseHookSettings(value) {
|
|
|
282
973
|
codex: parseHookTargetSettings(value.targets, defaults.targets.codex, "codex")
|
|
283
974
|
},
|
|
284
975
|
observability: {
|
|
285
|
-
metadataOnly:
|
|
286
|
-
rawPayloadStorage:
|
|
976
|
+
metadataOnly: true,
|
|
977
|
+
rawPayloadStorage: false,
|
|
287
978
|
appendEvents: optionalBoolean(observability?.appendEvents, defaults.observability.appendEvents, "hooks.observability.appendEvents")
|
|
288
979
|
},
|
|
289
980
|
learning: {
|
|
@@ -345,7 +1036,8 @@ function createDefaultSettings(os = process.platform) {
|
|
|
345
1036
|
lastRunAt: null
|
|
346
1037
|
},
|
|
347
1038
|
hooks: createDefaultHookSettings(),
|
|
348
|
-
teamRuntime: createDefaultTeamRuntimeSettings()
|
|
1039
|
+
teamRuntime: createDefaultTeamRuntimeSettings(),
|
|
1040
|
+
memory: createDefaultMemorySettings()
|
|
349
1041
|
};
|
|
350
1042
|
}
|
|
351
1043
|
function createDefaultTeamRuntimeSettings() {
|
|
@@ -353,7 +1045,16 @@ function createDefaultTeamRuntimeSettings() {
|
|
|
353
1045
|
defaultRuntime: "codex",
|
|
354
1046
|
defaultModel: null,
|
|
355
1047
|
defaultThinkingLevel: null,
|
|
356
|
-
recordTranscript: false
|
|
1048
|
+
recordTranscript: false,
|
|
1049
|
+
displayMode: "normal"
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
function createDefaultMemorySettings() {
|
|
1053
|
+
return {
|
|
1054
|
+
autoAccept: true,
|
|
1055
|
+
runtimeInjection: true,
|
|
1056
|
+
staleReview: true,
|
|
1057
|
+
lexicalIndex: true
|
|
357
1058
|
};
|
|
358
1059
|
}
|
|
359
1060
|
function mergeSettings(existing, defaults = createDefaultSettings()) {
|
|
@@ -392,10 +1093,24 @@ function mergeSettings(existing, defaults = createDefaultSettings()) {
|
|
|
392
1093
|
teamRuntime: {
|
|
393
1094
|
...defaults.teamRuntime,
|
|
394
1095
|
...existing.teamRuntime
|
|
1096
|
+
},
|
|
1097
|
+
memory: {
|
|
1098
|
+
...defaults.memory,
|
|
1099
|
+
...existing.memory
|
|
395
1100
|
}
|
|
396
1101
|
};
|
|
397
1102
|
return parseSettings(merged);
|
|
398
1103
|
}
|
|
1104
|
+
async function readRuntimeInjectionSettings(homeDir) {
|
|
1105
|
+
const paths = resolveEvoDevPaths(homeDir);
|
|
1106
|
+
try {
|
|
1107
|
+
return parseSettings(JSON.parse(await readFile2(paths.settingsPath, "utf8"))).memory;
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
if (isNotFoundError(error))
|
|
1110
|
+
return createDefaultMemorySettings();
|
|
1111
|
+
throw error;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
399
1114
|
function parseSettings(value) {
|
|
400
1115
|
const root = expectRecord2(value, "settings");
|
|
401
1116
|
const version = root.version;
|
|
@@ -427,10 +1142,21 @@ function parseSettings(value) {
|
|
|
427
1142
|
lastRunAt: expectNullableString(doctor.lastRunAt, "settings.doctor.lastRunAt")
|
|
428
1143
|
},
|
|
429
1144
|
hooks: parseHookSettings(root.hooks),
|
|
430
|
-
teamRuntime: parseTeamRuntimeSettings(root.teamRuntime ?? createDefaultTeamRuntimeSettings(), "settings.teamRuntime")
|
|
1145
|
+
teamRuntime: parseTeamRuntimeSettings(root.teamRuntime ?? createDefaultTeamRuntimeSettings(), "settings.teamRuntime"),
|
|
1146
|
+
memory: parseMemorySettings(root.memory ?? createDefaultMemorySettings(), "settings.memory")
|
|
431
1147
|
};
|
|
432
1148
|
return parsed;
|
|
433
1149
|
}
|
|
1150
|
+
function parseMemorySettings(value, path) {
|
|
1151
|
+
const input = expectRecord2(value, path);
|
|
1152
|
+
const defaults = createDefaultMemorySettings();
|
|
1153
|
+
return {
|
|
1154
|
+
autoAccept: input.autoAccept === undefined ? defaults.autoAccept : expectBoolean(input.autoAccept, `${path}.autoAccept`),
|
|
1155
|
+
runtimeInjection: input.runtimeInjection === undefined ? defaults.runtimeInjection : expectBoolean(input.runtimeInjection, `${path}.runtimeInjection`),
|
|
1156
|
+
staleReview: input.staleReview === undefined ? defaults.staleReview : expectBoolean(input.staleReview, `${path}.staleReview`),
|
|
1157
|
+
lexicalIndex: input.lexicalIndex === undefined ? defaults.lexicalIndex : expectBoolean(input.lexicalIndex, `${path}.lexicalIndex`)
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
434
1160
|
function parseTeamRuntimeSettings(value, path) {
|
|
435
1161
|
const input = expectRecord2(value, path);
|
|
436
1162
|
const defaults = createDefaultTeamRuntimeSettings();
|
|
@@ -442,9 +1168,17 @@ function parseTeamRuntimeSettings(value, path) {
|
|
|
442
1168
|
defaultRuntime,
|
|
443
1169
|
defaultModel: input.defaultModel === undefined ? defaults.defaultModel : expectNullableString(input.defaultModel, `${path}.defaultModel`),
|
|
444
1170
|
defaultThinkingLevel: input.defaultThinkingLevel === undefined ? defaults.defaultThinkingLevel : expectNullableString(input.defaultThinkingLevel, `${path}.defaultThinkingLevel`),
|
|
445
|
-
recordTranscript: input.recordTranscript === undefined ? defaults.recordTranscript : expectBoolean(input.recordTranscript, `${path}.recordTranscript`)
|
|
1171
|
+
recordTranscript: input.recordTranscript === undefined ? defaults.recordTranscript : expectBoolean(input.recordTranscript, `${path}.recordTranscript`),
|
|
1172
|
+
displayMode: parseTeamRuntimeDisplayMode(input.displayMode, defaults.displayMode, path)
|
|
446
1173
|
};
|
|
447
1174
|
}
|
|
1175
|
+
function parseTeamRuntimeDisplayMode(value, fallback, path) {
|
|
1176
|
+
if (value === undefined)
|
|
1177
|
+
return fallback;
|
|
1178
|
+
if (value === "normal" || value === "development")
|
|
1179
|
+
return value;
|
|
1180
|
+
throw new EvoDevConfigError(`Invalid ${path}.displayMode; expected normal or development`);
|
|
1181
|
+
}
|
|
448
1182
|
function parsePluginSettings(value, path) {
|
|
449
1183
|
const input = expectRecord2(value, path);
|
|
450
1184
|
const parsed = {
|
|
@@ -464,6 +1198,9 @@ function expectRecord2(value, path) {
|
|
|
464
1198
|
}
|
|
465
1199
|
return value;
|
|
466
1200
|
}
|
|
1201
|
+
function isNotFoundError(error) {
|
|
1202
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1203
|
+
}
|
|
467
1204
|
function expectString2(value, path) {
|
|
468
1205
|
if (typeof value !== "string" || value.length === 0) {
|
|
469
1206
|
throw new EvoDevConfigError(`Invalid ${path}; expected non-empty string`);
|
|
@@ -563,20 +1300,20 @@ function expectNonNegativeInteger(value, path) {
|
|
|
563
1300
|
return value;
|
|
564
1301
|
}
|
|
565
1302
|
// packages/core/src/config/store.ts
|
|
566
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
567
|
-
import { dirname } from "node:path";
|
|
1303
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
1304
|
+
import { dirname as dirname2 } from "node:path";
|
|
568
1305
|
function createCoreConfigStore(homeDir) {
|
|
569
1306
|
const paths = resolveEvoDevPaths(homeDir);
|
|
570
1307
|
return {
|
|
571
1308
|
paths,
|
|
572
1309
|
async ensureBaseDirs() {
|
|
573
|
-
await
|
|
574
|
-
await
|
|
575
|
-
await
|
|
576
|
-
await
|
|
577
|
-
await
|
|
578
|
-
await
|
|
579
|
-
await
|
|
1310
|
+
await mkdir2(paths.stateDir, { recursive: true });
|
|
1311
|
+
await mkdir2(paths.logsDir, { recursive: true });
|
|
1312
|
+
await mkdir2(paths.knowledgeDir, { recursive: true });
|
|
1313
|
+
await mkdir2(paths.evosCasesDir, { recursive: true });
|
|
1314
|
+
await mkdir2(paths.roleAgentsDir, { recursive: true });
|
|
1315
|
+
await mkdir2(paths.teamsDir, { recursive: true });
|
|
1316
|
+
await mkdir2(paths.runsDir, { recursive: true });
|
|
580
1317
|
},
|
|
581
1318
|
async ensureKnowledgeBase() {
|
|
582
1319
|
await ensureKnowledgeBaseFiles(paths);
|
|
@@ -624,9 +1361,10 @@ async function initializeCoreConfig(homeDir) {
|
|
|
624
1361
|
return store;
|
|
625
1362
|
}
|
|
626
1363
|
async function ensureKnowledgeBaseFiles(paths) {
|
|
627
|
-
await
|
|
628
|
-
await
|
|
629
|
-
await
|
|
1364
|
+
await mkdir2(paths.knowledgeDir, { recursive: true });
|
|
1365
|
+
await mkdir2(paths.evosCasesDir, { recursive: true });
|
|
1366
|
+
await ensureOkfKnowledgeBase(paths.homeDir);
|
|
1367
|
+
await writeTextIfMissing2(`${paths.knowledgeDir}/README.md`, [
|
|
630
1368
|
"# EvoDev Knowledge",
|
|
631
1369
|
"",
|
|
632
1370
|
"Local-private knowledge base for user-accepted facts, decisions, architecture notes, and reusable domain context.",
|
|
@@ -641,7 +1379,7 @@ async function ensureKnowledgeBaseFiles(paths) {
|
|
|
641
1379
|
roleTags: [],
|
|
642
1380
|
entries: []
|
|
643
1381
|
});
|
|
644
|
-
await
|
|
1382
|
+
await writeTextIfMissing2(`${paths.evosDir}/README.md`, [
|
|
645
1383
|
"# EvoDev Evos",
|
|
646
1384
|
"",
|
|
647
1385
|
"Local-private evolution case library for reviewed improvement cases and reusable process changes.",
|
|
@@ -650,7 +1388,7 @@ async function ensureKnowledgeBaseFiles(paths) {
|
|
|
650
1388
|
""
|
|
651
1389
|
].join(`
|
|
652
1390
|
`));
|
|
653
|
-
await
|
|
1391
|
+
await writeTextIfMissing2(`${paths.evosCasesDir}/README.md`, [
|
|
654
1392
|
"# Evolution Cases",
|
|
655
1393
|
"",
|
|
656
1394
|
"Store one reviewed evolution case per file. Do not store raw prompts, source dumps, secrets, transcripts, or raw command output here.",
|
|
@@ -663,7 +1401,7 @@ async function ensureKnowledgeBaseFiles(paths) {
|
|
|
663
1401
|
roleTags: [],
|
|
664
1402
|
cases: []
|
|
665
1403
|
});
|
|
666
|
-
await
|
|
1404
|
+
await writeTextIfMissing2(`${paths.roleAgentsDir}/README.md`, [
|
|
667
1405
|
"# Role Agents",
|
|
668
1406
|
"",
|
|
669
1407
|
"Local-private role agent registry for EvoDev-managed agent roles and user-reviewed role extensions.",
|
|
@@ -678,7 +1416,7 @@ async function ensureKnowledgeBaseFiles(paths) {
|
|
|
678
1416
|
roles: [],
|
|
679
1417
|
projectExtensions: []
|
|
680
1418
|
});
|
|
681
|
-
await
|
|
1419
|
+
await writeTextIfMissing2(`${paths.teamsDir}/README.md`, [
|
|
682
1420
|
"# Agent Teams",
|
|
683
1421
|
"",
|
|
684
1422
|
"Local-private EvoHub team registry for reviewed role-agent team definitions.",
|
|
@@ -696,7 +1434,7 @@ async function ensureKnowledgeBaseFiles(paths) {
|
|
|
696
1434
|
async function readJsonFile(filePath, parse) {
|
|
697
1435
|
let raw;
|
|
698
1436
|
try {
|
|
699
|
-
raw = await
|
|
1437
|
+
raw = await readFile3(filePath, "utf8");
|
|
700
1438
|
} catch (error) {
|
|
701
1439
|
throw new EvoDevConfigError(`Cannot read config file (${describeFileError(error)})`, filePath);
|
|
702
1440
|
}
|
|
@@ -727,7 +1465,7 @@ async function readJsonFileOrDefault(filePath, parse, fallback) {
|
|
|
727
1465
|
}
|
|
728
1466
|
async function writeIfMissing(filePath, value) {
|
|
729
1467
|
try {
|
|
730
|
-
await
|
|
1468
|
+
await readFile3(filePath, "utf8");
|
|
731
1469
|
} catch (error) {
|
|
732
1470
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
733
1471
|
await writeJsonFile(filePath, value);
|
|
@@ -739,7 +1477,7 @@ async function writeIfMissing(filePath, value) {
|
|
|
739
1477
|
async function writeIndexIfMissingOrMigrate(filePath, kind, defaults) {
|
|
740
1478
|
let raw;
|
|
741
1479
|
try {
|
|
742
|
-
raw = await
|
|
1480
|
+
raw = await readFile3(filePath, "utf8");
|
|
743
1481
|
} catch (error) {
|
|
744
1482
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
745
1483
|
await writeJsonFile(filePath, defaults);
|
|
@@ -760,21 +1498,21 @@ async function writeIndexIfMissingOrMigrate(filePath, kind, defaults) {
|
|
|
760
1498
|
return;
|
|
761
1499
|
await writeJsonFile(filePath, migrated);
|
|
762
1500
|
}
|
|
763
|
-
async function
|
|
1501
|
+
async function writeTextIfMissing2(filePath, value) {
|
|
764
1502
|
try {
|
|
765
|
-
await
|
|
1503
|
+
await readFile3(filePath, "utf8");
|
|
766
1504
|
} catch (error) {
|
|
767
1505
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
768
|
-
await
|
|
769
|
-
await
|
|
1506
|
+
await mkdir2(dirname2(filePath), { recursive: true });
|
|
1507
|
+
await writeFile2(filePath, value, "utf8");
|
|
770
1508
|
return;
|
|
771
1509
|
}
|
|
772
1510
|
throw new EvoDevConfigError(`Cannot inspect config file (${describeFileError(error)})`, filePath);
|
|
773
1511
|
}
|
|
774
1512
|
}
|
|
775
1513
|
async function writeJsonFile(filePath, value) {
|
|
776
|
-
await
|
|
777
|
-
await
|
|
1514
|
+
await mkdir2(dirname2(filePath), { recursive: true });
|
|
1515
|
+
await writeFile2(filePath, `${JSON.stringify(value, null, 2)}
|
|
778
1516
|
`, "utf8");
|
|
779
1517
|
}
|
|
780
1518
|
function describeFileError(error) {
|
|
@@ -791,6 +1529,7 @@ function isRecord2(value) {
|
|
|
791
1529
|
}
|
|
792
1530
|
export {
|
|
793
1531
|
resolveEvoDevPaths,
|
|
1532
|
+
readRuntimeInjectionSettings,
|
|
794
1533
|
parseSyncState,
|
|
795
1534
|
parseSettings,
|
|
796
1535
|
parseRegistry,
|
|
@@ -801,6 +1540,7 @@ export {
|
|
|
801
1540
|
createDefaultSyncState,
|
|
802
1541
|
createDefaultSettings,
|
|
803
1542
|
createDefaultRegistry,
|
|
1543
|
+
createDefaultMemorySettings,
|
|
804
1544
|
createDefaultInstallState,
|
|
805
1545
|
createCoreConfigStore,
|
|
806
1546
|
EvoDevConfigError
|