@fractary/codex 0.1.0 → 0.1.2

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.cjs CHANGED
@@ -1,12 +1,18 @@
1
1
  'use strict';
2
2
 
3
+ var path3 = require('path');
4
+ var child_process = require('child_process');
5
+ var micromatch2 = require('micromatch');
3
6
  var zod = require('zod');
4
7
  var yaml = require('js-yaml');
5
- var micromatch = require('micromatch');
8
+ var fs2 = require('fs/promises');
6
9
 
7
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
11
 
12
+ var path3__default = /*#__PURE__*/_interopDefault(path3);
13
+ var micromatch2__default = /*#__PURE__*/_interopDefault(micromatch2);
9
14
  var yaml__default = /*#__PURE__*/_interopDefault(yaml);
15
+ var fs2__default = /*#__PURE__*/_interopDefault(fs2);
10
16
 
11
17
  // src/errors/CodexError.ts
12
18
  var CodexError = class _CodexError extends Error {
@@ -34,6 +40,664 @@ var ValidationError = class _ValidationError extends CodexError {
34
40
  Object.setPrototypeOf(this, _ValidationError.prototype);
35
41
  }
36
42
  };
43
+ var FORBIDDEN_PATTERNS = [
44
+ /^\//,
45
+ // Absolute paths
46
+ /\.\./,
47
+ // Parent directory traversal
48
+ /^~/,
49
+ // Home directory expansion
50
+ /\0/
51
+ // Null bytes
52
+ ];
53
+ var FORBIDDEN_PROTOCOLS = ["file:", "http:", "https:", "ftp:", "data:", "javascript:"];
54
+ function validatePath(filePath) {
55
+ if (!filePath || filePath.trim() === "") {
56
+ return false;
57
+ }
58
+ for (const pattern of FORBIDDEN_PATTERNS) {
59
+ if (pattern.test(filePath)) {
60
+ return false;
61
+ }
62
+ }
63
+ const lowerPath = filePath.toLowerCase();
64
+ for (const protocol of FORBIDDEN_PROTOCOLS) {
65
+ if (lowerPath.startsWith(protocol)) {
66
+ return false;
67
+ }
68
+ }
69
+ const normalized = path3__default.default.normalize(filePath);
70
+ if (normalized.startsWith("..") || normalized.includes("/../") || normalized.includes("\\..\\")) {
71
+ return false;
72
+ }
73
+ return true;
74
+ }
75
+ function sanitizePath(filePath) {
76
+ if (!filePath) {
77
+ return "";
78
+ }
79
+ let sanitized = filePath;
80
+ for (const protocol of FORBIDDEN_PROTOCOLS) {
81
+ if (sanitized.toLowerCase().startsWith(protocol)) {
82
+ sanitized = sanitized.slice(protocol.length);
83
+ }
84
+ }
85
+ sanitized = sanitized.replace(/^\/+/, "");
86
+ sanitized = sanitized.replace(/^~\//, "");
87
+ sanitized = path3__default.default.normalize(sanitized);
88
+ sanitized = sanitized.replace(/\.\.\//g, "").replace(/\.\./g, "");
89
+ sanitized = sanitized.replace(/^\.\//, "");
90
+ sanitized = sanitized.replace(/\0/g, "");
91
+ return sanitized;
92
+ }
93
+ function validateOrg(org) {
94
+ if (!org || org.trim() === "") {
95
+ return false;
96
+ }
97
+ const orgPattern = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
98
+ return orgPattern.test(org) && org.length <= 39;
99
+ }
100
+ function validateProject(project) {
101
+ if (!project || project.trim() === "") {
102
+ return false;
103
+ }
104
+ const projectPattern = /^[a-zA-Z0-9._-]+$/;
105
+ return projectPattern.test(project) && project.length <= 100 && !project.includes("..");
106
+ }
107
+ function validateUri(uri) {
108
+ if (!uri || !uri.startsWith("codex://")) {
109
+ return false;
110
+ }
111
+ const pathPart = uri.slice("codex://".length);
112
+ const parts = pathPart.split("/");
113
+ if (parts.length < 2) {
114
+ return false;
115
+ }
116
+ const [org, project, ...rest] = parts;
117
+ if (!org || !validateOrg(org)) {
118
+ return false;
119
+ }
120
+ if (!project || !validateProject(project)) {
121
+ return false;
122
+ }
123
+ if (rest.length > 0) {
124
+ const filePath = rest.join("/");
125
+ if (!validatePath(filePath)) {
126
+ return false;
127
+ }
128
+ }
129
+ return true;
130
+ }
131
+
132
+ // src/references/parser.ts
133
+ var CODEX_URI_PREFIX = "codex://";
134
+ var LEGACY_REF_PREFIX = "@codex/";
135
+ function isValidUri(uri) {
136
+ return validateUri(uri);
137
+ }
138
+ function isLegacyReference(ref) {
139
+ return ref.startsWith(LEGACY_REF_PREFIX);
140
+ }
141
+ function convertLegacyReference(ref, defaultOrg) {
142
+ if (!isLegacyReference(ref)) {
143
+ return null;
144
+ }
145
+ const pathPart = ref.slice(LEGACY_REF_PREFIX.length);
146
+ const parts = pathPart.split("/");
147
+ if (parts.length < 1 || !parts[0]) {
148
+ return null;
149
+ }
150
+ const project = parts[0];
151
+ const filePath = parts.slice(1).join("/");
152
+ if (filePath) {
153
+ return `${CODEX_URI_PREFIX}${defaultOrg}/${project}/${filePath}`;
154
+ }
155
+ return `${CODEX_URI_PREFIX}${defaultOrg}/${project}`;
156
+ }
157
+ function parseReference(uri, options = {}) {
158
+ const { sanitize = true, strict = true } = options;
159
+ if (!uri.startsWith(CODEX_URI_PREFIX)) {
160
+ return null;
161
+ }
162
+ if (strict && !validateUri(uri)) {
163
+ return null;
164
+ }
165
+ const pathPart = uri.slice(CODEX_URI_PREFIX.length);
166
+ const parts = pathPart.split("/");
167
+ if (parts.length < 2) {
168
+ return null;
169
+ }
170
+ const org = parts[0];
171
+ const project = parts[1];
172
+ let filePath = parts.slice(2).join("/");
173
+ if (!org || !project) {
174
+ return null;
175
+ }
176
+ if (sanitize && filePath) {
177
+ if (!validatePath(filePath)) {
178
+ filePath = sanitizePath(filePath);
179
+ }
180
+ }
181
+ return {
182
+ uri,
183
+ org,
184
+ project,
185
+ path: filePath
186
+ };
187
+ }
188
+ function buildUri(org, project, path5) {
189
+ const base = `${CODEX_URI_PREFIX}${org}/${project}`;
190
+ if (path5) {
191
+ const cleanPath = path5.startsWith("/") ? path5.slice(1) : path5;
192
+ return `${base}/${cleanPath}`;
193
+ }
194
+ return base;
195
+ }
196
+ function getExtension(uri) {
197
+ const parsed = parseReference(uri, { strict: false });
198
+ if (!parsed || !parsed.path) {
199
+ return "";
200
+ }
201
+ const lastDot = parsed.path.lastIndexOf(".");
202
+ if (lastDot === -1 || lastDot === parsed.path.length - 1) {
203
+ return "";
204
+ }
205
+ return parsed.path.slice(lastDot + 1).toLowerCase();
206
+ }
207
+ function getFilename(uri) {
208
+ const parsed = parseReference(uri, { strict: false });
209
+ if (!parsed || !parsed.path) {
210
+ return "";
211
+ }
212
+ const lastSlash = parsed.path.lastIndexOf("/");
213
+ if (lastSlash === -1) {
214
+ return parsed.path;
215
+ }
216
+ return parsed.path.slice(lastSlash + 1);
217
+ }
218
+ function getDirectory(uri) {
219
+ const parsed = parseReference(uri, { strict: false });
220
+ if (!parsed || !parsed.path) {
221
+ return "";
222
+ }
223
+ const lastSlash = parsed.path.lastIndexOf("/");
224
+ if (lastSlash === -1) {
225
+ return "";
226
+ }
227
+ return parsed.path.slice(0, lastSlash);
228
+ }
229
+ var DEFAULT_CACHE_DIR = ".fractary/plugins/codex/cache";
230
+ function detectCurrentProject(cwd) {
231
+ try {
232
+ const options = cwd ? { cwd, encoding: "utf-8" } : { encoding: "utf-8" };
233
+ const stdout = child_process.execSync("git remote get-url origin", options);
234
+ const url = stdout.toString().trim();
235
+ const patterns = [
236
+ /github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/,
237
+ /gitlab\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/,
238
+ /bitbucket\.org[:/]([^/]+)\/([^/]+?)(?:\.git)?$/
239
+ ];
240
+ for (const pattern of patterns) {
241
+ const match = url.match(pattern);
242
+ if (match && match[1] && match[2]) {
243
+ return { org: match[1], project: match[2] };
244
+ }
245
+ }
246
+ } catch {
247
+ }
248
+ return { org: null, project: null };
249
+ }
250
+ function getCurrentContext(options = {}) {
251
+ if (options.currentOrg && options.currentProject) {
252
+ return { org: options.currentOrg, project: options.currentProject };
253
+ }
254
+ const envOrg = process.env.CODEX_CURRENT_ORG;
255
+ const envProject = process.env.CODEX_CURRENT_PROJECT;
256
+ if (envOrg && envProject) {
257
+ return { org: envOrg, project: envProject };
258
+ }
259
+ return detectCurrentProject(options.cwd);
260
+ }
261
+ function calculateCachePath(org, project, filePath, cacheDir) {
262
+ return path3__default.default.join(cacheDir, org, project, filePath);
263
+ }
264
+ function resolveReference(uri, options = {}) {
265
+ const parsed = parseReference(uri);
266
+ if (!parsed) {
267
+ return null;
268
+ }
269
+ const cacheDir = options.cacheDir || DEFAULT_CACHE_DIR;
270
+ const currentContext = getCurrentContext(options);
271
+ const cachePath = parsed.path ? calculateCachePath(parsed.org, parsed.project, parsed.path, cacheDir) : path3__default.default.join(cacheDir, parsed.org, parsed.project);
272
+ const isCurrentProject = currentContext.org === parsed.org && currentContext.project === parsed.project;
273
+ const resolved = {
274
+ ...parsed,
275
+ cachePath,
276
+ isCurrentProject
277
+ };
278
+ if (isCurrentProject && parsed.path) {
279
+ resolved.localPath = parsed.path;
280
+ }
281
+ return resolved;
282
+ }
283
+ function resolveReferences(uris, options = {}) {
284
+ return uris.map((uri) => resolveReference(uri, options)).filter((r) => r !== null);
285
+ }
286
+ function isCurrentProjectUri(uri, options = {}) {
287
+ const resolved = resolveReference(uri, options);
288
+ return resolved?.isCurrentProject ?? false;
289
+ }
290
+ function getRelativeCachePath(uri) {
291
+ const parsed = parseReference(uri);
292
+ if (!parsed) {
293
+ return null;
294
+ }
295
+ if (parsed.path) {
296
+ return path3__default.default.join(parsed.org, parsed.project, parsed.path);
297
+ }
298
+ return path3__default.default.join(parsed.org, parsed.project);
299
+ }
300
+
301
+ // src/types/built-in.ts
302
+ var TTL = {
303
+ ONE_HOUR: 3600,
304
+ ONE_DAY: 86400,
305
+ ONE_WEEK: 604800,
306
+ TWO_WEEKS: 1209600,
307
+ ONE_MONTH: 2592e3,
308
+ ONE_YEAR: 31536e3
309
+ };
310
+ var BUILT_IN_TYPES = {
311
+ docs: {
312
+ name: "docs",
313
+ description: "Documentation files",
314
+ patterns: ["docs/**", "README.md", "CLAUDE.md", "*.md"],
315
+ defaultTtl: TTL.ONE_WEEK,
316
+ archiveAfterDays: 365,
317
+ archiveStorage: "cloud"
318
+ },
319
+ specs: {
320
+ name: "specs",
321
+ description: "Technical specifications",
322
+ patterns: ["specs/**", "SPEC-*.md", "**/specs/**"],
323
+ defaultTtl: TTL.TWO_WEEKS,
324
+ archiveAfterDays: null,
325
+ // Never auto-archive specs
326
+ archiveStorage: null
327
+ },
328
+ logs: {
329
+ name: "logs",
330
+ description: "Session and workflow logs",
331
+ patterns: [".fractary/**/logs/**", "logs/**", "*.log"],
332
+ defaultTtl: TTL.ONE_DAY,
333
+ archiveAfterDays: 30,
334
+ archiveStorage: "cloud"
335
+ },
336
+ standards: {
337
+ name: "standards",
338
+ description: "Organization standards and guides",
339
+ patterns: ["standards/**", "guides/**", ".fractary/standards/**"],
340
+ defaultTtl: TTL.ONE_MONTH,
341
+ archiveAfterDays: null,
342
+ // Never auto-archive standards
343
+ archiveStorage: null
344
+ },
345
+ templates: {
346
+ name: "templates",
347
+ description: "Reusable templates",
348
+ patterns: ["templates/**", ".templates/**", ".fractary/templates/**"],
349
+ defaultTtl: TTL.TWO_WEEKS,
350
+ archiveAfterDays: 180,
351
+ archiveStorage: "cloud"
352
+ },
353
+ state: {
354
+ name: "state",
355
+ description: "Workflow state files",
356
+ patterns: [".fractary/**/state.json", ".fractary/**/state/**", "**/state.json"],
357
+ defaultTtl: TTL.ONE_HOUR,
358
+ archiveAfterDays: 7,
359
+ archiveStorage: "local"
360
+ },
361
+ config: {
362
+ name: "config",
363
+ description: "Configuration files",
364
+ patterns: [
365
+ ".fractary/**/*.json",
366
+ "*.config.js",
367
+ "*.config.ts",
368
+ "tsconfig.json",
369
+ "package.json"
370
+ ],
371
+ defaultTtl: TTL.ONE_DAY,
372
+ archiveAfterDays: null,
373
+ archiveStorage: null
374
+ },
375
+ systems: {
376
+ name: "systems",
377
+ description: "System definitions",
378
+ patterns: [".fractary/systems/**", "systems/**"],
379
+ defaultTtl: TTL.TWO_WEEKS,
380
+ archiveAfterDays: null,
381
+ archiveStorage: null
382
+ }
383
+ };
384
+ var DEFAULT_TYPE = {
385
+ name: "default",
386
+ description: "Default type for unmatched files",
387
+ patterns: ["**/*"],
388
+ defaultTtl: TTL.ONE_WEEK,
389
+ archiveAfterDays: 90,
390
+ archiveStorage: "cloud"
391
+ };
392
+ function getBuiltInType(name) {
393
+ return BUILT_IN_TYPES[name];
394
+ }
395
+ function getBuiltInTypeNames() {
396
+ return Object.keys(BUILT_IN_TYPES);
397
+ }
398
+ function isBuiltInType(name) {
399
+ return name in BUILT_IN_TYPES;
400
+ }
401
+ var TypeRegistry = class {
402
+ types = /* @__PURE__ */ new Map();
403
+ patternCache = /* @__PURE__ */ new Map();
404
+ constructor(options = {}) {
405
+ const { includeBuiltIn = true, customTypes = {} } = options;
406
+ if (includeBuiltIn) {
407
+ for (const [name, type] of Object.entries(BUILT_IN_TYPES)) {
408
+ this.types.set(name, type);
409
+ }
410
+ }
411
+ for (const [name, partialType] of Object.entries(customTypes)) {
412
+ this.register({
413
+ ...DEFAULT_TYPE,
414
+ ...partialType,
415
+ name
416
+ });
417
+ }
418
+ }
419
+ /**
420
+ * Get a type by name
421
+ *
422
+ * Custom types take precedence over built-in types with the same name.
423
+ *
424
+ * @param name - Type name
425
+ * @returns The type or undefined
426
+ */
427
+ get(name) {
428
+ return this.types.get(name);
429
+ }
430
+ /**
431
+ * Register a custom type
432
+ *
433
+ * @param type - The type to register
434
+ */
435
+ register(type) {
436
+ this.types.set(type.name, type);
437
+ this.patternCache.clear();
438
+ }
439
+ /**
440
+ * Unregister a type
441
+ *
442
+ * Note: Built-in types can be unregistered but this is not recommended.
443
+ *
444
+ * @param name - Type name to unregister
445
+ * @returns true if the type was removed
446
+ */
447
+ unregister(name) {
448
+ const result = this.types.delete(name);
449
+ if (result) {
450
+ this.patternCache.clear();
451
+ }
452
+ return result;
453
+ }
454
+ /**
455
+ * Check if a type exists
456
+ *
457
+ * @param name - Type name
458
+ * @returns true if the type exists
459
+ */
460
+ has(name) {
461
+ return this.types.has(name);
462
+ }
463
+ /**
464
+ * List all registered types
465
+ *
466
+ * @returns Array of all types
467
+ */
468
+ list() {
469
+ return Array.from(this.types.values());
470
+ }
471
+ /**
472
+ * List all type names
473
+ *
474
+ * @returns Array of type names
475
+ */
476
+ listNames() {
477
+ return Array.from(this.types.keys());
478
+ }
479
+ /**
480
+ * Detect the type of a file based on its path
481
+ *
482
+ * Types are checked in order of specificity (most specific patterns first).
483
+ * Returns 'default' if no type matches.
484
+ *
485
+ * @param filePath - File path to check
486
+ * @returns The detected type name
487
+ */
488
+ detectType(filePath) {
489
+ const cached = this.patternCache.get(filePath);
490
+ if (cached) {
491
+ return cached;
492
+ }
493
+ const normalizedPath = filePath.replace(/\\/g, "/");
494
+ const sortedTypes = this.list().sort((a, b) => {
495
+ const aMaxLen = Math.max(...a.patterns.map((p) => p.length));
496
+ const bMaxLen = Math.max(...b.patterns.map((p) => p.length));
497
+ return bMaxLen - aMaxLen;
498
+ });
499
+ for (const type of sortedTypes) {
500
+ if (type.name === "default") continue;
501
+ for (const pattern of type.patterns) {
502
+ if (micromatch2__default.default.isMatch(normalizedPath, pattern)) {
503
+ this.patternCache.set(filePath, type.name);
504
+ return type.name;
505
+ }
506
+ }
507
+ }
508
+ this.patternCache.set(filePath, "default");
509
+ return "default";
510
+ }
511
+ /**
512
+ * Get the TTL for a file based on its type
513
+ *
514
+ * @param filePath - File path
515
+ * @param override - Optional TTL override
516
+ * @returns TTL in seconds
517
+ */
518
+ getTtl(filePath, override) {
519
+ if (override !== void 0) {
520
+ return override;
521
+ }
522
+ const typeName = this.detectType(filePath);
523
+ const type = this.get(typeName) || DEFAULT_TYPE;
524
+ return type.defaultTtl;
525
+ }
526
+ /**
527
+ * Get archive configuration for a file
528
+ *
529
+ * @param filePath - File path
530
+ * @returns Archive configuration or null if archiving is disabled
531
+ */
532
+ getArchiveConfig(filePath) {
533
+ const typeName = this.detectType(filePath);
534
+ const type = this.get(typeName) || DEFAULT_TYPE;
535
+ if (type.archiveAfterDays === null || type.archiveStorage === null) {
536
+ return null;
537
+ }
538
+ return {
539
+ afterDays: type.archiveAfterDays,
540
+ storage: type.archiveStorage
541
+ };
542
+ }
543
+ /**
544
+ * Get all files that match a type's patterns
545
+ *
546
+ * @param typeName - Type name
547
+ * @param files - Array of file paths to filter
548
+ * @returns Filtered file paths
549
+ */
550
+ filterByType(typeName, files) {
551
+ const type = this.get(typeName);
552
+ if (!type) {
553
+ return [];
554
+ }
555
+ return files.filter((file) => {
556
+ const normalized = file.replace(/\\/g, "/");
557
+ return type.patterns.some((pattern) => micromatch2__default.default.isMatch(normalized, pattern));
558
+ });
559
+ }
560
+ /**
561
+ * Check if a type is a built-in type
562
+ *
563
+ * @param name - Type name
564
+ * @returns true if it's a built-in type
565
+ */
566
+ isBuiltIn(name) {
567
+ return isBuiltInType(name);
568
+ }
569
+ /**
570
+ * Get the number of registered types
571
+ */
572
+ get size() {
573
+ return this.types.size;
574
+ }
575
+ /**
576
+ * Clear pattern cache
577
+ *
578
+ * Call this if you need to force re-detection of types.
579
+ */
580
+ clearCache() {
581
+ this.patternCache.clear();
582
+ }
583
+ };
584
+ function createDefaultRegistry() {
585
+ return new TypeRegistry();
586
+ }
587
+ var CustomTypeSchema = zod.z.object({
588
+ description: zod.z.string().optional(),
589
+ patterns: zod.z.array(zod.z.string()).min(1),
590
+ defaultTtl: zod.z.number().positive().optional(),
591
+ archiveAfterDays: zod.z.number().positive().nullable().optional(),
592
+ archiveStorage: zod.z.enum(["local", "cloud", "drive"]).nullable().optional(),
593
+ syncPatterns: zod.z.array(zod.z.string()).optional(),
594
+ excludePatterns: zod.z.array(zod.z.string()).optional(),
595
+ permissions: zod.z.object({
596
+ include: zod.z.array(zod.z.string()),
597
+ exclude: zod.z.array(zod.z.string())
598
+ }).optional()
599
+ });
600
+ var TypesConfigSchema = zod.z.object({
601
+ custom: zod.z.record(zod.z.string(), CustomTypeSchema).optional()
602
+ });
603
+ function parseTtl(value) {
604
+ if (typeof value === "number") {
605
+ return value;
606
+ }
607
+ const match = value.match(/^(\d+)(s|m|h|d|w)$/);
608
+ if (!match || !match[1] || !match[2]) {
609
+ return TTL.ONE_WEEK;
610
+ }
611
+ const num = parseInt(match[1], 10);
612
+ const unit = match[2];
613
+ switch (unit) {
614
+ case "s":
615
+ return num;
616
+ case "m":
617
+ return num * 60;
618
+ case "h":
619
+ return num * 3600;
620
+ case "d":
621
+ return num * 86400;
622
+ case "w":
623
+ return num * 604800;
624
+ default:
625
+ return TTL.ONE_WEEK;
626
+ }
627
+ }
628
+ function validateCustomTypes(config) {
629
+ const result = {
630
+ valid: true,
631
+ errors: []
632
+ };
633
+ if (!config || typeof config !== "object") {
634
+ return result;
635
+ }
636
+ const typesConfig = config;
637
+ const customTypes = typesConfig.custom;
638
+ if (!customTypes) {
639
+ return result;
640
+ }
641
+ for (const [name, typeConfig] of Object.entries(customTypes)) {
642
+ if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
643
+ result.valid = false;
644
+ result.errors.push({
645
+ name,
646
+ field: "name",
647
+ message: "Type name must start with a letter and contain only lowercase letters, numbers, hyphens, and underscores"
648
+ });
649
+ }
650
+ const parseResult = CustomTypeSchema.safeParse(typeConfig);
651
+ if (!parseResult.success) {
652
+ result.valid = false;
653
+ for (const issue of parseResult.error.issues) {
654
+ result.errors.push({
655
+ name,
656
+ field: issue.path.join("."),
657
+ message: issue.message
658
+ });
659
+ }
660
+ }
661
+ }
662
+ return result;
663
+ }
664
+ function loadCustomTypes(config) {
665
+ const types = /* @__PURE__ */ new Map();
666
+ if (!config.custom) {
667
+ return types;
668
+ }
669
+ for (const [name, typeConfig] of Object.entries(config.custom)) {
670
+ const type = {
671
+ name,
672
+ description: typeConfig.description || `Custom type: ${name}`,
673
+ patterns: typeConfig.patterns,
674
+ defaultTtl: typeConfig.defaultTtl ?? DEFAULT_TYPE.defaultTtl,
675
+ archiveAfterDays: typeConfig.archiveAfterDays ?? DEFAULT_TYPE.archiveAfterDays,
676
+ archiveStorage: typeConfig.archiveStorage ?? DEFAULT_TYPE.archiveStorage,
677
+ syncPatterns: typeConfig.syncPatterns,
678
+ excludePatterns: typeConfig.excludePatterns,
679
+ permissions: typeConfig.permissions
680
+ };
681
+ types.set(name, type);
682
+ }
683
+ return types;
684
+ }
685
+ function mergeTypes(builtIn, custom) {
686
+ const merged = /* @__PURE__ */ new Map();
687
+ for (const [name, type] of Object.entries(builtIn)) {
688
+ merged.set(name, type);
689
+ }
690
+ for (const [name, type] of custom) {
691
+ merged.set(name, type);
692
+ }
693
+ return merged;
694
+ }
695
+ function extendType(baseName, overrides) {
696
+ return {
697
+ ...overrides,
698
+ name: baseName
699
+ };
700
+ }
37
701
  var MetadataSchema = zod.z.object({
38
702
  // Organizational
39
703
  org: zod.z.string().optional(),
@@ -41,6 +705,7 @@ var MetadataSchema = zod.z.object({
41
705
  // Sync rules
42
706
  codex_sync_include: zod.z.array(zod.z.string()).optional(),
43
707
  codex_sync_exclude: zod.z.array(zod.z.string()).optional(),
708
+ codex_sync_custom: zod.z.array(zod.z.string()).optional(),
44
709
  // Metadata
45
710
  title: zod.z.string().optional(),
46
711
  description: zod.z.string().optional(),
@@ -143,7 +808,7 @@ function extractRawFrontmatter(content) {
143
808
  }
144
809
  function matchPattern(pattern, value) {
145
810
  if (pattern === value) return true;
146
- return micromatch.isMatch(value, pattern);
811
+ return micromatch2.isMatch(value, pattern);
147
812
  }
148
813
  function matchAnyPattern(patterns, value) {
149
814
  if (patterns.length === 1 && patterns[0] === "*") {
@@ -389,28 +1054,3317 @@ function getTargetRepos(options) {
389
1054
  );
390
1055
  }
391
1056
 
1057
+ // src/core/custom/destinations.ts
1058
+ function parseCustomDestination(value) {
1059
+ if (typeof value !== "string" || !value) {
1060
+ throw new ValidationError("Custom destination must be a non-empty string");
1061
+ }
1062
+ const colonIndex = value.indexOf(":");
1063
+ if (colonIndex === -1) {
1064
+ throw new ValidationError(
1065
+ `Invalid custom destination format: "${value}". Expected format: "repo:path"`
1066
+ );
1067
+ }
1068
+ const repo = value.substring(0, colonIndex).trim();
1069
+ const path5 = value.substring(colonIndex + 1).trim();
1070
+ if (!repo) {
1071
+ throw new ValidationError(
1072
+ `Invalid custom destination: repository name cannot be empty in "${value}"`
1073
+ );
1074
+ }
1075
+ if (!path5) {
1076
+ throw new ValidationError(
1077
+ `Invalid custom destination: path cannot be empty in "${value}"`
1078
+ );
1079
+ }
1080
+ return { repo, path: path5 };
1081
+ }
1082
+ function getCustomSyncDestinations(metadata) {
1083
+ const customDestinations = metadata.codex_sync_custom;
1084
+ if (!customDestinations || !Array.isArray(customDestinations)) {
1085
+ return [];
1086
+ }
1087
+ const results = [];
1088
+ for (const destination of customDestinations) {
1089
+ try {
1090
+ const parsed = parseCustomDestination(destination);
1091
+ results.push(parsed);
1092
+ } catch (error) {
1093
+ continue;
1094
+ }
1095
+ }
1096
+ return results;
1097
+ }
1098
+
1099
+ // src/storage/provider.ts
1100
+ var DEFAULT_FETCH_OPTIONS = {
1101
+ timeout: 3e4,
1102
+ // 30 seconds
1103
+ maxRetries: 3,
1104
+ token: "",
1105
+ branch: "main",
1106
+ followRedirects: true,
1107
+ maxSize: 10 * 1024 * 1024
1108
+ // 10MB
1109
+ };
1110
+ function mergeFetchOptions(options) {
1111
+ return {
1112
+ ...DEFAULT_FETCH_OPTIONS,
1113
+ ...options
1114
+ };
1115
+ }
1116
+ function detectContentType(path5) {
1117
+ const ext = path5.split(".").pop()?.toLowerCase();
1118
+ const mimeTypes = {
1119
+ md: "text/markdown",
1120
+ markdown: "text/markdown",
1121
+ txt: "text/plain",
1122
+ json: "application/json",
1123
+ yaml: "application/x-yaml",
1124
+ yml: "application/x-yaml",
1125
+ xml: "application/xml",
1126
+ html: "text/html",
1127
+ htm: "text/html",
1128
+ css: "text/css",
1129
+ js: "application/javascript",
1130
+ ts: "application/typescript",
1131
+ py: "text/x-python",
1132
+ rb: "text/x-ruby",
1133
+ go: "text/x-go",
1134
+ rs: "text/x-rust",
1135
+ java: "text/x-java",
1136
+ c: "text/x-c",
1137
+ cpp: "text/x-c++",
1138
+ h: "text/x-c",
1139
+ hpp: "text/x-c++",
1140
+ sh: "application/x-sh",
1141
+ bash: "application/x-sh",
1142
+ zsh: "application/x-sh",
1143
+ pdf: "application/pdf",
1144
+ png: "image/png",
1145
+ jpg: "image/jpeg",
1146
+ jpeg: "image/jpeg",
1147
+ gif: "image/gif",
1148
+ svg: "image/svg+xml",
1149
+ webp: "image/webp"
1150
+ };
1151
+ return mimeTypes[ext || ""] || "application/octet-stream";
1152
+ }
1153
+ var LocalStorage = class {
1154
+ name = "local";
1155
+ type = "local";
1156
+ baseDir;
1157
+ constructor(options = {}) {
1158
+ this.baseDir = options.baseDir || process.cwd();
1159
+ }
1160
+ /**
1161
+ * Check if this provider can handle the reference
1162
+ *
1163
+ * Local provider handles references that are in the current project
1164
+ * and have a local path.
1165
+ */
1166
+ canHandle(reference) {
1167
+ return reference.isCurrentProject && !!reference.localPath;
1168
+ }
1169
+ /**
1170
+ * Fetch content from local filesystem
1171
+ */
1172
+ async fetch(reference, options) {
1173
+ const opts = mergeFetchOptions(options);
1174
+ if (!reference.localPath) {
1175
+ throw new Error(`No local path for reference: ${reference.uri}`);
1176
+ }
1177
+ const fullPath = path3__default.default.isAbsolute(reference.localPath) ? reference.localPath : path3__default.default.join(this.baseDir, reference.localPath);
1178
+ try {
1179
+ await fs2__default.default.access(fullPath);
1180
+ } catch {
1181
+ throw new Error(`File not found: ${fullPath}`);
1182
+ }
1183
+ const stats = await fs2__default.default.stat(fullPath);
1184
+ if (stats.size > opts.maxSize) {
1185
+ throw new Error(
1186
+ `File too large: ${stats.size} bytes (max: ${opts.maxSize} bytes)`
1187
+ );
1188
+ }
1189
+ const content = await fs2__default.default.readFile(fullPath);
1190
+ return {
1191
+ content,
1192
+ contentType: detectContentType(reference.localPath),
1193
+ size: content.length,
1194
+ source: "local",
1195
+ metadata: {
1196
+ path: fullPath,
1197
+ mtime: stats.mtime.toISOString()
1198
+ }
1199
+ };
1200
+ }
1201
+ /**
1202
+ * Check if file exists locally
1203
+ */
1204
+ async exists(reference) {
1205
+ if (!reference.localPath) {
1206
+ return false;
1207
+ }
1208
+ const fullPath = path3__default.default.isAbsolute(reference.localPath) ? reference.localPath : path3__default.default.join(this.baseDir, reference.localPath);
1209
+ try {
1210
+ await fs2__default.default.access(fullPath);
1211
+ return true;
1212
+ } catch {
1213
+ return false;
1214
+ }
1215
+ }
1216
+ /**
1217
+ * Read file content as string
1218
+ */
1219
+ async readText(filePath) {
1220
+ const fullPath = path3__default.default.isAbsolute(filePath) ? filePath : path3__default.default.join(this.baseDir, filePath);
1221
+ return fs2__default.default.readFile(fullPath, "utf-8");
1222
+ }
1223
+ /**
1224
+ * Write content to file
1225
+ */
1226
+ async write(filePath, content) {
1227
+ const fullPath = path3__default.default.isAbsolute(filePath) ? filePath : path3__default.default.join(this.baseDir, filePath);
1228
+ await fs2__default.default.mkdir(path3__default.default.dirname(fullPath), { recursive: true });
1229
+ await fs2__default.default.writeFile(fullPath, content);
1230
+ }
1231
+ /**
1232
+ * Delete a file
1233
+ */
1234
+ async delete(filePath) {
1235
+ const fullPath = path3__default.default.isAbsolute(filePath) ? filePath : path3__default.default.join(this.baseDir, filePath);
1236
+ try {
1237
+ await fs2__default.default.unlink(fullPath);
1238
+ return true;
1239
+ } catch {
1240
+ return false;
1241
+ }
1242
+ }
1243
+ /**
1244
+ * List files in a directory
1245
+ */
1246
+ async list(dirPath) {
1247
+ const fullPath = path3__default.default.isAbsolute(dirPath) ? dirPath : path3__default.default.join(this.baseDir, dirPath);
1248
+ try {
1249
+ const entries = await fs2__default.default.readdir(fullPath, { withFileTypes: true });
1250
+ return entries.filter((e) => e.isFile()).map((e) => path3__default.default.join(dirPath, e.name));
1251
+ } catch {
1252
+ return [];
1253
+ }
1254
+ }
1255
+ };
1256
+ function createLocalStorage(options) {
1257
+ return new LocalStorage(options);
1258
+ }
1259
+
1260
+ // src/storage/github.ts
1261
+ var GitHubStorage = class {
1262
+ name = "github";
1263
+ type = "github";
1264
+ apiBaseUrl;
1265
+ rawBaseUrl;
1266
+ defaultBranch;
1267
+ token;
1268
+ constructor(options = {}) {
1269
+ this.apiBaseUrl = options.apiBaseUrl || "https://api.github.com";
1270
+ this.rawBaseUrl = options.rawBaseUrl || "https://raw.githubusercontent.com";
1271
+ this.defaultBranch = options.defaultBranch || "main";
1272
+ this.token = options.token || (options.tokenEnv ? process.env[options.tokenEnv] : void 0);
1273
+ if (!this.token) {
1274
+ this.token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
1275
+ }
1276
+ }
1277
+ /**
1278
+ * Check if this provider can handle the reference
1279
+ *
1280
+ * GitHub provider handles references that are NOT in the current project
1281
+ * (those need to be fetched from GitHub).
1282
+ */
1283
+ canHandle(reference) {
1284
+ return !reference.isCurrentProject;
1285
+ }
1286
+ /**
1287
+ * Fetch content from GitHub
1288
+ */
1289
+ async fetch(reference, options) {
1290
+ const opts = mergeFetchOptions(options);
1291
+ const token = opts.token || this.token;
1292
+ const branch = opts.branch || this.defaultBranch;
1293
+ if (!reference.path) {
1294
+ throw new Error(`No path specified for reference: ${reference.uri}`);
1295
+ }
1296
+ try {
1297
+ return await this.fetchRaw(reference, branch, opts, token);
1298
+ } catch (error) {
1299
+ if (token) {
1300
+ return await this.fetchApi(reference, branch, opts, token);
1301
+ }
1302
+ throw error;
1303
+ }
1304
+ }
1305
+ /**
1306
+ * Fetch using raw.githubusercontent.com
1307
+ */
1308
+ async fetchRaw(reference, branch, opts, token) {
1309
+ const url = `${this.rawBaseUrl}/${reference.org}/${reference.project}/${branch}/${reference.path}`;
1310
+ const headers = {
1311
+ "User-Agent": "fractary-codex"
1312
+ };
1313
+ if (token) {
1314
+ headers["Authorization"] = `token ${token}`;
1315
+ }
1316
+ const controller = new AbortController();
1317
+ const timeoutId = setTimeout(() => controller.abort(), opts.timeout);
1318
+ try {
1319
+ const response = await fetch(url, {
1320
+ headers,
1321
+ signal: controller.signal,
1322
+ redirect: opts.followRedirects ? "follow" : "manual"
1323
+ });
1324
+ if (!response.ok) {
1325
+ throw new Error(`GitHub raw fetch failed: ${response.status} ${response.statusText}`);
1326
+ }
1327
+ const arrayBuffer = await response.arrayBuffer();
1328
+ const content = Buffer.from(arrayBuffer);
1329
+ if (content.length > opts.maxSize) {
1330
+ throw new Error(`Content too large: ${content.length} bytes (max: ${opts.maxSize} bytes)`);
1331
+ }
1332
+ return {
1333
+ content,
1334
+ contentType: response.headers.get("content-type") || detectContentType(reference.path),
1335
+ size: content.length,
1336
+ source: "github-raw",
1337
+ metadata: {
1338
+ url,
1339
+ branch
1340
+ }
1341
+ };
1342
+ } finally {
1343
+ clearTimeout(timeoutId);
1344
+ }
1345
+ }
1346
+ /**
1347
+ * Fetch using GitHub API (for private repos)
1348
+ */
1349
+ async fetchApi(reference, branch, opts, token) {
1350
+ const url = `${this.apiBaseUrl}/repos/${reference.org}/${reference.project}/contents/${reference.path}?ref=${branch}`;
1351
+ const headers = {
1352
+ "User-Agent": "fractary-codex",
1353
+ Accept: "application/vnd.github.v3+json",
1354
+ Authorization: `token ${token}`
1355
+ };
1356
+ const controller = new AbortController();
1357
+ const timeoutId = setTimeout(() => controller.abort(), opts.timeout);
1358
+ try {
1359
+ const response = await fetch(url, {
1360
+ headers,
1361
+ signal: controller.signal
1362
+ });
1363
+ if (!response.ok) {
1364
+ throw new Error(`GitHub API fetch failed: ${response.status} ${response.statusText}`);
1365
+ }
1366
+ const data = await response.json();
1367
+ if (data.type !== "file") {
1368
+ throw new Error(`Reference is not a file: ${reference.uri}`);
1369
+ }
1370
+ let content;
1371
+ if (data.content && data.encoding === "base64") {
1372
+ content = Buffer.from(data.content, "base64");
1373
+ } else if (data.download_url) {
1374
+ const downloadResponse = await fetch(data.download_url, {
1375
+ headers: { "User-Agent": "fractary-codex" },
1376
+ signal: controller.signal
1377
+ });
1378
+ const arrayBuffer = await downloadResponse.arrayBuffer();
1379
+ content = Buffer.from(arrayBuffer);
1380
+ } else {
1381
+ throw new Error(`No content available for: ${reference.uri}`);
1382
+ }
1383
+ if (content.length > opts.maxSize) {
1384
+ throw new Error(`Content too large: ${content.length} bytes (max: ${opts.maxSize} bytes)`);
1385
+ }
1386
+ return {
1387
+ content,
1388
+ contentType: detectContentType(reference.path),
1389
+ size: content.length,
1390
+ source: "github-api",
1391
+ metadata: {
1392
+ sha: data.sha,
1393
+ url,
1394
+ branch
1395
+ }
1396
+ };
1397
+ } finally {
1398
+ clearTimeout(timeoutId);
1399
+ }
1400
+ }
1401
+ /**
1402
+ * Check if file exists on GitHub
1403
+ */
1404
+ async exists(reference, options) {
1405
+ const opts = mergeFetchOptions(options);
1406
+ const token = opts.token || this.token;
1407
+ const branch = opts.branch || this.defaultBranch;
1408
+ if (!reference.path) {
1409
+ return false;
1410
+ }
1411
+ const url = `${this.rawBaseUrl}/${reference.org}/${reference.project}/${branch}/${reference.path}`;
1412
+ const headers = {
1413
+ "User-Agent": "fractary-codex"
1414
+ };
1415
+ if (token) {
1416
+ headers["Authorization"] = `token ${token}`;
1417
+ }
1418
+ try {
1419
+ const response = await fetch(url, {
1420
+ method: "HEAD",
1421
+ headers
1422
+ });
1423
+ return response.ok;
1424
+ } catch {
1425
+ return false;
1426
+ }
1427
+ }
1428
+ /**
1429
+ * Get repository metadata
1430
+ */
1431
+ async getRepoInfo(org, project) {
1432
+ const url = `${this.apiBaseUrl}/repos/${org}/${project}`;
1433
+ const headers = {
1434
+ "User-Agent": "fractary-codex",
1435
+ Accept: "application/vnd.github.v3+json"
1436
+ };
1437
+ if (this.token) {
1438
+ headers["Authorization"] = `token ${this.token}`;
1439
+ }
1440
+ try {
1441
+ const response = await fetch(url, { headers });
1442
+ if (!response.ok) {
1443
+ return null;
1444
+ }
1445
+ const data = await response.json();
1446
+ return {
1447
+ defaultBranch: data.default_branch,
1448
+ private: data.private,
1449
+ size: data.size
1450
+ };
1451
+ } catch {
1452
+ return null;
1453
+ }
1454
+ }
1455
+ };
1456
+ function createGitHubStorage(options) {
1457
+ return new GitHubStorage(options);
1458
+ }
1459
+
1460
+ // src/storage/http.ts
1461
+ var HttpStorage = class {
1462
+ name = "http";
1463
+ type = "http";
1464
+ defaultTimeout;
1465
+ defaultMaxSize;
1466
+ defaultHeaders;
1467
+ constructor(options = {}) {
1468
+ this.defaultTimeout = options.timeout || 3e4;
1469
+ this.defaultMaxSize = options.maxSize || 10 * 1024 * 1024;
1470
+ this.defaultHeaders = {
1471
+ "User-Agent": options.userAgent || "fractary-codex",
1472
+ ...options.headers
1473
+ };
1474
+ }
1475
+ /**
1476
+ * Check if this provider can handle the reference
1477
+ *
1478
+ * HTTP provider is a fallback - it can handle any reference
1479
+ * but should have lower priority than specialized providers.
1480
+ */
1481
+ canHandle(_reference) {
1482
+ return true;
1483
+ }
1484
+ /**
1485
+ * Fetch content from HTTP URL
1486
+ *
1487
+ * This method constructs a URL from the reference and fetches it.
1488
+ * The URL pattern is: https://raw.githubusercontent.com/{org}/{project}/{branch}/{path}
1489
+ *
1490
+ * For custom URL schemes, use the fetchUrl method directly.
1491
+ */
1492
+ async fetch(reference, options) {
1493
+ const opts = mergeFetchOptions(options);
1494
+ const branch = opts.branch || "main";
1495
+ if (!reference.path) {
1496
+ throw new Error(`No path specified for reference: ${reference.uri}`);
1497
+ }
1498
+ const url = `https://raw.githubusercontent.com/${reference.org}/${reference.project}/${branch}/${reference.path}`;
1499
+ return this.fetchUrl(url, opts);
1500
+ }
1501
+ /**
1502
+ * Fetch content from any URL
1503
+ */
1504
+ async fetchUrl(url, options) {
1505
+ const opts = mergeFetchOptions(options);
1506
+ const timeout = opts.timeout || this.defaultTimeout;
1507
+ const maxSize = opts.maxSize || this.defaultMaxSize;
1508
+ const headers = {
1509
+ ...this.defaultHeaders
1510
+ };
1511
+ if (opts.token) {
1512
+ headers["Authorization"] = `Bearer ${opts.token}`;
1513
+ }
1514
+ const controller = new AbortController();
1515
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1516
+ try {
1517
+ const response = await fetch(url, {
1518
+ headers,
1519
+ signal: controller.signal,
1520
+ redirect: opts.followRedirects ? "follow" : "manual"
1521
+ });
1522
+ if (!response.ok) {
1523
+ throw new Error(`HTTP fetch failed: ${response.status} ${response.statusText}`);
1524
+ }
1525
+ const contentLength = response.headers.get("content-length");
1526
+ if (contentLength && parseInt(contentLength, 10) > maxSize) {
1527
+ throw new Error(`Content too large: ${contentLength} bytes (max: ${maxSize} bytes)`);
1528
+ }
1529
+ const arrayBuffer = await response.arrayBuffer();
1530
+ const content = Buffer.from(arrayBuffer);
1531
+ if (content.length > maxSize) {
1532
+ throw new Error(`Content too large: ${content.length} bytes (max: ${maxSize} bytes)`);
1533
+ }
1534
+ const pathname = new URL(url).pathname;
1535
+ const filename = pathname.split("/").pop() || "";
1536
+ return {
1537
+ content,
1538
+ contentType: response.headers.get("content-type") || detectContentType(filename),
1539
+ size: content.length,
1540
+ source: "http",
1541
+ metadata: {
1542
+ url,
1543
+ status: response.status,
1544
+ headers: Object.fromEntries(response.headers.entries())
1545
+ }
1546
+ };
1547
+ } finally {
1548
+ clearTimeout(timeoutId);
1549
+ }
1550
+ }
1551
+ /**
1552
+ * Check if URL exists (HEAD request)
1553
+ */
1554
+ async exists(reference, options) {
1555
+ const opts = mergeFetchOptions(options);
1556
+ const branch = opts.branch || "main";
1557
+ if (!reference.path) {
1558
+ return false;
1559
+ }
1560
+ const url = `https://raw.githubusercontent.com/${reference.org}/${reference.project}/${branch}/${reference.path}`;
1561
+ return this.urlExists(url, opts);
1562
+ }
1563
+ /**
1564
+ * Check if any URL exists
1565
+ */
1566
+ async urlExists(url, options) {
1567
+ const opts = mergeFetchOptions(options);
1568
+ const timeout = opts.timeout || this.defaultTimeout;
1569
+ const headers = {
1570
+ ...this.defaultHeaders
1571
+ };
1572
+ if (opts.token) {
1573
+ headers["Authorization"] = `Bearer ${opts.token}`;
1574
+ }
1575
+ const controller = new AbortController();
1576
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1577
+ try {
1578
+ const response = await fetch(url, {
1579
+ method: "HEAD",
1580
+ headers,
1581
+ signal: controller.signal,
1582
+ redirect: "follow"
1583
+ });
1584
+ return response.ok;
1585
+ } catch {
1586
+ return false;
1587
+ } finally {
1588
+ clearTimeout(timeoutId);
1589
+ }
1590
+ }
1591
+ };
1592
+ function createHttpStorage(options) {
1593
+ return new HttpStorage(options);
1594
+ }
1595
+
1596
+ // src/storage/manager.ts
1597
+ var StorageManager = class {
1598
+ providers = /* @__PURE__ */ new Map();
1599
+ priority;
1600
+ constructor(config = {}) {
1601
+ this.providers.set("local", new LocalStorage(config.local));
1602
+ this.providers.set("github", new GitHubStorage(config.github));
1603
+ this.providers.set("http", new HttpStorage(config.http));
1604
+ this.priority = config.priority || ["local", "github", "http"];
1605
+ }
1606
+ /**
1607
+ * Register a custom storage provider
1608
+ */
1609
+ registerProvider(provider) {
1610
+ this.providers.set(provider.type, provider);
1611
+ }
1612
+ /**
1613
+ * Remove a storage provider
1614
+ */
1615
+ removeProvider(type) {
1616
+ return this.providers.delete(type);
1617
+ }
1618
+ /**
1619
+ * Get a provider by type
1620
+ */
1621
+ getProvider(type) {
1622
+ return this.providers.get(type);
1623
+ }
1624
+ /**
1625
+ * Get all registered providers
1626
+ */
1627
+ getProviders() {
1628
+ return Array.from(this.providers.values());
1629
+ }
1630
+ /**
1631
+ * Find the best provider for a reference
1632
+ */
1633
+ findProvider(reference) {
1634
+ for (const type of this.priority) {
1635
+ const provider = this.providers.get(type);
1636
+ if (provider && provider.canHandle(reference)) {
1637
+ return provider;
1638
+ }
1639
+ }
1640
+ return null;
1641
+ }
1642
+ /**
1643
+ * Fetch content for a reference
1644
+ *
1645
+ * Tries providers in priority order until one succeeds.
1646
+ */
1647
+ async fetch(reference, options) {
1648
+ const errors = [];
1649
+ for (const type of this.priority) {
1650
+ const provider = this.providers.get(type);
1651
+ if (!provider || !provider.canHandle(reference)) {
1652
+ continue;
1653
+ }
1654
+ try {
1655
+ return await provider.fetch(reference, options);
1656
+ } catch (error) {
1657
+ errors.push(error instanceof Error ? error : new Error(String(error)));
1658
+ }
1659
+ }
1660
+ if (errors.length === 0) {
1661
+ throw new Error(`No provider can handle reference: ${reference.uri}`);
1662
+ }
1663
+ const firstError = errors[0];
1664
+ if (firstError) {
1665
+ if (errors.length > 1) {
1666
+ throw new Error(`All providers failed for ${reference.uri}. First error: ${firstError.message}`);
1667
+ }
1668
+ throw firstError;
1669
+ }
1670
+ throw new Error(`Unknown error fetching ${reference.uri}`);
1671
+ }
1672
+ /**
1673
+ * Check if content exists for a reference
1674
+ *
1675
+ * Returns true if any provider reports the content exists.
1676
+ */
1677
+ async exists(reference, options) {
1678
+ for (const type of this.priority) {
1679
+ const provider = this.providers.get(type);
1680
+ if (!provider || !provider.canHandle(reference)) {
1681
+ continue;
1682
+ }
1683
+ try {
1684
+ if (await provider.exists(reference, options)) {
1685
+ return true;
1686
+ }
1687
+ } catch {
1688
+ }
1689
+ }
1690
+ return false;
1691
+ }
1692
+ /**
1693
+ * Fetch content using a specific provider
1694
+ */
1695
+ async fetchWith(type, reference, options) {
1696
+ const provider = this.providers.get(type);
1697
+ if (!provider) {
1698
+ throw new Error(`Provider not found: ${type}`);
1699
+ }
1700
+ return provider.fetch(reference, options);
1701
+ }
1702
+ /**
1703
+ * Fetch multiple references in parallel
1704
+ */
1705
+ async fetchMany(references, options) {
1706
+ const results = /* @__PURE__ */ new Map();
1707
+ const promises = references.map(async (ref) => {
1708
+ try {
1709
+ const result = await this.fetch(ref, options);
1710
+ results.set(ref.uri, result);
1711
+ } catch (error) {
1712
+ results.set(ref.uri, error instanceof Error ? error : new Error(String(error)));
1713
+ }
1714
+ });
1715
+ await Promise.all(promises);
1716
+ return results;
1717
+ }
1718
+ /**
1719
+ * Get provider status (for diagnostics)
1720
+ */
1721
+ getStatus() {
1722
+ return this.priority.map((type, index) => {
1723
+ const provider = this.providers.get(type);
1724
+ return {
1725
+ type,
1726
+ name: provider?.name || type,
1727
+ priority: index
1728
+ };
1729
+ });
1730
+ }
1731
+ };
1732
+ function createStorageManager(config) {
1733
+ return new StorageManager(config);
1734
+ }
1735
+ var defaultManager = null;
1736
+ function getDefaultStorageManager() {
1737
+ if (!defaultManager) {
1738
+ defaultManager = createStorageManager();
1739
+ }
1740
+ return defaultManager;
1741
+ }
1742
+ function setDefaultStorageManager(manager) {
1743
+ defaultManager = manager;
1744
+ }
1745
+
1746
+ // src/cache/entry.ts
1747
+ function calculateContentHash(content) {
1748
+ let hash = 2166136261;
1749
+ for (let i = 0; i < content.length; i++) {
1750
+ hash ^= content[i] ?? 0;
1751
+ hash = Math.imul(hash, 16777619);
1752
+ }
1753
+ return (hash >>> 0).toString(16).padStart(8, "0");
1754
+ }
1755
+ function createCacheEntry(uri, result, ttl) {
1756
+ const now = Date.now();
1757
+ const metadata = {
1758
+ uri,
1759
+ cachedAt: now,
1760
+ expiresAt: now + ttl * 1e3,
1761
+ ttl,
1762
+ contentHash: calculateContentHash(result.content),
1763
+ size: result.size,
1764
+ contentType: result.contentType,
1765
+ source: result.source,
1766
+ accessCount: 1,
1767
+ lastAccessedAt: now,
1768
+ providerMetadata: result.metadata
1769
+ };
1770
+ if (result.metadata?.etag && typeof result.metadata.etag === "string") {
1771
+ metadata.etag = result.metadata.etag;
1772
+ }
1773
+ if (result.metadata?.lastModified && typeof result.metadata.lastModified === "string") {
1774
+ metadata.lastModified = result.metadata.lastModified;
1775
+ }
1776
+ return {
1777
+ metadata,
1778
+ content: result.content
1779
+ };
1780
+ }
1781
+ function getCacheEntryStatus(entry) {
1782
+ const now = Date.now();
1783
+ if (now < entry.metadata.expiresAt) {
1784
+ return "fresh";
1785
+ }
1786
+ const staleWindow = 5 * 60 * 1e3;
1787
+ if (now < entry.metadata.expiresAt + staleWindow) {
1788
+ return "stale";
1789
+ }
1790
+ return "expired";
1791
+ }
1792
+ function isCacheEntryValid(entry) {
1793
+ const status = getCacheEntryStatus(entry);
1794
+ return status === "fresh" || status === "stale";
1795
+ }
1796
+ function isCacheEntryFresh(entry) {
1797
+ return getCacheEntryStatus(entry) === "fresh";
1798
+ }
1799
+ function touchCacheEntry(entry) {
1800
+ entry.metadata.accessCount++;
1801
+ entry.metadata.lastAccessedAt = Date.now();
1802
+ }
1803
+ function serializeCacheEntry(entry) {
1804
+ return {
1805
+ metadata: entry.metadata,
1806
+ content: entry.content.toString("base64")
1807
+ };
1808
+ }
1809
+ function deserializeCacheEntry(serialized) {
1810
+ return {
1811
+ metadata: serialized.metadata,
1812
+ content: Buffer.from(serialized.content, "base64")
1813
+ };
1814
+ }
1815
+ function getRemainingTtl(entry) {
1816
+ const remaining = entry.metadata.expiresAt - Date.now();
1817
+ return Math.max(0, Math.floor(remaining / 1e3));
1818
+ }
1819
+ function hasContentChanged(entry, newContent) {
1820
+ return entry.metadata.contentHash !== calculateContentHash(newContent);
1821
+ }
1822
+ function getCacheEntryAge(entry) {
1823
+ return Math.floor((Date.now() - entry.metadata.cachedAt) / 1e3);
1824
+ }
1825
+ var CachePersistence = class {
1826
+ cacheDir;
1827
+ extension;
1828
+ atomicWrites;
1829
+ constructor(options) {
1830
+ this.cacheDir = options.cacheDir;
1831
+ this.extension = options.extension || ".cache";
1832
+ this.atomicWrites = options.atomicWrites ?? true;
1833
+ }
1834
+ /**
1835
+ * Get the cache file path for a URI
1836
+ */
1837
+ getCachePath(uri) {
1838
+ const match = uri.match(/^codex:\/\/([^/]+)\/([^/]+)(?:\/(.*))?$/);
1839
+ if (!match || !match[1] || !match[2]) {
1840
+ throw new Error(`Invalid codex URI: ${uri}`);
1841
+ }
1842
+ const [, org, project, filePath] = match;
1843
+ const relativePath = filePath || "index";
1844
+ return path3__default.default.join(this.cacheDir, org, project, relativePath + this.extension);
1845
+ }
1846
+ /**
1847
+ * Get the metadata file path for a URI
1848
+ */
1849
+ getMetadataPath(uri) {
1850
+ return this.getCachePath(uri).replace(this.extension, ".meta.json");
1851
+ }
1852
+ /**
1853
+ * Read a cache entry from disk
1854
+ */
1855
+ async read(uri) {
1856
+ const cachePath = this.getCachePath(uri);
1857
+ const metadataPath = this.getMetadataPath(uri);
1858
+ try {
1859
+ const [metadataJson, content] = await Promise.all([
1860
+ fs2__default.default.readFile(metadataPath, "utf-8"),
1861
+ fs2__default.default.readFile(cachePath)
1862
+ ]);
1863
+ const metadata = JSON.parse(metadataJson);
1864
+ return {
1865
+ metadata,
1866
+ content
1867
+ };
1868
+ } catch (error) {
1869
+ if (error.code === "ENOENT") {
1870
+ return null;
1871
+ }
1872
+ throw error;
1873
+ }
1874
+ }
1875
+ /**
1876
+ * Write a cache entry to disk
1877
+ */
1878
+ async write(entry) {
1879
+ const cachePath = this.getCachePath(entry.metadata.uri);
1880
+ const metadataPath = this.getMetadataPath(entry.metadata.uri);
1881
+ await fs2__default.default.mkdir(path3__default.default.dirname(cachePath), { recursive: true });
1882
+ if (this.atomicWrites) {
1883
+ const tempCachePath = cachePath + ".tmp";
1884
+ const tempMetadataPath = metadataPath + ".tmp";
1885
+ try {
1886
+ await Promise.all([
1887
+ fs2__default.default.writeFile(tempCachePath, entry.content),
1888
+ fs2__default.default.writeFile(tempMetadataPath, JSON.stringify(entry.metadata, null, 2))
1889
+ ]);
1890
+ await Promise.all([
1891
+ fs2__default.default.rename(tempCachePath, cachePath),
1892
+ fs2__default.default.rename(tempMetadataPath, metadataPath)
1893
+ ]);
1894
+ } catch (error) {
1895
+ await Promise.all([
1896
+ fs2__default.default.unlink(tempCachePath).catch(() => {
1897
+ }),
1898
+ fs2__default.default.unlink(tempMetadataPath).catch(() => {
1899
+ })
1900
+ ]);
1901
+ throw error;
1902
+ }
1903
+ } else {
1904
+ await Promise.all([
1905
+ fs2__default.default.writeFile(cachePath, entry.content),
1906
+ fs2__default.default.writeFile(metadataPath, JSON.stringify(entry.metadata, null, 2))
1907
+ ]);
1908
+ }
1909
+ }
1910
+ /**
1911
+ * Delete a cache entry
1912
+ */
1913
+ async delete(uri) {
1914
+ const cachePath = this.getCachePath(uri);
1915
+ const metadataPath = this.getMetadataPath(uri);
1916
+ try {
1917
+ await Promise.all([fs2__default.default.unlink(cachePath), fs2__default.default.unlink(metadataPath)]);
1918
+ return true;
1919
+ } catch (error) {
1920
+ if (error.code === "ENOENT") {
1921
+ return false;
1922
+ }
1923
+ throw error;
1924
+ }
1925
+ }
1926
+ /**
1927
+ * Check if a cache entry exists
1928
+ */
1929
+ async exists(uri) {
1930
+ const cachePath = this.getCachePath(uri);
1931
+ try {
1932
+ await fs2__default.default.access(cachePath);
1933
+ return true;
1934
+ } catch {
1935
+ return false;
1936
+ }
1937
+ }
1938
+ /**
1939
+ * List all cached URIs
1940
+ */
1941
+ async list() {
1942
+ const uris = [];
1943
+ try {
1944
+ const orgs = await fs2__default.default.readdir(this.cacheDir);
1945
+ for (const org of orgs) {
1946
+ const orgPath = path3__default.default.join(this.cacheDir, org);
1947
+ const orgStat = await fs2__default.default.stat(orgPath);
1948
+ if (!orgStat.isDirectory()) continue;
1949
+ const projects = await fs2__default.default.readdir(orgPath);
1950
+ for (const project of projects) {
1951
+ const projectPath = path3__default.default.join(orgPath, project);
1952
+ const projectStat = await fs2__default.default.stat(projectPath);
1953
+ if (!projectStat.isDirectory()) continue;
1954
+ const files = await this.listFilesRecursive(projectPath);
1955
+ for (const file of files) {
1956
+ if (file.endsWith(this.extension)) {
1957
+ const relativePath = path3__default.default.relative(projectPath, file);
1958
+ const filePath = relativePath.slice(0, -this.extension.length);
1959
+ uris.push(`codex://${org}/${project}/${filePath}`);
1960
+ }
1961
+ }
1962
+ }
1963
+ }
1964
+ } catch (error) {
1965
+ if (error.code === "ENOENT") {
1966
+ return [];
1967
+ }
1968
+ throw error;
1969
+ }
1970
+ return uris;
1971
+ }
1972
+ /**
1973
+ * Recursively list files in a directory
1974
+ */
1975
+ async listFilesRecursive(dir) {
1976
+ const files = [];
1977
+ const entries = await fs2__default.default.readdir(dir, { withFileTypes: true });
1978
+ for (const entry of entries) {
1979
+ const fullPath = path3__default.default.join(dir, entry.name);
1980
+ if (entry.isDirectory()) {
1981
+ files.push(...await this.listFilesRecursive(fullPath));
1982
+ } else if (entry.isFile()) {
1983
+ files.push(fullPath);
1984
+ }
1985
+ }
1986
+ return files;
1987
+ }
1988
+ /**
1989
+ * Clear all cache entries
1990
+ */
1991
+ async clear() {
1992
+ let count = 0;
1993
+ try {
1994
+ const orgs = await fs2__default.default.readdir(this.cacheDir);
1995
+ for (const org of orgs) {
1996
+ const orgPath = path3__default.default.join(this.cacheDir, org);
1997
+ const stat = await fs2__default.default.stat(orgPath);
1998
+ if (stat.isDirectory()) {
1999
+ await fs2__default.default.rm(orgPath, { recursive: true });
2000
+ count++;
2001
+ }
2002
+ }
2003
+ } catch (error) {
2004
+ if (error.code !== "ENOENT") {
2005
+ throw error;
2006
+ }
2007
+ }
2008
+ return count;
2009
+ }
2010
+ /**
2011
+ * Clear expired entries
2012
+ */
2013
+ async clearExpired() {
2014
+ const uris = await this.list();
2015
+ let cleared = 0;
2016
+ for (const uri of uris) {
2017
+ const entry = await this.read(uri);
2018
+ if (entry && entry.metadata.expiresAt < Date.now()) {
2019
+ if (await this.delete(uri)) {
2020
+ cleared++;
2021
+ }
2022
+ }
2023
+ }
2024
+ return cleared;
2025
+ }
2026
+ /**
2027
+ * Get cache statistics
2028
+ */
2029
+ async getStats() {
2030
+ const stats = {
2031
+ entryCount: 0,
2032
+ totalSize: 0,
2033
+ freshCount: 0,
2034
+ staleCount: 0,
2035
+ expiredCount: 0
2036
+ };
2037
+ const uris = await this.list();
2038
+ const now = Date.now();
2039
+ const staleWindow = 5 * 60 * 1e3;
2040
+ for (const uri of uris) {
2041
+ const entry = await this.read(uri);
2042
+ if (entry) {
2043
+ stats.entryCount++;
2044
+ stats.totalSize += entry.metadata.size;
2045
+ if (now < entry.metadata.expiresAt) {
2046
+ stats.freshCount++;
2047
+ } else if (now < entry.metadata.expiresAt + staleWindow) {
2048
+ stats.staleCount++;
2049
+ } else {
2050
+ stats.expiredCount++;
2051
+ }
2052
+ }
2053
+ }
2054
+ return stats;
2055
+ }
2056
+ /**
2057
+ * Ensure cache directory exists
2058
+ */
2059
+ async ensureDir() {
2060
+ await fs2__default.default.mkdir(this.cacheDir, { recursive: true });
2061
+ }
2062
+ /**
2063
+ * Get cache directory path
2064
+ */
2065
+ getCacheDir() {
2066
+ return this.cacheDir;
2067
+ }
2068
+ };
2069
+ function createCachePersistence(options) {
2070
+ return new CachePersistence(options);
2071
+ }
2072
+
2073
+ // src/cache/manager.ts
2074
+ var CacheManager = class {
2075
+ memoryCache = /* @__PURE__ */ new Map();
2076
+ persistence;
2077
+ storage = null;
2078
+ config;
2079
+ // LRU tracking
2080
+ accessOrder = [];
2081
+ // Background refresh tracking
2082
+ refreshPromises = /* @__PURE__ */ new Map();
2083
+ constructor(config) {
2084
+ this.config = {
2085
+ cacheDir: config.cacheDir,
2086
+ defaultTtl: config.defaultTtl ?? 3600,
2087
+ // 1 hour default
2088
+ maxMemoryEntries: config.maxMemoryEntries ?? 1e3,
2089
+ maxMemorySize: config.maxMemorySize ?? 50 * 1024 * 1024,
2090
+ // 50MB
2091
+ enablePersistence: config.enablePersistence ?? true,
2092
+ staleWhileRevalidate: config.staleWhileRevalidate ?? true,
2093
+ backgroundRefreshThreshold: config.backgroundRefreshThreshold ?? 60
2094
+ // 1 minute
2095
+ };
2096
+ this.persistence = this.config.enablePersistence ? createCachePersistence({ cacheDir: this.config.cacheDir }) : null;
2097
+ }
2098
+ /**
2099
+ * Set the storage manager for fetching
2100
+ */
2101
+ setStorageManager(storage) {
2102
+ this.storage = storage;
2103
+ }
2104
+ /**
2105
+ * Get content for a reference
2106
+ *
2107
+ * Implements cache-first strategy with stale-while-revalidate.
2108
+ */
2109
+ async get(reference, options) {
2110
+ const ttl = options?.ttl ?? this.config.defaultTtl;
2111
+ let entry = this.memoryCache.get(reference.uri);
2112
+ if (!entry && this.persistence) {
2113
+ entry = await this.persistence.read(reference.uri) ?? void 0;
2114
+ if (entry) {
2115
+ this.setMemoryEntry(reference.uri, entry);
2116
+ }
2117
+ }
2118
+ if (entry) {
2119
+ const status = getCacheEntryStatus(entry);
2120
+ if (status === "fresh") {
2121
+ touchCacheEntry(entry);
2122
+ return this.entryToResult(entry);
2123
+ }
2124
+ if (status === "stale" && this.config.staleWhileRevalidate) {
2125
+ touchCacheEntry(entry);
2126
+ const result = this.entryToResult(entry);
2127
+ this.backgroundRefresh(reference, ttl, options);
2128
+ return result;
2129
+ }
2130
+ }
2131
+ return this.fetchAndCache(reference, ttl, options);
2132
+ }
2133
+ /**
2134
+ * Check if content is cached
2135
+ */
2136
+ async has(uri) {
2137
+ if (this.memoryCache.has(uri)) {
2138
+ return true;
2139
+ }
2140
+ if (this.persistence) {
2141
+ return this.persistence.exists(uri);
2142
+ }
2143
+ return false;
2144
+ }
2145
+ /**
2146
+ * Get cache entry without fetching
2147
+ */
2148
+ async lookup(uri) {
2149
+ let entry = this.memoryCache.get(uri);
2150
+ if (entry) {
2151
+ return {
2152
+ entry,
2153
+ hit: true,
2154
+ fresh: isCacheEntryFresh(entry),
2155
+ source: "memory"
2156
+ };
2157
+ }
2158
+ if (this.persistence) {
2159
+ entry = await this.persistence.read(uri) ?? void 0;
2160
+ if (entry) {
2161
+ this.setMemoryEntry(uri, entry);
2162
+ return {
2163
+ entry,
2164
+ hit: true,
2165
+ fresh: isCacheEntryFresh(entry),
2166
+ source: "disk"
2167
+ };
2168
+ }
2169
+ }
2170
+ return {
2171
+ entry: null,
2172
+ hit: false,
2173
+ fresh: false,
2174
+ source: "none"
2175
+ };
2176
+ }
2177
+ /**
2178
+ * Store content in cache
2179
+ */
2180
+ async set(uri, result, ttl) {
2181
+ const actualTtl = ttl ?? this.config.defaultTtl;
2182
+ const entry = createCacheEntry(uri, result, actualTtl);
2183
+ this.setMemoryEntry(uri, entry);
2184
+ if (this.persistence) {
2185
+ await this.persistence.write(entry);
2186
+ }
2187
+ return entry;
2188
+ }
2189
+ /**
2190
+ * Invalidate a cache entry
2191
+ */
2192
+ async invalidate(uri) {
2193
+ let removed = false;
2194
+ if (this.memoryCache.has(uri)) {
2195
+ this.memoryCache.delete(uri);
2196
+ this.removeFromAccessOrder(uri);
2197
+ removed = true;
2198
+ }
2199
+ if (this.persistence) {
2200
+ const diskRemoved = await this.persistence.delete(uri);
2201
+ removed = removed || diskRemoved;
2202
+ }
2203
+ return removed;
2204
+ }
2205
+ /**
2206
+ * Invalidate all entries matching a pattern
2207
+ */
2208
+ async invalidatePattern(pattern) {
2209
+ let count = 0;
2210
+ for (const uri of this.memoryCache.keys()) {
2211
+ if (pattern.test(uri)) {
2212
+ this.memoryCache.delete(uri);
2213
+ this.removeFromAccessOrder(uri);
2214
+ count++;
2215
+ }
2216
+ }
2217
+ if (this.persistence) {
2218
+ const uris = await this.persistence.list();
2219
+ for (const uri of uris) {
2220
+ if (pattern.test(uri)) {
2221
+ await this.persistence.delete(uri);
2222
+ count++;
2223
+ }
2224
+ }
2225
+ }
2226
+ return count;
2227
+ }
2228
+ /**
2229
+ * Clear all cache entries
2230
+ */
2231
+ async clear() {
2232
+ this.memoryCache.clear();
2233
+ this.accessOrder = [];
2234
+ if (this.persistence) {
2235
+ await this.persistence.clear();
2236
+ }
2237
+ }
2238
+ /**
2239
+ * Clear expired entries
2240
+ */
2241
+ async clearExpired() {
2242
+ let count = 0;
2243
+ const now = Date.now();
2244
+ for (const [uri, entry] of this.memoryCache) {
2245
+ if (entry.metadata.expiresAt < now) {
2246
+ this.memoryCache.delete(uri);
2247
+ this.removeFromAccessOrder(uri);
2248
+ count++;
2249
+ }
2250
+ }
2251
+ if (this.persistence) {
2252
+ count += await this.persistence.clearExpired();
2253
+ }
2254
+ return count;
2255
+ }
2256
+ /**
2257
+ * Get cache statistics
2258
+ */
2259
+ async getStats() {
2260
+ let diskStats = {
2261
+ entryCount: 0,
2262
+ totalSize: 0,
2263
+ freshCount: 0,
2264
+ staleCount: 0,
2265
+ expiredCount: 0
2266
+ };
2267
+ if (this.persistence) {
2268
+ diskStats = await this.persistence.getStats();
2269
+ }
2270
+ let memorySize = 0;
2271
+ for (const entry of this.memoryCache.values()) {
2272
+ memorySize += entry.metadata.size;
2273
+ }
2274
+ return {
2275
+ ...diskStats,
2276
+ memoryEntries: this.memoryCache.size,
2277
+ memorySize
2278
+ };
2279
+ }
2280
+ /**
2281
+ * Preload content into cache
2282
+ */
2283
+ async preload(references, options) {
2284
+ if (!this.storage) {
2285
+ throw new Error("Storage manager not set");
2286
+ }
2287
+ await Promise.all(
2288
+ references.map(async (ref) => {
2289
+ try {
2290
+ await this.get(ref, options);
2291
+ } catch {
2292
+ }
2293
+ })
2294
+ );
2295
+ }
2296
+ /**
2297
+ * Get metadata for a cached entry
2298
+ */
2299
+ async getMetadata(uri) {
2300
+ const result = await this.lookup(uri);
2301
+ return result.entry?.metadata ?? null;
2302
+ }
2303
+ /**
2304
+ * Get remaining TTL for an entry
2305
+ */
2306
+ async getTtl(uri) {
2307
+ const result = await this.lookup(uri);
2308
+ if (result.entry) {
2309
+ return getRemainingTtl(result.entry);
2310
+ }
2311
+ return null;
2312
+ }
2313
+ /**
2314
+ * Fetch content and store in cache
2315
+ */
2316
+ async fetchAndCache(reference, ttl, options) {
2317
+ if (!this.storage) {
2318
+ throw new Error("Storage manager not set");
2319
+ }
2320
+ const result = await this.storage.fetch(reference, options);
2321
+ await this.set(reference.uri, result, ttl);
2322
+ return result;
2323
+ }
2324
+ /**
2325
+ * Background refresh for stale-while-revalidate
2326
+ */
2327
+ backgroundRefresh(reference, ttl, options) {
2328
+ const uri = reference.uri;
2329
+ if (this.refreshPromises.has(uri)) {
2330
+ return;
2331
+ }
2332
+ const promise = this.fetchAndCache(reference, ttl, options).then(() => {
2333
+ const entry = this.memoryCache.get(uri);
2334
+ return entry ?? null;
2335
+ }).catch(() => null).finally(() => {
2336
+ this.refreshPromises.delete(uri);
2337
+ });
2338
+ this.refreshPromises.set(uri, promise);
2339
+ }
2340
+ /**
2341
+ * Set entry in memory cache with LRU eviction
2342
+ */
2343
+ setMemoryEntry(uri, entry) {
2344
+ this.removeFromAccessOrder(uri);
2345
+ this.accessOrder.push(uri);
2346
+ this.memoryCache.set(uri, entry);
2347
+ this.evictIfNeeded();
2348
+ }
2349
+ /**
2350
+ * Evict entries if over limits
2351
+ */
2352
+ evictIfNeeded() {
2353
+ while (this.memoryCache.size > this.config.maxMemoryEntries) {
2354
+ this.evictOldest();
2355
+ }
2356
+ let totalSize = 0;
2357
+ for (const entry of this.memoryCache.values()) {
2358
+ totalSize += entry.metadata.size;
2359
+ }
2360
+ while (totalSize > this.config.maxMemorySize && this.memoryCache.size > 0) {
2361
+ const evicted = this.evictOldest();
2362
+ if (evicted) {
2363
+ totalSize -= evicted.metadata.size;
2364
+ } else {
2365
+ break;
2366
+ }
2367
+ }
2368
+ }
2369
+ /**
2370
+ * Evict the oldest entry (LRU)
2371
+ */
2372
+ evictOldest() {
2373
+ if (this.accessOrder.length === 0) {
2374
+ return null;
2375
+ }
2376
+ const oldest = this.accessOrder.shift();
2377
+ if (oldest) {
2378
+ const entry = this.memoryCache.get(oldest);
2379
+ this.memoryCache.delete(oldest);
2380
+ return entry ?? null;
2381
+ }
2382
+ return null;
2383
+ }
2384
+ /**
2385
+ * Remove URI from access order
2386
+ */
2387
+ removeFromAccessOrder(uri) {
2388
+ const index = this.accessOrder.indexOf(uri);
2389
+ if (index !== -1) {
2390
+ this.accessOrder.splice(index, 1);
2391
+ }
2392
+ }
2393
+ /**
2394
+ * Convert cache entry to fetch result
2395
+ */
2396
+ entryToResult(entry) {
2397
+ return {
2398
+ content: entry.content,
2399
+ contentType: entry.metadata.contentType,
2400
+ size: entry.metadata.size,
2401
+ source: entry.metadata.source,
2402
+ metadata: entry.metadata.providerMetadata
2403
+ };
2404
+ }
2405
+ };
2406
+ function createCacheManager(config) {
2407
+ return new CacheManager(config);
2408
+ }
2409
+ var defaultCacheManager = null;
2410
+ function getDefaultCacheManager() {
2411
+ if (!defaultCacheManager) {
2412
+ defaultCacheManager = createCacheManager({
2413
+ cacheDir: ".fractary/plugins/codex/cache"
2414
+ });
2415
+ }
2416
+ return defaultCacheManager;
2417
+ }
2418
+ function setDefaultCacheManager(manager) {
2419
+ defaultCacheManager = manager;
2420
+ }
2421
+
2422
+ // src/mcp/tools.ts
2423
+ var CODEX_TOOLS = [
2424
+ {
2425
+ name: "codex_fetch",
2426
+ description: "Fetch a document from the Codex knowledge base by URI. Returns the document content.",
2427
+ inputSchema: {
2428
+ type: "object",
2429
+ properties: {
2430
+ uri: {
2431
+ type: "string",
2432
+ description: "Codex URI in format: codex://org/project/path/to/file.md"
2433
+ },
2434
+ branch: {
2435
+ type: "string",
2436
+ description: "Git branch to fetch from (default: main)"
2437
+ },
2438
+ noCache: {
2439
+ type: "boolean",
2440
+ description: "Bypass cache and fetch fresh content"
2441
+ }
2442
+ },
2443
+ required: ["uri"]
2444
+ }
2445
+ },
2446
+ {
2447
+ name: "codex_search",
2448
+ description: "Search for documents in the Codex knowledge base.",
2449
+ inputSchema: {
2450
+ type: "object",
2451
+ properties: {
2452
+ query: {
2453
+ type: "string",
2454
+ description: "Search query string"
2455
+ },
2456
+ org: {
2457
+ type: "string",
2458
+ description: "Filter by organization"
2459
+ },
2460
+ project: {
2461
+ type: "string",
2462
+ description: "Filter by project"
2463
+ },
2464
+ limit: {
2465
+ type: "number",
2466
+ description: "Maximum number of results (default: 10)"
2467
+ },
2468
+ type: {
2469
+ type: "string",
2470
+ description: "Filter by artifact type (e.g., docs, specs, logs)"
2471
+ }
2472
+ },
2473
+ required: ["query"]
2474
+ }
2475
+ },
2476
+ {
2477
+ name: "codex_list",
2478
+ description: "List documents in the Codex cache.",
2479
+ inputSchema: {
2480
+ type: "object",
2481
+ properties: {
2482
+ org: {
2483
+ type: "string",
2484
+ description: "Filter by organization"
2485
+ },
2486
+ project: {
2487
+ type: "string",
2488
+ description: "Filter by project"
2489
+ },
2490
+ includeExpired: {
2491
+ type: "boolean",
2492
+ description: "Include expired cache entries"
2493
+ }
2494
+ }
2495
+ }
2496
+ },
2497
+ {
2498
+ name: "codex_invalidate",
2499
+ description: "Invalidate cached documents matching a pattern.",
2500
+ inputSchema: {
2501
+ type: "object",
2502
+ properties: {
2503
+ pattern: {
2504
+ type: "string",
2505
+ description: "URI pattern to invalidate (supports regex)"
2506
+ }
2507
+ },
2508
+ required: ["pattern"]
2509
+ }
2510
+ }
2511
+ ];
2512
+ function textResult(text, isError = false) {
2513
+ return {
2514
+ content: [{ type: "text", text }],
2515
+ isError
2516
+ };
2517
+ }
2518
+ function resourceResult(uri, content, mimeType) {
2519
+ return {
2520
+ content: [
2521
+ {
2522
+ type: "resource",
2523
+ resource: {
2524
+ uri,
2525
+ mimeType,
2526
+ text: content
2527
+ }
2528
+ }
2529
+ ]
2530
+ };
2531
+ }
2532
+ async function handleFetch(args, ctx) {
2533
+ const { uri, branch, noCache } = args;
2534
+ const ref = resolveReference(uri);
2535
+ if (!ref) {
2536
+ return textResult(`Invalid codex URI: ${uri}`, true);
2537
+ }
2538
+ try {
2539
+ let result;
2540
+ if (noCache) {
2541
+ result = await ctx.storage.fetch(ref, { branch });
2542
+ await ctx.cache.set(uri, result);
2543
+ } else {
2544
+ result = await ctx.cache.get(ref, { branch });
2545
+ }
2546
+ const content = result.content.toString("utf-8");
2547
+ return resourceResult(uri, content, result.contentType);
2548
+ } catch (error) {
2549
+ const message = error instanceof Error ? error.message : String(error);
2550
+ return textResult(`Failed to fetch ${uri}: ${message}`, true);
2551
+ }
2552
+ }
2553
+ async function handleSearch(args, ctx) {
2554
+ const { query, org, project, limit = 10 } = args;
2555
+ const stats = await ctx.cache.getStats();
2556
+ if (stats.entryCount === 0) {
2557
+ return textResult("No documents in cache. Use codex_fetch to load documents first.");
2558
+ }
2559
+ const message = `Search functionality requires a search index.
2560
+ Query: "${query}"
2561
+ Filters: org=${org || "any"}, project=${project || "any"}
2562
+ Limit: ${limit}
2563
+
2564
+ To fetch documents, use codex_fetch with a specific URI like:
2565
+ codex://org/project/docs/file.md`;
2566
+ return textResult(message);
2567
+ }
2568
+ async function handleList(args, ctx) {
2569
+ const { org, project, includeExpired } = args;
2570
+ const stats = await ctx.cache.getStats();
2571
+ let message = `Cache Statistics:
2572
+ - Total entries: ${stats.entryCount}
2573
+ - Memory entries: ${stats.memoryEntries}
2574
+ - Memory size: ${formatBytes(stats.memorySize)}
2575
+ - Total size: ${formatBytes(stats.totalSize)}
2576
+ - Fresh: ${stats.freshCount}
2577
+ - Stale: ${stats.staleCount}
2578
+ - Expired: ${stats.expiredCount}`;
2579
+ if (org) {
2580
+ message += `
2581
+
2582
+ Filtered by org: ${org}`;
2583
+ }
2584
+ if (project) {
2585
+ message += `
2586
+ Filtered by project: ${project}`;
2587
+ }
2588
+ if (includeExpired) {
2589
+ message += `
2590
+ Including expired entries`;
2591
+ }
2592
+ return textResult(message);
2593
+ }
2594
+ async function handleInvalidate(args, ctx) {
2595
+ const { pattern } = args;
2596
+ try {
2597
+ const regex = new RegExp(pattern);
2598
+ const count = await ctx.cache.invalidatePattern(regex);
2599
+ return textResult(`Invalidated ${count} cache entries matching pattern: ${pattern}`);
2600
+ } catch (error) {
2601
+ const message = error instanceof Error ? error.message : String(error);
2602
+ return textResult(`Invalid pattern: ${message}`, true);
2603
+ }
2604
+ }
2605
+ async function handleToolCall(name, args, ctx) {
2606
+ switch (name) {
2607
+ case "codex_fetch":
2608
+ return handleFetch(args, ctx);
2609
+ case "codex_search":
2610
+ return handleSearch(args, ctx);
2611
+ case "codex_list":
2612
+ return handleList(args, ctx);
2613
+ case "codex_invalidate":
2614
+ return handleInvalidate(args, ctx);
2615
+ default:
2616
+ return textResult(`Unknown tool: ${name}`, true);
2617
+ }
2618
+ }
2619
+ function formatBytes(bytes) {
2620
+ if (bytes === 0) return "0 B";
2621
+ const k = 1024;
2622
+ const sizes = ["B", "KB", "MB", "GB"];
2623
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
2624
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
2625
+ }
2626
+
2627
+ // src/mcp/server.ts
2628
+ var McpServer = class {
2629
+ config;
2630
+ toolContext;
2631
+ constructor(config) {
2632
+ this.config = {
2633
+ name: config.name ?? "codex",
2634
+ version: config.version ?? "1.0.0",
2635
+ cache: config.cache,
2636
+ storage: config.storage
2637
+ };
2638
+ this.toolContext = {
2639
+ cache: config.cache,
2640
+ storage: config.storage
2641
+ };
2642
+ }
2643
+ /**
2644
+ * Get server info
2645
+ */
2646
+ getServerInfo() {
2647
+ return {
2648
+ name: this.config.name,
2649
+ version: this.config.version,
2650
+ capabilities: this.getCapabilities()
2651
+ };
2652
+ }
2653
+ /**
2654
+ * Get server capabilities
2655
+ */
2656
+ getCapabilities() {
2657
+ return {
2658
+ tools: {
2659
+ listChanged: false
2660
+ },
2661
+ resources: {
2662
+ subscribe: false,
2663
+ listChanged: false
2664
+ }
2665
+ };
2666
+ }
2667
+ /**
2668
+ * List available tools
2669
+ */
2670
+ listTools() {
2671
+ return CODEX_TOOLS;
2672
+ }
2673
+ /**
2674
+ * Call a tool
2675
+ */
2676
+ async callTool(name, args) {
2677
+ return handleToolCall(name, args, this.toolContext);
2678
+ }
2679
+ /**
2680
+ * List available resources
2681
+ */
2682
+ async listResources() {
2683
+ const resources = [];
2684
+ const stats = await this.config.cache.getStats();
2685
+ resources.push({
2686
+ uri: "codex://cache/summary",
2687
+ name: "Cache Summary",
2688
+ description: `${stats.entryCount} cached documents`,
2689
+ mimeType: "text/plain"
2690
+ });
2691
+ return resources;
2692
+ }
2693
+ /**
2694
+ * List resource templates
2695
+ */
2696
+ listResourceTemplates() {
2697
+ return [
2698
+ {
2699
+ uriTemplate: "codex://{org}/{project}/{path}",
2700
+ name: "Codex Document",
2701
+ description: "Fetch a document from the Codex knowledge base",
2702
+ mimeType: "text/markdown"
2703
+ }
2704
+ ];
2705
+ }
2706
+ /**
2707
+ * Read a resource
2708
+ */
2709
+ async readResource(uri) {
2710
+ if (uri === "codex://cache/summary") {
2711
+ const stats = await this.config.cache.getStats();
2712
+ return [
2713
+ {
2714
+ uri,
2715
+ mimeType: "text/plain",
2716
+ text: `Cache Statistics:
2717
+ - Total entries: ${stats.entryCount}
2718
+ - Memory entries: ${stats.memoryEntries}
2719
+ - Fresh: ${stats.freshCount}
2720
+ - Stale: ${stats.staleCount}
2721
+ - Expired: ${stats.expiredCount}`
2722
+ }
2723
+ ];
2724
+ }
2725
+ const ref = resolveReference(uri);
2726
+ if (!ref) {
2727
+ throw new Error(`Invalid codex URI: ${uri}`);
2728
+ }
2729
+ const result = await this.config.cache.get(ref);
2730
+ return [
2731
+ {
2732
+ uri,
2733
+ mimeType: result.contentType,
2734
+ text: result.content.toString("utf-8")
2735
+ }
2736
+ ];
2737
+ }
2738
+ /**
2739
+ * Handle JSON-RPC request
2740
+ *
2741
+ * This method handles the low-level MCP protocol messages.
2742
+ */
2743
+ async handleRequest(method, params) {
2744
+ switch (method) {
2745
+ case "initialize":
2746
+ return {
2747
+ protocolVersion: "2024-11-05",
2748
+ serverInfo: this.getServerInfo(),
2749
+ capabilities: this.getCapabilities()
2750
+ };
2751
+ case "tools/list":
2752
+ return { tools: this.listTools() };
2753
+ case "tools/call": {
2754
+ const { name, arguments: args } = params;
2755
+ return await this.callTool(name, args);
2756
+ }
2757
+ case "resources/list":
2758
+ return { resources: await this.listResources() };
2759
+ case "resources/templates/list":
2760
+ return { resourceTemplates: this.listResourceTemplates() };
2761
+ case "resources/read": {
2762
+ const { uri } = params;
2763
+ return { contents: await this.readResource(uri) };
2764
+ }
2765
+ case "prompts/list":
2766
+ return { prompts: [] };
2767
+ default:
2768
+ throw new Error(`Unknown method: ${method}`);
2769
+ }
2770
+ }
2771
+ /**
2772
+ * Process a JSON-RPC message
2773
+ */
2774
+ async processMessage(message) {
2775
+ let id = null;
2776
+ try {
2777
+ const request = JSON.parse(message);
2778
+ id = request.id;
2779
+ const { method, params } = request;
2780
+ const result = await this.handleRequest(method, params);
2781
+ return JSON.stringify({
2782
+ jsonrpc: "2.0",
2783
+ id,
2784
+ result
2785
+ });
2786
+ } catch (error) {
2787
+ const errorMessage = error instanceof Error ? error.message : String(error);
2788
+ return JSON.stringify({
2789
+ jsonrpc: "2.0",
2790
+ id,
2791
+ error: {
2792
+ code: -32603,
2793
+ message: errorMessage
2794
+ }
2795
+ });
2796
+ }
2797
+ }
2798
+ };
2799
+ function createMcpServer(config) {
2800
+ return new McpServer(config);
2801
+ }
2802
+
2803
+ // src/sync/types.ts
2804
+ var DEFAULT_SYNC_CONFIG = {
2805
+ defaultDirection: "to-codex",
2806
+ rules: [],
2807
+ defaultExcludes: [
2808
+ "**/node_modules/**",
2809
+ "**/.git/**",
2810
+ "**/.DS_Store",
2811
+ "**/Thumbs.db",
2812
+ "**/*.log",
2813
+ "**/.env*",
2814
+ "**/dist/**",
2815
+ "**/build/**",
2816
+ "**/coverage/**"
2817
+ ],
2818
+ deleteOrphans: false,
2819
+ conflictStrategy: "newest"
2820
+ };
2821
+ function evaluatePath(path5, rules, direction, defaultExcludes = []) {
2822
+ for (const pattern of defaultExcludes) {
2823
+ if (micromatch2__default.default.isMatch(path5, pattern)) {
2824
+ return {
2825
+ path: path5,
2826
+ shouldSync: false,
2827
+ reason: `Excluded by default pattern: ${pattern}`
2828
+ };
2829
+ }
2830
+ }
2831
+ const sortedRules = [...rules].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
2832
+ for (const rule of sortedRules) {
2833
+ if (rule.direction && rule.direction !== direction) {
2834
+ continue;
2835
+ }
2836
+ if (micromatch2__default.default.isMatch(path5, rule.pattern)) {
2837
+ return {
2838
+ path: path5,
2839
+ shouldSync: rule.include,
2840
+ matchedRule: rule,
2841
+ reason: rule.include ? `Included by rule: ${rule.pattern}` : `Excluded by rule: ${rule.pattern}`
2842
+ };
2843
+ }
2844
+ }
2845
+ return {
2846
+ path: path5,
2847
+ shouldSync: true,
2848
+ reason: "No matching rule, included by default"
2849
+ };
2850
+ }
2851
+ function evaluatePaths(paths, rules, direction, defaultExcludes = []) {
2852
+ const results = /* @__PURE__ */ new Map();
2853
+ for (const path5 of paths) {
2854
+ results.set(path5, evaluatePath(path5, rules, direction, defaultExcludes));
2855
+ }
2856
+ return results;
2857
+ }
2858
+ function filterSyncablePaths(paths, rules, direction, defaultExcludes = []) {
2859
+ return paths.filter(
2860
+ (path5) => evaluatePath(path5, rules, direction, defaultExcludes).shouldSync
2861
+ );
2862
+ }
2863
+ function createRulesFromPatterns(include = [], exclude = []) {
2864
+ const rules = [];
2865
+ for (const pattern of exclude) {
2866
+ rules.push({
2867
+ pattern,
2868
+ include: false,
2869
+ priority: 100
2870
+ });
2871
+ }
2872
+ for (const pattern of include) {
2873
+ rules.push({
2874
+ pattern,
2875
+ include: true,
2876
+ priority: 50
2877
+ });
2878
+ }
2879
+ return rules;
2880
+ }
2881
+ function mergeRules(...ruleSets) {
2882
+ return ruleSets.flat();
2883
+ }
2884
+ function summarizeEvaluations(results) {
2885
+ const summary = {
2886
+ total: results.size,
2887
+ included: 0,
2888
+ excluded: 0,
2889
+ byReason: {}
2890
+ };
2891
+ for (const result of results.values()) {
2892
+ if (result.shouldSync) {
2893
+ summary.included++;
2894
+ } else {
2895
+ summary.excluded++;
2896
+ }
2897
+ summary.byReason[result.reason] = (summary.byReason[result.reason] ?? 0) + 1;
2898
+ }
2899
+ return summary;
2900
+ }
2901
+ function validateRules(rules) {
2902
+ const errors = [];
2903
+ for (let i = 0; i < rules.length; i++) {
2904
+ const rule = rules[i];
2905
+ if (!rule) continue;
2906
+ if (!rule.pattern || rule.pattern.trim() === "") {
2907
+ errors.push(`Rule ${i}: pattern is empty`);
2908
+ continue;
2909
+ }
2910
+ try {
2911
+ micromatch2__default.default.isMatch("test", rule.pattern);
2912
+ } catch {
2913
+ errors.push(`Rule ${i}: invalid pattern "${rule.pattern}"`);
2914
+ }
2915
+ if (rule.direction && !["to-codex", "from-codex", "bidirectional"].includes(rule.direction)) {
2916
+ errors.push(`Rule ${i}: invalid direction "${rule.direction}"`);
2917
+ }
2918
+ }
2919
+ return errors;
2920
+ }
2921
+
2922
+ // src/sync/planner.ts
2923
+ function createSyncPlan(sourceFiles, targetFiles, options, config) {
2924
+ const direction = options.direction ?? config.defaultDirection;
2925
+ const targetMap = new Map(targetFiles.map((f) => [f.path, f]));
2926
+ const sourceMap = new Map(sourceFiles.map((f) => [f.path, f]));
2927
+ const files = [];
2928
+ const conflicts = [];
2929
+ const skipped = [];
2930
+ const rules = [
2931
+ ...config.rules,
2932
+ ...(options.include ?? []).map((p) => ({ pattern: p, include: true, priority: 200 })),
2933
+ ...(options.exclude ?? []).map((p) => ({ pattern: p, include: false, priority: 200 }))
2934
+ ];
2935
+ const excludes = [...config.defaultExcludes];
2936
+ for (const sourceFile of sourceFiles) {
2937
+ const evaluation = evaluatePath(sourceFile.path, rules, direction, excludes);
2938
+ if (!evaluation.shouldSync) {
2939
+ skipped.push({
2940
+ path: sourceFile.path,
2941
+ operation: "skip",
2942
+ size: sourceFile.size,
2943
+ reason: evaluation.reason
2944
+ });
2945
+ continue;
2946
+ }
2947
+ const targetFile = targetMap.get(sourceFile.path);
2948
+ if (!targetFile) {
2949
+ files.push({
2950
+ path: sourceFile.path,
2951
+ operation: "create",
2952
+ size: sourceFile.size,
2953
+ mtime: sourceFile.mtime,
2954
+ hash: sourceFile.hash
2955
+ });
2956
+ } else {
2957
+ const isDifferent = sourceFile.hash !== targetFile.hash;
2958
+ if (isDifferent) {
2959
+ if (options.force) {
2960
+ files.push({
2961
+ path: sourceFile.path,
2962
+ operation: "update",
2963
+ size: sourceFile.size,
2964
+ mtime: sourceFile.mtime,
2965
+ hash: sourceFile.hash
2966
+ });
2967
+ } else if (targetFile.mtime > sourceFile.mtime) {
2968
+ conflicts.push({
2969
+ path: sourceFile.path,
2970
+ operation: "conflict",
2971
+ size: sourceFile.size,
2972
+ mtime: sourceFile.mtime,
2973
+ reason: "Target file is newer than source"
2974
+ });
2975
+ } else {
2976
+ files.push({
2977
+ path: sourceFile.path,
2978
+ operation: "update",
2979
+ size: sourceFile.size,
2980
+ mtime: sourceFile.mtime,
2981
+ hash: sourceFile.hash
2982
+ });
2983
+ }
2984
+ } else {
2985
+ skipped.push({
2986
+ path: sourceFile.path,
2987
+ operation: "skip",
2988
+ size: sourceFile.size,
2989
+ reason: "Files are identical"
2990
+ });
2991
+ }
2992
+ }
2993
+ }
2994
+ if (options.delete) {
2995
+ for (const targetFile of targetFiles) {
2996
+ if (!sourceMap.has(targetFile.path)) {
2997
+ const evaluation = evaluatePath(targetFile.path, rules, direction, excludes);
2998
+ if (evaluation.shouldSync) {
2999
+ files.push({
3000
+ path: targetFile.path,
3001
+ operation: "delete",
3002
+ size: targetFile.size
3003
+ });
3004
+ }
3005
+ }
3006
+ }
3007
+ }
3008
+ let limitedFiles = files;
3009
+ if (options.maxFiles && files.length > options.maxFiles) {
3010
+ limitedFiles = files.slice(0, options.maxFiles);
3011
+ for (const file of files.slice(options.maxFiles)) {
3012
+ skipped.push({
3013
+ ...file,
3014
+ operation: "skip",
3015
+ reason: "Exceeded max files limit"
3016
+ });
3017
+ }
3018
+ }
3019
+ const totalBytes = limitedFiles.reduce((sum, f) => sum + (f.size ?? 0), 0);
3020
+ return {
3021
+ direction,
3022
+ source: direction === "from-codex" ? "codex" : "local",
3023
+ target: direction === "from-codex" ? "local" : "codex",
3024
+ files: limitedFiles,
3025
+ totalFiles: limitedFiles.length,
3026
+ totalBytes,
3027
+ conflicts,
3028
+ skipped
3029
+ };
3030
+ }
3031
+ function estimateSyncTime(plan, bytesPerSecond = 1024 * 1024) {
3032
+ const transferTime = plan.totalBytes / bytesPerSecond * 1e3;
3033
+ const overheadPerFile = 50;
3034
+ const overhead = plan.totalFiles * overheadPerFile;
3035
+ return Math.ceil(transferTime + overhead);
3036
+ }
3037
+ function createEmptySyncPlan(direction = "to-codex") {
3038
+ return {
3039
+ direction,
3040
+ source: direction === "from-codex" ? "codex" : "local",
3041
+ target: direction === "from-codex" ? "local" : "codex",
3042
+ files: [],
3043
+ totalFiles: 0,
3044
+ totalBytes: 0,
3045
+ conflicts: [],
3046
+ skipped: []
3047
+ };
3048
+ }
3049
+ function filterPlanOperations(plan, operations) {
3050
+ const filtered = plan.files.filter(
3051
+ (f) => operations.includes(f.operation)
3052
+ );
3053
+ return {
3054
+ ...plan,
3055
+ files: filtered,
3056
+ totalFiles: filtered.length,
3057
+ totalBytes: filtered.reduce((sum, f) => sum + (f.size ?? 0), 0)
3058
+ };
3059
+ }
3060
+ function getPlanStats(plan) {
3061
+ return {
3062
+ creates: plan.files.filter((f) => f.operation === "create").length,
3063
+ updates: plan.files.filter((f) => f.operation === "update").length,
3064
+ deletes: plan.files.filter((f) => f.operation === "delete").length,
3065
+ skips: plan.skipped.length,
3066
+ conflicts: plan.conflicts.length,
3067
+ totalBytes: plan.totalBytes
3068
+ };
3069
+ }
3070
+ function formatPlanSummary(plan) {
3071
+ const stats = getPlanStats(plan);
3072
+ const lines = [
3073
+ `Sync Plan: ${plan.source} \u2192 ${plan.target}`,
3074
+ `Direction: ${plan.direction}`,
3075
+ "",
3076
+ `Operations:`,
3077
+ ` Create: ${stats.creates} files`,
3078
+ ` Update: ${stats.updates} files`,
3079
+ ` Delete: ${stats.deletes} files`,
3080
+ ` Skip: ${stats.skips} files`,
3081
+ ""
3082
+ ];
3083
+ if (stats.conflicts > 0) {
3084
+ lines.push(`\u26A0\uFE0F Conflicts: ${stats.conflicts} files`);
3085
+ lines.push("");
3086
+ }
3087
+ lines.push(`Total: ${plan.totalFiles} files (${formatBytes2(stats.totalBytes)})`);
3088
+ return lines.join("\n");
3089
+ }
3090
+ function formatBytes2(bytes) {
3091
+ if (bytes === 0) return "0 B";
3092
+ const k = 1024;
3093
+ const sizes = ["B", "KB", "MB", "GB"];
3094
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
3095
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
3096
+ }
3097
+
3098
+ // src/sync/manager.ts
3099
+ var SyncManager = class {
3100
+ localStorage;
3101
+ config;
3102
+ manifestPath;
3103
+ manifest = null;
3104
+ constructor(options) {
3105
+ this.localStorage = options.localStorage;
3106
+ this.config = {
3107
+ ...DEFAULT_SYNC_CONFIG,
3108
+ ...options.config
3109
+ };
3110
+ this.manifestPath = options.manifestPath ?? ".fractary/codex-sync-manifest.json";
3111
+ }
3112
+ /**
3113
+ * Load the sync manifest
3114
+ */
3115
+ async loadManifest() {
3116
+ try {
3117
+ const content = await this.localStorage.readText(this.manifestPath);
3118
+ this.manifest = JSON.parse(content);
3119
+ return this.manifest;
3120
+ } catch {
3121
+ return null;
3122
+ }
3123
+ }
3124
+ /**
3125
+ * Save the sync manifest
3126
+ */
3127
+ async saveManifest(manifest) {
3128
+ this.manifest = manifest;
3129
+ await this.localStorage.write(this.manifestPath, JSON.stringify(manifest, null, 2));
3130
+ }
3131
+ /**
3132
+ * Get or create manifest
3133
+ */
3134
+ async getOrCreateManifest(org, project) {
3135
+ let manifest = await this.loadManifest();
3136
+ if (!manifest || manifest.org !== org || manifest.project !== project) {
3137
+ manifest = {
3138
+ version: 1,
3139
+ org,
3140
+ project,
3141
+ lastSync: 0,
3142
+ entries: {}
3143
+ };
3144
+ }
3145
+ return manifest;
3146
+ }
3147
+ /**
3148
+ * List local files
3149
+ */
3150
+ async listLocalFiles(directory) {
3151
+ const files = await this.localStorage.list(directory);
3152
+ const fileInfos = [];
3153
+ for (const file of files) {
3154
+ try {
3155
+ const content = await this.localStorage.readText(file);
3156
+ const buffer = Buffer.from(content);
3157
+ fileInfos.push({
3158
+ path: file,
3159
+ size: buffer.length,
3160
+ mtime: Date.now(),
3161
+ // Would need fs.stat for real mtime
3162
+ hash: calculateContentHash(buffer)
3163
+ });
3164
+ } catch {
3165
+ }
3166
+ }
3167
+ return fileInfos;
3168
+ }
3169
+ /**
3170
+ * Create a sync plan
3171
+ *
3172
+ * @param _org - Organization (reserved for future use)
3173
+ * @param _project - Project (reserved for future use)
3174
+ * @param sourceDir - Source directory to sync from
3175
+ * @param targetFiles - Files currently in the target
3176
+ * @param options - Sync options
3177
+ */
3178
+ async createPlan(_org, _project, sourceDir, targetFiles, options) {
3179
+ const sourceFiles = await this.listLocalFiles(sourceDir);
3180
+ const plan = createSyncPlan(
3181
+ sourceFiles,
3182
+ targetFiles,
3183
+ options ?? {},
3184
+ this.config
3185
+ );
3186
+ plan.estimatedTime = estimateSyncTime(plan);
3187
+ return plan;
3188
+ }
3189
+ /**
3190
+ * Execute a sync plan (dry run)
3191
+ *
3192
+ * In a real implementation, this would actually copy files.
3193
+ * For the SDK, we provide the plan and let consumers execute.
3194
+ */
3195
+ async executePlan(plan, options) {
3196
+ const startTime = Date.now();
3197
+ const errors = [];
3198
+ let synced = 0;
3199
+ let failed = 0;
3200
+ if (options?.dryRun) {
3201
+ return {
3202
+ success: true,
3203
+ plan,
3204
+ synced: plan.totalFiles,
3205
+ failed: 0,
3206
+ skipped: plan.skipped.length,
3207
+ errors: [],
3208
+ duration: Date.now() - startTime,
3209
+ timestamp: Date.now()
3210
+ };
3211
+ }
3212
+ for (let i = 0; i < plan.files.length; i++) {
3213
+ const file = plan.files[i];
3214
+ if (!file) continue;
3215
+ try {
3216
+ if (options?.onProgress) {
3217
+ options.onProgress(i + 1, plan.totalFiles, file.path);
3218
+ }
3219
+ synced++;
3220
+ } catch (error) {
3221
+ failed++;
3222
+ errors.push({
3223
+ path: file.path,
3224
+ error: error instanceof Error ? error.message : String(error)
3225
+ });
3226
+ }
3227
+ }
3228
+ return {
3229
+ success: failed === 0,
3230
+ plan,
3231
+ synced,
3232
+ failed,
3233
+ skipped: plan.skipped.length,
3234
+ errors,
3235
+ duration: Date.now() - startTime,
3236
+ timestamp: Date.now()
3237
+ };
3238
+ }
3239
+ /**
3240
+ * Update manifest after sync
3241
+ */
3242
+ async updateManifest(org, project, syncedFiles) {
3243
+ const manifest = await this.getOrCreateManifest(org, project);
3244
+ const now = Date.now();
3245
+ for (const file of syncedFiles) {
3246
+ if (file.operation === "delete") {
3247
+ delete manifest.entries[file.path];
3248
+ } else if (file.operation === "create" || file.operation === "update") {
3249
+ manifest.entries[file.path] = {
3250
+ path: file.path,
3251
+ hash: file.hash ?? "",
3252
+ size: file.size ?? 0,
3253
+ syncedAt: now,
3254
+ source: "local"
3255
+ };
3256
+ }
3257
+ }
3258
+ manifest.lastSync = now;
3259
+ await this.saveManifest(manifest);
3260
+ }
3261
+ /**
3262
+ * Get sync status for a file
3263
+ */
3264
+ async getFileStatus(path5) {
3265
+ const manifest = await this.loadManifest();
3266
+ return manifest?.entries[path5] ?? null;
3267
+ }
3268
+ /**
3269
+ * Check if a file is synced
3270
+ */
3271
+ async isFileSynced(path5) {
3272
+ const status = await this.getFileStatus(path5);
3273
+ return status !== null;
3274
+ }
3275
+ /**
3276
+ * Get last sync timestamp
3277
+ */
3278
+ async getLastSyncTime() {
3279
+ const manifest = await this.loadManifest();
3280
+ return manifest?.lastSync ?? null;
3281
+ }
3282
+ /**
3283
+ * Clear sync manifest
3284
+ */
3285
+ async clearManifest() {
3286
+ try {
3287
+ await this.localStorage.delete(this.manifestPath);
3288
+ this.manifest = null;
3289
+ } catch {
3290
+ }
3291
+ }
3292
+ /**
3293
+ * Get sync configuration
3294
+ */
3295
+ getConfig() {
3296
+ return { ...this.config };
3297
+ }
3298
+ /**
3299
+ * Update sync configuration
3300
+ */
3301
+ updateConfig(updates) {
3302
+ this.config = {
3303
+ ...this.config,
3304
+ ...updates
3305
+ };
3306
+ }
3307
+ };
3308
+ function createSyncManager(config) {
3309
+ return new SyncManager(config);
3310
+ }
3311
+
3312
+ // src/permissions/types.ts
3313
+ var DEFAULT_PERMISSION_CONFIG = {
3314
+ defaultLevel: "read",
3315
+ defaultAllow: true,
3316
+ rules: [],
3317
+ enforced: false
3318
+ };
3319
+ var PERMISSION_LEVEL_ORDER = ["none", "read", "write", "admin"];
3320
+ function levelGrants(granted, required) {
3321
+ const grantedIndex = PERMISSION_LEVEL_ORDER.indexOf(granted);
3322
+ const requiredIndex = PERMISSION_LEVEL_ORDER.indexOf(required);
3323
+ return grantedIndex >= requiredIndex;
3324
+ }
3325
+ function maxLevel(a, b) {
3326
+ const aIndex = PERMISSION_LEVEL_ORDER.indexOf(a);
3327
+ const bIndex = PERMISSION_LEVEL_ORDER.indexOf(b);
3328
+ return PERMISSION_LEVEL_ORDER[Math.max(aIndex, bIndex)] ?? "none";
3329
+ }
3330
+ function minLevel(a, b) {
3331
+ const aIndex = PERMISSION_LEVEL_ORDER.indexOf(a);
3332
+ const bIndex = PERMISSION_LEVEL_ORDER.indexOf(b);
3333
+ return PERMISSION_LEVEL_ORDER[Math.min(aIndex, bIndex)] ?? "none";
3334
+ }
3335
+ function ruleMatchesContext(rule, context) {
3336
+ if (rule.enabled === false) {
3337
+ return false;
3338
+ }
3339
+ switch (rule.scope) {
3340
+ case "org":
3341
+ if (rule.org && context.org !== rule.org) {
3342
+ return false;
3343
+ }
3344
+ break;
3345
+ case "project":
3346
+ if (rule.org && context.org !== rule.org) {
3347
+ return false;
3348
+ }
3349
+ if (rule.project && context.project !== rule.project) {
3350
+ return false;
3351
+ }
3352
+ break;
3353
+ }
3354
+ return true;
3355
+ }
3356
+ function ruleMatchesPath(rule, path5) {
3357
+ return micromatch2__default.default.isMatch(path5, rule.pattern);
3358
+ }
3359
+ function ruleMatchesAction(rule, action) {
3360
+ return rule.actions.includes(action);
3361
+ }
3362
+ function evaluatePermission(path5, action, context, config) {
3363
+ const sortedRules = [...config.rules].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
3364
+ for (const rule of sortedRules) {
3365
+ if (!ruleMatchesContext(rule, context)) {
3366
+ continue;
3367
+ }
3368
+ if (!ruleMatchesPath(rule, path5)) {
3369
+ continue;
3370
+ }
3371
+ if (!ruleMatchesAction(rule, action)) {
3372
+ continue;
3373
+ }
3374
+ return {
3375
+ allowed: rule.level !== "none",
3376
+ level: rule.level,
3377
+ matchedRule: rule,
3378
+ reason: `Matched rule: ${rule.description ?? rule.pattern}`
3379
+ };
3380
+ }
3381
+ return {
3382
+ allowed: config.defaultAllow,
3383
+ level: config.defaultLevel,
3384
+ reason: config.defaultAllow ? "Allowed by default" : "Denied by default"
3385
+ };
3386
+ }
3387
+ function isAllowed(path5, action, context, config) {
3388
+ const result = evaluatePermission(path5, action, context, config);
3389
+ return result.allowed;
3390
+ }
3391
+ function hasPermission(path5, action, requiredLevel, context, config) {
3392
+ const result = evaluatePermission(path5, action, context, config);
3393
+ return levelGrants(result.level, requiredLevel);
3394
+ }
3395
+ function evaluatePermissions(paths, action, context, config) {
3396
+ const results = /* @__PURE__ */ new Map();
3397
+ for (const path5 of paths) {
3398
+ results.set(path5, evaluatePermission(path5, action, context, config));
3399
+ }
3400
+ return results;
3401
+ }
3402
+ function filterByPermission(paths, action, context, config, requiredLevel = "read") {
3403
+ return paths.filter((path5) => {
3404
+ const result = evaluatePermission(path5, action, context, config);
3405
+ return levelGrants(result.level, requiredLevel);
3406
+ });
3407
+ }
3408
+ function validateRules2(rules) {
3409
+ const errors = [];
3410
+ for (let i = 0; i < rules.length; i++) {
3411
+ const rule = rules[i];
3412
+ if (!rule) continue;
3413
+ if (!rule.pattern || rule.pattern.trim() === "") {
3414
+ errors.push(`Rule ${i}: pattern is empty`);
3415
+ continue;
3416
+ }
3417
+ try {
3418
+ micromatch2__default.default.isMatch("test", rule.pattern);
3419
+ } catch {
3420
+ errors.push(`Rule ${i}: invalid pattern "${rule.pattern}"`);
3421
+ }
3422
+ if (!rule.actions || rule.actions.length === 0) {
3423
+ errors.push(`Rule ${i}: actions are empty`);
3424
+ }
3425
+ if (rule.scope === "org" && !rule.org) {
3426
+ errors.push(`Rule ${i}: org scope requires org field`);
3427
+ }
3428
+ if (rule.scope === "project" && !rule.project) {
3429
+ errors.push(`Rule ${i}: project scope requires project field`);
3430
+ }
3431
+ }
3432
+ return errors;
3433
+ }
3434
+ function createRule(options) {
3435
+ return {
3436
+ pattern: options.pattern,
3437
+ actions: options.actions,
3438
+ level: options.level,
3439
+ scope: options.scope ?? "global",
3440
+ org: options.org,
3441
+ project: options.project,
3442
+ priority: options.priority ?? 0,
3443
+ description: options.description,
3444
+ enabled: true
3445
+ };
3446
+ }
3447
+ var CommonRules = {
3448
+ /** Allow all actions for docs */
3449
+ allowDocs: () => createRule({
3450
+ pattern: "docs/**",
3451
+ actions: ["fetch", "cache", "sync"],
3452
+ level: "read",
3453
+ description: "Allow read access to docs"
3454
+ }),
3455
+ /** Deny access to private files */
3456
+ denyPrivate: () => createRule({
3457
+ pattern: "**/.private/**",
3458
+ actions: ["fetch", "cache", "sync"],
3459
+ level: "none",
3460
+ priority: 100,
3461
+ description: "Deny access to private files"
3462
+ }),
3463
+ /** Read-only access to specs */
3464
+ readOnlySpecs: () => createRule({
3465
+ pattern: "specs/**",
3466
+ actions: ["fetch", "cache"],
3467
+ level: "read",
3468
+ description: "Read-only access to specs"
3469
+ }),
3470
+ /** Admin access for management */
3471
+ adminManage: () => createRule({
3472
+ pattern: "**",
3473
+ actions: ["manage", "invalidate"],
3474
+ level: "admin",
3475
+ description: "Admin access for management operations"
3476
+ })
3477
+ };
3478
+
3479
+ // src/permissions/manager.ts
3480
+ var PermissionManager = class {
3481
+ config;
3482
+ defaultContext;
3483
+ constructor(options = {}) {
3484
+ this.config = {
3485
+ ...DEFAULT_PERMISSION_CONFIG,
3486
+ ...options.config,
3487
+ // Create a new rules array to avoid shared state
3488
+ rules: options.config?.rules ? [...options.config.rules] : []
3489
+ };
3490
+ this.defaultContext = options.defaultContext ?? {};
3491
+ }
3492
+ /**
3493
+ * Check if an action is allowed for a path
3494
+ */
3495
+ isAllowed(path5, action, context) {
3496
+ const result = this.evaluate(path5, action, context);
3497
+ return result.allowed;
3498
+ }
3499
+ /**
3500
+ * Check if a permission level is granted
3501
+ */
3502
+ hasPermission(path5, action, requiredLevel, context) {
3503
+ const result = this.evaluate(path5, action, context);
3504
+ return levelGrants(result.level, requiredLevel);
3505
+ }
3506
+ /**
3507
+ * Evaluate permission for a path and action
3508
+ */
3509
+ evaluate(path5, action, context) {
3510
+ const mergedContext = { ...this.defaultContext, ...context };
3511
+ if (!this.config.enforced) {
3512
+ return {
3513
+ allowed: true,
3514
+ level: "admin",
3515
+ reason: "Permissions not enforced"
3516
+ };
3517
+ }
3518
+ return evaluatePermission(path5, action, mergedContext, this.config);
3519
+ }
3520
+ /**
3521
+ * Filter paths by permission
3522
+ */
3523
+ filterAllowed(paths, action, context, requiredLevel = "read") {
3524
+ const mergedContext = { ...this.defaultContext, ...context };
3525
+ if (!this.config.enforced) {
3526
+ return paths;
3527
+ }
3528
+ return filterByPermission(paths, action, mergedContext, this.config, requiredLevel);
3529
+ }
3530
+ /**
3531
+ * Add a permission rule
3532
+ */
3533
+ addRule(rule) {
3534
+ const errors = validateRules2([rule]);
3535
+ if (errors.length > 0) {
3536
+ throw new Error(`Invalid rule: ${errors.join(", ")}`);
3537
+ }
3538
+ this.config.rules.push(rule);
3539
+ }
3540
+ /**
3541
+ * Remove a permission rule by ID
3542
+ */
3543
+ removeRule(id) {
3544
+ const index = this.config.rules.findIndex((r) => r.id === id);
3545
+ if (index === -1) {
3546
+ return false;
3547
+ }
3548
+ this.config.rules.splice(index, 1);
3549
+ return true;
3550
+ }
3551
+ /**
3552
+ * Get all rules
3553
+ */
3554
+ getRules() {
3555
+ return [...this.config.rules];
3556
+ }
3557
+ /**
3558
+ * Clear all rules
3559
+ */
3560
+ clearRules() {
3561
+ this.config.rules = [];
3562
+ }
3563
+ /**
3564
+ * Set rules (replace all)
3565
+ */
3566
+ setRules(rules) {
3567
+ const errors = validateRules2(rules);
3568
+ if (errors.length > 0) {
3569
+ throw new Error(`Invalid rules: ${errors.join(", ")}`);
3570
+ }
3571
+ this.config.rules = [...rules];
3572
+ }
3573
+ /**
3574
+ * Get configuration
3575
+ */
3576
+ getConfig() {
3577
+ return { ...this.config, rules: [...this.config.rules] };
3578
+ }
3579
+ /**
3580
+ * Update configuration
3581
+ */
3582
+ updateConfig(updates) {
3583
+ this.config = {
3584
+ ...this.config,
3585
+ ...updates
3586
+ };
3587
+ }
3588
+ /**
3589
+ * Set default context
3590
+ */
3591
+ setDefaultContext(context) {
3592
+ this.defaultContext = { ...context };
3593
+ }
3594
+ /**
3595
+ * Get default context
3596
+ */
3597
+ getDefaultContext() {
3598
+ return { ...this.defaultContext };
3599
+ }
3600
+ /**
3601
+ * Enable permission enforcement
3602
+ */
3603
+ enable() {
3604
+ this.config.enforced = true;
3605
+ }
3606
+ /**
3607
+ * Disable permission enforcement
3608
+ */
3609
+ disable() {
3610
+ this.config.enforced = false;
3611
+ }
3612
+ /**
3613
+ * Check if permissions are enforced
3614
+ */
3615
+ isEnforced() {
3616
+ return this.config.enforced;
3617
+ }
3618
+ /**
3619
+ * Assert permission (throws if denied)
3620
+ */
3621
+ assertPermission(path5, action, requiredLevel = "read", context) {
3622
+ const result = this.evaluate(path5, action, context);
3623
+ if (!levelGrants(result.level, requiredLevel)) {
3624
+ throw new PermissionDeniedError(
3625
+ `Permission denied for ${action} on ${path5}: ${result.reason}`,
3626
+ path5,
3627
+ action,
3628
+ result
3629
+ );
3630
+ }
3631
+ }
3632
+ };
3633
+ var PermissionDeniedError = class extends Error {
3634
+ constructor(message, path5, action, result) {
3635
+ super(message);
3636
+ this.path = path5;
3637
+ this.action = action;
3638
+ this.result = result;
3639
+ this.name = "PermissionDeniedError";
3640
+ }
3641
+ };
3642
+ function createPermissionManager(config) {
3643
+ return new PermissionManager(config);
3644
+ }
3645
+ var defaultManager2 = null;
3646
+ function getDefaultPermissionManager() {
3647
+ if (!defaultManager2) {
3648
+ defaultManager2 = createPermissionManager();
3649
+ }
3650
+ return defaultManager2;
3651
+ }
3652
+ function setDefaultPermissionManager(manager) {
3653
+ defaultManager2 = manager;
3654
+ }
3655
+
3656
+ // src/migration/detector.ts
3657
+ function detectVersion(config) {
3658
+ if (!config || typeof config !== "object") {
3659
+ return {
3660
+ version: "unknown",
3661
+ confidence: "high",
3662
+ reason: "Invalid configuration object"
3663
+ };
3664
+ }
3665
+ const obj = config;
3666
+ if (obj.version === "3.0") {
3667
+ return {
3668
+ version: "3.0",
3669
+ confidence: "high",
3670
+ reason: "Explicit version field found"
3671
+ };
3672
+ }
3673
+ if (isModernConfig(config)) {
3674
+ return {
3675
+ version: "3.0",
3676
+ confidence: "medium",
3677
+ reason: "Modern structure detected (organization object, sync.patterns)"
3678
+ };
3679
+ }
3680
+ if (isLegacyConfig(config)) {
3681
+ return {
3682
+ version: "2.x",
3683
+ confidence: "high",
3684
+ reason: "Legacy structure detected (org/defaultOrg/codexRepo at top level)"
3685
+ };
3686
+ }
3687
+ const knownFields = [
3688
+ "org",
3689
+ "defaultOrg",
3690
+ "codexRepo",
3691
+ "autoSync",
3692
+ "syncRules",
3693
+ "organization",
3694
+ "sync",
3695
+ "cache"
3696
+ ];
3697
+ const hasKnownFields = knownFields.some((field) => field in obj);
3698
+ if (hasKnownFields) {
3699
+ return {
3700
+ version: "2.x",
3701
+ confidence: "low",
3702
+ reason: "Some known fields found, assuming v2.x"
3703
+ };
3704
+ }
3705
+ return {
3706
+ version: "unknown",
3707
+ confidence: "high",
3708
+ reason: "No recognizable configuration structure"
3709
+ };
3710
+ }
3711
+ function isModernConfig(config) {
3712
+ if (!config || typeof config !== "object") {
3713
+ return false;
3714
+ }
3715
+ const obj = config;
3716
+ if (!obj.organization || typeof obj.organization !== "object") {
3717
+ return false;
3718
+ }
3719
+ const org = obj.organization;
3720
+ if (typeof org.name !== "string" || typeof org.codexRepo !== "string") {
3721
+ return false;
3722
+ }
3723
+ if (!obj.sync || typeof obj.sync !== "object") {
3724
+ return false;
3725
+ }
3726
+ const sync = obj.sync;
3727
+ if (!Array.isArray(sync.patterns)) {
3728
+ return false;
3729
+ }
3730
+ return true;
3731
+ }
3732
+ function isLegacyConfig(config) {
3733
+ if (!config || typeof config !== "object") {
3734
+ return false;
3735
+ }
3736
+ const obj = config;
3737
+ const legacyMarkers = ["org", "defaultOrg", "codexRepo", "codexPath"];
3738
+ const hasLegacyMarker = legacyMarkers.some(
3739
+ (marker) => marker in obj && typeof obj[marker] === "string"
3740
+ );
3741
+ if (hasLegacyMarker) {
3742
+ return true;
3743
+ }
3744
+ if (Array.isArray(obj.autoSync)) {
3745
+ return true;
3746
+ }
3747
+ if (obj.syncRules && typeof obj.syncRules === "object") {
3748
+ const syncRules = obj.syncRules;
3749
+ if (Array.isArray(syncRules.include) || Array.isArray(syncRules.exclude)) {
3750
+ return true;
3751
+ }
3752
+ }
3753
+ return false;
3754
+ }
3755
+ function needsMigration(config) {
3756
+ const detection = detectVersion(config);
3757
+ return detection.version === "2.x";
3758
+ }
3759
+ function getMigrationRequirements(config) {
3760
+ const requirements = [];
3761
+ if (!config || typeof config !== "object") {
3762
+ requirements.push("Valid configuration object required");
3763
+ return requirements;
3764
+ }
3765
+ const obj = config;
3766
+ if (!obj.org && !obj.defaultOrg) {
3767
+ requirements.push("Organization name is required (org or defaultOrg)");
3768
+ }
3769
+ if (!obj.codexRepo && !obj.codexPath) {
3770
+ requirements.push("Codex repository name is required (codexRepo or codexPath)");
3771
+ }
3772
+ return requirements;
3773
+ }
3774
+
3775
+ // src/migration/migrator.ts
3776
+ var DEFAULT_MIGRATION_OPTIONS = {
3777
+ preserveUnknown: false,
3778
+ strict: false,
3779
+ defaultOrg: "organization",
3780
+ defaultCodexRepo: "codex"
3781
+ };
3782
+ function migrateConfig(config, options = {}) {
3783
+ const mergedOptions = { ...DEFAULT_MIGRATION_OPTIONS, ...options };
3784
+ const result = {
3785
+ success: false,
3786
+ warnings: [],
3787
+ errors: [],
3788
+ changes: []
3789
+ };
3790
+ const detection = detectVersion(config);
3791
+ if (detection.version === "3.0") {
3792
+ if (isModernConfig(config)) {
3793
+ result.success = true;
3794
+ result.config = config;
3795
+ result.warnings.push("Configuration is already v3.0, no migration needed");
3796
+ return result;
3797
+ }
3798
+ }
3799
+ if (detection.version === "unknown") {
3800
+ result.errors.push(`Cannot migrate: ${detection.reason}`);
3801
+ return result;
3802
+ }
3803
+ if (!isLegacyConfig(config)) {
3804
+ result.errors.push("Configuration does not match expected v2.x format");
3805
+ return result;
3806
+ }
3807
+ try {
3808
+ const migrated = performMigration(config, mergedOptions, result);
3809
+ result.config = migrated;
3810
+ result.success = true;
3811
+ } catch (error) {
3812
+ result.errors.push(
3813
+ `Migration failed: ${error instanceof Error ? error.message : String(error)}`
3814
+ );
3815
+ }
3816
+ return result;
3817
+ }
3818
+ function performMigration(legacy, options, result) {
3819
+ const orgName = legacy.org || legacy.defaultOrg || options.defaultOrg;
3820
+ if (legacy.org && legacy.defaultOrg && legacy.org !== legacy.defaultOrg) {
3821
+ result.warnings.push(
3822
+ `Both 'org' and 'defaultOrg' specified with different values. Using 'org': ${legacy.org}`
3823
+ );
3824
+ }
3825
+ if (!legacy.org && !legacy.defaultOrg) {
3826
+ result.changes.push({
3827
+ type: "added",
3828
+ path: "organization.name",
3829
+ newValue: orgName,
3830
+ description: `Added default organization name: ${orgName}`
3831
+ });
3832
+ } else {
3833
+ const fieldUsed = legacy.org ? "org" : "defaultOrg";
3834
+ result.changes.push({
3835
+ type: "renamed",
3836
+ path: "organization.name",
3837
+ oldValue: fieldUsed,
3838
+ newValue: "organization.name",
3839
+ description: `Renamed '${fieldUsed}' to 'organization.name'`
3840
+ });
3841
+ }
3842
+ const codexRepo = legacy.codexRepo || legacy.codexPath || options.defaultCodexRepo;
3843
+ if (legacy.codexRepo && legacy.codexPath && legacy.codexRepo !== legacy.codexPath) {
3844
+ result.warnings.push(
3845
+ `Both 'codexRepo' and 'codexPath' specified with different values. Using 'codexRepo': ${legacy.codexRepo}`
3846
+ );
3847
+ }
3848
+ if (!legacy.codexRepo && !legacy.codexPath) {
3849
+ result.changes.push({
3850
+ type: "added",
3851
+ path: "organization.codexRepo",
3852
+ newValue: codexRepo,
3853
+ description: `Added default codex repository: ${codexRepo}`
3854
+ });
3855
+ } else {
3856
+ const fieldUsed = legacy.codexRepo ? "codexRepo" : "codexPath";
3857
+ result.changes.push({
3858
+ type: "renamed",
3859
+ path: "organization.codexRepo",
3860
+ oldValue: fieldUsed,
3861
+ newValue: "organization.codexRepo",
3862
+ description: `Renamed '${fieldUsed}' to 'organization.codexRepo'`
3863
+ });
3864
+ }
3865
+ const patterns = [];
3866
+ if (legacy.autoSync && legacy.autoSync.length > 0) {
3867
+ for (const legacyPattern of legacy.autoSync) {
3868
+ const modernPattern = migrateSyncPattern(legacyPattern, result);
3869
+ patterns.push(modernPattern);
3870
+ }
3871
+ result.changes.push({
3872
+ type: "transformed",
3873
+ path: "sync.patterns",
3874
+ oldValue: "autoSync",
3875
+ newValue: "sync.patterns",
3876
+ description: `Converted ${legacy.autoSync.length} auto-sync pattern(s) to modern format`
3877
+ });
3878
+ }
3879
+ const include = [];
3880
+ const exclude = [];
3881
+ if (legacy.syncRules) {
3882
+ if (legacy.syncRules.include) {
3883
+ include.push(...legacy.syncRules.include);
3884
+ result.changes.push({
3885
+ type: "renamed",
3886
+ path: "sync.include",
3887
+ oldValue: "syncRules.include",
3888
+ newValue: "sync.include",
3889
+ description: "Moved syncRules.include to sync.include"
3890
+ });
3891
+ }
3892
+ if (legacy.syncRules.exclude) {
3893
+ exclude.push(...legacy.syncRules.exclude);
3894
+ result.changes.push({
3895
+ type: "renamed",
3896
+ path: "sync.exclude",
3897
+ oldValue: "syncRules.exclude",
3898
+ newValue: "sync.exclude",
3899
+ description: "Moved syncRules.exclude to sync.exclude"
3900
+ });
3901
+ }
3902
+ }
3903
+ const modern = {
3904
+ version: "3.0",
3905
+ organization: {
3906
+ name: orgName,
3907
+ codexRepo
3908
+ },
3909
+ sync: {
3910
+ patterns,
3911
+ include,
3912
+ exclude,
3913
+ defaultDirection: "to-codex"
3914
+ }
3915
+ };
3916
+ if (legacy.environments) {
3917
+ modern.environments = {};
3918
+ for (const [envName, envConfig] of Object.entries(legacy.environments)) {
3919
+ const envResult = {
3920
+ success: false,
3921
+ warnings: [],
3922
+ errors: [],
3923
+ changes: []
3924
+ };
3925
+ try {
3926
+ const migratedEnv = performMigration(
3927
+ { ...legacy, ...envConfig },
3928
+ { ...options },
3929
+ envResult
3930
+ );
3931
+ modern.environments[envName] = {
3932
+ organization: migratedEnv.organization,
3933
+ sync: migratedEnv.sync
3934
+ };
3935
+ result.changes.push({
3936
+ type: "transformed",
3937
+ path: `environments.${envName}`,
3938
+ description: `Migrated environment '${envName}'`
3939
+ });
3940
+ result.warnings.push(...envResult.warnings.map((w) => `[${envName}] ${w}`));
3941
+ } catch {
3942
+ result.warnings.push(`Failed to migrate environment '${envName}', skipping`);
3943
+ }
3944
+ }
3945
+ }
3946
+ result.changes.push({
3947
+ type: "added",
3948
+ path: "version",
3949
+ newValue: "3.0",
3950
+ description: "Added version field"
3951
+ });
3952
+ return modern;
3953
+ }
3954
+ function migrateSyncPattern(legacy, result) {
3955
+ const modern = {
3956
+ pattern: legacy.pattern
3957
+ };
3958
+ if (legacy.destination) {
3959
+ modern.target = legacy.destination;
3960
+ }
3961
+ if (legacy.bidirectional) {
3962
+ modern.direction = "bidirectional";
3963
+ }
3964
+ if (legacy.targets && legacy.targets.length > 0) {
3965
+ result.warnings.push(
3966
+ `Pattern '${legacy.pattern}' had multiple targets (${legacy.targets.join(", ")}). In v3.0, each target should be a separate pattern.`
3967
+ );
3968
+ if (!modern.target && legacy.targets[0]) {
3969
+ modern.target = legacy.targets[0];
3970
+ }
3971
+ }
3972
+ return modern;
3973
+ }
3974
+ function validateMigratedConfig(config) {
3975
+ const errors = [];
3976
+ if (config.version !== "3.0") {
3977
+ errors.push(`Invalid version: ${config.version}, expected '3.0'`);
3978
+ }
3979
+ if (!config.organization) {
3980
+ errors.push("Missing organization configuration");
3981
+ } else {
3982
+ if (!config.organization.name) {
3983
+ errors.push("Missing organization.name");
3984
+ }
3985
+ if (!config.organization.codexRepo) {
3986
+ errors.push("Missing organization.codexRepo");
3987
+ }
3988
+ }
3989
+ if (!config.sync) {
3990
+ errors.push("Missing sync configuration");
3991
+ } else {
3992
+ if (!Array.isArray(config.sync.patterns)) {
3993
+ errors.push("sync.patterns must be an array");
3994
+ }
3995
+ if (!Array.isArray(config.sync.include)) {
3996
+ errors.push("sync.include must be an array");
3997
+ }
3998
+ if (!Array.isArray(config.sync.exclude)) {
3999
+ errors.push("sync.exclude must be an array");
4000
+ }
4001
+ }
4002
+ return errors;
4003
+ }
4004
+ function generateMigrationReport(result) {
4005
+ const lines = ["# Migration Report", ""];
4006
+ if (result.success) {
4007
+ lines.push("Status: SUCCESS");
4008
+ } else {
4009
+ lines.push("Status: FAILED");
4010
+ }
4011
+ lines.push("");
4012
+ if (result.changes.length > 0) {
4013
+ lines.push("## Changes", "");
4014
+ for (const change of result.changes) {
4015
+ lines.push(`- [${change.type.toUpperCase()}] ${change.path}: ${change.description}`);
4016
+ }
4017
+ lines.push("");
4018
+ }
4019
+ if (result.warnings.length > 0) {
4020
+ lines.push("## Warnings", "");
4021
+ for (const warning of result.warnings) {
4022
+ lines.push(`- ${warning}`);
4023
+ }
4024
+ lines.push("");
4025
+ }
4026
+ if (result.errors.length > 0) {
4027
+ lines.push("## Errors", "");
4028
+ for (const error of result.errors) {
4029
+ lines.push(`- ${error}`);
4030
+ }
4031
+ lines.push("");
4032
+ }
4033
+ return lines.join("\n");
4034
+ }
4035
+ function createEmptyModernConfig(org, codexRepo) {
4036
+ return {
4037
+ version: "3.0",
4038
+ organization: {
4039
+ name: org,
4040
+ codexRepo
4041
+ },
4042
+ sync: {
4043
+ patterns: [],
4044
+ include: ["**/*.md", "CLAUDE.md"],
4045
+ exclude: ["**/node_modules/**", "**/.git/**"],
4046
+ defaultDirection: "to-codex"
4047
+ }
4048
+ };
4049
+ }
4050
+
4051
+ // src/migration/references.ts
4052
+ var LEGACY_PATTERNS = {
4053
+ /** @codex: style references */
4054
+ CODEX_TAG: /@codex:\s*([^\s\]]+)/g,
4055
+ /** [codex:...] style references */
4056
+ CODEX_BRACKET: /\[codex:\s*([^\]]+)\]/g,
4057
+ /** {{codex:...}} style references */
4058
+ CODEX_MUSTACHE: /\{\{codex:\s*([^}]+)\}\}/g,
4059
+ /** Simple path references in specific contexts */
4060
+ SIMPLE_PATH: /codex\/([a-zA-Z0-9_-]+\/[a-zA-Z0-9_\-/.]+)/g
4061
+ };
4062
+ function convertLegacyReferences(text, options = {}) {
4063
+ const result = {
4064
+ original: text,
4065
+ converted: text,
4066
+ references: [],
4067
+ modified: false
4068
+ };
4069
+ result.converted = processPattern(
4070
+ result.converted,
4071
+ LEGACY_PATTERNS.CODEX_TAG,
4072
+ "codex-tag",
4073
+ options,
4074
+ result.references
4075
+ );
4076
+ result.converted = processPattern(
4077
+ result.converted,
4078
+ LEGACY_PATTERNS.CODEX_BRACKET,
4079
+ "codex-bracket",
4080
+ options,
4081
+ result.references
4082
+ );
4083
+ result.converted = processPattern(
4084
+ result.converted,
4085
+ LEGACY_PATTERNS.CODEX_MUSTACHE,
4086
+ "codex-mustache",
4087
+ options,
4088
+ result.references
4089
+ );
4090
+ result.converted = processPattern(
4091
+ result.converted,
4092
+ LEGACY_PATTERNS.SIMPLE_PATH,
4093
+ "simple-path",
4094
+ options,
4095
+ result.references
4096
+ );
4097
+ result.modified = result.original !== result.converted;
4098
+ return result;
4099
+ }
4100
+ function processPattern(text, pattern, format, options, references) {
4101
+ pattern.lastIndex = 0;
4102
+ return text.replace(pattern, (match, captured, offset) => {
4103
+ const uri = convertToUri(captured.trim(), options);
4104
+ references.push({
4105
+ original: match,
4106
+ uri,
4107
+ position: offset,
4108
+ format
4109
+ });
4110
+ if (options.preserveWrapper) {
4111
+ switch (format) {
4112
+ case "codex-tag":
4113
+ return `@codex: ${uri}`;
4114
+ case "codex-bracket":
4115
+ return `[${uri}]`;
4116
+ case "codex-mustache":
4117
+ return `{{${uri}}}`;
4118
+ case "simple-path":
4119
+ return uri;
4120
+ }
4121
+ }
4122
+ return uri;
4123
+ });
4124
+ }
4125
+ function convertToUri(reference, options = {}) {
4126
+ const trimmed = reference.trim();
4127
+ if (trimmed.startsWith("codex://")) {
4128
+ return trimmed;
4129
+ }
4130
+ const parts = parseReference2(trimmed);
4131
+ const org = parts.org || options.defaultOrg || "_";
4132
+ const project = parts.project || options.defaultProject || "_";
4133
+ const path5 = parts.path;
4134
+ return `codex://${org}/${project}/${path5}`;
4135
+ }
4136
+ function parseReference2(reference) {
4137
+ const trimmed = reference.trim();
4138
+ if (trimmed.startsWith("./") || trimmed.startsWith("../")) {
4139
+ return { path: trimmed };
4140
+ }
4141
+ const parts = trimmed.split("/");
4142
+ if (parts.length >= 3) {
4143
+ const [org, project, ...rest] = parts;
4144
+ return {
4145
+ org: org || void 0,
4146
+ project: project || void 0,
4147
+ path: rest.join("/")
4148
+ };
4149
+ }
4150
+ if (parts.length === 2) {
4151
+ return { path: trimmed };
4152
+ }
4153
+ return { path: trimmed };
4154
+ }
4155
+ function findLegacyReferences(text) {
4156
+ const references = [];
4157
+ for (const [name, pattern] of Object.entries(LEGACY_PATTERNS)) {
4158
+ pattern.lastIndex = 0;
4159
+ let match;
4160
+ while ((match = pattern.exec(text)) !== null) {
4161
+ const format = name.toLowerCase().replace("_", "-");
4162
+ references.push({
4163
+ original: match[0],
4164
+ uri: match[1] || "",
4165
+ position: match.index,
4166
+ format
4167
+ });
4168
+ }
4169
+ }
4170
+ references.sort((a, b) => a.position - b.position);
4171
+ return references;
4172
+ }
4173
+ function hasLegacyReferences(text) {
4174
+ for (const pattern of Object.values(LEGACY_PATTERNS)) {
4175
+ pattern.lastIndex = 0;
4176
+ if (pattern.test(text)) {
4177
+ return true;
4178
+ }
4179
+ }
4180
+ return false;
4181
+ }
4182
+ function migrateFileReferences(content, options = {}) {
4183
+ return convertLegacyReferences(content, options);
4184
+ }
4185
+ function generateReferenceMigrationSummary(results) {
4186
+ const lines = ["# Reference Migration Summary", ""];
4187
+ let totalRefs = 0;
4188
+ let modifiedFiles = 0;
4189
+ for (const result of results) {
4190
+ if (result.modified) {
4191
+ modifiedFiles++;
4192
+ }
4193
+ totalRefs += result.references.length;
4194
+ }
4195
+ lines.push(`- Total files processed: ${results.length}`);
4196
+ lines.push(`- Files with changes: ${modifiedFiles}`);
4197
+ lines.push(`- Total references converted: ${totalRefs}`);
4198
+ lines.push("");
4199
+ const byFormat = /* @__PURE__ */ new Map();
4200
+ for (const result of results) {
4201
+ for (const ref of result.references) {
4202
+ byFormat.set(ref.format, (byFormat.get(ref.format) || 0) + 1);
4203
+ }
4204
+ }
4205
+ if (byFormat.size > 0) {
4206
+ lines.push("## References by Format", "");
4207
+ for (const [format, count] of byFormat.entries()) {
4208
+ lines.push(`- ${format}: ${count}`);
4209
+ }
4210
+ }
4211
+ return lines.join("\n");
4212
+ }
4213
+
392
4214
  exports.AutoSyncPatternSchema = AutoSyncPatternSchema;
4215
+ exports.BUILT_IN_TYPES = BUILT_IN_TYPES;
4216
+ exports.CODEX_TOOLS = CODEX_TOOLS;
4217
+ exports.CODEX_URI_PREFIX = CODEX_URI_PREFIX;
4218
+ exports.CacheManager = CacheManager;
4219
+ exports.CachePersistence = CachePersistence;
393
4220
  exports.CodexConfigSchema = CodexConfigSchema;
394
4221
  exports.CodexError = CodexError;
4222
+ exports.CommonRules = CommonRules;
395
4223
  exports.ConfigurationError = ConfigurationError;
4224
+ exports.CustomTypeSchema = CustomTypeSchema;
4225
+ exports.DEFAULT_CACHE_DIR = DEFAULT_CACHE_DIR;
4226
+ exports.DEFAULT_FETCH_OPTIONS = DEFAULT_FETCH_OPTIONS;
4227
+ exports.DEFAULT_MIGRATION_OPTIONS = DEFAULT_MIGRATION_OPTIONS;
4228
+ exports.DEFAULT_PERMISSION_CONFIG = DEFAULT_PERMISSION_CONFIG;
4229
+ exports.DEFAULT_SYNC_CONFIG = DEFAULT_SYNC_CONFIG;
4230
+ exports.DEFAULT_TYPE = DEFAULT_TYPE;
4231
+ exports.GitHubStorage = GitHubStorage;
4232
+ exports.HttpStorage = HttpStorage;
4233
+ exports.LEGACY_PATTERNS = LEGACY_PATTERNS;
4234
+ exports.LEGACY_REF_PREFIX = LEGACY_REF_PREFIX;
4235
+ exports.LocalStorage = LocalStorage;
4236
+ exports.McpServer = McpServer;
396
4237
  exports.MetadataSchema = MetadataSchema;
4238
+ exports.PERMISSION_LEVEL_ORDER = PERMISSION_LEVEL_ORDER;
4239
+ exports.PermissionDeniedError = PermissionDeniedError;
4240
+ exports.PermissionManager = PermissionManager;
4241
+ exports.StorageManager = StorageManager;
4242
+ exports.SyncManager = SyncManager;
397
4243
  exports.SyncRulesSchema = SyncRulesSchema;
4244
+ exports.TTL = TTL;
4245
+ exports.TypeRegistry = TypeRegistry;
4246
+ exports.TypesConfigSchema = TypesConfigSchema;
398
4247
  exports.ValidationError = ValidationError;
4248
+ exports.buildUri = buildUri;
4249
+ exports.calculateCachePath = calculateCachePath;
4250
+ exports.calculateContentHash = calculateContentHash;
4251
+ exports.convertLegacyReference = convertLegacyReference;
4252
+ exports.convertLegacyReferences = convertLegacyReferences;
4253
+ exports.convertToUri = convertToUri;
4254
+ exports.createCacheEntry = createCacheEntry;
4255
+ exports.createCacheManager = createCacheManager;
4256
+ exports.createCachePersistence = createCachePersistence;
4257
+ exports.createDefaultRegistry = createDefaultRegistry;
4258
+ exports.createEmptyModernConfig = createEmptyModernConfig;
4259
+ exports.createEmptySyncPlan = createEmptySyncPlan;
4260
+ exports.createGitHubStorage = createGitHubStorage;
4261
+ exports.createHttpStorage = createHttpStorage;
4262
+ exports.createLocalStorage = createLocalStorage;
4263
+ exports.createMcpServer = createMcpServer;
4264
+ exports.createPermissionManager = createPermissionManager;
4265
+ exports.createRule = createRule;
4266
+ exports.createRulesFromPatterns = createRulesFromPatterns;
4267
+ exports.createStorageManager = createStorageManager;
4268
+ exports.createSyncManager = createSyncManager;
4269
+ exports.createSyncPlan = createSyncPlan;
4270
+ exports.deserializeCacheEntry = deserializeCacheEntry;
4271
+ exports.detectContentType = detectContentType;
4272
+ exports.detectCurrentProject = detectCurrentProject;
4273
+ exports.detectVersion = detectVersion;
4274
+ exports.estimateSyncTime = estimateSyncTime;
4275
+ exports.evaluatePath = evaluatePath;
4276
+ exports.evaluatePaths = evaluatePaths;
399
4277
  exports.evaluatePatterns = evaluatePatterns;
4278
+ exports.evaluatePermission = evaluatePermission;
4279
+ exports.evaluatePermissions = evaluatePermissions;
4280
+ exports.extendType = extendType;
400
4281
  exports.extractOrgFromRepoName = extractOrgFromRepoName;
401
4282
  exports.extractRawFrontmatter = extractRawFrontmatter;
402
4283
  exports.filterByPatterns = filterByPatterns;
4284
+ exports.filterByPermission = filterByPermission;
4285
+ exports.filterPlanOperations = filterPlanOperations;
4286
+ exports.filterSyncablePaths = filterSyncablePaths;
4287
+ exports.findLegacyReferences = findLegacyReferences;
4288
+ exports.formatPlanSummary = formatPlanSummary;
4289
+ exports.generateMigrationReport = generateMigrationReport;
4290
+ exports.generateReferenceMigrationSummary = generateReferenceMigrationSummary;
4291
+ exports.getBuiltInType = getBuiltInType;
4292
+ exports.getBuiltInTypeNames = getBuiltInTypeNames;
4293
+ exports.getCacheEntryAge = getCacheEntryAge;
4294
+ exports.getCacheEntryStatus = getCacheEntryStatus;
4295
+ exports.getCurrentContext = getCurrentContext;
4296
+ exports.getCustomSyncDestinations = getCustomSyncDestinations;
4297
+ exports.getDefaultCacheManager = getDefaultCacheManager;
403
4298
  exports.getDefaultConfig = getDefaultConfig;
404
4299
  exports.getDefaultDirectories = getDefaultDirectories;
4300
+ exports.getDefaultPermissionManager = getDefaultPermissionManager;
405
4301
  exports.getDefaultRules = getDefaultRules;
4302
+ exports.getDefaultStorageManager = getDefaultStorageManager;
4303
+ exports.getDirectory = getDirectory;
4304
+ exports.getExtension = getExtension;
4305
+ exports.getFilename = getFilename;
4306
+ exports.getMigrationRequirements = getMigrationRequirements;
4307
+ exports.getPlanStats = getPlanStats;
4308
+ exports.getRelativeCachePath = getRelativeCachePath;
4309
+ exports.getRemainingTtl = getRemainingTtl;
406
4310
  exports.getTargetRepos = getTargetRepos;
4311
+ exports.handleFetch = handleFetch;
4312
+ exports.handleInvalidate = handleInvalidate;
4313
+ exports.handleList = handleList;
4314
+ exports.handleSearch = handleSearch;
4315
+ exports.handleToolCall = handleToolCall;
4316
+ exports.hasContentChanged = hasContentChanged;
407
4317
  exports.hasFrontmatter = hasFrontmatter;
4318
+ exports.hasLegacyReferences = hasLegacyReferences;
4319
+ exports.hasPermissionLevel = hasPermission;
4320
+ exports.isBuiltInType = isBuiltInType;
4321
+ exports.isCacheEntryFresh = isCacheEntryFresh;
4322
+ exports.isCacheEntryValid = isCacheEntryValid;
4323
+ exports.isCurrentProjectUri = isCurrentProjectUri;
4324
+ exports.isLegacyConfig = isLegacyConfig;
4325
+ exports.isLegacyReference = isLegacyReference;
4326
+ exports.isModernConfig = isModernConfig;
4327
+ exports.isPermissionAllowed = isAllowed;
4328
+ exports.isValidUri = isValidUri;
4329
+ exports.levelGrants = levelGrants;
408
4330
  exports.loadConfig = loadConfig;
4331
+ exports.loadCustomTypes = loadCustomTypes;
409
4332
  exports.matchAnyPattern = matchAnyPattern;
410
4333
  exports.matchPattern = matchPattern;
4334
+ exports.maxLevel = maxLevel;
4335
+ exports.mergeFetchOptions = mergeFetchOptions;
4336
+ exports.mergeRules = mergeRules;
4337
+ exports.mergeTypes = mergeTypes;
4338
+ exports.migrateConfig = migrateConfig;
4339
+ exports.migrateFileReferences = migrateFileReferences;
4340
+ exports.minLevel = minLevel;
4341
+ exports.needsMigration = needsMigration;
4342
+ exports.parseCustomDestination = parseCustomDestination;
411
4343
  exports.parseMetadata = parseMetadata;
4344
+ exports.parseReference = parseReference;
4345
+ exports.parseTtl = parseTtl;
412
4346
  exports.resolveOrganization = resolveOrganization;
4347
+ exports.resolveReference = resolveReference;
4348
+ exports.resolveReferences = resolveReferences;
4349
+ exports.ruleMatchesAction = ruleMatchesAction;
4350
+ exports.ruleMatchesContext = ruleMatchesContext;
4351
+ exports.ruleMatchesPath = ruleMatchesPath;
4352
+ exports.sanitizePath = sanitizePath;
4353
+ exports.serializeCacheEntry = serializeCacheEntry;
4354
+ exports.setDefaultCacheManager = setDefaultCacheManager;
4355
+ exports.setDefaultPermissionManager = setDefaultPermissionManager;
4356
+ exports.setDefaultStorageManager = setDefaultStorageManager;
413
4357
  exports.shouldSyncToRepo = shouldSyncToRepo;
4358
+ exports.summarizeEvaluations = summarizeEvaluations;
4359
+ exports.touchCacheEntry = touchCacheEntry;
4360
+ exports.validateCustomTypes = validateCustomTypes;
414
4361
  exports.validateMetadata = validateMetadata;
4362
+ exports.validateMigratedConfig = validateMigratedConfig;
4363
+ exports.validateOrg = validateOrg;
4364
+ exports.validatePath = validatePath;
4365
+ exports.validatePermissionRules = validateRules2;
4366
+ exports.validateProject = validateProject;
4367
+ exports.validateRules = validateRules;
4368
+ exports.validateUri = validateUri;
415
4369
  //# sourceMappingURL=index.cjs.map
416
4370
  //# sourceMappingURL=index.cjs.map