@evo-dev/core 0.0.1-alpha.12 → 0.0.1-alpha.14

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.
@@ -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 readFile2 } from "node:fs/promises";
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: join(paths2.knowledgeDir, "okf"),
317
- indexesDir: join(paths2.knowledgeDir, "indexes"),
318
- tmpDir: join(paths2.knowledgeDir, "tmp")
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
- 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")
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 readFile(file.path, "utf8");
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 = join(knowledgeDir, ".git");
782
+ const gitDir = join3(knowledgeDir, ".git");
431
783
  if (await pathExists2(gitDir))
432
784
  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
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(join(gitDir, "config"), [
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(join(gitDir, "info", "exclude"), [
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(dirname(path), { recursive: true });
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 = join(dir, "index.md");
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 = join(dir, "log.md");
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
- if (input.projectKey !== undefined && concept.repoTags.length > 0 && !concept.repoTags.includes(sanitizeSlug(input.projectKey)) && !concept.tags.includes(`repo:${sanitizeSlug(input.projectKey)}`)) {
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 readdir(root, { withFileTypes: true });
1117
+ const entries = await readdir2(root, { withFileTypes: true });
765
1118
  const files = [];
766
1119
  for (const entry of entries) {
767
- const path = join(root, entry.name);
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 = join(okfDir, clean);
794
- const rel = relative(okfDir, resolved);
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 relative(okfDir, path).replace(/\\/gu, "/");
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(dirname(path), { recursive: true });
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 stat(path);
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 readFile2(paths2.settingsPath, "utf8"))).memory;
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 readFile3, writeFile as writeFile2 } from "node:fs/promises";
1391
- import { dirname as dirname2 } from "node:path";
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, parse) {
1874
+ async function readJsonFile(filePath, parse2) {
1522
1875
  let raw;
1523
1876
  try {
1524
- raw = await readFile3(filePath, "utf8");
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 parse(json);
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, parse, fallback) {
1896
+ async function readJsonFileOrDefault(filePath, parse2, fallback) {
1544
1897
  try {
1545
- return await readJsonFile(filePath, parse);
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 readFile3(filePath, "utf8");
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 readFile3(filePath, "utf8");
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 readFile3(filePath, "utf8");
1943
+ await readFile4(filePath, "utf8");
1591
1944
  } catch (error) {
1592
1945
  if (isNodeError(error) && error.code === "ENOENT") {
1593
- await mkdir2(dirname2(filePath), { recursive: true });
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(dirname2(filePath), { recursive: true });
1954
+ await mkdir2(dirname4(filePath), { recursive: true });
1602
1955
  await writeFile2(filePath, `${JSON.stringify(value, null, 2)}
1603
1956
  `, "utf8");
1604
1957
  }