@evo-dev/core 0.0.1-alpha.1 → 0.0.1-alpha.10
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 +117 -114
- 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 +925 -97
- package/dist/index.js +13107 -5618
- package/package.json +5 -1
- package/src/agents/index.ts +56 -264
- package/src/code-agent-traces/index.ts +520 -0
- package/src/config/index.ts +5 -0
- package/src/config/paths.ts +1 -1
- package/src/config/settings.ts +149 -0
- package/src/config/store.ts +2 -0
- package/src/daemon/index.ts +99 -50
- package/src/evolution/candidates/index.ts +564 -0
- package/src/evolution/control/index.ts +20 -0
- package/src/evolution/evidence/analysis.ts +533 -0
- package/src/evolution/evidence/index.ts +3 -0
- package/src/evolution/evidence/session-memory/analysis.ts +281 -0
- package/src/evolution/evidence/session-memory/constants.ts +9 -0
- package/src/evolution/evidence/session-memory/index.ts +7 -0
- package/src/evolution/evidence/session-memory/paths.ts +29 -0
- package/src/evolution/evidence/session-memory/policy.ts +39 -0
- package/src/evolution/evidence/session-memory/segment.ts +202 -0
- package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
- package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
- package/src/evolution/evidence/session-memory/storage.ts +379 -0
- package/src/evolution/evidence/session-memory/types.ts +221 -0
- package/src/evolution/evidence/session-memory/updater.ts +191 -0
- package/src/evolution/formatters.ts +169 -0
- package/src/evolution/index.ts +16 -2356
- package/src/evolution/knowledge/index.ts +5427 -0
- package/src/evolution/paths.ts +44 -0
- package/src/evolution/processor/distillation.ts +518 -0
- package/src/evolution/processor/index.ts +3 -0
- package/src/evolution/processor/process.ts +528 -0
- package/src/{learning → evolution/review}/index.ts +10 -14
- package/src/evolution/schema.ts +568 -0
- package/src/evolution/shared.ts +758 -0
- package/src/evolution/triggers/classification.ts +102 -0
- package/src/evolution/triggers/index.ts +295 -0
- package/src/hooks/index.ts +438 -179
- package/src/index.ts +12 -3
- package/src/projects/index.ts +453 -0
- package/src/runtime-logs/index.ts +490 -24
- package/src/team/index.ts +1429 -185
- package/src/team/mcp.ts +9 -5
- package/src/team/prompts.ts +141 -0
- package/src/utils/errors.ts +13 -0
- package/src/utils/fs.ts +40 -0
- package/src/utils/hash.ts +9 -0
- package/src/utils/ids.ts +12 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/parsing.ts +11 -0
- package/src/utils/text.ts +18 -0
- package/src/utils/time.ts +5 -0
- package/src/workflow/index.ts +3 -21
- package/src/project/index.ts +0 -507
- package/src/task/index.ts +0 -840
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,7 +116,79 @@ function expectString(value, path) {
|
|
|
116
116
|
}
|
|
117
117
|
return value;
|
|
118
118
|
}
|
|
119
|
-
// packages/core/src/
|
|
119
|
+
// packages/core/src/config/settings.ts
|
|
120
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
121
|
+
// packages/core/src/utils/parsing.ts
|
|
122
|
+
function optionalBoolean(value, fallback) {
|
|
123
|
+
return typeof value === "boolean" ? value : fallback;
|
|
124
|
+
}
|
|
125
|
+
function positiveInteger(value, fallback) {
|
|
126
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : fallback;
|
|
127
|
+
}
|
|
128
|
+
// packages/core/src/evolution/evidence/session-memory/sensitivity.ts
|
|
129
|
+
var SESSION_MEMORY_CREDENTIAL_REDACTION = "[credential-redacted]";
|
|
130
|
+
var CREDENTIAL_FIELD_KEYS = new Set([
|
|
131
|
+
"accesstoken",
|
|
132
|
+
"apikey",
|
|
133
|
+
"auth",
|
|
134
|
+
"authtoken",
|
|
135
|
+
"authorization",
|
|
136
|
+
"clientsecret",
|
|
137
|
+
"cookie",
|
|
138
|
+
"credential",
|
|
139
|
+
"credentials",
|
|
140
|
+
"idtoken",
|
|
141
|
+
"password",
|
|
142
|
+
"passwd",
|
|
143
|
+
"privatekey",
|
|
144
|
+
"proxyauthorization",
|
|
145
|
+
"refreshtoken",
|
|
146
|
+
"secret",
|
|
147
|
+
"secretvalue",
|
|
148
|
+
"sessiontoken",
|
|
149
|
+
"setcookie",
|
|
150
|
+
"signingkey",
|
|
151
|
+
"token"
|
|
152
|
+
]);
|
|
153
|
+
var CREDENTIAL_KEY_PATTERN = String.raw`(?:access[_-]?token|api[_-]?key|auth(?:orization)?|client[_-]?secret|cookie|credential(?:s)?|id[_-]?token|password|passwd|private[_-]?key|proxy[_-]?authorization|refresh[_-]?token|secret(?:[_-]?value)?|session[_-]?token|set[_-]?cookie|signing[_-]?key|token)`;
|
|
154
|
+
var CREDENTIAL_SHAPE_PATTERNS = [
|
|
155
|
+
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/i,
|
|
156
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
157
|
+
/\bgh[pousr]_[A-Za-z0-9]{20,}\b/,
|
|
158
|
+
/\bxox[baprs]-[A-Za-z0-9-]{16,}\b/,
|
|
159
|
+
/\bBearer\s+[A-Za-z0-9._~+/=-]+/i,
|
|
160
|
+
/\bBasic\s+[A-Za-z0-9+/]+={0,2}/i,
|
|
161
|
+
new RegExp(String.raw`(?:^|[^A-Za-z0-9_])["']?${CREDENTIAL_KEY_PATTERN}["']?\s*[:=]\s*["']?[A-Za-z0-9][A-Za-z0-9._~+/=-]*`, "i"),
|
|
162
|
+
new RegExp(String.raw`(?:^|[^A-Za-z0-9_])["']?${CREDENTIAL_KEY_PATTERN}["']?\s*[:=]\s*["'](?!\[credential-redacted\]["'])[^"'\r\n]+["']`, "i")
|
|
163
|
+
];
|
|
164
|
+
var PRIVATE_KEY_BLOCK_PATTERN = /-----BEGIN ([A-Z0-9 ]*PRIVATE KEY)-----[\s\S]*?-----END \1-----/giu;
|
|
165
|
+
var AUTHORIZATION_HEADER_PATTERN = /(^|[\r\n])(\s*(?:authorization|proxy-authorization)\s*:\s*)(?:Bearer|Basic)\s+[^\r\n]+/gimu;
|
|
166
|
+
var COOKIE_HEADER_PATTERN = /(^|[\r\n])(\s*(?:cookie|set-cookie)\s*:\s*)[^\r\n]+/gimu;
|
|
167
|
+
var BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/giu;
|
|
168
|
+
var BASIC_PATTERN = /\bBasic\s+[A-Za-z0-9+/]+={0,2}/giu;
|
|
169
|
+
var AWS_ACCESS_KEY_PATTERN = /\bAKIA[0-9A-Z]{16}\b/gu;
|
|
170
|
+
var GITHUB_TOKEN_PATTERN = /\bgh[pousr]_[A-Za-z0-9]{20,}\b/gu;
|
|
171
|
+
var SLACK_TOKEN_PATTERN = /\bxox[baprs]-[A-Za-z0-9-]{16,}\b/gu;
|
|
172
|
+
var QUOTED_SERIALIZED_CREDENTIAL_PATTERN = new RegExp(String.raw`(^|[^A-Za-z0-9_])(["']?${CREDENTIAL_KEY_PATTERN}["']?\s*[:=]\s*)(["'])([^"'\r\n]+)(\3)`, "gimu");
|
|
173
|
+
var SERIALIZED_CREDENTIAL_PATTERN = new RegExp(String.raw`(^|[^A-Za-z0-9_])(["']?${CREDENTIAL_KEY_PATTERN}["']?\s*[:=]\s*)(["']?)([A-Za-z0-9][A-Za-z0-9._~+/=-]*)(\3)`, "gimu");
|
|
174
|
+
var URL_USERINFO_PATTERN = /(https?:\/\/)[^@\s/"']+@/giu;
|
|
175
|
+
var URL_CREDENTIAL_PARAMETER_PATTERN = new RegExp(String.raw`([?&#]["']?${CREDENTIAL_KEY_PATTERN}["']?=)[^&#\s"'<>]+`, "giu");
|
|
176
|
+
function redactSessionMemoryCredentialText(value) {
|
|
177
|
+
let redacted = value.replace(PRIVATE_KEY_BLOCK_PATTERN, SESSION_MEMORY_CREDENTIAL_REDACTION).replace(AUTHORIZATION_HEADER_PATTERN, (_, boundary, prefix) => {
|
|
178
|
+
return `${boundary}${prefix}${SESSION_MEMORY_CREDENTIAL_REDACTION}`;
|
|
179
|
+
}).replace(COOKIE_HEADER_PATTERN, (_, boundary, prefix) => {
|
|
180
|
+
return `${boundary}${prefix}${SESSION_MEMORY_CREDENTIAL_REDACTION}`;
|
|
181
|
+
}).replace(BEARER_PATTERN, `Bearer ${SESSION_MEMORY_CREDENTIAL_REDACTION}`).replace(BASIC_PATTERN, `Basic ${SESSION_MEMORY_CREDENTIAL_REDACTION}`).replace(AWS_ACCESS_KEY_PATTERN, SESSION_MEMORY_CREDENTIAL_REDACTION).replace(GITHUB_TOKEN_PATTERN, SESSION_MEMORY_CREDENTIAL_REDACTION).replace(SLACK_TOKEN_PATTERN, SESSION_MEMORY_CREDENTIAL_REDACTION).replace(QUOTED_SERIALIZED_CREDENTIAL_PATTERN, (_, boundary, prefix, quote) => `${boundary}${prefix}${quote}${SESSION_MEMORY_CREDENTIAL_REDACTION}${quote}`).replace(SERIALIZED_CREDENTIAL_PATTERN, (_, boundary, prefix, quote, credentialValue) => {
|
|
182
|
+
const trailingPunctuation = quote === "" ? credentialValue.match(/[.,;!?]+$/u)?.[0] ?? "" : "";
|
|
183
|
+
return `${boundary}${prefix}${quote}${SESSION_MEMORY_CREDENTIAL_REDACTION}${quote}${trailingPunctuation}`;
|
|
184
|
+
}).replace(URL_CREDENTIAL_PARAMETER_PATTERN, (_, prefix) => `${prefix}${SESSION_MEMORY_CREDENTIAL_REDACTION}`).replace(URL_USERINFO_PATTERN, (_, protocol) => protocol);
|
|
185
|
+
if (redacted === "")
|
|
186
|
+
redacted = value;
|
|
187
|
+
return { value: redacted, redacted: redacted !== value };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// packages/core/src/evolution/shared.ts
|
|
191
|
+
var MAX_PROPOSED_CHANGE_LENGTH = 16 * 1024;
|
|
120
192
|
var PROCESS_LOCK_STALE_MS = 5 * 60 * 1000;
|
|
121
193
|
var FORBIDDEN_RAW_KEYS = new Set([
|
|
122
194
|
"commandhistory",
|
|
@@ -152,43 +224,693 @@ var FORBIDDEN_RAW_KEYS = new Set([
|
|
|
152
224
|
"transcriptbody",
|
|
153
225
|
"transcripttext"
|
|
154
226
|
]);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
"
|
|
184
|
-
"
|
|
227
|
+
// packages/core/src/evolution/evidence/session-memory/policy.ts
|
|
228
|
+
function createDefaultSessionMemoryPolicy() {
|
|
229
|
+
return {
|
|
230
|
+
enabled: true,
|
|
231
|
+
storeRawSegments: true,
|
|
232
|
+
maxRawSegmentBytes: 200000,
|
|
233
|
+
retentionDays: 30,
|
|
234
|
+
minimumMessageTokensToInit: 1e4,
|
|
235
|
+
minimumTokensBetweenUpdate: 5000,
|
|
236
|
+
toolCallsBetweenUpdates: 9
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function parseSessionMemoryPolicy(value) {
|
|
240
|
+
const defaults = createDefaultSessionMemoryPolicy();
|
|
241
|
+
if (value === undefined)
|
|
242
|
+
return defaults;
|
|
243
|
+
return {
|
|
244
|
+
enabled: optionalBoolean(value.enabled, defaults.enabled),
|
|
245
|
+
storeRawSegments: optionalBoolean(value.storeRawSegments, defaults.storeRawSegments),
|
|
246
|
+
maxRawSegmentBytes: positiveInteger(value.maxRawSegmentBytes, defaults.maxRawSegmentBytes),
|
|
247
|
+
retentionDays: positiveInteger(value.retentionDays, defaults.retentionDays),
|
|
248
|
+
minimumMessageTokensToInit: positiveInteger(value.minimumMessageTokensToInit, defaults.minimumMessageTokensToInit),
|
|
249
|
+
minimumTokensBetweenUpdate: positiveInteger(value.minimumTokensBetweenUpdate, defaults.minimumTokensBetweenUpdate),
|
|
250
|
+
toolCallsBetweenUpdates: positiveInteger(value.toolCallsBetweenUpdates, defaults.toolCallsBetweenUpdates)
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
// packages/core/src/runtime-logs/index.ts
|
|
254
|
+
var SAFE_EXECUTION_METADATA_KEYS = new Set([
|
|
255
|
+
"phase",
|
|
256
|
+
"runtimeSurface",
|
|
257
|
+
"stdinBytes",
|
|
258
|
+
"durationMs",
|
|
259
|
+
"enabled",
|
|
260
|
+
"stateWriteCount",
|
|
261
|
+
"warningCount",
|
|
262
|
+
"commandClass",
|
|
185
263
|
"exitCode",
|
|
186
|
-
"id",
|
|
187
264
|
"status",
|
|
188
|
-
"
|
|
189
|
-
"
|
|
265
|
+
"redactionCount",
|
|
266
|
+
"redactionLabels",
|
|
267
|
+
"normalizedEventType"
|
|
268
|
+
]);
|
|
269
|
+
var SAFE_REDACTION_LABELS = new Set([
|
|
270
|
+
"raw-command",
|
|
271
|
+
"raw-command-output",
|
|
272
|
+
"raw-prompt",
|
|
273
|
+
"sensitive-command",
|
|
274
|
+
"sensitive-text"
|
|
190
275
|
]);
|
|
191
276
|
|
|
277
|
+
// packages/core/src/evolution/evidence/session-memory/constants.ts
|
|
278
|
+
var DEFAULT_MAX_RAW_EVENT_BYTES = 64 * 1024;
|
|
279
|
+
// packages/core/src/evolution/knowledge/index.ts
|
|
280
|
+
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
281
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
282
|
+
var RESERVED_OKF_FILENAMES = new Set(["index.md", "log.md"]);
|
|
283
|
+
var ACTIVE_OKF_REVIEW_STATES = ["accepted", "auto-accepted"];
|
|
284
|
+
var OKF_REVIEW_STATES = [
|
|
285
|
+
"auto-accepted",
|
|
286
|
+
"accepted",
|
|
287
|
+
"needs-human",
|
|
288
|
+
"rejected",
|
|
289
|
+
"deferred",
|
|
290
|
+
"stale",
|
|
291
|
+
"deprecated",
|
|
292
|
+
"revoked",
|
|
293
|
+
"superseded",
|
|
294
|
+
"auto-stored/unreviewed"
|
|
295
|
+
];
|
|
296
|
+
var OKF_LIFECYCLE_STATUSES = [
|
|
297
|
+
"active",
|
|
298
|
+
"stale",
|
|
299
|
+
"deprecated",
|
|
300
|
+
"revoked",
|
|
301
|
+
"superseded"
|
|
302
|
+
];
|
|
303
|
+
var BEHAVIOR_CHANGE_KINDS = new Set([
|
|
304
|
+
"skill-improvement",
|
|
305
|
+
"role-agent-suggestion",
|
|
306
|
+
"team-suggestion",
|
|
307
|
+
"workflow-improvement",
|
|
308
|
+
"task-split-improvement",
|
|
309
|
+
"tool-use-improvement",
|
|
310
|
+
"repo-asset-suggestion"
|
|
311
|
+
]);
|
|
312
|
+
function resolveOkfKnowledgePaths(homeDir) {
|
|
313
|
+
const paths2 = resolveEvoDevPaths(homeDir);
|
|
314
|
+
return {
|
|
315
|
+
knowledgeDir: paths2.knowledgeDir,
|
|
316
|
+
okfDir: join(paths2.knowledgeDir, "okf"),
|
|
317
|
+
indexesDir: join(paths2.knowledgeDir, "indexes"),
|
|
318
|
+
tmpDir: join(paths2.knowledgeDir, "tmp")
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
async function ensureOkfKnowledgeBase(homeDir) {
|
|
322
|
+
const paths2 = resolveOkfKnowledgePaths(homeDir);
|
|
323
|
+
await ensureLocalKnowledgeGitRepository(paths2.knowledgeDir);
|
|
324
|
+
await mkdir(paths2.indexesDir, { recursive: true });
|
|
325
|
+
await mkdir(paths2.tmpDir, { recursive: true });
|
|
326
|
+
const directories = [
|
|
327
|
+
{
|
|
328
|
+
path: "",
|
|
329
|
+
title: "EvoDev Knowledge",
|
|
330
|
+
description: "User-local active engineering knowledge."
|
|
331
|
+
},
|
|
332
|
+
{ path: "concepts", title: "Core Concepts", description: "Canonical core knowledge." },
|
|
333
|
+
{ path: "concepts/rules", title: "Rules", description: "Normative engineering rules." },
|
|
334
|
+
{
|
|
335
|
+
path: "concepts/decisions",
|
|
336
|
+
title: "Decisions",
|
|
337
|
+
description: "Durable engineering decisions."
|
|
338
|
+
},
|
|
339
|
+
{ path: "concepts/patterns", title: "Patterns", description: "Reusable engineering patterns." },
|
|
340
|
+
{ path: "concepts/warnings", title: "Warnings", description: "Risks and anti-patterns." },
|
|
341
|
+
{ path: "concepts/checklists", title: "Checklists", description: "Verification checklists." },
|
|
342
|
+
{
|
|
343
|
+
path: "concepts/verification",
|
|
344
|
+
title: "Verification",
|
|
345
|
+
description: "Repeatable verification patterns."
|
|
346
|
+
},
|
|
347
|
+
{ path: "concepts/evos", title: "Evolution Cases", description: "Reviewed evolution cases." },
|
|
348
|
+
{ path: "concepts/glossary", title: "Glossary", description: "Terms and taxonomy notes." },
|
|
349
|
+
{ path: "repos", title: "Repositories", description: "Repository attention overlays." },
|
|
350
|
+
{ path: "roles", title: "Roles", description: "Role attention overlays." },
|
|
351
|
+
{ path: "workflows", title: "Workflows", description: "Workflow attention overlays." },
|
|
352
|
+
{ path: "references", title: "References", description: "Cited references." }
|
|
353
|
+
];
|
|
354
|
+
for (const directory of directories) {
|
|
355
|
+
await ensureOkfDirectory(paths2.okfDir, directory.path, directory.title, directory.description);
|
|
356
|
+
}
|
|
357
|
+
await rebuildOkfKnowledgeIndexes({ homeDir });
|
|
358
|
+
}
|
|
359
|
+
async function rebuildOkfKnowledgeIndexes(input) {
|
|
360
|
+
const paths2 = resolveOkfKnowledgePaths(input.homeDir);
|
|
361
|
+
await mkdir(paths2.indexesDir, { recursive: true });
|
|
362
|
+
const concepts = (await listOkfKnowledgeConcepts({ homeDir: input.homeDir })).filter(isActiveOkfConcept);
|
|
363
|
+
const conceptSummaries = concepts.map((concept) => ({
|
|
364
|
+
id: concept.id,
|
|
365
|
+
type: concept.type,
|
|
366
|
+
title: concept.title,
|
|
367
|
+
description: concept.description,
|
|
368
|
+
sourceLink: concept.sourceLink,
|
|
369
|
+
stableKey: concept.stableKey,
|
|
370
|
+
reviewState: concept.reviewState,
|
|
371
|
+
tags: concept.tags,
|
|
372
|
+
repoTags: concept.repoTags,
|
|
373
|
+
roleTags: concept.roleTags,
|
|
374
|
+
workflowTags: concept.workflowTags,
|
|
375
|
+
pathScopes: concept.pathScopes
|
|
376
|
+
}));
|
|
377
|
+
const pathsWritten = [
|
|
378
|
+
join(paths2.indexesDir, "index.json"),
|
|
379
|
+
join(paths2.indexesDir, "concepts.json"),
|
|
380
|
+
join(paths2.indexesDir, "repos.json"),
|
|
381
|
+
join(paths2.indexesDir, "roles.json"),
|
|
382
|
+
join(paths2.indexesDir, "workflows.json")
|
|
383
|
+
];
|
|
384
|
+
await writeJson2(pathsWritten[0], {
|
|
385
|
+
schemaVersion: 1,
|
|
386
|
+
kind: "okf-knowledge-index",
|
|
387
|
+
conceptCount: concepts.length,
|
|
388
|
+
updatedAt: new Date().toISOString()
|
|
389
|
+
}, { overwrite: true });
|
|
390
|
+
await writeJson2(pathsWritten[1], {
|
|
391
|
+
schemaVersion: 1,
|
|
392
|
+
kind: "okf-concepts-index",
|
|
393
|
+
concepts: conceptSummaries
|
|
394
|
+
}, { overwrite: true });
|
|
395
|
+
await writeJson2(pathsWritten[2], {
|
|
396
|
+
schemaVersion: 1,
|
|
397
|
+
kind: "okf-repos-index",
|
|
398
|
+
repos: groupConceptsByTag(concepts, "repoTags")
|
|
399
|
+
}, { overwrite: true });
|
|
400
|
+
await writeJson2(pathsWritten[3], {
|
|
401
|
+
schemaVersion: 1,
|
|
402
|
+
kind: "okf-roles-index",
|
|
403
|
+
roles: groupConceptsByTag(concepts, "roleTags")
|
|
404
|
+
}, { overwrite: true });
|
|
405
|
+
await writeJson2(pathsWritten[4], {
|
|
406
|
+
schemaVersion: 1,
|
|
407
|
+
kind: "okf-workflows-index",
|
|
408
|
+
workflows: groupConceptsByTag(concepts, "workflowTags")
|
|
409
|
+
}, { overwrite: true });
|
|
410
|
+
return pathsWritten;
|
|
411
|
+
}
|
|
412
|
+
async function listOkfKnowledgeConcepts(input) {
|
|
413
|
+
const okfDir = resolveOkfKnowledgePaths(input.homeDir).okfDir;
|
|
414
|
+
if (!await pathExists2(okfDir))
|
|
415
|
+
return [];
|
|
416
|
+
const files = await listMarkdownFiles(okfDir);
|
|
417
|
+
const concepts = [];
|
|
418
|
+
for (const file of files) {
|
|
419
|
+
if (RESERVED_OKF_FILENAMES.has(file.name))
|
|
420
|
+
continue;
|
|
421
|
+
const content = await readFile(file.path, "utf8");
|
|
422
|
+
const parsed = parseOkfConceptFile(okfDir, file.path, content);
|
|
423
|
+
if (parsed !== null)
|
|
424
|
+
concepts.push(parsed);
|
|
425
|
+
}
|
|
426
|
+
return concepts.filter((concept) => matchesConceptFilters(concept, input)).sort((left, right) => left.id.localeCompare(right.id));
|
|
427
|
+
}
|
|
428
|
+
async function ensureLocalKnowledgeGitRepository(knowledgeDir) {
|
|
429
|
+
await mkdir(knowledgeDir, { recursive: true });
|
|
430
|
+
const gitDir = join(knowledgeDir, ".git");
|
|
431
|
+
if (await pathExists2(gitDir))
|
|
432
|
+
return;
|
|
433
|
+
await mkdir(join(gitDir, "objects", "info"), { recursive: true });
|
|
434
|
+
await mkdir(join(gitDir, "objects", "pack"), { recursive: true });
|
|
435
|
+
await mkdir(join(gitDir, "refs", "heads"), { recursive: true });
|
|
436
|
+
await mkdir(join(gitDir, "refs", "tags"), { recursive: true });
|
|
437
|
+
await mkdir(join(gitDir, "info"), { recursive: true });
|
|
438
|
+
await writeTextIfMissing(join(gitDir, "HEAD"), `ref: refs/heads/main
|
|
439
|
+
`);
|
|
440
|
+
await writeTextIfMissing(join(gitDir, "config"), [
|
|
441
|
+
"[core]",
|
|
442
|
+
"\trepositoryformatversion = 0",
|
|
443
|
+
"\tfilemode = true",
|
|
444
|
+
"\tbare = false",
|
|
445
|
+
"\tlogallrefupdates = true",
|
|
446
|
+
""
|
|
447
|
+
].join(`
|
|
448
|
+
`));
|
|
449
|
+
await writeTextIfMissing(join(gitDir, "info", "exclude"), [
|
|
450
|
+
"# EvoDev user-local knowledge git repository.",
|
|
451
|
+
"# No remote is configured by default.",
|
|
452
|
+
""
|
|
453
|
+
].join(`
|
|
454
|
+
`));
|
|
455
|
+
}
|
|
456
|
+
async function writeTextIfMissing(path, value) {
|
|
457
|
+
if (await pathExists2(path))
|
|
458
|
+
return;
|
|
459
|
+
await mkdir(dirname(path), { recursive: true });
|
|
460
|
+
await writeFile(path, value, { encoding: "utf8", flag: "wx" });
|
|
461
|
+
}
|
|
462
|
+
function sanitizeOkfText(value) {
|
|
463
|
+
return redactSessionMemoryCredentialText(value).value.replace(/\s+/gu, " ").trim().slice(0, 800);
|
|
464
|
+
}
|
|
465
|
+
async function ensureOkfDirectory(okfDir, relativeDir, title, description) {
|
|
466
|
+
const dir = relativeDir === "" || relativeDir === "." ? okfDir : resolveOkfTargetPath(okfDir, relativeDir);
|
|
467
|
+
await mkdir(dir, { recursive: true });
|
|
468
|
+
const isRoot = dir === okfDir;
|
|
469
|
+
const indexPath = join(dir, "index.md");
|
|
470
|
+
if (!await pathExists2(indexPath)) {
|
|
471
|
+
await writeFile(indexPath, isRoot ? [
|
|
472
|
+
"---",
|
|
473
|
+
`okf_version: ${yamlString("0.1")}`,
|
|
474
|
+
`title: ${yamlString(title)}`,
|
|
475
|
+
`description: ${yamlString(description)}`,
|
|
476
|
+
"---",
|
|
477
|
+
"",
|
|
478
|
+
`# ${title}`,
|
|
479
|
+
"",
|
|
480
|
+
description,
|
|
481
|
+
""
|
|
482
|
+
].join(`
|
|
483
|
+
`) : [`# ${title}`, "", description, ""].join(`
|
|
484
|
+
`), "utf8");
|
|
485
|
+
}
|
|
486
|
+
const logPath = join(dir, "log.md");
|
|
487
|
+
if (!await pathExists2(logPath)) {
|
|
488
|
+
await writeFile(logPath, [
|
|
489
|
+
"# Directory Update Log",
|
|
490
|
+
"",
|
|
491
|
+
`## ${todayIsoDate()}`,
|
|
492
|
+
"* **Initialization**: Created OKF directory.",
|
|
493
|
+
""
|
|
494
|
+
].join(`
|
|
495
|
+
`), "utf8");
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
function parseOkfConceptFile(okfDir, filePath, content) {
|
|
499
|
+
const parsed = extractFrontmatter(content);
|
|
500
|
+
if (parsed === null)
|
|
501
|
+
return null;
|
|
502
|
+
const rel = toOkfRelativePath(okfDir, filePath);
|
|
503
|
+
const id = rel.replace(/\.md$/, "");
|
|
504
|
+
const frontmatter = parsed.frontmatter;
|
|
505
|
+
const stableKey = readIndentedYamlScalar(frontmatter, "stableKey") ?? `legacy:${rel.replace(/\.md$/, "")}`;
|
|
506
|
+
const reviewState = parseOkfReviewState(readIndentedYamlScalar(frontmatter, "reviewState") ?? "auto-stored/unreviewed");
|
|
507
|
+
const type = readYamlScalar(frontmatter, "type") ?? "Unknown";
|
|
508
|
+
const title = readYamlScalar(frontmatter, "title") ?? titleFromSlug(id.split("/").at(-1) ?? id);
|
|
509
|
+
const description = readYamlScalar(frontmatter, "description") ?? "";
|
|
510
|
+
const tags = readYamlList(frontmatter, "tags");
|
|
511
|
+
const repoTags = readYamlList(frontmatter, "repoTags");
|
|
512
|
+
const roleTags = readYamlList(frontmatter, "roleTags");
|
|
513
|
+
const workflowTags = readYamlList(frontmatter, "workflowTags");
|
|
514
|
+
const pathScopes = readYamlList(frontmatter, "pathScopes");
|
|
515
|
+
const lifecycleParsed = parseOkfLifecycle(frontmatter, {
|
|
516
|
+
type,
|
|
517
|
+
path: rel,
|
|
518
|
+
tags,
|
|
519
|
+
title,
|
|
520
|
+
reviewState,
|
|
521
|
+
createdAt: readYamlScalar(frontmatter, "timestamp") ?? undefined
|
|
522
|
+
});
|
|
523
|
+
return {
|
|
524
|
+
id,
|
|
525
|
+
path: filePath,
|
|
526
|
+
sourceLink: `/${rel}`,
|
|
527
|
+
type,
|
|
528
|
+
stableKey,
|
|
529
|
+
reviewState,
|
|
530
|
+
lifecycle: lifecycleParsed.lifecycle,
|
|
531
|
+
lifecyclePersisted: lifecycleParsed.persisted,
|
|
532
|
+
title,
|
|
533
|
+
description,
|
|
534
|
+
tags,
|
|
535
|
+
repoTags,
|
|
536
|
+
roleTags,
|
|
537
|
+
workflowTags,
|
|
538
|
+
pathScopes,
|
|
539
|
+
body: parsed.body
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function matchesConceptFilters(concept, input) {
|
|
543
|
+
if (input.projectKey !== undefined && concept.repoTags.length > 0 && !concept.repoTags.includes(sanitizeSlug(input.projectKey)) && !concept.tags.includes(`repo:${sanitizeSlug(input.projectKey)}`)) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
if (input.roleId !== undefined && concept.roleTags.length > 0 && !concept.roleTags.includes(sanitizeSlug(input.roleId)) && !concept.tags.includes(`role:${sanitizeSlug(input.roleId)}`)) {
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
if (input.workflowId !== undefined && concept.workflowTags.length > 0 && !concept.workflowTags.includes(sanitizeSlug(input.workflowId)) && !concept.tags.includes(`workflow:${sanitizeSlug(input.workflowId)}`)) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
if (input.paths !== undefined && input.paths.length > 0 && concept.pathScopes.length > 0) {
|
|
553
|
+
return input.paths.some((path) => concept.pathScopes.some((scope) => path.startsWith(scope) || scope.startsWith(path)));
|
|
554
|
+
}
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
function isActiveOkfConcept(concept) {
|
|
558
|
+
return resolveOkfConceptQueryEligibility(concept, {
|
|
559
|
+
includeStale: false,
|
|
560
|
+
now: new Date
|
|
561
|
+
}).include;
|
|
562
|
+
}
|
|
563
|
+
function isActiveOkfReviewState(reviewState) {
|
|
564
|
+
return ACTIVE_OKF_REVIEW_STATES.includes(reviewState);
|
|
565
|
+
}
|
|
566
|
+
function resolveOkfConceptQueryEligibility(concept, input) {
|
|
567
|
+
if (concept.lifecycle.status === "deprecated" || concept.lifecycle.status === "revoked" || concept.lifecycle.status === "superseded" || concept.reviewState === "deprecated" || concept.reviewState === "revoked" || concept.reviewState === "superseded") {
|
|
568
|
+
return { include: false, stale: false, scoreAdjustment: 0, reason: "inactive" };
|
|
569
|
+
}
|
|
570
|
+
const staleByStatus = concept.lifecycle.status === "stale" || concept.reviewState === "stale";
|
|
571
|
+
const staleByDate = isLifecycleDateDue(concept.lifecycle.staleAfter, input.now);
|
|
572
|
+
const stale = staleByStatus || staleByDate;
|
|
573
|
+
if (stale) {
|
|
574
|
+
return {
|
|
575
|
+
include: input.includeStale,
|
|
576
|
+
stale: input.includeStale,
|
|
577
|
+
scoreAdjustment: input.includeStale ? -250 : 0,
|
|
578
|
+
reason: staleByStatus ? "status=stale" : `staleAfter=${concept.lifecycle.staleAfter}`
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
return {
|
|
582
|
+
include: isActiveOkfReviewState(concept.reviewState) && concept.lifecycle.status === "active",
|
|
583
|
+
stale: false,
|
|
584
|
+
scoreAdjustment: 0,
|
|
585
|
+
reason: "active"
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function isLifecycleDateDue(value, now) {
|
|
589
|
+
const normalized = normalizeIsoDateString(value);
|
|
590
|
+
return normalized !== null && Date.parse(normalized) <= now.getTime();
|
|
591
|
+
}
|
|
592
|
+
function createDefaultOkfLifecycle(input) {
|
|
593
|
+
const createdAt = normalizeIsoDateString(input.createdAt) ?? "1970-01-01T00:00:00.000Z";
|
|
594
|
+
const policy2 = resolveOkfLifecyclePolicy(input);
|
|
595
|
+
return {
|
|
596
|
+
status: lifecycleStatusFromReviewState(input.reviewState),
|
|
597
|
+
createdAt,
|
|
598
|
+
lastVerifiedAt: createdAt,
|
|
599
|
+
reviewAfter: addDaysIso(createdAt, policy2.reviewAfterDays),
|
|
600
|
+
staleAfter: addDaysIso(createdAt, policy2.staleAfterDays),
|
|
601
|
+
supersedes: [],
|
|
602
|
+
supersededBy: null,
|
|
603
|
+
revokedAt: null,
|
|
604
|
+
revokedReason: null
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function parseOkfLifecycle(frontmatter, input) {
|
|
608
|
+
const defaults = createDefaultOkfLifecycle(input);
|
|
609
|
+
const block = extractNestedYamlBlock(frontmatter, "evodev", "lifecycle");
|
|
610
|
+
if (block === null)
|
|
611
|
+
return { lifecycle: defaults, persisted: false };
|
|
612
|
+
const status = readIndentedYamlScalar(block, "status");
|
|
613
|
+
const createdAt = readIndentedYamlScalar(block, "createdAt");
|
|
614
|
+
const lastVerifiedAt = readIndentedYamlScalar(block, "lastVerifiedAt");
|
|
615
|
+
const reviewAfter = readIndentedYamlScalar(block, "reviewAfter");
|
|
616
|
+
const staleAfter = readIndentedYamlScalar(block, "staleAfter");
|
|
617
|
+
const revokedAt = readIndentedYamlScalar(block, "revokedAt");
|
|
618
|
+
return {
|
|
619
|
+
persisted: true,
|
|
620
|
+
lifecycle: {
|
|
621
|
+
status: isOkfLifecycleStatus(status) ? status : defaults.status,
|
|
622
|
+
createdAt: normalizeIsoDateString(createdAt) ?? defaults.createdAt,
|
|
623
|
+
lastVerifiedAt: normalizeIsoDateString(lastVerifiedAt) ?? defaults.lastVerifiedAt,
|
|
624
|
+
reviewAfter: normalizeIsoDateString(reviewAfter) ?? defaults.reviewAfter,
|
|
625
|
+
staleAfter: normalizeIsoDateString(staleAfter) ?? defaults.staleAfter,
|
|
626
|
+
supersedes: readYamlList(block, "supersedes"),
|
|
627
|
+
supersededBy: readNullableLifecycleString(readIndentedYamlScalar(block, "supersededBy")),
|
|
628
|
+
revokedAt: readNullableLifecycleString(revokedAt) === null ? null : normalizeIsoDateString(revokedAt),
|
|
629
|
+
revokedReason: readNullableLifecycleString(readIndentedYamlScalar(block, "revokedReason"))
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
function lifecycleStatusFromReviewState(reviewState) {
|
|
634
|
+
if (reviewState === "stale")
|
|
635
|
+
return "stale";
|
|
636
|
+
if (reviewState === "deprecated")
|
|
637
|
+
return "deprecated";
|
|
638
|
+
if (reviewState === "revoked")
|
|
639
|
+
return "revoked";
|
|
640
|
+
if (reviewState === "superseded")
|
|
641
|
+
return "superseded";
|
|
642
|
+
return "active";
|
|
643
|
+
}
|
|
644
|
+
function resolveOkfLifecyclePolicy(input) {
|
|
645
|
+
const comparable = `${input.type} ${input.path} ${input.tags.join(" ")} ${input.title}`.toLowerCase();
|
|
646
|
+
if (/\bverification\b/u.test(comparable))
|
|
647
|
+
return { reviewAfterDays: 120, staleAfterDays: 240 };
|
|
648
|
+
if (/\bworkflow\b/u.test(comparable))
|
|
649
|
+
return { reviewAfterDays: 60, staleAfterDays: 120 };
|
|
650
|
+
return { reviewAfterDays: 90, staleAfterDays: 180 };
|
|
651
|
+
}
|
|
652
|
+
function isOkfLifecycleStatus(value) {
|
|
653
|
+
return OKF_LIFECYCLE_STATUSES.includes(value);
|
|
654
|
+
}
|
|
655
|
+
function normalizeIsoDateString(value) {
|
|
656
|
+
if (value === undefined || value === null || value === "" || value === "null")
|
|
657
|
+
return null;
|
|
658
|
+
const time2 = Date.parse(value);
|
|
659
|
+
if (!Number.isFinite(time2))
|
|
660
|
+
return null;
|
|
661
|
+
return new Date(time2).toISOString();
|
|
662
|
+
}
|
|
663
|
+
function addDaysIso(value, days) {
|
|
664
|
+
const date = new Date(value);
|
|
665
|
+
date.setUTCDate(date.getUTCDate() + days);
|
|
666
|
+
return date.toISOString();
|
|
667
|
+
}
|
|
668
|
+
function readNullableLifecycleString(value) {
|
|
669
|
+
if (value === null)
|
|
670
|
+
return null;
|
|
671
|
+
const normalized = sanitizeOkfText(value);
|
|
672
|
+
return normalized === "" || normalized === "null" ? null : normalized;
|
|
673
|
+
}
|
|
674
|
+
function yamlString(value) {
|
|
675
|
+
return JSON.stringify(value);
|
|
676
|
+
}
|
|
677
|
+
function extractFrontmatter(content) {
|
|
678
|
+
if (!content.startsWith(`---
|
|
679
|
+
`))
|
|
680
|
+
return null;
|
|
681
|
+
const end = content.indexOf(`
|
|
682
|
+
---`, 4);
|
|
683
|
+
if (end < 0)
|
|
684
|
+
return null;
|
|
685
|
+
const frontmatter = content.slice(4, end).trim();
|
|
686
|
+
const body = content.slice(end + 4).replace(/^\n/u, "");
|
|
687
|
+
return { frontmatter, body };
|
|
688
|
+
}
|
|
689
|
+
function extractNestedYamlBlock(frontmatter, rootKey, childKey) {
|
|
690
|
+
const lines = frontmatter.split(`
|
|
691
|
+
`);
|
|
692
|
+
const rootIndex = lines.findIndex((line) => line.trim() === `${rootKey}:`);
|
|
693
|
+
if (rootIndex < 0)
|
|
694
|
+
return null;
|
|
695
|
+
const rootEnd = findYamlBlockEnd(lines, rootIndex, 0);
|
|
696
|
+
const childIndex = lines.findIndex((line, index) => index > rootIndex && index < rootEnd && line.startsWith(" ") && line.trim() === `${childKey}:`);
|
|
697
|
+
if (childIndex < 0)
|
|
698
|
+
return null;
|
|
699
|
+
const childEnd = findYamlBlockEnd(lines, childIndex, 2);
|
|
700
|
+
return lines.slice(childIndex + 1, childEnd).join(`
|
|
701
|
+
`);
|
|
702
|
+
}
|
|
703
|
+
function findYamlBlockEnd(lines, startIndex, parentIndent) {
|
|
704
|
+
for (let index = startIndex + 1;index < lines.length; index += 1) {
|
|
705
|
+
const line = lines[index] ?? "";
|
|
706
|
+
if (line.trim() === "")
|
|
707
|
+
continue;
|
|
708
|
+
const indent = line.length - line.trimStart().length;
|
|
709
|
+
if (indent <= parentIndent)
|
|
710
|
+
return index;
|
|
711
|
+
}
|
|
712
|
+
return lines.length;
|
|
713
|
+
}
|
|
714
|
+
function readYamlScalar(frontmatter, key) {
|
|
715
|
+
const match = frontmatter.match(new RegExp(`^${escapeRegExp(key)}:\\s*(.+?)\\s*$`, "m"));
|
|
716
|
+
if (match?.[1] === undefined)
|
|
717
|
+
return null;
|
|
718
|
+
return parseYamlValue(match[1]);
|
|
719
|
+
}
|
|
720
|
+
function readIndentedYamlScalar(frontmatter, key) {
|
|
721
|
+
const match = frontmatter.match(new RegExp(`^\\s*${escapeRegExp(key)}:\\s*(.+?)\\s*$`, "m"));
|
|
722
|
+
if (match?.[1] === undefined)
|
|
723
|
+
return null;
|
|
724
|
+
return parseYamlValue(match[1]);
|
|
725
|
+
}
|
|
726
|
+
function parseOkfReviewState(value) {
|
|
727
|
+
return OKF_REVIEW_STATES.includes(value) ? value : "auto-stored/unreviewed";
|
|
728
|
+
}
|
|
729
|
+
function readYamlList(frontmatter, key) {
|
|
730
|
+
const inline = frontmatter.match(new RegExp(`^\\s*${escapeRegExp(key)}:\\s*\\[(.*?)\\]\\s*$`, "m"));
|
|
731
|
+
if (inline?.[1] !== undefined) {
|
|
732
|
+
return inline[1].split(",").map((item) => parseYamlValue(item.trim())).filter((item) => item !== "");
|
|
733
|
+
}
|
|
734
|
+
const lines = frontmatter.split(`
|
|
735
|
+
`);
|
|
736
|
+
const values = [];
|
|
737
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
738
|
+
const line = lines[index] ?? "";
|
|
739
|
+
if (!line.trim().startsWith(`${key}:`))
|
|
740
|
+
continue;
|
|
741
|
+
for (let next = index + 1;next < lines.length; next += 1) {
|
|
742
|
+
const candidate = lines[next] ?? "";
|
|
743
|
+
if (!/^\s+-\s+/u.test(candidate))
|
|
744
|
+
break;
|
|
745
|
+
values.push(parseYamlValue(candidate.replace(/^\s+-\s+/u, "")));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return [...new Set(values.filter((value) => value !== ""))];
|
|
749
|
+
}
|
|
750
|
+
function parseYamlValue(value) {
|
|
751
|
+
const trimmed = value.trim();
|
|
752
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
753
|
+
try {
|
|
754
|
+
return JSON.parse(trimmed);
|
|
755
|
+
} catch {
|
|
756
|
+
return trimmed.slice(1, -1);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return trimmed.replace(/^['"]|['"]$/gu, "");
|
|
760
|
+
}
|
|
761
|
+
async function listMarkdownFiles(root) {
|
|
762
|
+
if (!await pathExists2(root))
|
|
763
|
+
return [];
|
|
764
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
765
|
+
const files = [];
|
|
766
|
+
for (const entry of entries) {
|
|
767
|
+
const path = join(root, entry.name);
|
|
768
|
+
if (entry.isDirectory()) {
|
|
769
|
+
files.push(...await listMarkdownFiles(path));
|
|
770
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
771
|
+
files.push({ path, name: entry.name });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return files;
|
|
775
|
+
}
|
|
776
|
+
function groupConceptsByTag(concepts, field) {
|
|
777
|
+
const groups = new Map;
|
|
778
|
+
for (const concept of concepts) {
|
|
779
|
+
for (const id of concept[field]) {
|
|
780
|
+
groups.set(id, [
|
|
781
|
+
...groups.get(id) ?? [],
|
|
782
|
+
{ id: concept.id, title: concept.title, sourceLink: concept.sourceLink }
|
|
783
|
+
]);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return [...groups.entries()].map(([id, groupedConcepts]) => ({ id, concepts: groupedConcepts }));
|
|
787
|
+
}
|
|
788
|
+
function resolveOkfTargetPath(okfDir, targetPath) {
|
|
789
|
+
const clean = targetPath.replace(/^\/+/u, "");
|
|
790
|
+
if (clean.split("/").some((segment) => segment === ".." || segment === "." || segment === "")) {
|
|
791
|
+
throw new Error(`Unsafe OKF target path: ${targetPath}`);
|
|
792
|
+
}
|
|
793
|
+
const resolved = join(okfDir, clean);
|
|
794
|
+
const rel = relative(okfDir, resolved);
|
|
795
|
+
if (rel.startsWith("..") || rel === "")
|
|
796
|
+
throw new Error(`Unsafe OKF target path: ${targetPath}`);
|
|
797
|
+
return resolved;
|
|
798
|
+
}
|
|
799
|
+
function toOkfRelativePath(okfDir, path) {
|
|
800
|
+
return relative(okfDir, path).replace(/\\/gu, "/");
|
|
801
|
+
}
|
|
802
|
+
function sanitizeSlug(value) {
|
|
803
|
+
const slug = value.trim().toLowerCase().replace(/[^a-z0-9._/-]+/gu, "-").replace(/\/+/gu, "/").replace(/^-+|-+$/gu, "");
|
|
804
|
+
return slug === "" ? "unknown" : slug;
|
|
805
|
+
}
|
|
806
|
+
function titleFromSlug(value) {
|
|
807
|
+
return value.replace(/\.md$/u, "").split(/[/-]/u).filter(Boolean).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
808
|
+
}
|
|
809
|
+
function escapeRegExp(value) {
|
|
810
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
811
|
+
}
|
|
812
|
+
function todayIsoDate() {
|
|
813
|
+
return new Date().toISOString().slice(0, 10);
|
|
814
|
+
}
|
|
815
|
+
async function writeJson2(path, value, options = {}) {
|
|
816
|
+
await mkdir(dirname(path), { recursive: true });
|
|
817
|
+
const flag = options.overwrite === true ? "w" : "wx";
|
|
818
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}
|
|
819
|
+
`, { encoding: "utf8", flag });
|
|
820
|
+
}
|
|
821
|
+
async function pathExists2(path) {
|
|
822
|
+
try {
|
|
823
|
+
await stat(path);
|
|
824
|
+
return true;
|
|
825
|
+
} catch (error) {
|
|
826
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
827
|
+
return false;
|
|
828
|
+
throw error;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// packages/core/src/team/prompts.ts
|
|
833
|
+
var TEAM_ROLE_STARTUP_PROMPT_TEMPLATE = [
|
|
834
|
+
"You are an EvoDev managed role agent.",
|
|
835
|
+
"Team run: {{runId}}",
|
|
836
|
+
"Repository: {{repoRoot}}",
|
|
837
|
+
"Role id: {{roleId}}",
|
|
838
|
+
"Role name: {{roleName}}",
|
|
839
|
+
"Runtime: {{runtime}}",
|
|
840
|
+
"Native Code Agent binding: {{nativeAgentBinding}}",
|
|
841
|
+
"Model: {{model}}",
|
|
842
|
+
"Thinking level: {{thinkingLevel}}",
|
|
843
|
+
"Write mode: {{writeMode}}",
|
|
844
|
+
"Transcript recording: {{transcriptRecording}}",
|
|
845
|
+
"",
|
|
846
|
+
"Current known agents:",
|
|
847
|
+
"{{roster}}",
|
|
848
|
+
"",
|
|
849
|
+
"EvoDev team runtime control contract:",
|
|
850
|
+
"This run is already inside the EvoDev-managed team runtime.",
|
|
851
|
+
"For EvoDev role-agent lifecycle, use the current EvoDev run control plane.",
|
|
852
|
+
"Unprefixed user requests such as 'start the team', 'execute team', 'create agents', or 'spawn roles' mean: use the current EvoDev run control plane.",
|
|
853
|
+
"Do not answer those requests with only a role plan when a role agent should be created.",
|
|
854
|
+
"",
|
|
855
|
+
"Use Teams MCP as the primary control plane:",
|
|
856
|
+
"- list_agents: discover current EvoDev role agents.",
|
|
857
|
+
"- spawn_role: create or reuse exactly one EvoDev role agent.",
|
|
858
|
+
"- send_message: communicate through the EvoDev broker.",
|
|
859
|
+
"- stop_role: stop a role agent when allowed.",
|
|
860
|
+
"If Teams MCP is unavailable, fall back to the EvoDev CLI from this repository:",
|
|
861
|
+
"- evodev team spawn --role <roleId>",
|
|
862
|
+
"- evodev team send --to <roleId> --message <text>",
|
|
863
|
+
"- evodev team status",
|
|
864
|
+
"Teams MCP defaults are inherited from the environment:",
|
|
865
|
+
"EVODEV_TEAM_RUN_ID={{runId}}",
|
|
866
|
+
"EVODEV_TEAM_ROLE_ID={{roleId}}",
|
|
867
|
+
"When calling Teams MCP tools, let the MCP server-bound environment identify this run and role.",
|
|
868
|
+
"Do not operate tmux directly for role lifecycle; let EvoDev create and track panes.",
|
|
869
|
+
"Any server-bound role may request role lifecycle changes; EvoDev records and tracks panes but does not use role policy to stop execution.",
|
|
870
|
+
"{{roleGuidance}}",
|
|
871
|
+
"{{nativeAgentInstruction}}",
|
|
872
|
+
"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.",
|
|
873
|
+
"{{scopedContext}}",
|
|
874
|
+
"",
|
|
875
|
+
"{{rolePrompt}}"
|
|
876
|
+
].join(`
|
|
877
|
+
`);
|
|
878
|
+
var MAIN_ROLE_GUIDANCE = [
|
|
879
|
+
"As main, you are the planner and delegator for complex work, not a standby worker.",
|
|
880
|
+
"On each user prompt or hook-delivered inbox message, decide whether to answer directly, ask for clarification, or use team execution.",
|
|
881
|
+
"Use team execution only when role separation improves correctness, coverage, safety, or latency.",
|
|
882
|
+
"When team execution is needed, create an upfront task plan with required role ids, role-specific assignments, dependencies, and runnable batches.",
|
|
883
|
+
"Spawn or reuse all roles needed for the first runnable batch and send each role a self-contained task message.",
|
|
884
|
+
"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.",
|
|
885
|
+
"Use list_agents only for one-time roster discovery when the current roster is genuinely unknown, never as a progress check.",
|
|
886
|
+
"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.",
|
|
887
|
+
"While delegated role work is pending, do not perform concrete implementation, testing, package research, or review work yourself."
|
|
888
|
+
].join(" ");
|
|
889
|
+
|
|
890
|
+
// packages/core/src/team/index.ts
|
|
891
|
+
var TEAM_INTERNAL_WAKE_SIGNAL = [
|
|
892
|
+
"[EvoDev internal wake signal]",
|
|
893
|
+
"No user request is included in this message. Continue only from EvoDev team inbox messages injected by hooks."
|
|
894
|
+
].join(`
|
|
895
|
+
`);
|
|
896
|
+
var BUILT_IN_TEAM_DEFINITION = {
|
|
897
|
+
version: 1,
|
|
898
|
+
name: "builtin-minimal-team",
|
|
899
|
+
description: "Built-in minimal EvoDev team fallback.",
|
|
900
|
+
agents: {
|
|
901
|
+
executor: "builtin:executor",
|
|
902
|
+
reviewer: "builtin:reviewer",
|
|
903
|
+
tester: "builtin:tester"
|
|
904
|
+
},
|
|
905
|
+
body: [
|
|
906
|
+
"# Built-in Minimal Team",
|
|
907
|
+
"",
|
|
908
|
+
"Use role agents only when delegation improves correctness, coverage, safety, or latency.",
|
|
909
|
+
"Spawn roles on demand and send self-contained assignments through Teams MCP."
|
|
910
|
+
].join(`
|
|
911
|
+
`)
|
|
912
|
+
};
|
|
913
|
+
|
|
192
914
|
// packages/core/src/hooks/index.ts
|
|
193
915
|
var CANONICAL_HOOK_EVENT_TYPES = [
|
|
194
916
|
"SessionStart",
|
|
@@ -242,6 +964,26 @@ var DEFAULT_EVENT_SETTINGS = {
|
|
|
242
964
|
WorktreeCreate: true,
|
|
243
965
|
WorktreeRemove: true
|
|
244
966
|
};
|
|
967
|
+
var TEAM_MESSAGE_DELIVERY_EVENTS = new Set([
|
|
968
|
+
"SessionStart",
|
|
969
|
+
"UserPromptSubmit",
|
|
970
|
+
"PostToolUse",
|
|
971
|
+
"PostToolUseFailure",
|
|
972
|
+
"Stop",
|
|
973
|
+
"TeammateIdle",
|
|
974
|
+
"SubagentStop",
|
|
975
|
+
"TaskCompleted"
|
|
976
|
+
]);
|
|
977
|
+
var CODEX_STOP_EVENTS_WITHOUT_ADDITIONAL_CONTEXT = new Set([
|
|
978
|
+
"Stop",
|
|
979
|
+
"SubagentStop"
|
|
980
|
+
]);
|
|
981
|
+
var COMPLETION_EVENTS_WITHOUT_DEVELOPMENT_DIAGNOSTICS = new Set([
|
|
982
|
+
"Stop",
|
|
983
|
+
"SubagentStop",
|
|
984
|
+
"TaskCompleted",
|
|
985
|
+
"TeammateIdle"
|
|
986
|
+
]);
|
|
245
987
|
function createDefaultHookSettings() {
|
|
246
988
|
return {
|
|
247
989
|
enabled: true,
|
|
@@ -256,8 +998,8 @@ function createDefaultHookSettings() {
|
|
|
256
998
|
}
|
|
257
999
|
},
|
|
258
1000
|
observability: {
|
|
259
|
-
metadataOnly:
|
|
260
|
-
rawPayloadStorage:
|
|
1001
|
+
metadataOnly: true,
|
|
1002
|
+
rawPayloadStorage: false,
|
|
261
1003
|
appendEvents: false
|
|
262
1004
|
},
|
|
263
1005
|
learning: {
|
|
@@ -273,18 +1015,18 @@ function parseHookSettings(value) {
|
|
|
273
1015
|
if (!isRecord(value))
|
|
274
1016
|
throw new Error("Invalid hooks settings; expected object.");
|
|
275
1017
|
const observability = isRecord(value.observability) ? value.observability : undefined;
|
|
276
|
-
|
|
277
|
-
|
|
1018
|
+
optionalBoolean2(observability?.metadataOnly, defaults.observability.metadataOnly, "hooks.observability.metadataOnly");
|
|
1019
|
+
optionalBoolean2(observability?.rawPayloadStorage, defaults.observability.rawPayloadStorage, "hooks.observability.rawPayloadStorage");
|
|
278
1020
|
return {
|
|
279
|
-
enabled:
|
|
1021
|
+
enabled: optionalBoolean2(value.enabled, defaults.enabled, "hooks.enabled"),
|
|
280
1022
|
targets: {
|
|
281
1023
|
claude: parseHookTargetSettings(value.targets, defaults.targets.claude, "claude"),
|
|
282
1024
|
codex: parseHookTargetSettings(value.targets, defaults.targets.codex, "codex")
|
|
283
1025
|
},
|
|
284
1026
|
observability: {
|
|
285
|
-
metadataOnly:
|
|
286
|
-
rawPayloadStorage:
|
|
287
|
-
appendEvents:
|
|
1027
|
+
metadataOnly: true,
|
|
1028
|
+
rawPayloadStorage: false,
|
|
1029
|
+
appendEvents: optionalBoolean2(observability?.appendEvents, defaults.observability.appendEvents, "hooks.observability.appendEvents")
|
|
288
1030
|
},
|
|
289
1031
|
learning: {
|
|
290
1032
|
emitCandidates: false,
|
|
@@ -298,14 +1040,14 @@ function parseHookTargetSettings(value, defaults, target) {
|
|
|
298
1040
|
const events = isRecord(targetSettings.events) ? targetSettings.events : {};
|
|
299
1041
|
const parsedEvents = { ...defaults.events };
|
|
300
1042
|
for (const eventType of CANONICAL_HOOK_EVENT_TYPES) {
|
|
301
|
-
parsedEvents[eventType] =
|
|
1043
|
+
parsedEvents[eventType] = optionalBoolean2(events[eventType], defaults.events[eventType], `hooks.targets.${target}.events.${eventType}`);
|
|
302
1044
|
}
|
|
303
1045
|
return {
|
|
304
|
-
enabled:
|
|
1046
|
+
enabled: optionalBoolean2(targetSettings.enabled, defaults.enabled, `hooks.targets.${target}.enabled`),
|
|
305
1047
|
events: parsedEvents
|
|
306
1048
|
};
|
|
307
1049
|
}
|
|
308
|
-
function
|
|
1050
|
+
function optionalBoolean2(value, fallback, path) {
|
|
309
1051
|
if (value === undefined)
|
|
310
1052
|
return fallback;
|
|
311
1053
|
if (typeof value !== "boolean")
|
|
@@ -345,7 +1087,9 @@ function createDefaultSettings(os = process.platform) {
|
|
|
345
1087
|
lastRunAt: null
|
|
346
1088
|
},
|
|
347
1089
|
hooks: createDefaultHookSettings(),
|
|
348
|
-
teamRuntime: createDefaultTeamRuntimeSettings()
|
|
1090
|
+
teamRuntime: createDefaultTeamRuntimeSettings(),
|
|
1091
|
+
memory: createDefaultMemorySettings(),
|
|
1092
|
+
evolution: createDefaultEvolutionSettings()
|
|
349
1093
|
};
|
|
350
1094
|
}
|
|
351
1095
|
function createDefaultTeamRuntimeSettings() {
|
|
@@ -353,7 +1097,26 @@ function createDefaultTeamRuntimeSettings() {
|
|
|
353
1097
|
defaultRuntime: "codex",
|
|
354
1098
|
defaultModel: null,
|
|
355
1099
|
defaultThinkingLevel: null,
|
|
356
|
-
recordTranscript: false
|
|
1100
|
+
recordTranscript: false,
|
|
1101
|
+
displayMode: "normal"
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
function createDefaultMemorySettings() {
|
|
1105
|
+
return {
|
|
1106
|
+
autoAccept: true,
|
|
1107
|
+
runtimeInjection: true,
|
|
1108
|
+
staleReview: true,
|
|
1109
|
+
lexicalIndex: true,
|
|
1110
|
+
sessionMemory: createDefaultSessionMemoryPolicy()
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
function createDefaultEvolutionSettings() {
|
|
1114
|
+
return {
|
|
1115
|
+
automation: {
|
|
1116
|
+
knowledge: true,
|
|
1117
|
+
semanticKnowledge: false,
|
|
1118
|
+
recommendations: false
|
|
1119
|
+
}
|
|
357
1120
|
};
|
|
358
1121
|
}
|
|
359
1122
|
function mergeSettings(existing, defaults = createDefaultSettings()) {
|
|
@@ -392,10 +1155,32 @@ function mergeSettings(existing, defaults = createDefaultSettings()) {
|
|
|
392
1155
|
teamRuntime: {
|
|
393
1156
|
...defaults.teamRuntime,
|
|
394
1157
|
...existing.teamRuntime
|
|
1158
|
+
},
|
|
1159
|
+
memory: {
|
|
1160
|
+
...defaults.memory,
|
|
1161
|
+
...existing.memory
|
|
1162
|
+
},
|
|
1163
|
+
evolution: {
|
|
1164
|
+
...defaults.evolution,
|
|
1165
|
+
...existing.evolution,
|
|
1166
|
+
automation: {
|
|
1167
|
+
...defaults.evolution.automation,
|
|
1168
|
+
...existing.evolution?.automation
|
|
1169
|
+
}
|
|
395
1170
|
}
|
|
396
1171
|
};
|
|
397
1172
|
return parseSettings(merged);
|
|
398
1173
|
}
|
|
1174
|
+
async function readRuntimeInjectionSettings(homeDir) {
|
|
1175
|
+
const paths2 = resolveEvoDevPaths(homeDir);
|
|
1176
|
+
try {
|
|
1177
|
+
return parseSettings(JSON.parse(await readFile2(paths2.settingsPath, "utf8"))).memory;
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
if (isNotFoundError2(error))
|
|
1180
|
+
return createDefaultMemorySettings();
|
|
1181
|
+
throw error;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
399
1184
|
function parseSettings(value) {
|
|
400
1185
|
const root = expectRecord2(value, "settings");
|
|
401
1186
|
const version = root.version;
|
|
@@ -427,10 +1212,35 @@ function parseSettings(value) {
|
|
|
427
1212
|
lastRunAt: expectNullableString(doctor.lastRunAt, "settings.doctor.lastRunAt")
|
|
428
1213
|
},
|
|
429
1214
|
hooks: parseHookSettings(root.hooks),
|
|
430
|
-
teamRuntime: parseTeamRuntimeSettings(root.teamRuntime ?? createDefaultTeamRuntimeSettings(), "settings.teamRuntime")
|
|
1215
|
+
teamRuntime: parseTeamRuntimeSettings(root.teamRuntime ?? createDefaultTeamRuntimeSettings(), "settings.teamRuntime"),
|
|
1216
|
+
memory: parseMemorySettings(root.memory ?? createDefaultMemorySettings(), "settings.memory"),
|
|
1217
|
+
evolution: parseEvolutionSettings(root.evolution ?? createDefaultEvolutionSettings(), "settings.evolution")
|
|
431
1218
|
};
|
|
432
1219
|
return parsed;
|
|
433
1220
|
}
|
|
1221
|
+
function parseEvolutionSettings(value, path) {
|
|
1222
|
+
const input = expectRecord2(value, path);
|
|
1223
|
+
const defaults = createDefaultEvolutionSettings();
|
|
1224
|
+
const automation = expectRecord2(input.automation ?? defaults.automation, `${path}.automation`);
|
|
1225
|
+
return {
|
|
1226
|
+
automation: {
|
|
1227
|
+
knowledge: automation.knowledge === undefined ? defaults.automation.knowledge : expectBoolean(automation.knowledge, `${path}.automation.knowledge`),
|
|
1228
|
+
semanticKnowledge: automation.semanticKnowledge === undefined ? defaults.automation.semanticKnowledge : expectBoolean(automation.semanticKnowledge, `${path}.automation.semanticKnowledge`),
|
|
1229
|
+
recommendations: automation.recommendations === undefined ? defaults.automation.recommendations : expectBoolean(automation.recommendations, `${path}.automation.recommendations`)
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
function parseMemorySettings(value, path) {
|
|
1234
|
+
const input = expectRecord2(value, path);
|
|
1235
|
+
const defaults = createDefaultMemorySettings();
|
|
1236
|
+
return {
|
|
1237
|
+
autoAccept: input.autoAccept === undefined ? defaults.autoAccept : expectBoolean(input.autoAccept, `${path}.autoAccept`),
|
|
1238
|
+
runtimeInjection: input.runtimeInjection === undefined ? defaults.runtimeInjection : expectBoolean(input.runtimeInjection, `${path}.runtimeInjection`),
|
|
1239
|
+
staleReview: input.staleReview === undefined ? defaults.staleReview : expectBoolean(input.staleReview, `${path}.staleReview`),
|
|
1240
|
+
lexicalIndex: input.lexicalIndex === undefined ? defaults.lexicalIndex : expectBoolean(input.lexicalIndex, `${path}.lexicalIndex`),
|
|
1241
|
+
sessionMemory: parseSessionMemoryPolicy(isPlainRecord(input.sessionMemory) ? input.sessionMemory : defaults.sessionMemory)
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
434
1244
|
function parseTeamRuntimeSettings(value, path) {
|
|
435
1245
|
const input = expectRecord2(value, path);
|
|
436
1246
|
const defaults = createDefaultTeamRuntimeSettings();
|
|
@@ -442,9 +1252,17 @@ function parseTeamRuntimeSettings(value, path) {
|
|
|
442
1252
|
defaultRuntime,
|
|
443
1253
|
defaultModel: input.defaultModel === undefined ? defaults.defaultModel : expectNullableString(input.defaultModel, `${path}.defaultModel`),
|
|
444
1254
|
defaultThinkingLevel: input.defaultThinkingLevel === undefined ? defaults.defaultThinkingLevel : expectNullableString(input.defaultThinkingLevel, `${path}.defaultThinkingLevel`),
|
|
445
|
-
recordTranscript: input.recordTranscript === undefined ? defaults.recordTranscript : expectBoolean(input.recordTranscript, `${path}.recordTranscript`)
|
|
1255
|
+
recordTranscript: input.recordTranscript === undefined ? defaults.recordTranscript : expectBoolean(input.recordTranscript, `${path}.recordTranscript`),
|
|
1256
|
+
displayMode: parseTeamRuntimeDisplayMode(input.displayMode, defaults.displayMode, path)
|
|
446
1257
|
};
|
|
447
1258
|
}
|
|
1259
|
+
function parseTeamRuntimeDisplayMode(value, fallback, path) {
|
|
1260
|
+
if (value === undefined)
|
|
1261
|
+
return fallback;
|
|
1262
|
+
if (value === "normal" || value === "development")
|
|
1263
|
+
return value;
|
|
1264
|
+
throw new EvoDevConfigError(`Invalid ${path}.displayMode; expected normal or development`);
|
|
1265
|
+
}
|
|
448
1266
|
function parsePluginSettings(value, path) {
|
|
449
1267
|
const input = expectRecord2(value, path);
|
|
450
1268
|
const parsed = {
|
|
@@ -464,6 +1282,12 @@ function expectRecord2(value, path) {
|
|
|
464
1282
|
}
|
|
465
1283
|
return value;
|
|
466
1284
|
}
|
|
1285
|
+
function isPlainRecord(value) {
|
|
1286
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1287
|
+
}
|
|
1288
|
+
function isNotFoundError2(error) {
|
|
1289
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1290
|
+
}
|
|
467
1291
|
function expectString2(value, path) {
|
|
468
1292
|
if (typeof value !== "string" || value.length === 0) {
|
|
469
1293
|
throw new EvoDevConfigError(`Invalid ${path}; expected non-empty string`);
|
|
@@ -563,53 +1387,53 @@ function expectNonNegativeInteger(value, path) {
|
|
|
563
1387
|
return value;
|
|
564
1388
|
}
|
|
565
1389
|
// packages/core/src/config/store.ts
|
|
566
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
567
|
-
import { dirname } from "node:path";
|
|
1390
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
1391
|
+
import { dirname as dirname2 } from "node:path";
|
|
568
1392
|
function createCoreConfigStore(homeDir) {
|
|
569
|
-
const
|
|
1393
|
+
const paths2 = resolveEvoDevPaths(homeDir);
|
|
570
1394
|
return {
|
|
571
|
-
paths,
|
|
1395
|
+
paths: paths2,
|
|
572
1396
|
async ensureBaseDirs() {
|
|
573
|
-
await
|
|
574
|
-
await
|
|
575
|
-
await
|
|
576
|
-
await
|
|
577
|
-
await
|
|
578
|
-
await
|
|
579
|
-
await
|
|
1397
|
+
await mkdir2(paths2.stateDir, { recursive: true });
|
|
1398
|
+
await mkdir2(paths2.logsDir, { recursive: true });
|
|
1399
|
+
await mkdir2(paths2.knowledgeDir, { recursive: true });
|
|
1400
|
+
await mkdir2(paths2.evosCasesDir, { recursive: true });
|
|
1401
|
+
await mkdir2(paths2.roleAgentsDir, { recursive: true });
|
|
1402
|
+
await mkdir2(paths2.teamsDir, { recursive: true });
|
|
1403
|
+
await mkdir2(paths2.runsDir, { recursive: true });
|
|
580
1404
|
},
|
|
581
1405
|
async ensureKnowledgeBase() {
|
|
582
|
-
await ensureKnowledgeBaseFiles(
|
|
1406
|
+
await ensureKnowledgeBaseFiles(paths2);
|
|
583
1407
|
},
|
|
584
1408
|
async readSettings() {
|
|
585
|
-
return readJsonFile(
|
|
1409
|
+
return readJsonFile(paths2.settingsPath, parseSettings);
|
|
586
1410
|
},
|
|
587
1411
|
async writeSettings(settings) {
|
|
588
|
-
await
|
|
1412
|
+
await writeJsonFile2(paths2.settingsPath, parseSettings(settings));
|
|
589
1413
|
},
|
|
590
1414
|
async mergeAndWriteSettings(input) {
|
|
591
|
-
const current = await readJsonFileOrDefault(
|
|
1415
|
+
const current = await readJsonFileOrDefault(paths2.settingsPath, parseSettings, createDefaultSettings());
|
|
592
1416
|
const merged = mergeSettings(input, current);
|
|
593
|
-
await
|
|
1417
|
+
await writeJsonFile2(paths2.settingsPath, merged);
|
|
594
1418
|
return merged;
|
|
595
1419
|
},
|
|
596
1420
|
async readRegistry() {
|
|
597
|
-
return readJsonFile(
|
|
1421
|
+
return readJsonFile(paths2.registryPath, parseRegistry);
|
|
598
1422
|
},
|
|
599
1423
|
async writeRegistry(registry) {
|
|
600
|
-
await
|
|
1424
|
+
await writeJsonFile2(paths2.registryPath, parseRegistry(registry));
|
|
601
1425
|
},
|
|
602
1426
|
async readInstallState() {
|
|
603
|
-
return readJsonFile(
|
|
1427
|
+
return readJsonFile(paths2.installStatePath, parseInstallState);
|
|
604
1428
|
},
|
|
605
1429
|
async writeInstallState(state) {
|
|
606
|
-
await
|
|
1430
|
+
await writeJsonFile2(paths2.installStatePath, parseInstallState(state));
|
|
607
1431
|
},
|
|
608
1432
|
async readSyncState() {
|
|
609
|
-
return readJsonFile(
|
|
1433
|
+
return readJsonFile(paths2.syncStatePath, parseSyncState);
|
|
610
1434
|
},
|
|
611
1435
|
async writeSyncState(state) {
|
|
612
|
-
await
|
|
1436
|
+
await writeJsonFile2(paths2.syncStatePath, parseSyncState(state));
|
|
613
1437
|
}
|
|
614
1438
|
};
|
|
615
1439
|
}
|
|
@@ -623,10 +1447,11 @@ async function initializeCoreConfig(homeDir) {
|
|
|
623
1447
|
await writeIfMissing(store.paths.syncStatePath, createDefaultSyncState());
|
|
624
1448
|
return store;
|
|
625
1449
|
}
|
|
626
|
-
async function ensureKnowledgeBaseFiles(
|
|
627
|
-
await
|
|
628
|
-
await
|
|
629
|
-
await
|
|
1450
|
+
async function ensureKnowledgeBaseFiles(paths2) {
|
|
1451
|
+
await mkdir2(paths2.knowledgeDir, { recursive: true });
|
|
1452
|
+
await mkdir2(paths2.evosCasesDir, { recursive: true });
|
|
1453
|
+
await ensureOkfKnowledgeBase(paths2.homeDir);
|
|
1454
|
+
await writeTextIfMissing2(`${paths2.knowledgeDir}/README.md`, [
|
|
630
1455
|
"# EvoDev Knowledge",
|
|
631
1456
|
"",
|
|
632
1457
|
"Local-private knowledge base for user-accepted facts, decisions, architecture notes, and reusable domain context.",
|
|
@@ -635,13 +1460,13 @@ async function ensureKnowledgeBaseFiles(paths) {
|
|
|
635
1460
|
""
|
|
636
1461
|
].join(`
|
|
637
1462
|
`));
|
|
638
|
-
await writeIndexIfMissingOrMigrate(
|
|
1463
|
+
await writeIndexIfMissingOrMigrate(paths2.knowledgeIndexPath, "knowledge-index", {
|
|
639
1464
|
version: 1,
|
|
640
1465
|
kind: "knowledge-index",
|
|
641
1466
|
roleTags: [],
|
|
642
1467
|
entries: []
|
|
643
1468
|
});
|
|
644
|
-
await
|
|
1469
|
+
await writeTextIfMissing2(`${paths2.evosDir}/README.md`, [
|
|
645
1470
|
"# EvoDev Evos",
|
|
646
1471
|
"",
|
|
647
1472
|
"Local-private evolution case library for reviewed improvement cases and reusable process changes.",
|
|
@@ -650,20 +1475,20 @@ async function ensureKnowledgeBaseFiles(paths) {
|
|
|
650
1475
|
""
|
|
651
1476
|
].join(`
|
|
652
1477
|
`));
|
|
653
|
-
await
|
|
1478
|
+
await writeTextIfMissing2(`${paths2.evosCasesDir}/README.md`, [
|
|
654
1479
|
"# Evolution Cases",
|
|
655
1480
|
"",
|
|
656
1481
|
"Store one reviewed evolution case per file. Do not store raw prompts, source dumps, secrets, transcripts, or raw command output here.",
|
|
657
1482
|
""
|
|
658
1483
|
].join(`
|
|
659
1484
|
`));
|
|
660
|
-
await writeIndexIfMissingOrMigrate(
|
|
1485
|
+
await writeIndexIfMissingOrMigrate(paths2.evosIndexPath, "evos-index", {
|
|
661
1486
|
version: 1,
|
|
662
1487
|
kind: "evos-index",
|
|
663
1488
|
roleTags: [],
|
|
664
1489
|
cases: []
|
|
665
1490
|
});
|
|
666
|
-
await
|
|
1491
|
+
await writeTextIfMissing2(`${paths2.roleAgentsDir}/README.md`, [
|
|
667
1492
|
"# Role Agents",
|
|
668
1493
|
"",
|
|
669
1494
|
"Local-private role agent registry for EvoDev-managed agent roles and user-reviewed role extensions.",
|
|
@@ -672,13 +1497,13 @@ async function ensureKnowledgeBaseFiles(paths) {
|
|
|
672
1497
|
""
|
|
673
1498
|
].join(`
|
|
674
1499
|
`));
|
|
675
|
-
await writeIndexIfMissingOrMigrate(
|
|
1500
|
+
await writeIndexIfMissingOrMigrate(paths2.roleAgentsIndexPath, "role-agent-index", {
|
|
676
1501
|
version: 1,
|
|
677
1502
|
kind: "role-agent-index",
|
|
678
1503
|
roles: [],
|
|
679
1504
|
projectExtensions: []
|
|
680
1505
|
});
|
|
681
|
-
await
|
|
1506
|
+
await writeTextIfMissing2(`${paths2.teamsDir}/README.md`, [
|
|
682
1507
|
"# Agent Teams",
|
|
683
1508
|
"",
|
|
684
1509
|
"Local-private EvoHub team registry for reviewed role-agent team definitions.",
|
|
@@ -687,7 +1512,7 @@ async function ensureKnowledgeBaseFiles(paths) {
|
|
|
687
1512
|
""
|
|
688
1513
|
].join(`
|
|
689
1514
|
`));
|
|
690
|
-
await writeIndexIfMissingOrMigrate(
|
|
1515
|
+
await writeIndexIfMissingOrMigrate(paths2.teamsIndexPath, "agent-team-index", {
|
|
691
1516
|
version: 1,
|
|
692
1517
|
kind: "agent-team-index",
|
|
693
1518
|
teams: []
|
|
@@ -696,7 +1521,7 @@ async function ensureKnowledgeBaseFiles(paths) {
|
|
|
696
1521
|
async function readJsonFile(filePath, parse) {
|
|
697
1522
|
let raw;
|
|
698
1523
|
try {
|
|
699
|
-
raw = await
|
|
1524
|
+
raw = await readFile3(filePath, "utf8");
|
|
700
1525
|
} catch (error) {
|
|
701
1526
|
throw new EvoDevConfigError(`Cannot read config file (${describeFileError(error)})`, filePath);
|
|
702
1527
|
}
|
|
@@ -727,10 +1552,10 @@ async function readJsonFileOrDefault(filePath, parse, fallback) {
|
|
|
727
1552
|
}
|
|
728
1553
|
async function writeIfMissing(filePath, value) {
|
|
729
1554
|
try {
|
|
730
|
-
await
|
|
1555
|
+
await readFile3(filePath, "utf8");
|
|
731
1556
|
} catch (error) {
|
|
732
1557
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
733
|
-
await
|
|
1558
|
+
await writeJsonFile2(filePath, value);
|
|
734
1559
|
return;
|
|
735
1560
|
}
|
|
736
1561
|
throw new EvoDevConfigError(`Cannot inspect config file (${describeFileError(error)})`, filePath);
|
|
@@ -739,10 +1564,10 @@ async function writeIfMissing(filePath, value) {
|
|
|
739
1564
|
async function writeIndexIfMissingOrMigrate(filePath, kind, defaults) {
|
|
740
1565
|
let raw;
|
|
741
1566
|
try {
|
|
742
|
-
raw = await
|
|
1567
|
+
raw = await readFile3(filePath, "utf8");
|
|
743
1568
|
} catch (error) {
|
|
744
1569
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
745
|
-
await
|
|
1570
|
+
await writeJsonFile2(filePath, defaults);
|
|
746
1571
|
return;
|
|
747
1572
|
}
|
|
748
1573
|
throw new EvoDevConfigError(`Cannot inspect config file (${describeFileError(error)})`, filePath);
|
|
@@ -758,23 +1583,23 @@ async function writeIndexIfMissingOrMigrate(filePath, kind, defaults) {
|
|
|
758
1583
|
const migrated = { ...defaults, ...existing };
|
|
759
1584
|
if (Object.keys(defaults).every((key) => (key in existing)))
|
|
760
1585
|
return;
|
|
761
|
-
await
|
|
1586
|
+
await writeJsonFile2(filePath, migrated);
|
|
762
1587
|
}
|
|
763
|
-
async function
|
|
1588
|
+
async function writeTextIfMissing2(filePath, value) {
|
|
764
1589
|
try {
|
|
765
|
-
await
|
|
1590
|
+
await readFile3(filePath, "utf8");
|
|
766
1591
|
} catch (error) {
|
|
767
1592
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
768
|
-
await
|
|
769
|
-
await
|
|
1593
|
+
await mkdir2(dirname2(filePath), { recursive: true });
|
|
1594
|
+
await writeFile2(filePath, value, "utf8");
|
|
770
1595
|
return;
|
|
771
1596
|
}
|
|
772
1597
|
throw new EvoDevConfigError(`Cannot inspect config file (${describeFileError(error)})`, filePath);
|
|
773
1598
|
}
|
|
774
1599
|
}
|
|
775
|
-
async function
|
|
776
|
-
await
|
|
777
|
-
await
|
|
1600
|
+
async function writeJsonFile2(filePath, value) {
|
|
1601
|
+
await mkdir2(dirname2(filePath), { recursive: true });
|
|
1602
|
+
await writeFile2(filePath, `${JSON.stringify(value, null, 2)}
|
|
778
1603
|
`, "utf8");
|
|
779
1604
|
}
|
|
780
1605
|
function describeFileError(error) {
|
|
@@ -791,6 +1616,7 @@ function isRecord2(value) {
|
|
|
791
1616
|
}
|
|
792
1617
|
export {
|
|
793
1618
|
resolveEvoDevPaths,
|
|
1619
|
+
readRuntimeInjectionSettings,
|
|
794
1620
|
parseSyncState,
|
|
795
1621
|
parseSettings,
|
|
796
1622
|
parseRegistry,
|
|
@@ -801,7 +1627,9 @@ export {
|
|
|
801
1627
|
createDefaultSyncState,
|
|
802
1628
|
createDefaultSettings,
|
|
803
1629
|
createDefaultRegistry,
|
|
1630
|
+
createDefaultMemorySettings,
|
|
804
1631
|
createDefaultInstallState,
|
|
1632
|
+
createDefaultEvolutionSettings,
|
|
805
1633
|
createCoreConfigStore,
|
|
806
1634
|
EvoDevConfigError
|
|
807
1635
|
};
|