@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evo-dev/core",
3
- "version": "0.0.1-alpha.13",
3
+ "version": "0.0.1-alpha.15",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -3,6 +3,11 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
3
3
  import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
4
4
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
5
5
  import { resolveEvoDevPaths } from "../../config/paths.ts";
6
+ import {
7
+ canonicalizeProjectKey,
8
+ listEquivalentProjectKeys,
9
+ listProjectAliases,
10
+ } from "../../projects/index.ts";
6
11
  import { createStableId, normalizeTimestamp } from "../../utils/index.ts";
7
12
  import { listEvolutionEvosCases } from "../candidates/index.ts";
8
13
  import {
@@ -377,6 +382,7 @@ interface OkfPaths {
377
382
 
378
383
  interface KnowledgeQueryScope {
379
384
  projectKey?: string;
385
+ projectKeys?: string[];
380
386
  roleId?: string;
381
387
  workflowId?: string;
382
388
  paths: string[];
@@ -2367,6 +2373,13 @@ export async function listOkfKnowledgeConcepts(input: {
2367
2373
  }): Promise<OkfKnowledgeConcept[]> {
2368
2374
  const okfDir = resolveOkfKnowledgePaths(input.homeDir).okfDir;
2369
2375
  if (!(await pathExists(okfDir))) return [];
2376
+ const equivalentProjectKeys =
2377
+ input.projectKey === undefined
2378
+ ? undefined
2379
+ : await listEquivalentProjectKeys({
2380
+ homeDir: input.homeDir,
2381
+ projectKey: input.projectKey,
2382
+ });
2370
2383
  const files = await listMarkdownFiles(okfDir);
2371
2384
  const concepts: OkfKnowledgeConcept[] = [];
2372
2385
  for (const file of files) {
@@ -2376,7 +2389,7 @@ export async function listOkfKnowledgeConcepts(input: {
2376
2389
  if (parsed !== null) concepts.push(parsed);
2377
2390
  }
2378
2391
  return concepts
2379
- .filter((concept) => matchesConceptFilters(concept, input))
2392
+ .filter((concept) => matchesConceptFilters(concept, input, equivalentProjectKeys))
2380
2393
  .sort((left, right) => left.id.localeCompare(right.id));
2381
2394
  }
2382
2395
 
@@ -2406,7 +2419,7 @@ export async function queryOkfKnowledge(input: {
2406
2419
  now?: string | Date;
2407
2420
  }): Promise<OkfKnowledgeQueryResult> {
2408
2421
  const okfDir = resolveOkfKnowledgePaths(input.homeDir).okfDir;
2409
- const scope = normalizeKnowledgeQueryScope(input);
2422
+ const scope = await normalizeKnowledgeQueryScope(input);
2410
2423
  const queryText = normalizeKnowledgeQueryText(input.queryText);
2411
2424
  const now = normalizeQueryNow(input.now);
2412
2425
  const warnings: string[] = [];
@@ -2705,17 +2718,39 @@ export async function queryScopedOkfKnowledgeContext(input: {
2705
2718
  now?: string | Date;
2706
2719
  }): Promise<OkfKnowledgeQueryResult> {
2707
2720
  const result = await queryOkfKnowledge(input);
2708
- const evosCases = await listEvolutionEvosCases({
2709
- homeDir: input.homeDir,
2710
- projectKey: input.projectKey,
2711
- roleId: input.roleId,
2712
- reviewStates: ["accepted", "auto-accepted"],
2713
- });
2721
+ const equivalentProjectKeys =
2722
+ result.projectKey === undefined
2723
+ ? [undefined]
2724
+ : await listEquivalentProjectKeys({
2725
+ homeDir: input.homeDir,
2726
+ projectKey: result.projectKey,
2727
+ });
2728
+ const evosResults = await Promise.all(
2729
+ equivalentProjectKeys.map((projectKey) =>
2730
+ listEvolutionEvosCases({
2731
+ homeDir: input.homeDir,
2732
+ ...(projectKey === undefined ? {} : { projectKey }),
2733
+ roleId: input.roleId,
2734
+ reviewStates: ["accepted", "auto-accepted"],
2735
+ }),
2736
+ ),
2737
+ );
2738
+ const evosCases = {
2739
+ cases: [
2740
+ ...new Map(
2741
+ evosResults
2742
+ .flatMap((entry) => entry.cases)
2743
+ .map((evosCase) => [`${evosCase.projectKey}\0${evosCase.id}`, evosCase]),
2744
+ ).values(),
2745
+ ],
2746
+ warnings: [...new Set(evosResults.flatMap((entry) => entry.warnings))],
2747
+ };
2714
2748
  return mergeAcceptedEvosCasesIntoKnowledgeQuery(
2715
2749
  result,
2716
2750
  evosCases.cases,
2717
2751
  evosCases.warnings,
2718
2752
  input.limit,
2753
+ equivalentProjectKeys.flatMap((projectKey) => (projectKey === undefined ? [] : [projectKey])),
2719
2754
  );
2720
2755
  }
2721
2756
 
@@ -2724,6 +2759,7 @@ export function mergeAcceptedEvosCasesIntoKnowledgeQuery(
2724
2759
  cases: EvolutionEvosCase[],
2725
2760
  warnings: string[] = [],
2726
2761
  limit?: number,
2762
+ equivalentProjectKeys: string[] = [],
2727
2763
  ): OkfKnowledgeQueryResult {
2728
2764
  const startRank = result.items.length + 1;
2729
2765
  const evosItems = cases.flatMap<OkfKnowledgeContextItem>((evosCase, index) => {
@@ -2738,8 +2774,10 @@ export function mergeAcceptedEvosCasesIntoKnowledgeQuery(
2738
2774
  : []),
2739
2775
  ];
2740
2776
  const structuredScore =
2741
- (result.projectKey !== undefined && evosCase.projectKey === result.projectKey ? 100 : 0) +
2742
- (result.roleId !== undefined && evosCase.roleTags.includes(result.roleId) ? 100 : 0);
2777
+ (result.projectKey !== undefined &&
2778
+ [result.projectKey, ...equivalentProjectKeys].includes(evosCase.projectKey)
2779
+ ? 100
2780
+ : 0) + (result.roleId !== undefined && evosCase.roleTags.includes(result.roleId) ? 100 : 0);
2743
2781
  return [
2744
2782
  {
2745
2783
  id: evosCase.id,
@@ -4107,12 +4145,16 @@ function parseOkfConceptFile(
4107
4145
  function matchesConceptFilters(
4108
4146
  concept: OkfKnowledgeConcept,
4109
4147
  input: { projectKey?: string; roleId?: string; workflowId?: string; paths?: string[] },
4148
+ equivalentProjectKeys?: string[],
4110
4149
  ): boolean {
4150
+ const projectKeys = (equivalentProjectKeys ?? []).map(sanitizeSlug);
4111
4151
  if (
4112
4152
  input.projectKey !== undefined &&
4113
4153
  concept.repoTags.length > 0 &&
4114
- !concept.repoTags.includes(sanitizeSlug(input.projectKey)) &&
4115
- !concept.tags.includes(`repo:${sanitizeSlug(input.projectKey)}`)
4154
+ ![sanitizeSlug(input.projectKey), ...projectKeys].some(
4155
+ (projectKey) =>
4156
+ concept.repoTags.includes(projectKey) || concept.tags.includes(`repo:${projectKey}`),
4157
+ )
4116
4158
  ) {
4117
4159
  return false;
4118
4160
  }
@@ -4140,14 +4182,40 @@ function matchesConceptFilters(
4140
4182
  return true;
4141
4183
  }
4142
4184
 
4143
- function normalizeKnowledgeQueryScope(input: {
4185
+ async function normalizeKnowledgeQueryScope(input: {
4186
+ homeDir: string;
4144
4187
  projectKey?: string;
4145
4188
  roleId?: string;
4146
4189
  workflowId?: string;
4147
4190
  paths?: string[];
4148
- }): KnowledgeQueryScope {
4191
+ }): Promise<KnowledgeQueryScope> {
4192
+ const aliases =
4193
+ input.projectKey === undefined
4194
+ ? {}
4195
+ : await listProjectAliases({
4196
+ homeDir: input.homeDir,
4197
+ });
4198
+ const requestedProjectKey =
4199
+ input.projectKey === undefined ? undefined : sanitizeSlug(input.projectKey);
4200
+ const projectKey =
4201
+ requestedProjectKey === undefined
4202
+ ? undefined
4203
+ : canonicalizeProjectKey(requestedProjectKey, aliases);
4204
+ const projectKeys =
4205
+ projectKey === undefined
4206
+ ? undefined
4207
+ : [
4208
+ ...new Set([
4209
+ projectKey,
4210
+ requestedProjectKey ?? projectKey,
4211
+ ...Object.keys(aliases).filter(
4212
+ (alias) => canonicalizeProjectKey(alias, aliases) === projectKey,
4213
+ ),
4214
+ ]),
4215
+ ].map(sanitizeSlug);
4149
4216
  return {
4150
- projectKey: input.projectKey === undefined ? undefined : sanitizeSlug(input.projectKey),
4217
+ projectKey,
4218
+ projectKeys,
4151
4219
  roleId: input.roleId === undefined ? undefined : sanitizeSlug(input.roleId),
4152
4220
  workflowId: input.workflowId === undefined ? undefined : sanitizeSlug(input.workflowId),
4153
4221
  paths: (input.paths ?? []).flatMap((path) => {
@@ -4368,8 +4436,10 @@ function matchOkfConceptForContext(
4368
4436
 
4369
4437
  const repoTags = collectScopedTags(concept, "repo");
4370
4438
  if (scope.projectKey !== undefined) {
4371
- if (repoTags.length > 0 && !repoTags.includes(scope.projectKey)) return null;
4372
- if (repoTags.includes(scope.projectKey)) {
4439
+ const projectKeys = scope.projectKeys ?? [scope.projectKey];
4440
+ const matchedProjectKey = projectKeys.find((projectKey) => repoTags.includes(projectKey));
4441
+ if (repoTags.length > 0 && matchedProjectKey === undefined) return null;
4442
+ if (matchedProjectKey !== undefined) {
4373
4443
  reasons.push(`repo:${scope.projectKey}`);
4374
4444
  exactMatches += 1;
4375
4445
  }
@@ -4441,7 +4511,12 @@ function matchOverlayScope(
4441
4511
  ): "repo" | "role" | "workflow" | "none" | null {
4442
4512
  const [root, id] = conceptId.split("/");
4443
4513
  if (root === "repos") {
4444
- if (scope.projectKey !== undefined && id !== scope.projectKey) return null;
4514
+ if (
4515
+ scope.projectKey !== undefined &&
4516
+ !(scope.projectKeys ?? [scope.projectKey]).includes(id ?? "")
4517
+ ) {
4518
+ return null;
4519
+ }
4445
4520
  return scope.projectKey === undefined ? "none" : "repo";
4446
4521
  }
4447
4522
  if (root === "roles") {