@evo-dev/core 0.0.1-alpha.13 → 0.0.1-alpha.15
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/dist/config/index.js +400 -47
- package/dist/index.js +6424 -5987
- package/package.json +1 -1
- package/src/evolution/knowledge/index.ts +93 -18
- package/src/projects/index.ts +490 -9
- package/src/runtime-logs/index.ts +96 -1
package/dist/config/index.js
CHANGED
|
@@ -117,7 +117,25 @@ function expectString(value, path) {
|
|
|
117
117
|
return value;
|
|
118
118
|
}
|
|
119
119
|
// packages/core/src/config/settings.ts
|
|
120
|
-
import { readFile as
|
|
120
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
121
|
+
|
|
122
|
+
// packages/core/src/utils/errors.ts
|
|
123
|
+
function isNotFoundError(error) {
|
|
124
|
+
return error instanceof Error && (("code" in error) && error.code === "ENOENT" || error.message.includes("ENOENT"));
|
|
125
|
+
}
|
|
126
|
+
// packages/core/src/utils/hash.ts
|
|
127
|
+
import { createHash } from "node:crypto";
|
|
128
|
+
function sha256Hex(value) {
|
|
129
|
+
return createHash("sha256").update(value).digest("hex");
|
|
130
|
+
}
|
|
131
|
+
function sha256Short(value, length = 16) {
|
|
132
|
+
return sha256Hex(value).slice(0, length);
|
|
133
|
+
}
|
|
134
|
+
// packages/core/src/utils/ids.ts
|
|
135
|
+
function sanitizeStorageId(value, fallbackPrefix) {
|
|
136
|
+
const sanitized = value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120);
|
|
137
|
+
return sanitized === "" || sanitized === "." || sanitized === ".." ? `${fallbackPrefix}-local` : sanitized;
|
|
138
|
+
}
|
|
121
139
|
// packages/core/src/utils/parsing.ts
|
|
122
140
|
function optionalBoolean(value, fallback) {
|
|
123
141
|
return typeof value === "boolean" ? value : fallback;
|
|
@@ -250,7 +268,13 @@ function parseSessionMemoryPolicy(value) {
|
|
|
250
268
|
toolCallsBetweenUpdates: positiveInteger(value.toolCallsBetweenUpdates, defaults.toolCallsBetweenUpdates)
|
|
251
269
|
};
|
|
252
270
|
}
|
|
271
|
+
// packages/core/src/projects/index.ts
|
|
272
|
+
import { lstat, readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
273
|
+
import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute2, join as join2, parse } from "node:path";
|
|
274
|
+
|
|
253
275
|
// packages/core/src/runtime-logs/index.ts
|
|
276
|
+
import { lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
277
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
254
278
|
var SAFE_EXECUTION_METADATA_KEYS = new Set([
|
|
255
279
|
"phase",
|
|
256
280
|
"runtimeSurface",
|
|
@@ -266,6 +290,7 @@ var SAFE_EXECUTION_METADATA_KEYS = new Set([
|
|
|
266
290
|
"redactionLabels",
|
|
267
291
|
"normalizedEventType"
|
|
268
292
|
]);
|
|
293
|
+
var SENSITIVE_EVENT_TEXT_PATTERN = /https?:\/\/\S+|(^|[^a-z0-9])(secret|token|password|passwd|private|internal|api[_-]?key|apikey|credential|credentials|stdout|stderr|transcript|formattedresponse|additionalcontext)([^a-z0-9]|$)|raw[\s_-]?(payload|prompt|output|source)|source[\s_-]?dump/i;
|
|
269
294
|
var SAFE_REDACTION_LABELS = new Set([
|
|
270
295
|
"raw-command",
|
|
271
296
|
"raw-command-output",
|
|
@@ -273,12 +298,335 @@ var SAFE_REDACTION_LABELS = new Set([
|
|
|
273
298
|
"sensitive-command",
|
|
274
299
|
"sensitive-text"
|
|
275
300
|
]);
|
|
301
|
+
function resolveProjectLogIdentity(homeDir, repoRoot) {
|
|
302
|
+
const identityHomeDir = realpathDirectoryOrFallback(homeDir);
|
|
303
|
+
const workspaceRoot = realpathDirectoryOrFallback(repoRoot);
|
|
304
|
+
const workspaceKey = resolvePathProjectLogKey(identityHomeDir, workspaceRoot);
|
|
305
|
+
const gitCommonDir = resolveGitCommonDir(workspaceRoot);
|
|
306
|
+
if (gitCommonDir === null) {
|
|
307
|
+
return {
|
|
308
|
+
projectKey: workspaceKey,
|
|
309
|
+
workspaceKey,
|
|
310
|
+
projectRoot: workspaceRoot,
|
|
311
|
+
workspaceRoot,
|
|
312
|
+
gitCommonDir: null,
|
|
313
|
+
linkedWorktree: false
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const commonDirOwnsWorktree = basename(gitCommonDir) === ".git";
|
|
317
|
+
const projectIdentityPath = commonDirOwnsWorktree ? dirname(gitCommonDir) : gitCommonDir;
|
|
318
|
+
const projectRoot = commonDirOwnsWorktree ? projectIdentityPath : workspaceRoot;
|
|
319
|
+
return {
|
|
320
|
+
projectKey: resolvePathProjectLogKey(identityHomeDir, projectIdentityPath),
|
|
321
|
+
workspaceKey,
|
|
322
|
+
projectRoot,
|
|
323
|
+
workspaceRoot,
|
|
324
|
+
gitCommonDir,
|
|
325
|
+
linkedWorktree: workspaceKey !== resolvePathProjectLogKey(identityHomeDir, projectIdentityPath)
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function resolvePathProjectLogKey(homeDir, repoRoot) {
|
|
329
|
+
const trimmedHome = stripTrailingSlash2(homeDir);
|
|
330
|
+
const trimmedRepo = stripTrailingSlash2(repoRoot);
|
|
331
|
+
const relativePath = relative(trimmedHome, trimmedRepo);
|
|
332
|
+
const source = relativePath !== "" && !relativePath.startsWith("..") && !isAbsolute(relativePath) ? relativePath : trimmedRepo.replace(/^[/\\]+/, "");
|
|
333
|
+
const projectKey = source.split(/[/\\]+/).filter(Boolean).map((part) => sanitizePathSegment(part)).join("-") || "project-local";
|
|
334
|
+
return sanitizePersistentIdentifier(projectKey, "project");
|
|
335
|
+
}
|
|
336
|
+
function realpathDirectoryOrFallback(path) {
|
|
337
|
+
try {
|
|
338
|
+
return realpathSync(path);
|
|
339
|
+
} catch {
|
|
340
|
+
if (!isAbsolute(path))
|
|
341
|
+
return stripTrailingSlash2(path);
|
|
342
|
+
const missingSegments = [];
|
|
343
|
+
let current = stripTrailingSlash2(path);
|
|
344
|
+
for (;; ) {
|
|
345
|
+
const parent = dirname(current);
|
|
346
|
+
if (parent === current)
|
|
347
|
+
return stripTrailingSlash2(path);
|
|
348
|
+
missingSegments.unshift(basename(current));
|
|
349
|
+
current = parent;
|
|
350
|
+
try {
|
|
351
|
+
return join(realpathSync(current), ...missingSegments);
|
|
352
|
+
} catch {}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function resolveGitCommonDir(workspaceRoot) {
|
|
357
|
+
const markerPath = join(workspaceRoot, ".git");
|
|
358
|
+
let gitDir;
|
|
359
|
+
try {
|
|
360
|
+
const marker = lstatSync(markerPath);
|
|
361
|
+
if (marker.isDirectory()) {
|
|
362
|
+
gitDir = realpathSync(markerPath);
|
|
363
|
+
} else if (marker.isFile()) {
|
|
364
|
+
const value = readFileSync(markerPath, "utf8").trim();
|
|
365
|
+
const match = value.match(/^gitdir:\s*(.+)$/u);
|
|
366
|
+
if (match?.[1] === undefined || match[1].includes("\x00"))
|
|
367
|
+
return null;
|
|
368
|
+
gitDir = realpathSync(isAbsolute(match[1]) ? match[1] : resolve(workspaceRoot, match[1]));
|
|
369
|
+
} else {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
} catch {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
const commonDirValue = readFileSync(join(gitDir, "commondir"), "utf8").trim();
|
|
377
|
+
if (commonDirValue === "" || commonDirValue.includes("\x00"))
|
|
378
|
+
return gitDir;
|
|
379
|
+
return realpathSync(isAbsolute(commonDirValue) ? commonDirValue : resolve(gitDir, commonDirValue));
|
|
380
|
+
} catch {
|
|
381
|
+
return gitDir;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function sanitizePathSegment(value) {
|
|
385
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || "session-local";
|
|
386
|
+
}
|
|
387
|
+
function sanitizePersistentIdentifier(value, prefix) {
|
|
388
|
+
const pathSafe = value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || `${prefix}-local`;
|
|
389
|
+
if (!SENSITIVE_EVENT_TEXT_PATTERN.test(value) && !SENSITIVE_EVENT_TEXT_PATTERN.test(pathSafe)) {
|
|
390
|
+
return pathSafe;
|
|
391
|
+
}
|
|
392
|
+
return `${prefix}-${sha256Short(value)}`;
|
|
393
|
+
}
|
|
394
|
+
function stripTrailingSlash2(path) {
|
|
395
|
+
if (path === "/" || /^[A-Za-z]:[\\/]?$/.test(path))
|
|
396
|
+
return path;
|
|
397
|
+
return path.replace(/[/\\]+$/, "");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// packages/core/src/projects/index.ts
|
|
401
|
+
class ProjectRegistrationError extends Error {
|
|
402
|
+
code;
|
|
403
|
+
constructor(code, message) {
|
|
404
|
+
super(message);
|
|
405
|
+
this.name = "ProjectRegistrationError";
|
|
406
|
+
this.code = code;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function resolveProjectRegistryPaths(homeDir) {
|
|
410
|
+
const paths = resolveEvoDevPaths(homeDir);
|
|
411
|
+
const discoveredDir = join2(paths.stateDir, "projects", "discovered");
|
|
412
|
+
const registeredDir = join2(paths.rootDir, "projects");
|
|
413
|
+
const workspaceDir = join2(paths.stateDir, "projects", "workspaces");
|
|
414
|
+
const aliasDir = join2(paths.stateDir, "projects", "aliases");
|
|
415
|
+
return {
|
|
416
|
+
discoveredDir,
|
|
417
|
+
registeredDir,
|
|
418
|
+
workspaceDir,
|
|
419
|
+
aliasDir,
|
|
420
|
+
discoveredPath: (projectKey) => join2(discoveredDir, `${projectKey}.json`),
|
|
421
|
+
registeredPath: (projectKey) => join2(registeredDir, projectKey, "workspace.json"),
|
|
422
|
+
workspacePath: (workspaceKey) => join2(workspaceDir, `${workspaceKey}.json`),
|
|
423
|
+
aliasPath: (aliasProjectKey) => join2(aliasDir, `${aliasProjectKey}.json`)
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
async function resolveProjectWorkspaceFromCwd(input) {
|
|
427
|
+
if (!isAbsolute2(input.cwd))
|
|
428
|
+
return null;
|
|
429
|
+
let cwd;
|
|
430
|
+
try {
|
|
431
|
+
const info = await stat(input.cwd);
|
|
432
|
+
if (!info.isDirectory())
|
|
433
|
+
return null;
|
|
434
|
+
cwd = await realpath(input.cwd);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (isNotFoundError(error))
|
|
437
|
+
return null;
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
const gitRoot = await findNearestGitRoot(cwd);
|
|
441
|
+
const workspaceRoot = gitRoot ?? cwd;
|
|
442
|
+
const identity = resolveProjectLogIdentity(input.homeDir, workspaceRoot);
|
|
443
|
+
return {
|
|
444
|
+
projectKey: sanitizeStorageId(identity.projectKey, "project"),
|
|
445
|
+
workspaceKey: sanitizeStorageId(identity.workspaceKey, "workspace"),
|
|
446
|
+
displayName: basename2(identity.projectRoot) || parse(identity.projectRoot).root,
|
|
447
|
+
projectRoot: identity.projectRoot,
|
|
448
|
+
workspaceRoot,
|
|
449
|
+
workspaceKind: gitRoot === null ? "directory" : "git",
|
|
450
|
+
gitCommonDir: identity.gitCommonDir,
|
|
451
|
+
linkedWorktree: identity.linkedWorktree,
|
|
452
|
+
cwd
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
async function listProjectAliases(input) {
|
|
456
|
+
const paths = resolveProjectRegistryPaths(input.homeDir);
|
|
457
|
+
const aliases = {};
|
|
458
|
+
for (const path of await listJsonFilePaths(paths.aliasDir)) {
|
|
459
|
+
const record = await readProjectAlias(path);
|
|
460
|
+
if (record !== null)
|
|
461
|
+
aliases[record.aliasProjectKey] = record.canonicalProjectKey;
|
|
462
|
+
}
|
|
463
|
+
for (const path of await listJsonFilePaths(paths.discoveredDir)) {
|
|
464
|
+
const record = await readDiscoveredProject(path);
|
|
465
|
+
if (record !== null) {
|
|
466
|
+
await collectAvailableProjectAlias(input.homeDir, record, aliases);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
for (const projectKey of await listDirectoryNames2(paths.registeredDir)) {
|
|
470
|
+
const record = await readRegisteredProject(join2(paths.registeredDir, projectKey, "workspace.json"));
|
|
471
|
+
if (record !== null) {
|
|
472
|
+
await collectAvailableProjectAlias(input.homeDir, record, aliases);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return normalizeProjectAliases(aliases);
|
|
476
|
+
}
|
|
477
|
+
function canonicalizeProjectKey(projectKey, aliases) {
|
|
478
|
+
let current = projectKey;
|
|
479
|
+
const seen = new Set;
|
|
480
|
+
while (!seen.has(current)) {
|
|
481
|
+
seen.add(current);
|
|
482
|
+
const next = aliases[current];
|
|
483
|
+
if (next === undefined || next === current)
|
|
484
|
+
return current;
|
|
485
|
+
current = next;
|
|
486
|
+
}
|
|
487
|
+
return projectKey;
|
|
488
|
+
}
|
|
489
|
+
async function listEquivalentProjectKeys(input) {
|
|
490
|
+
const aliases = await listProjectAliases({ homeDir: input.homeDir });
|
|
491
|
+
const canonical = canonicalizeProjectKey(input.projectKey, aliases);
|
|
492
|
+
return [
|
|
493
|
+
...new Set([
|
|
494
|
+
canonical,
|
|
495
|
+
input.projectKey,
|
|
496
|
+
...Object.keys(aliases).filter((alias) => canonicalizeProjectKey(alias, aliases) === canonical)
|
|
497
|
+
])
|
|
498
|
+
].sort();
|
|
499
|
+
}
|
|
500
|
+
async function collectAvailableProjectAlias(homeDir, record, aliases) {
|
|
501
|
+
const workspace = await resolveProjectWorkspaceFromCwd({
|
|
502
|
+
homeDir,
|
|
503
|
+
cwd: record.workspaceRoot
|
|
504
|
+
}).catch(() => null);
|
|
505
|
+
if (workspace === null)
|
|
506
|
+
return;
|
|
507
|
+
if (record.projectKey !== workspace.projectKey) {
|
|
508
|
+
aliases[record.projectKey] = workspace.projectKey;
|
|
509
|
+
}
|
|
510
|
+
if (workspace.workspaceKey !== workspace.projectKey) {
|
|
511
|
+
aliases[workspace.workspaceKey] = workspace.projectKey;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function normalizeProjectAliases(aliases) {
|
|
515
|
+
return Object.fromEntries(Object.keys(aliases).sort().flatMap((alias) => {
|
|
516
|
+
const canonical = canonicalizeProjectKey(alias, aliases);
|
|
517
|
+
return canonical === alias ? [] : [[alias, canonical]];
|
|
518
|
+
}));
|
|
519
|
+
}
|
|
520
|
+
async function findNearestGitRoot(cwd) {
|
|
521
|
+
let current = cwd;
|
|
522
|
+
for (;; ) {
|
|
523
|
+
try {
|
|
524
|
+
const marker = await lstat(join2(current, ".git"));
|
|
525
|
+
if (marker.isDirectory() || marker.isFile())
|
|
526
|
+
return current;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
if (!isNotFoundError(error))
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
531
|
+
const parent = dirname2(current);
|
|
532
|
+
if (parent === current)
|
|
533
|
+
return null;
|
|
534
|
+
current = parent;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async function readDiscoveredProject(path) {
|
|
538
|
+
try {
|
|
539
|
+
return parseDiscoveredProject(JSON.parse(await readFile(path, "utf8")));
|
|
540
|
+
} catch (error) {
|
|
541
|
+
if (isNotFoundError(error) || error instanceof ProjectRegistrationError)
|
|
542
|
+
return null;
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async function readRegisteredProject(path) {
|
|
547
|
+
try {
|
|
548
|
+
return parseRegisteredProject(JSON.parse(await readFile(path, "utf8")));
|
|
549
|
+
} catch (error) {
|
|
550
|
+
if (isNotFoundError(error) || error instanceof ProjectRegistrationError)
|
|
551
|
+
return null;
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
async function readProjectAlias(path) {
|
|
556
|
+
try {
|
|
557
|
+
return parseProjectAlias(JSON.parse(await readFile(path, "utf8")));
|
|
558
|
+
} catch (error) {
|
|
559
|
+
if (isNotFoundError(error) || error instanceof ProjectRegistrationError)
|
|
560
|
+
return null;
|
|
561
|
+
throw error;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function parseDiscoveredProject(value) {
|
|
565
|
+
const record = requireRecord(value);
|
|
566
|
+
if (record.schemaVersion !== 1 || record.kind !== "discovered-project" || !isProjectKey(record.projectKey) || !isNonEmptyString(record.displayName) || !isAbsoluteString(record.workspaceRoot) || !isAbsoluteString(record.lastCwd) || !isWorkspaceKind(record.workspaceKind) || !isDiscoveryTargetArray(record.sourceTargets) || !(record.lastSessionKey === null || isNonEmptyString(record.lastSessionKey)) || !isNonEmptyString(record.firstSeenAt) || !isNonEmptyString(record.lastSeenAt) || record.localOnly !== true || record.sourceContentStored !== false) {
|
|
567
|
+
throw new ProjectRegistrationError("invalid", "Discovered project record is invalid.");
|
|
568
|
+
}
|
|
569
|
+
return record;
|
|
570
|
+
}
|
|
571
|
+
function parseRegisteredProject(value) {
|
|
572
|
+
const record = requireRecord(value);
|
|
573
|
+
if (record.schemaVersion !== 1 || record.kind !== "registered-project" || !isProjectKey(record.projectKey) || !isNonEmptyString(record.displayName) || !isAbsoluteString(record.workspaceRoot) || !isWorkspaceKind(record.workspaceKind) || !isNonEmptyString(record.registeredAt) || !isNonEmptyString(record.lastSeenAt) || record.localOnly !== true || record.sourceContentStored !== false) {
|
|
574
|
+
throw new ProjectRegistrationError("invalid", "Registered project record is invalid.");
|
|
575
|
+
}
|
|
576
|
+
return record;
|
|
577
|
+
}
|
|
578
|
+
function parseProjectAlias(value) {
|
|
579
|
+
const record = requireRecord(value);
|
|
580
|
+
if (record.schemaVersion !== 1 || record.kind !== "project-alias" || !isProjectKey(record.aliasProjectKey) || !isProjectKey(record.canonicalProjectKey) || record.aliasProjectKey === record.canonicalProjectKey || !isAbsoluteString(record.workspaceRoot) || !isNonEmptyString(record.createdAt) || !isNonEmptyString(record.updatedAt) || record.localOnly !== true || record.sourceContentStored !== false) {
|
|
581
|
+
throw new ProjectRegistrationError("invalid", "Project alias record is invalid.");
|
|
582
|
+
}
|
|
583
|
+
return record;
|
|
584
|
+
}
|
|
585
|
+
async function listJsonFilePaths(path) {
|
|
586
|
+
try {
|
|
587
|
+
return (await readdir(path, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => join2(path, entry.name)).sort();
|
|
588
|
+
} catch (error) {
|
|
589
|
+
if (isNotFoundError(error))
|
|
590
|
+
return [];
|
|
591
|
+
throw error;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
async function listDirectoryNames2(path) {
|
|
595
|
+
try {
|
|
596
|
+
return (await readdir(path, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
597
|
+
} catch (error) {
|
|
598
|
+
if (isNotFoundError(error))
|
|
599
|
+
return [];
|
|
600
|
+
throw error;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function isProjectKey(value) {
|
|
604
|
+
return typeof value === "string" && value !== "." && value !== ".." && /^[A-Za-z0-9._-]{1,120}$/.test(value);
|
|
605
|
+
}
|
|
606
|
+
function isNonEmptyString(value) {
|
|
607
|
+
return typeof value === "string" && value.trim() !== "";
|
|
608
|
+
}
|
|
609
|
+
function isAbsoluteString(value) {
|
|
610
|
+
return isNonEmptyString(value) && isAbsolute2(value);
|
|
611
|
+
}
|
|
612
|
+
function isWorkspaceKind(value) {
|
|
613
|
+
return value === "git" || value === "directory";
|
|
614
|
+
}
|
|
615
|
+
function isDiscoveryTargetArray(value) {
|
|
616
|
+
return Array.isArray(value) && value.length > 0 && value.every((item) => item === "claude" || item === "codex");
|
|
617
|
+
}
|
|
618
|
+
function requireRecord(value) {
|
|
619
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
620
|
+
throw new ProjectRegistrationError("invalid", "Project record must be an object.");
|
|
621
|
+
}
|
|
622
|
+
return value;
|
|
623
|
+
}
|
|
276
624
|
|
|
277
625
|
// packages/core/src/evolution/evidence/session-memory/constants.ts
|
|
278
626
|
var DEFAULT_MAX_RAW_EVENT_BYTES = 64 * 1024;
|
|
279
627
|
// 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";
|
|
628
|
+
import { mkdir, readFile as readFile2, readdir as readdir2, rename, rm, stat as stat2, writeFile } from "node:fs/promises";
|
|
629
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, join as join3, relative as relative2, resolve as resolve2 } from "node:path";
|
|
282
630
|
var RESERVED_OKF_FILENAMES = new Set(["index.md", "log.md"]);
|
|
283
631
|
var ACTIVE_OKF_REVIEW_STATES = ["accepted", "auto-accepted"];
|
|
284
632
|
var OKF_REVIEW_STATES = [
|
|
@@ -313,9 +661,9 @@ function resolveOkfKnowledgePaths(homeDir) {
|
|
|
313
661
|
const paths2 = resolveEvoDevPaths(homeDir);
|
|
314
662
|
return {
|
|
315
663
|
knowledgeDir: paths2.knowledgeDir,
|
|
316
|
-
okfDir:
|
|
317
|
-
indexesDir:
|
|
318
|
-
tmpDir:
|
|
664
|
+
okfDir: join3(paths2.knowledgeDir, "okf"),
|
|
665
|
+
indexesDir: join3(paths2.knowledgeDir, "indexes"),
|
|
666
|
+
tmpDir: join3(paths2.knowledgeDir, "tmp")
|
|
319
667
|
};
|
|
320
668
|
}
|
|
321
669
|
async function ensureOkfKnowledgeBase(homeDir) {
|
|
@@ -375,11 +723,11 @@ async function rebuildOkfKnowledgeIndexes(input) {
|
|
|
375
723
|
pathScopes: concept.pathScopes
|
|
376
724
|
}));
|
|
377
725
|
const pathsWritten = [
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
726
|
+
join3(paths2.indexesDir, "index.json"),
|
|
727
|
+
join3(paths2.indexesDir, "concepts.json"),
|
|
728
|
+
join3(paths2.indexesDir, "repos.json"),
|
|
729
|
+
join3(paths2.indexesDir, "roles.json"),
|
|
730
|
+
join3(paths2.indexesDir, "workflows.json")
|
|
383
731
|
];
|
|
384
732
|
await writeJson2(pathsWritten[0], {
|
|
385
733
|
schemaVersion: 1,
|
|
@@ -413,31 +761,35 @@ async function listOkfKnowledgeConcepts(input) {
|
|
|
413
761
|
const okfDir = resolveOkfKnowledgePaths(input.homeDir).okfDir;
|
|
414
762
|
if (!await pathExists2(okfDir))
|
|
415
763
|
return [];
|
|
764
|
+
const equivalentProjectKeys = input.projectKey === undefined ? undefined : await listEquivalentProjectKeys({
|
|
765
|
+
homeDir: input.homeDir,
|
|
766
|
+
projectKey: input.projectKey
|
|
767
|
+
});
|
|
416
768
|
const files = await listMarkdownFiles(okfDir);
|
|
417
769
|
const concepts = [];
|
|
418
770
|
for (const file of files) {
|
|
419
771
|
if (RESERVED_OKF_FILENAMES.has(file.name))
|
|
420
772
|
continue;
|
|
421
|
-
const content = await
|
|
773
|
+
const content = await readFile2(file.path, "utf8");
|
|
422
774
|
const parsed = parseOkfConceptFile(okfDir, file.path, content);
|
|
423
775
|
if (parsed !== null)
|
|
424
776
|
concepts.push(parsed);
|
|
425
777
|
}
|
|
426
|
-
return concepts.filter((concept) => matchesConceptFilters(concept, input)).sort((left, right) => left.id.localeCompare(right.id));
|
|
778
|
+
return concepts.filter((concept) => matchesConceptFilters(concept, input, equivalentProjectKeys)).sort((left, right) => left.id.localeCompare(right.id));
|
|
427
779
|
}
|
|
428
780
|
async function ensureLocalKnowledgeGitRepository(knowledgeDir) {
|
|
429
781
|
await mkdir(knowledgeDir, { recursive: true });
|
|
430
|
-
const gitDir =
|
|
782
|
+
const gitDir = join3(knowledgeDir, ".git");
|
|
431
783
|
if (await pathExists2(gitDir))
|
|
432
784
|
return;
|
|
433
|
-
await mkdir(
|
|
434
|
-
await mkdir(
|
|
435
|
-
await mkdir(
|
|
436
|
-
await mkdir(
|
|
437
|
-
await mkdir(
|
|
438
|
-
await writeTextIfMissing(
|
|
785
|
+
await mkdir(join3(gitDir, "objects", "info"), { recursive: true });
|
|
786
|
+
await mkdir(join3(gitDir, "objects", "pack"), { recursive: true });
|
|
787
|
+
await mkdir(join3(gitDir, "refs", "heads"), { recursive: true });
|
|
788
|
+
await mkdir(join3(gitDir, "refs", "tags"), { recursive: true });
|
|
789
|
+
await mkdir(join3(gitDir, "info"), { recursive: true });
|
|
790
|
+
await writeTextIfMissing(join3(gitDir, "HEAD"), `ref: refs/heads/main
|
|
439
791
|
`);
|
|
440
|
-
await writeTextIfMissing(
|
|
792
|
+
await writeTextIfMissing(join3(gitDir, "config"), [
|
|
441
793
|
"[core]",
|
|
442
794
|
"\trepositoryformatversion = 0",
|
|
443
795
|
"\tfilemode = true",
|
|
@@ -446,7 +798,7 @@ async function ensureLocalKnowledgeGitRepository(knowledgeDir) {
|
|
|
446
798
|
""
|
|
447
799
|
].join(`
|
|
448
800
|
`));
|
|
449
|
-
await writeTextIfMissing(
|
|
801
|
+
await writeTextIfMissing(join3(gitDir, "info", "exclude"), [
|
|
450
802
|
"# EvoDev user-local knowledge git repository.",
|
|
451
803
|
"# No remote is configured by default.",
|
|
452
804
|
""
|
|
@@ -456,7 +808,7 @@ async function ensureLocalKnowledgeGitRepository(knowledgeDir) {
|
|
|
456
808
|
async function writeTextIfMissing(path, value) {
|
|
457
809
|
if (await pathExists2(path))
|
|
458
810
|
return;
|
|
459
|
-
await mkdir(
|
|
811
|
+
await mkdir(dirname3(path), { recursive: true });
|
|
460
812
|
await writeFile(path, value, { encoding: "utf8", flag: "wx" });
|
|
461
813
|
}
|
|
462
814
|
function sanitizeOkfText(value) {
|
|
@@ -466,7 +818,7 @@ async function ensureOkfDirectory(okfDir, relativeDir, title, description) {
|
|
|
466
818
|
const dir = relativeDir === "" || relativeDir === "." ? okfDir : resolveOkfTargetPath(okfDir, relativeDir);
|
|
467
819
|
await mkdir(dir, { recursive: true });
|
|
468
820
|
const isRoot = dir === okfDir;
|
|
469
|
-
const indexPath =
|
|
821
|
+
const indexPath = join3(dir, "index.md");
|
|
470
822
|
if (!await pathExists2(indexPath)) {
|
|
471
823
|
await writeFile(indexPath, isRoot ? [
|
|
472
824
|
"---",
|
|
@@ -483,7 +835,7 @@ async function ensureOkfDirectory(okfDir, relativeDir, title, description) {
|
|
|
483
835
|
`) : [`# ${title}`, "", description, ""].join(`
|
|
484
836
|
`), "utf8");
|
|
485
837
|
}
|
|
486
|
-
const logPath =
|
|
838
|
+
const logPath = join3(dir, "log.md");
|
|
487
839
|
if (!await pathExists2(logPath)) {
|
|
488
840
|
await writeFile(logPath, [
|
|
489
841
|
"# Directory Update Log",
|
|
@@ -539,8 +891,9 @@ function parseOkfConceptFile(okfDir, filePath, content) {
|
|
|
539
891
|
body: parsed.body
|
|
540
892
|
};
|
|
541
893
|
}
|
|
542
|
-
function matchesConceptFilters(concept, input) {
|
|
543
|
-
|
|
894
|
+
function matchesConceptFilters(concept, input, equivalentProjectKeys) {
|
|
895
|
+
const projectKeys = (equivalentProjectKeys ?? []).map(sanitizeSlug);
|
|
896
|
+
if (input.projectKey !== undefined && concept.repoTags.length > 0 && ![sanitizeSlug(input.projectKey), ...projectKeys].some((projectKey) => concept.repoTags.includes(projectKey) || concept.tags.includes(`repo:${projectKey}`))) {
|
|
544
897
|
return false;
|
|
545
898
|
}
|
|
546
899
|
if (input.roleId !== undefined && concept.roleTags.length > 0 && !concept.roleTags.includes(sanitizeSlug(input.roleId)) && !concept.tags.includes(`role:${sanitizeSlug(input.roleId)}`)) {
|
|
@@ -761,10 +1114,10 @@ function parseYamlValue(value) {
|
|
|
761
1114
|
async function listMarkdownFiles(root) {
|
|
762
1115
|
if (!await pathExists2(root))
|
|
763
1116
|
return [];
|
|
764
|
-
const entries = await
|
|
1117
|
+
const entries = await readdir2(root, { withFileTypes: true });
|
|
765
1118
|
const files = [];
|
|
766
1119
|
for (const entry of entries) {
|
|
767
|
-
const path =
|
|
1120
|
+
const path = join3(root, entry.name);
|
|
768
1121
|
if (entry.isDirectory()) {
|
|
769
1122
|
files.push(...await listMarkdownFiles(path));
|
|
770
1123
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -790,14 +1143,14 @@ function resolveOkfTargetPath(okfDir, targetPath) {
|
|
|
790
1143
|
if (clean.split("/").some((segment) => segment === ".." || segment === "." || segment === "")) {
|
|
791
1144
|
throw new Error(`Unsafe OKF target path: ${targetPath}`);
|
|
792
1145
|
}
|
|
793
|
-
const resolved =
|
|
794
|
-
const rel =
|
|
1146
|
+
const resolved = join3(okfDir, clean);
|
|
1147
|
+
const rel = relative2(okfDir, resolved);
|
|
795
1148
|
if (rel.startsWith("..") || rel === "")
|
|
796
1149
|
throw new Error(`Unsafe OKF target path: ${targetPath}`);
|
|
797
1150
|
return resolved;
|
|
798
1151
|
}
|
|
799
1152
|
function toOkfRelativePath(okfDir, path) {
|
|
800
|
-
return
|
|
1153
|
+
return relative2(okfDir, path).replace(/\\/gu, "/");
|
|
801
1154
|
}
|
|
802
1155
|
function sanitizeSlug(value) {
|
|
803
1156
|
const slug = value.trim().toLowerCase().replace(/[^a-z0-9._/-]+/gu, "-").replace(/\/+/gu, "/").replace(/^-+|-+$/gu, "");
|
|
@@ -813,14 +1166,14 @@ function todayIsoDate() {
|
|
|
813
1166
|
return new Date().toISOString().slice(0, 10);
|
|
814
1167
|
}
|
|
815
1168
|
async function writeJson2(path, value, options = {}) {
|
|
816
|
-
await mkdir(
|
|
1169
|
+
await mkdir(dirname3(path), { recursive: true });
|
|
817
1170
|
const flag = options.overwrite === true ? "w" : "wx";
|
|
818
1171
|
await writeFile(path, `${JSON.stringify(value, null, 2)}
|
|
819
1172
|
`, { encoding: "utf8", flag });
|
|
820
1173
|
}
|
|
821
1174
|
async function pathExists2(path) {
|
|
822
1175
|
try {
|
|
823
|
-
await
|
|
1176
|
+
await stat2(path);
|
|
824
1177
|
return true;
|
|
825
1178
|
} catch (error) {
|
|
826
1179
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
@@ -1174,7 +1527,7 @@ function mergeSettings(existing, defaults = createDefaultSettings()) {
|
|
|
1174
1527
|
async function readRuntimeInjectionSettings(homeDir) {
|
|
1175
1528
|
const paths2 = resolveEvoDevPaths(homeDir);
|
|
1176
1529
|
try {
|
|
1177
|
-
return parseSettings(JSON.parse(await
|
|
1530
|
+
return parseSettings(JSON.parse(await readFile3(paths2.settingsPath, "utf8"))).memory;
|
|
1178
1531
|
} catch (error) {
|
|
1179
1532
|
if (isNotFoundError2(error))
|
|
1180
1533
|
return createDefaultMemorySettings();
|
|
@@ -1387,8 +1740,8 @@ function expectNonNegativeInteger(value, path) {
|
|
|
1387
1740
|
return value;
|
|
1388
1741
|
}
|
|
1389
1742
|
// packages/core/src/config/store.ts
|
|
1390
|
-
import { mkdir as mkdir2, readFile as
|
|
1391
|
-
import { dirname as
|
|
1743
|
+
import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
|
|
1744
|
+
import { dirname as dirname4 } from "node:path";
|
|
1392
1745
|
function createCoreConfigStore(homeDir) {
|
|
1393
1746
|
const paths2 = resolveEvoDevPaths(homeDir);
|
|
1394
1747
|
return {
|
|
@@ -1518,10 +1871,10 @@ async function ensureKnowledgeBaseFiles(paths2) {
|
|
|
1518
1871
|
teams: []
|
|
1519
1872
|
});
|
|
1520
1873
|
}
|
|
1521
|
-
async function readJsonFile(filePath,
|
|
1874
|
+
async function readJsonFile(filePath, parse2) {
|
|
1522
1875
|
let raw;
|
|
1523
1876
|
try {
|
|
1524
|
-
raw = await
|
|
1877
|
+
raw = await readFile4(filePath, "utf8");
|
|
1525
1878
|
} catch (error) {
|
|
1526
1879
|
throw new EvoDevConfigError(`Cannot read config file (${describeFileError(error)})`, filePath);
|
|
1527
1880
|
}
|
|
@@ -1532,7 +1885,7 @@ async function readJsonFile(filePath, parse) {
|
|
|
1532
1885
|
throw new EvoDevConfigError(`Invalid JSON (${describeFileError(error)})`, filePath);
|
|
1533
1886
|
}
|
|
1534
1887
|
try {
|
|
1535
|
-
return
|
|
1888
|
+
return parse2(json);
|
|
1536
1889
|
} catch (error) {
|
|
1537
1890
|
if (error instanceof EvoDevConfigError) {
|
|
1538
1891
|
throw new EvoDevConfigError(error.message, filePath);
|
|
@@ -1540,9 +1893,9 @@ async function readJsonFile(filePath, parse) {
|
|
|
1540
1893
|
throw error;
|
|
1541
1894
|
}
|
|
1542
1895
|
}
|
|
1543
|
-
async function readJsonFileOrDefault(filePath,
|
|
1896
|
+
async function readJsonFileOrDefault(filePath, parse2, fallback) {
|
|
1544
1897
|
try {
|
|
1545
|
-
return await readJsonFile(filePath,
|
|
1898
|
+
return await readJsonFile(filePath, parse2);
|
|
1546
1899
|
} catch (error) {
|
|
1547
1900
|
if (error instanceof EvoDevConfigError && error.message.includes("ENOENT")) {
|
|
1548
1901
|
return fallback;
|
|
@@ -1552,7 +1905,7 @@ async function readJsonFileOrDefault(filePath, parse, fallback) {
|
|
|
1552
1905
|
}
|
|
1553
1906
|
async function writeIfMissing(filePath, value) {
|
|
1554
1907
|
try {
|
|
1555
|
-
await
|
|
1908
|
+
await readFile4(filePath, "utf8");
|
|
1556
1909
|
} catch (error) {
|
|
1557
1910
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
1558
1911
|
await writeJsonFile2(filePath, value);
|
|
@@ -1564,7 +1917,7 @@ async function writeIfMissing(filePath, value) {
|
|
|
1564
1917
|
async function writeIndexIfMissingOrMigrate(filePath, kind, defaults) {
|
|
1565
1918
|
let raw;
|
|
1566
1919
|
try {
|
|
1567
|
-
raw = await
|
|
1920
|
+
raw = await readFile4(filePath, "utf8");
|
|
1568
1921
|
} catch (error) {
|
|
1569
1922
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
1570
1923
|
await writeJsonFile2(filePath, defaults);
|
|
@@ -1587,10 +1940,10 @@ async function writeIndexIfMissingOrMigrate(filePath, kind, defaults) {
|
|
|
1587
1940
|
}
|
|
1588
1941
|
async function writeTextIfMissing2(filePath, value) {
|
|
1589
1942
|
try {
|
|
1590
|
-
await
|
|
1943
|
+
await readFile4(filePath, "utf8");
|
|
1591
1944
|
} catch (error) {
|
|
1592
1945
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
1593
|
-
await mkdir2(
|
|
1946
|
+
await mkdir2(dirname4(filePath), { recursive: true });
|
|
1594
1947
|
await writeFile2(filePath, value, "utf8");
|
|
1595
1948
|
return;
|
|
1596
1949
|
}
|
|
@@ -1598,7 +1951,7 @@ async function writeTextIfMissing2(filePath, value) {
|
|
|
1598
1951
|
}
|
|
1599
1952
|
}
|
|
1600
1953
|
async function writeJsonFile2(filePath, value) {
|
|
1601
|
-
await mkdir2(
|
|
1954
|
+
await mkdir2(dirname4(filePath), { recursive: true });
|
|
1602
1955
|
await writeFile2(filePath, `${JSON.stringify(value, null, 2)}
|
|
1603
1956
|
`, "utf8");
|
|
1604
1957
|
}
|