@fractary/codex 0.2.1 → 0.3.0

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/index.d.cts CHANGED
@@ -44,7 +44,7 @@ interface ResolveOptions {
44
44
  currentProject?: string;
45
45
  cwd?: string;
46
46
  }
47
- declare const DEFAULT_CACHE_DIR = ".fractary/plugins/codex/cache";
47
+ declare const DEFAULT_CACHE_DIR = ".fractary/codex/cache";
48
48
  declare function detectCurrentProject(cwd?: string): {
49
49
  org: string | null;
50
50
  project: string | null;
@@ -892,6 +892,30 @@ interface PlanStats {
892
892
  declare function getPlanStats(plan: SyncPlan): PlanStats;
893
893
  declare function formatPlanSummary(plan: SyncPlan): string;
894
894
 
895
+ interface RoutedFileInfo {
896
+ path: string;
897
+ size: number;
898
+ mtime: number;
899
+ hash: string;
900
+ metadata: Metadata;
901
+ sourceProject: string;
902
+ }
903
+ interface RoutingScanStats {
904
+ totalScanned: number;
905
+ totalMatched: number;
906
+ totalSkipped: number;
907
+ sourceProjects: string[];
908
+ durationMs: number;
909
+ errors: Array<{
910
+ path: string;
911
+ error: string;
912
+ }>;
913
+ }
914
+ interface RoutingScanResult {
915
+ files: RoutedFileInfo[];
916
+ stats: RoutingScanStats;
917
+ }
918
+
895
919
  interface SyncManagerConfig {
896
920
  localStorage: LocalStorage;
897
921
  config?: Partial<SyncConfig>;
@@ -908,6 +932,9 @@ declare class SyncManager {
908
932
  getOrCreateManifest(org: string, project: string): Promise<SyncManifest>;
909
933
  listLocalFiles(directory: string): Promise<FileInfo[]>;
910
934
  createPlan(_org: string, _project: string, sourceDir: string, targetFiles: FileInfo[], options?: SyncOptions): Promise<SyncPlan>;
935
+ createRoutingAwarePlan(org: string, project: string, codexDir: string, options?: SyncOptions): Promise<SyncPlan & {
936
+ routingScan?: RoutingScanResult;
937
+ }>;
911
938
  executePlan(plan: SyncPlan, options?: SyncOptions): Promise<SyncResult>;
912
939
  updateManifest(org: string, project: string, syncedFiles: FileSyncStatus[]): Promise<void>;
913
940
  getFileStatus(path: string): Promise<SyncManifestEntry | null>;
package/dist/index.d.ts CHANGED
@@ -44,7 +44,7 @@ interface ResolveOptions {
44
44
  currentProject?: string;
45
45
  cwd?: string;
46
46
  }
47
- declare const DEFAULT_CACHE_DIR = ".fractary/plugins/codex/cache";
47
+ declare const DEFAULT_CACHE_DIR = ".fractary/codex/cache";
48
48
  declare function detectCurrentProject(cwd?: string): {
49
49
  org: string | null;
50
50
  project: string | null;
@@ -892,6 +892,30 @@ interface PlanStats {
892
892
  declare function getPlanStats(plan: SyncPlan): PlanStats;
893
893
  declare function formatPlanSummary(plan: SyncPlan): string;
894
894
 
895
+ interface RoutedFileInfo {
896
+ path: string;
897
+ size: number;
898
+ mtime: number;
899
+ hash: string;
900
+ metadata: Metadata;
901
+ sourceProject: string;
902
+ }
903
+ interface RoutingScanStats {
904
+ totalScanned: number;
905
+ totalMatched: number;
906
+ totalSkipped: number;
907
+ sourceProjects: string[];
908
+ durationMs: number;
909
+ errors: Array<{
910
+ path: string;
911
+ error: string;
912
+ }>;
913
+ }
914
+ interface RoutingScanResult {
915
+ files: RoutedFileInfo[];
916
+ stats: RoutingScanStats;
917
+ }
918
+
895
919
  interface SyncManagerConfig {
896
920
  localStorage: LocalStorage;
897
921
  config?: Partial<SyncConfig>;
@@ -908,6 +932,9 @@ declare class SyncManager {
908
932
  getOrCreateManifest(org: string, project: string): Promise<SyncManifest>;
909
933
  listLocalFiles(directory: string): Promise<FileInfo[]>;
910
934
  createPlan(_org: string, _project: string, sourceDir: string, targetFiles: FileInfo[], options?: SyncOptions): Promise<SyncPlan>;
935
+ createRoutingAwarePlan(org: string, project: string, codexDir: string, options?: SyncOptions): Promise<SyncPlan & {
936
+ routingScan?: RoutingScanResult;
937
+ }>;
911
938
  executePlan(plan: SyncPlan, options?: SyncOptions): Promise<SyncResult>;
912
939
  updateManifest(org: string, project: string, syncedFiles: FileSyncStatus[]): Promise<void>;
913
940
  getFileStatus(path: string): Promise<SyncManifestEntry | null>;
package/dist/index.js CHANGED
@@ -176,10 +176,10 @@ function parseReference(uri, options = {}) {
176
176
  path: filePath
177
177
  };
178
178
  }
179
- function buildUri(org, project, path5) {
179
+ function buildUri(org, project, path6) {
180
180
  const base = `${CODEX_URI_PREFIX}${org}/${project}`;
181
- if (path5) {
182
- const cleanPath = path5.startsWith("/") ? path5.slice(1) : path5;
181
+ if (path6) {
182
+ const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
183
183
  return `${base}/${cleanPath}`;
184
184
  }
185
185
  return base;
@@ -217,7 +217,7 @@ function getDirectory(uri) {
217
217
  }
218
218
  return parsed.path.slice(0, lastSlash);
219
219
  }
220
- var DEFAULT_CACHE_DIR = ".fractary/plugins/codex/cache";
220
+ var DEFAULT_CACHE_DIR = ".fractary/codex/cache";
221
221
  function detectCurrentProject(cwd) {
222
222
  try {
223
223
  const options = cwd ? { cwd, encoding: "utf-8" } : { encoding: "utf-8" };
@@ -1057,18 +1057,18 @@ function parseCustomDestination(value) {
1057
1057
  );
1058
1058
  }
1059
1059
  const repo = value.substring(0, colonIndex).trim();
1060
- const path5 = value.substring(colonIndex + 1).trim();
1060
+ const path6 = value.substring(colonIndex + 1).trim();
1061
1061
  if (!repo) {
1062
1062
  throw new ValidationError(
1063
1063
  `Invalid custom destination: repository name cannot be empty in "${value}"`
1064
1064
  );
1065
1065
  }
1066
- if (!path5) {
1066
+ if (!path6) {
1067
1067
  throw new ValidationError(
1068
1068
  `Invalid custom destination: path cannot be empty in "${value}"`
1069
1069
  );
1070
1070
  }
1071
- return { repo, path: path5 };
1071
+ return { repo, path: path6 };
1072
1072
  }
1073
1073
  function getCustomSyncDestinations(metadata) {
1074
1074
  const customDestinations = metadata.codex_sync_custom;
@@ -1104,8 +1104,8 @@ function mergeFetchOptions(options) {
1104
1104
  ...options
1105
1105
  };
1106
1106
  }
1107
- function detectContentType(path5) {
1108
- const ext = path5.split(".").pop()?.toLowerCase();
1107
+ function detectContentType(path6) {
1108
+ const ext = path6.split(".").pop()?.toLowerCase();
1109
1109
  const mimeTypes = {
1110
1110
  md: "text/markdown",
1111
1111
  markdown: "text/markdown",
@@ -2428,11 +2428,11 @@ var DEFAULT_SYNC_CONFIG = {
2428
2428
  deleteOrphans: false,
2429
2429
  conflictStrategy: "newest"
2430
2430
  };
2431
- function evaluatePath(path5, rules, direction, defaultExcludes = []) {
2431
+ function evaluatePath(path6, rules, direction, defaultExcludes = []) {
2432
2432
  for (const pattern of defaultExcludes) {
2433
- if (micromatch2.isMatch(path5, pattern)) {
2433
+ if (micromatch2.isMatch(path6, pattern)) {
2434
2434
  return {
2435
- path: path5,
2435
+ path: path6,
2436
2436
  shouldSync: false,
2437
2437
  reason: `Excluded by default pattern: ${pattern}`
2438
2438
  };
@@ -2443,9 +2443,9 @@ function evaluatePath(path5, rules, direction, defaultExcludes = []) {
2443
2443
  if (rule.direction && rule.direction !== direction) {
2444
2444
  continue;
2445
2445
  }
2446
- if (micromatch2.isMatch(path5, rule.pattern)) {
2446
+ if (micromatch2.isMatch(path6, rule.pattern)) {
2447
2447
  return {
2448
- path: path5,
2448
+ path: path6,
2449
2449
  shouldSync: rule.include,
2450
2450
  matchedRule: rule,
2451
2451
  reason: rule.include ? `Included by rule: ${rule.pattern}` : `Excluded by rule: ${rule.pattern}`
@@ -2453,21 +2453,21 @@ function evaluatePath(path5, rules, direction, defaultExcludes = []) {
2453
2453
  }
2454
2454
  }
2455
2455
  return {
2456
- path: path5,
2456
+ path: path6,
2457
2457
  shouldSync: true,
2458
2458
  reason: "No matching rule, included by default"
2459
2459
  };
2460
2460
  }
2461
2461
  function evaluatePaths(paths, rules, direction, defaultExcludes = []) {
2462
2462
  const results = /* @__PURE__ */ new Map();
2463
- for (const path5 of paths) {
2464
- results.set(path5, evaluatePath(path5, rules, direction, defaultExcludes));
2463
+ for (const path6 of paths) {
2464
+ results.set(path6, evaluatePath(path6, rules, direction, defaultExcludes));
2465
2465
  }
2466
2466
  return results;
2467
2467
  }
2468
2468
  function filterSyncablePaths(paths, rules, direction, defaultExcludes = []) {
2469
2469
  return paths.filter(
2470
- (path5) => evaluatePath(path5, rules, direction, defaultExcludes).shouldSync
2470
+ (path6) => evaluatePath(path6, rules, direction, defaultExcludes).shouldSync
2471
2471
  );
2472
2472
  }
2473
2473
  function createRulesFromPatterns(include = [], exclude = []) {
@@ -2704,6 +2704,123 @@ function formatBytes(bytes) {
2704
2704
  const i = Math.floor(Math.log(bytes) / Math.log(k));
2705
2705
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
2706
2706
  }
2707
+ async function scanCodexWithRouting(options) {
2708
+ const {
2709
+ codexDir,
2710
+ targetProject,
2711
+ org,
2712
+ rules,
2713
+ storage,
2714
+ skipNoFrontmatter = true,
2715
+ maxFileSize = 10 * 1024 * 1024
2716
+ // 10MB default
2717
+ } = options;
2718
+ const startTime = Date.now();
2719
+ const routedFiles = [];
2720
+ const sourceProjectsSet = /* @__PURE__ */ new Set();
2721
+ const errors = [];
2722
+ let totalScanned = 0;
2723
+ let totalSkipped = 0;
2724
+ const allFiles = await listAllFilesRecursive(codexDir);
2725
+ for (const filePath of allFiles) {
2726
+ totalScanned++;
2727
+ try {
2728
+ if (!filePath.endsWith(".md")) {
2729
+ totalSkipped++;
2730
+ continue;
2731
+ }
2732
+ const fullPath = path3.join(codexDir, filePath);
2733
+ const stats = await fs2.stat(fullPath);
2734
+ if (stats.size > maxFileSize) {
2735
+ totalSkipped++;
2736
+ errors.push({
2737
+ path: filePath,
2738
+ error: `File too large (${stats.size} bytes, max: ${maxFileSize})`
2739
+ });
2740
+ continue;
2741
+ }
2742
+ const content = await storage.readText(fullPath);
2743
+ const parseResult = parseMetadata(content, { strict: false });
2744
+ if (skipNoFrontmatter && Object.keys(parseResult.metadata).length === 0) {
2745
+ totalSkipped++;
2746
+ continue;
2747
+ }
2748
+ const sourceProject = extractProjectFromPath(filePath, org);
2749
+ const shouldSync = shouldSyncToRepo({
2750
+ filePath,
2751
+ fileMetadata: parseResult.metadata,
2752
+ targetRepo: targetProject,
2753
+ sourceRepo: sourceProject,
2754
+ rules
2755
+ });
2756
+ if (shouldSync) {
2757
+ const buffer = Buffer.from(content);
2758
+ const hash = calculateContentHash(buffer);
2759
+ routedFiles.push({
2760
+ path: filePath,
2761
+ size: buffer.length,
2762
+ mtime: stats.mtimeMs,
2763
+ hash,
2764
+ metadata: parseResult.metadata,
2765
+ sourceProject
2766
+ });
2767
+ sourceProjectsSet.add(sourceProject);
2768
+ } else {
2769
+ totalSkipped++;
2770
+ }
2771
+ } catch (error) {
2772
+ totalSkipped++;
2773
+ errors.push({
2774
+ path: filePath,
2775
+ error: error instanceof Error ? error.message : String(error)
2776
+ });
2777
+ }
2778
+ }
2779
+ const durationMs = Date.now() - startTime;
2780
+ return {
2781
+ files: routedFiles,
2782
+ stats: {
2783
+ totalScanned,
2784
+ totalMatched: routedFiles.length,
2785
+ totalSkipped,
2786
+ sourceProjects: Array.from(sourceProjectsSet).sort(),
2787
+ durationMs,
2788
+ errors
2789
+ }
2790
+ };
2791
+ }
2792
+ function extractProjectFromPath(filePath, org) {
2793
+ const normalizedPath = filePath.replace(/\\/g, "/");
2794
+ const withoutOrg = normalizedPath.startsWith(`${org}/`) ? normalizedPath.slice(org.length + 1) : normalizedPath;
2795
+ const firstSlash = withoutOrg.indexOf("/");
2796
+ if (firstSlash === -1) {
2797
+ return withoutOrg;
2798
+ }
2799
+ return withoutOrg.slice(0, firstSlash);
2800
+ }
2801
+ async function listAllFilesRecursive(dirPath) {
2802
+ const files = [];
2803
+ async function scanDirectory(currentPath, relativePath = "") {
2804
+ try {
2805
+ const entries = await fs2.readdir(currentPath, { withFileTypes: true });
2806
+ for (const entry of entries) {
2807
+ const entryPath = path3.join(currentPath, entry.name);
2808
+ const entryRelativePath = relativePath ? path3.join(relativePath, entry.name) : entry.name;
2809
+ if (entry.isDirectory()) {
2810
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") {
2811
+ continue;
2812
+ }
2813
+ await scanDirectory(entryPath, entryRelativePath);
2814
+ } else if (entry.isFile()) {
2815
+ files.push(entryRelativePath);
2816
+ }
2817
+ }
2818
+ } catch {
2819
+ }
2820
+ }
2821
+ await scanDirectory(dirPath);
2822
+ return files;
2823
+ }
2707
2824
 
2708
2825
  // src/sync/manager.ts
2709
2826
  var SyncManager = class {
@@ -2796,6 +2913,56 @@ var SyncManager = class {
2796
2913
  plan.estimatedTime = estimateSyncTime(plan);
2797
2914
  return plan;
2798
2915
  }
2916
+ /**
2917
+ * Create a routing-aware sync plan
2918
+ *
2919
+ * Scans entire codex repository and evaluates routing rules to find all files
2920
+ * that should sync to the target project based on codex_sync_include patterns.
2921
+ *
2922
+ * This enables cross-project knowledge sharing where files from multiple
2923
+ * projects can be synced to the target based on their frontmatter metadata.
2924
+ *
2925
+ * @param org - Organization name (e.g., "corthosai")
2926
+ * @param project - Target project name (e.g., "lake.corthonomy.ai")
2927
+ * @param codexDir - Path to codex repository directory
2928
+ * @param options - Sync options
2929
+ * @returns Sync plan with routing metadata
2930
+ *
2931
+ * @example
2932
+ * ```typescript
2933
+ * const plan = await manager.createRoutingAwarePlan(
2934
+ * 'corthosai',
2935
+ * 'lake.corthonomy.ai',
2936
+ * '/path/to/codex.corthos.ai',
2937
+ * { direction: 'from-codex' }
2938
+ * )
2939
+ *
2940
+ * console.log(`Found ${plan.totalFiles} files from ${plan.metadata.scannedProjects.length} projects`)
2941
+ * ```
2942
+ */
2943
+ async createRoutingAwarePlan(org, project, codexDir, options) {
2944
+ const routingScan = await scanCodexWithRouting({
2945
+ codexDir,
2946
+ targetProject: project,
2947
+ org,
2948
+ rules: void 0,
2949
+ // Use default routing rules (preventSelfSync, preventCodexSync, etc.)
2950
+ storage: this.localStorage
2951
+ });
2952
+ const sourceFiles = routingScan.files.map((rf) => ({
2953
+ path: rf.path,
2954
+ size: rf.size,
2955
+ mtime: rf.mtime,
2956
+ hash: rf.hash
2957
+ }));
2958
+ const targetFiles = await this.listLocalFiles(process.cwd());
2959
+ const plan = createSyncPlan(sourceFiles, targetFiles, options ?? {}, this.config);
2960
+ plan.estimatedTime = estimateSyncTime(plan);
2961
+ return {
2962
+ ...plan,
2963
+ routingScan
2964
+ };
2965
+ }
2799
2966
  /**
2800
2967
  * Execute a sync plan (dry run)
2801
2968
  *
@@ -2871,15 +3038,15 @@ var SyncManager = class {
2871
3038
  /**
2872
3039
  * Get sync status for a file
2873
3040
  */
2874
- async getFileStatus(path5) {
3041
+ async getFileStatus(path6) {
2875
3042
  const manifest = await this.loadManifest();
2876
- return manifest?.entries[path5] ?? null;
3043
+ return manifest?.entries[path6] ?? null;
2877
3044
  }
2878
3045
  /**
2879
3046
  * Check if a file is synced
2880
3047
  */
2881
- async isFileSynced(path5) {
2882
- const status = await this.getFileStatus(path5);
3048
+ async isFileSynced(path6) {
3049
+ const status = await this.getFileStatus(path6);
2883
3050
  return status !== null;
2884
3051
  }
2885
3052
  /**
@@ -2963,19 +3130,19 @@ function ruleMatchesContext(rule, context) {
2963
3130
  }
2964
3131
  return true;
2965
3132
  }
2966
- function ruleMatchesPath(rule, path5) {
2967
- return micromatch2.isMatch(path5, rule.pattern);
3133
+ function ruleMatchesPath(rule, path6) {
3134
+ return micromatch2.isMatch(path6, rule.pattern);
2968
3135
  }
2969
3136
  function ruleMatchesAction(rule, action) {
2970
3137
  return rule.actions.includes(action);
2971
3138
  }
2972
- function evaluatePermission(path5, action, context, config) {
3139
+ function evaluatePermission(path6, action, context, config) {
2973
3140
  const sortedRules = [...config.rules].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
2974
3141
  for (const rule of sortedRules) {
2975
3142
  if (!ruleMatchesContext(rule, context)) {
2976
3143
  continue;
2977
3144
  }
2978
- if (!ruleMatchesPath(rule, path5)) {
3145
+ if (!ruleMatchesPath(rule, path6)) {
2979
3146
  continue;
2980
3147
  }
2981
3148
  if (!ruleMatchesAction(rule, action)) {
@@ -2994,24 +3161,24 @@ function evaluatePermission(path5, action, context, config) {
2994
3161
  reason: config.defaultAllow ? "Allowed by default" : "Denied by default"
2995
3162
  };
2996
3163
  }
2997
- function isAllowed(path5, action, context, config) {
2998
- const result = evaluatePermission(path5, action, context, config);
3164
+ function isAllowed(path6, action, context, config) {
3165
+ const result = evaluatePermission(path6, action, context, config);
2999
3166
  return result.allowed;
3000
3167
  }
3001
- function hasPermission(path5, action, requiredLevel, context, config) {
3002
- const result = evaluatePermission(path5, action, context, config);
3168
+ function hasPermission(path6, action, requiredLevel, context, config) {
3169
+ const result = evaluatePermission(path6, action, context, config);
3003
3170
  return levelGrants(result.level, requiredLevel);
3004
3171
  }
3005
3172
  function evaluatePermissions(paths, action, context, config) {
3006
3173
  const results = /* @__PURE__ */ new Map();
3007
- for (const path5 of paths) {
3008
- results.set(path5, evaluatePermission(path5, action, context, config));
3174
+ for (const path6 of paths) {
3175
+ results.set(path6, evaluatePermission(path6, action, context, config));
3009
3176
  }
3010
3177
  return results;
3011
3178
  }
3012
3179
  function filterByPermission(paths, action, context, config, requiredLevel = "read") {
3013
- return paths.filter((path5) => {
3014
- const result = evaluatePermission(path5, action, context, config);
3180
+ return paths.filter((path6) => {
3181
+ const result = evaluatePermission(path6, action, context, config);
3015
3182
  return levelGrants(result.level, requiredLevel);
3016
3183
  });
3017
3184
  }
@@ -3102,21 +3269,21 @@ var PermissionManager = class {
3102
3269
  /**
3103
3270
  * Check if an action is allowed for a path
3104
3271
  */
3105
- isAllowed(path5, action, context) {
3106
- const result = this.evaluate(path5, action, context);
3272
+ isAllowed(path6, action, context) {
3273
+ const result = this.evaluate(path6, action, context);
3107
3274
  return result.allowed;
3108
3275
  }
3109
3276
  /**
3110
3277
  * Check if a permission level is granted
3111
3278
  */
3112
- hasPermission(path5, action, requiredLevel, context) {
3113
- const result = this.evaluate(path5, action, context);
3279
+ hasPermission(path6, action, requiredLevel, context) {
3280
+ const result = this.evaluate(path6, action, context);
3114
3281
  return levelGrants(result.level, requiredLevel);
3115
3282
  }
3116
3283
  /**
3117
3284
  * Evaluate permission for a path and action
3118
3285
  */
3119
- evaluate(path5, action, context) {
3286
+ evaluate(path6, action, context) {
3120
3287
  const mergedContext = { ...this.defaultContext, ...context };
3121
3288
  if (!this.config.enforced) {
3122
3289
  return {
@@ -3125,7 +3292,7 @@ var PermissionManager = class {
3125
3292
  reason: "Permissions not enforced"
3126
3293
  };
3127
3294
  }
3128
- return evaluatePermission(path5, action, mergedContext, this.config);
3295
+ return evaluatePermission(path6, action, mergedContext, this.config);
3129
3296
  }
3130
3297
  /**
3131
3298
  * Filter paths by permission
@@ -3228,12 +3395,12 @@ var PermissionManager = class {
3228
3395
  /**
3229
3396
  * Assert permission (throws if denied)
3230
3397
  */
3231
- assertPermission(path5, action, requiredLevel = "read", context) {
3232
- const result = this.evaluate(path5, action, context);
3398
+ assertPermission(path6, action, requiredLevel = "read", context) {
3399
+ const result = this.evaluate(path6, action, context);
3233
3400
  if (!levelGrants(result.level, requiredLevel)) {
3234
3401
  throw new PermissionDeniedError(
3235
- `Permission denied for ${action} on ${path5}: ${result.reason}`,
3236
- path5,
3402
+ `Permission denied for ${action} on ${path6}: ${result.reason}`,
3403
+ path6,
3237
3404
  action,
3238
3405
  result
3239
3406
  );
@@ -3241,9 +3408,9 @@ var PermissionManager = class {
3241
3408
  }
3242
3409
  };
3243
3410
  var PermissionDeniedError = class extends Error {
3244
- constructor(message, path5, action, result) {
3411
+ constructor(message, path6, action, result) {
3245
3412
  super(message);
3246
- this.path = path5;
3413
+ this.path = path6;
3247
3414
  this.action = action;
3248
3415
  this.result = result;
3249
3416
  this.name = "PermissionDeniedError";
@@ -3740,8 +3907,8 @@ function convertToUri(reference, options = {}) {
3740
3907
  const parts = parseReference2(trimmed);
3741
3908
  const org = parts.org || options.defaultOrg || "_";
3742
3909
  const project = parts.project || options.defaultProject || "_";
3743
- const path5 = parts.path;
3744
- return `codex://${org}/${project}/${path5}`;
3910
+ const path6 = parts.path;
3911
+ return `codex://${org}/${project}/${path6}`;
3745
3912
  }
3746
3913
  function parseReference2(reference) {
3747
3914
  const trimmed = reference.trim();