@fractary/codex 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import micromatch3 from 'micromatch';
2
- import path3 from 'path';
3
- import { execSync } from 'child_process';
2
+ import * as path3 from 'path';
3
+ import path3__default from 'path';
4
+ import { execFile, execSync } from 'child_process';
4
5
  import { z } from 'zod';
5
6
  import yaml from 'js-yaml';
6
- import fs2 from 'fs/promises';
7
+ import * as fs3 from 'fs/promises';
8
+ import fs3__default from 'fs/promises';
9
+ import { promisify } from 'util';
7
10
 
8
11
  var __defProp = Object.defineProperty;
9
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -160,7 +163,7 @@ function validatePath(filePath) {
160
163
  return false;
161
164
  }
162
165
  }
163
- const normalized = path3.normalize(filePath);
166
+ const normalized = path3__default.normalize(filePath);
164
167
  if (normalized.startsWith("..") || normalized.includes("/../") || normalized.includes("\\..\\")) {
165
168
  return false;
166
169
  }
@@ -178,7 +181,7 @@ function sanitizePath(filePath) {
178
181
  }
179
182
  sanitized = sanitized.replace(/^\/+/, "");
180
183
  sanitized = sanitized.replace(/^~\//, "");
181
- sanitized = path3.normalize(sanitized);
184
+ sanitized = path3__default.normalize(sanitized);
182
185
  sanitized = sanitized.replace(/\.\.\//g, "").replace(/\.\./g, "");
183
186
  sanitized = sanitized.replace(/^\.\//, "");
184
187
  sanitized = sanitized.replace(/\0/g, "");
@@ -279,10 +282,10 @@ function parseReference(uri, options = {}) {
279
282
  path: filePath
280
283
  };
281
284
  }
282
- function buildUri(org, project, path6) {
285
+ function buildUri(org, project, path7) {
283
286
  const base = `${CODEX_URI_PREFIX}${org}/${project}`;
284
- if (path6) {
285
- const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
287
+ if (path7) {
288
+ const cleanPath = path7.startsWith("/") ? path7.slice(1) : path7;
286
289
  return `${base}/${cleanPath}`;
287
290
  }
288
291
  return base;
@@ -353,7 +356,7 @@ function getCurrentContext(options = {}) {
353
356
  return detectCurrentProject(options.cwd);
354
357
  }
355
358
  function calculateCachePath(org, project, filePath, cacheDir) {
356
- return path3.join(cacheDir, org, project, filePath);
359
+ return path3__default.join(cacheDir, org, project, filePath);
357
360
  }
358
361
  function resolveReference(uri, options = {}) {
359
362
  const parsed = parseReference(uri);
@@ -362,7 +365,7 @@ function resolveReference(uri, options = {}) {
362
365
  }
363
366
  const cacheDir = options.cacheDir || DEFAULT_CACHE_DIR;
364
367
  const currentContext = getCurrentContext(options);
365
- const cachePath = parsed.path ? calculateCachePath(parsed.org, parsed.project, parsed.path, cacheDir) : path3.join(cacheDir, parsed.org, parsed.project);
368
+ const cachePath = parsed.path ? calculateCachePath(parsed.org, parsed.project, parsed.path, cacheDir) : path3__default.join(cacheDir, parsed.org, parsed.project);
366
369
  const isCurrentProject = currentContext.org === parsed.org && currentContext.project === parsed.project;
367
370
  const resolved = {
368
371
  ...parsed,
@@ -371,9 +374,45 @@ function resolveReference(uri, options = {}) {
371
374
  };
372
375
  if (isCurrentProject && parsed.path) {
373
376
  resolved.localPath = parsed.path;
377
+ if (options.config) {
378
+ const fileSource = detectFilePluginSource(parsed.path, options.config);
379
+ if (fileSource) {
380
+ resolved.sourceType = "file-plugin";
381
+ resolved.filePluginSource = fileSource.name;
382
+ resolved.localPath = fileSource.fullPath;
383
+ }
384
+ }
374
385
  }
375
386
  return resolved;
376
387
  }
388
+ function detectFilePluginSource(filePath, config) {
389
+ if (!config?.file?.sources) {
390
+ return null;
391
+ }
392
+ for (const [sourceName, source] of Object.entries(config.file.sources)) {
393
+ const basePath = source.local?.base_path;
394
+ if (!basePath) {
395
+ continue;
396
+ }
397
+ const normalizedPath = filePath.replace(/^\.\//, "").replace(/^\//, "");
398
+ const normalizedBasePath = basePath.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "");
399
+ if (normalizedPath.startsWith(normalizedBasePath)) {
400
+ return {
401
+ name: sourceName,
402
+ fullPath: path3__default.join(basePath, normalizedPath.substring(normalizedBasePath.length).replace(/^\//, ""))
403
+ };
404
+ }
405
+ const sourceNameInPath = normalizedBasePath.split("/").pop();
406
+ if (sourceNameInPath && normalizedPath.startsWith(sourceNameInPath + "/")) {
407
+ const pathWithoutSource = normalizedPath.substring(sourceNameInPath.length + 1);
408
+ return {
409
+ name: sourceName,
410
+ fullPath: path3__default.join(basePath, pathWithoutSource)
411
+ };
412
+ }
413
+ }
414
+ return null;
415
+ }
377
416
  function resolveReferences(uris, options = {}) {
378
417
  return uris.map((uri) => resolveReference(uri, options)).filter((r) => r !== null);
379
418
  }
@@ -387,9 +426,9 @@ function getRelativeCachePath(uri) {
387
426
  return null;
388
427
  }
389
428
  if (parsed.path) {
390
- return path3.join(parsed.org, parsed.project, parsed.path);
429
+ return path3__default.join(parsed.org, parsed.project, parsed.path);
391
430
  }
392
- return path3.join(parsed.org, parsed.project);
431
+ return path3__default.join(parsed.org, parsed.project);
393
432
  }
394
433
 
395
434
  // src/types/built-in.ts
@@ -834,6 +873,16 @@ var DirectionalSyncSchema = z.object({
834
873
  default_to_codex: z.array(z.string()).optional(),
835
874
  default_from_codex: z.array(z.string()).optional()
836
875
  });
876
+ var ArchiveProjectConfigSchema = z.object({
877
+ enabled: z.boolean(),
878
+ handler: z.enum(["s3", "r2", "gcs", "local"]),
879
+ bucket: z.string().optional(),
880
+ prefix: z.string().optional(),
881
+ patterns: z.array(z.string()).optional()
882
+ });
883
+ var ArchiveConfigSchema = z.object({
884
+ projects: z.record(ArchiveProjectConfigSchema)
885
+ });
837
886
  var CodexConfigSchema = z.object({
838
887
  organizationSlug: z.string(),
839
888
  directories: z.object({
@@ -843,8 +892,45 @@ var CodexConfigSchema = z.object({
843
892
  }).optional(),
844
893
  rules: SyncRulesSchema.optional(),
845
894
  // Directional sync configuration
846
- sync: DirectionalSyncSchema.optional()
895
+ sync: DirectionalSyncSchema.optional(),
896
+ // Archive configuration
897
+ archive: ArchiveConfigSchema.optional()
847
898
  }).strict();
899
+ var FileSourceSchema = z.object({
900
+ type: z.enum(["s3", "r2", "gcs", "local"]),
901
+ bucket: z.string().optional(),
902
+ prefix: z.string().optional(),
903
+ region: z.string().optional(),
904
+ local: z.object({
905
+ base_path: z.string()
906
+ }),
907
+ push: z.object({
908
+ compress: z.boolean().optional(),
909
+ keep_local: z.boolean().optional()
910
+ }).optional(),
911
+ auth: z.object({
912
+ profile: z.string().optional()
913
+ }).optional()
914
+ }).refine(
915
+ (data) => {
916
+ if (data.type !== "local" && !data.bucket) {
917
+ return false;
918
+ }
919
+ return true;
920
+ },
921
+ {
922
+ message: "Bucket is required for s3, r2, and gcs storage types",
923
+ path: ["bucket"]
924
+ }
925
+ );
926
+ var FileConfigSchema = z.object({
927
+ schema_version: z.string(),
928
+ sources: z.record(FileSourceSchema)
929
+ });
930
+ z.object({
931
+ file: FileConfigSchema.optional(),
932
+ codex: CodexConfigSchema.optional()
933
+ });
848
934
  function parseMetadata(content, options = {}) {
849
935
  const { strict = true, normalize = true } = options;
850
936
  const normalizedContent = normalize ? content.replace(/\r\n/g, "\n") : content;
@@ -968,11 +1054,17 @@ function getDefaultRules() {
968
1054
  defaultExclude: []
969
1055
  };
970
1056
  }
1057
+ function getDefaultArchiveConfig() {
1058
+ return {
1059
+ projects: {}
1060
+ };
1061
+ }
971
1062
  function getDefaultConfig(orgSlug) {
972
1063
  return {
973
1064
  organizationSlug: orgSlug,
974
1065
  directories: getDefaultDirectories(orgSlug),
975
- rules: getDefaultRules()
1066
+ rules: getDefaultRules(),
1067
+ archive: getDefaultArchiveConfig()
976
1068
  };
977
1069
  }
978
1070
 
@@ -1168,18 +1260,18 @@ function parseCustomDestination(value) {
1168
1260
  );
1169
1261
  }
1170
1262
  const repo = value.substring(0, colonIndex).trim();
1171
- const path6 = value.substring(colonIndex + 1).trim();
1263
+ const path7 = value.substring(colonIndex + 1).trim();
1172
1264
  if (!repo) {
1173
1265
  throw new ValidationError(
1174
1266
  `Invalid custom destination: repository name cannot be empty in "${value}"`
1175
1267
  );
1176
1268
  }
1177
- if (!path6) {
1269
+ if (!path7) {
1178
1270
  throw new ValidationError(
1179
1271
  `Invalid custom destination: path cannot be empty in "${value}"`
1180
1272
  );
1181
1273
  }
1182
- return { repo, path: path6 };
1274
+ return { repo, path: path7 };
1183
1275
  }
1184
1276
  function getCustomSyncDestinations(metadata) {
1185
1277
  const customDestinations = metadata.codex_sync_custom;
@@ -1215,8 +1307,8 @@ function mergeFetchOptions(options) {
1215
1307
  ...options
1216
1308
  };
1217
1309
  }
1218
- function detectContentType(path6) {
1219
- const ext = path6.split(".").pop()?.toLowerCase();
1310
+ function detectContentType(path7) {
1311
+ const ext = path7.split(".").pop()?.toLowerCase();
1220
1312
  const mimeTypes = {
1221
1313
  md: "text/markdown",
1222
1314
  markdown: "text/markdown",
@@ -1276,19 +1368,19 @@ var LocalStorage = class {
1276
1368
  if (!reference.localPath) {
1277
1369
  throw new Error(`No local path for reference: ${reference.uri}`);
1278
1370
  }
1279
- const fullPath = path3.isAbsolute(reference.localPath) ? reference.localPath : path3.join(this.baseDir, reference.localPath);
1371
+ const fullPath = path3__default.isAbsolute(reference.localPath) ? reference.localPath : path3__default.join(this.baseDir, reference.localPath);
1280
1372
  try {
1281
- await fs2.access(fullPath);
1373
+ await fs3__default.access(fullPath);
1282
1374
  } catch {
1283
1375
  throw new Error(`File not found: ${fullPath}`);
1284
1376
  }
1285
- const stats = await fs2.stat(fullPath);
1377
+ const stats = await fs3__default.stat(fullPath);
1286
1378
  if (stats.size > opts.maxSize) {
1287
1379
  throw new Error(
1288
1380
  `File too large: ${stats.size} bytes (max: ${opts.maxSize} bytes)`
1289
1381
  );
1290
1382
  }
1291
- const content = await fs2.readFile(fullPath);
1383
+ const content = await fs3__default.readFile(fullPath);
1292
1384
  return {
1293
1385
  content,
1294
1386
  contentType: detectContentType(reference.localPath),
@@ -1307,9 +1399,9 @@ var LocalStorage = class {
1307
1399
  if (!reference.localPath) {
1308
1400
  return false;
1309
1401
  }
1310
- const fullPath = path3.isAbsolute(reference.localPath) ? reference.localPath : path3.join(this.baseDir, reference.localPath);
1402
+ const fullPath = path3__default.isAbsolute(reference.localPath) ? reference.localPath : path3__default.join(this.baseDir, reference.localPath);
1311
1403
  try {
1312
- await fs2.access(fullPath);
1404
+ await fs3__default.access(fullPath);
1313
1405
  return true;
1314
1406
  } catch {
1315
1407
  return false;
@@ -1319,24 +1411,24 @@ var LocalStorage = class {
1319
1411
  * Read file content as string
1320
1412
  */
1321
1413
  async readText(filePath) {
1322
- const fullPath = path3.isAbsolute(filePath) ? filePath : path3.join(this.baseDir, filePath);
1323
- return fs2.readFile(fullPath, "utf-8");
1414
+ const fullPath = path3__default.isAbsolute(filePath) ? filePath : path3__default.join(this.baseDir, filePath);
1415
+ return fs3__default.readFile(fullPath, "utf-8");
1324
1416
  }
1325
1417
  /**
1326
1418
  * Write content to file
1327
1419
  */
1328
1420
  async write(filePath, content) {
1329
- const fullPath = path3.isAbsolute(filePath) ? filePath : path3.join(this.baseDir, filePath);
1330
- await fs2.mkdir(path3.dirname(fullPath), { recursive: true });
1331
- await fs2.writeFile(fullPath, content);
1421
+ const fullPath = path3__default.isAbsolute(filePath) ? filePath : path3__default.join(this.baseDir, filePath);
1422
+ await fs3__default.mkdir(path3__default.dirname(fullPath), { recursive: true });
1423
+ await fs3__default.writeFile(fullPath, content);
1332
1424
  }
1333
1425
  /**
1334
1426
  * Delete a file
1335
1427
  */
1336
1428
  async delete(filePath) {
1337
- const fullPath = path3.isAbsolute(filePath) ? filePath : path3.join(this.baseDir, filePath);
1429
+ const fullPath = path3__default.isAbsolute(filePath) ? filePath : path3__default.join(this.baseDir, filePath);
1338
1430
  try {
1339
- await fs2.unlink(fullPath);
1431
+ await fs3__default.unlink(fullPath);
1340
1432
  return true;
1341
1433
  } catch {
1342
1434
  return false;
@@ -1346,10 +1438,10 @@ var LocalStorage = class {
1346
1438
  * List files in a directory
1347
1439
  */
1348
1440
  async list(dirPath) {
1349
- const fullPath = path3.isAbsolute(dirPath) ? dirPath : path3.join(this.baseDir, dirPath);
1441
+ const fullPath = path3__default.isAbsolute(dirPath) ? dirPath : path3__default.join(this.baseDir, dirPath);
1350
1442
  try {
1351
- const entries = await fs2.readdir(fullPath, { withFileTypes: true });
1352
- return entries.filter((e) => e.isFile()).map((e) => path3.join(dirPath, e.name));
1443
+ const entries = await fs3__default.readdir(fullPath, { withFileTypes: true });
1444
+ return entries.filter((e) => e.isFile()).map((e) => path3__default.join(dirPath, e.name));
1353
1445
  } catch {
1354
1446
  return [];
1355
1447
  }
@@ -1695,6 +1787,484 @@ function createHttpStorage(options) {
1695
1787
  return new HttpStorage(options);
1696
1788
  }
1697
1789
 
1790
+ // src/file-integration/source-resolver.ts
1791
+ var FileSourceResolver = class {
1792
+ constructor(config) {
1793
+ this.config = config;
1794
+ this.initializeSources();
1795
+ }
1796
+ sources = /* @__PURE__ */ new Map();
1797
+ /**
1798
+ * Initialize sources from config
1799
+ */
1800
+ initializeSources() {
1801
+ if (!this.config.file?.sources) {
1802
+ return;
1803
+ }
1804
+ for (const [name, sourceConfig] of Object.entries(this.config.file.sources)) {
1805
+ const resolved = {
1806
+ name,
1807
+ type: "file-plugin",
1808
+ localPath: sourceConfig.local.base_path,
1809
+ isCurrentProject: true,
1810
+ config: sourceConfig
1811
+ };
1812
+ if (sourceConfig.bucket && sourceConfig.type !== "local") {
1813
+ resolved.bucket = sourceConfig.bucket;
1814
+ resolved.prefix = sourceConfig.prefix;
1815
+ resolved.remotePath = this.buildRemotePath(sourceConfig);
1816
+ }
1817
+ this.sources.set(name, resolved);
1818
+ }
1819
+ }
1820
+ /**
1821
+ * Build remote path from source config
1822
+ */
1823
+ buildRemotePath(source) {
1824
+ const protocol = source.type === "s3" ? "s3://" : source.type === "r2" ? "r2://" : "gcs://";
1825
+ const bucket = source.bucket;
1826
+ const prefix = source.prefix ? `/${source.prefix}` : "";
1827
+ return `${protocol}${bucket}${prefix}`;
1828
+ }
1829
+ /**
1830
+ * Get all available file plugin sources
1831
+ *
1832
+ * @returns Array of resolved file sources
1833
+ */
1834
+ getAvailableSources() {
1835
+ return Array.from(this.sources.values());
1836
+ }
1837
+ /**
1838
+ * Resolve a source by name
1839
+ *
1840
+ * @param name - Source name (e.g., "specs", "logs")
1841
+ * @returns Resolved source or null if not found
1842
+ */
1843
+ resolveSource(name) {
1844
+ return this.sources.get(name) || null;
1845
+ }
1846
+ /**
1847
+ * Check if a path belongs to any file plugin source
1848
+ *
1849
+ * @param path - File path to check
1850
+ * @returns True if path matches any source's base_path
1851
+ */
1852
+ isFilePluginPath(path7) {
1853
+ return this.getSourceForPath(path7) !== null;
1854
+ }
1855
+ /**
1856
+ * Get the source for a given path
1857
+ *
1858
+ * Matches path against all source base_paths and returns the matching source.
1859
+ * Uses longest-match strategy if multiple sources match.
1860
+ *
1861
+ * @param path - File path to match
1862
+ * @returns Matching source or null
1863
+ */
1864
+ getSourceForPath(path7) {
1865
+ let bestMatch = null;
1866
+ let bestMatchLength = 0;
1867
+ for (const source of this.sources.values()) {
1868
+ const normalizedPath = this.normalizePath(path7);
1869
+ const normalizedBasePath = this.normalizePath(source.localPath);
1870
+ if (normalizedPath.startsWith(normalizedBasePath)) {
1871
+ const matchLength = normalizedBasePath.length;
1872
+ if (matchLength > bestMatchLength) {
1873
+ bestMatch = source;
1874
+ bestMatchLength = matchLength;
1875
+ }
1876
+ }
1877
+ }
1878
+ return bestMatch;
1879
+ }
1880
+ /**
1881
+ * Normalize path for comparison
1882
+ * - Remove leading "./" or "/"
1883
+ * - Remove trailing "/"
1884
+ * - Convert to lowercase for case-insensitive comparison
1885
+ */
1886
+ normalizePath(path7) {
1887
+ return path7.replace(/^\.\//, "").replace(/^\//, "").replace(/\/$/, "").toLowerCase();
1888
+ }
1889
+ /**
1890
+ * Get source names
1891
+ *
1892
+ * @returns Array of source names
1893
+ */
1894
+ getSourceNames() {
1895
+ return Array.from(this.sources.keys());
1896
+ }
1897
+ /**
1898
+ * Check if sources are configured
1899
+ *
1900
+ * @returns True if any file plugin sources are configured
1901
+ */
1902
+ hasSources() {
1903
+ return this.sources.size > 0;
1904
+ }
1905
+ };
1906
+
1907
+ // src/storage/errors.ts
1908
+ var FilePluginFileNotFoundError = class _FilePluginFileNotFoundError extends Error {
1909
+ constructor(filePath, sourceName, options) {
1910
+ const includeCloudSuggestions = options?.includeCloudSuggestions !== false;
1911
+ const storageType = options?.storageType;
1912
+ let message = `File not found: ${filePath}
1913
+
1914
+ `;
1915
+ if (includeCloudSuggestions) {
1916
+ if (storageType) {
1917
+ message += `This file may be in cloud storage (${storageType}).
1918
+
1919
+ `;
1920
+ } else {
1921
+ message += `This file may not have been synced from remote storage yet.
1922
+ `;
1923
+ }
1924
+ message += `To fetch from cloud storage, run:
1925
+ `;
1926
+ message += ` file pull ${sourceName}
1927
+
1928
+ `;
1929
+ message += `Or sync all sources:
1930
+ `;
1931
+ message += ` file sync`;
1932
+ } else {
1933
+ message += `Please ensure the file exists locally or pull it from cloud storage.`;
1934
+ }
1935
+ super(message);
1936
+ this.filePath = filePath;
1937
+ this.sourceName = sourceName;
1938
+ this.name = "FilePluginFileNotFoundError";
1939
+ if (Error.captureStackTrace) {
1940
+ Error.captureStackTrace(this, _FilePluginFileNotFoundError);
1941
+ }
1942
+ }
1943
+ };
1944
+
1945
+ // src/storage/file-plugin.ts
1946
+ var FilePluginStorage = class {
1947
+ constructor(options) {
1948
+ this.options = options;
1949
+ this.sourceResolver = new FileSourceResolver(options.config);
1950
+ this.baseDir = options.baseDir || process.cwd();
1951
+ }
1952
+ name = "file-plugin";
1953
+ type = "local";
1954
+ // Reuse local type for compatibility
1955
+ sourceResolver;
1956
+ baseDir;
1957
+ /**
1958
+ * Check if this provider can handle the reference
1959
+ *
1960
+ * Only handles:
1961
+ * - Current project references
1962
+ * - With sourceType === 'file-plugin'
1963
+ *
1964
+ * @param reference - Resolved reference
1965
+ * @returns True if this provider can handle the reference
1966
+ */
1967
+ canHandle(reference) {
1968
+ return reference.isCurrentProject && reference.sourceType === "file-plugin";
1969
+ }
1970
+ /**
1971
+ * Fetch content for a reference
1972
+ *
1973
+ * Reads from local filesystem based on the resolved local path.
1974
+ * If file not found and S3 fallback is enabled, throws helpful error.
1975
+ *
1976
+ * @param reference - Resolved reference
1977
+ * @param options - Fetch options (unused for local reads)
1978
+ * @returns Fetch result with content
1979
+ */
1980
+ async fetch(reference, _options) {
1981
+ if (!reference.localPath) {
1982
+ throw new Error(`File plugin reference missing localPath: ${reference.uri}`);
1983
+ }
1984
+ if (!reference.filePluginSource) {
1985
+ throw new Error(`File plugin reference missing source name: ${reference.uri}`);
1986
+ }
1987
+ const absolutePath = path3.isAbsolute(reference.localPath) ? path3.resolve(reference.localPath) : path3.resolve(this.baseDir, reference.localPath);
1988
+ const source = this.sourceResolver.resolveSource(reference.filePluginSource);
1989
+ if (source) {
1990
+ const allowedDir = path3.resolve(this.baseDir, source.localPath);
1991
+ if (!absolutePath.startsWith(allowedDir + path3.sep) && absolutePath !== allowedDir) {
1992
+ throw new Error(
1993
+ `Path traversal detected: ${reference.localPath} resolves outside allowed directory ${source.localPath}`
1994
+ );
1995
+ }
1996
+ }
1997
+ try {
1998
+ const content = await fs3.readFile(absolutePath);
1999
+ const contentType = this.detectContentType(absolutePath);
2000
+ return {
2001
+ content,
2002
+ contentType,
2003
+ size: content.length,
2004
+ source: "file-plugin",
2005
+ metadata: {
2006
+ filePluginSource: reference.filePluginSource,
2007
+ localPath: absolutePath
2008
+ }
2009
+ };
2010
+ } catch (error) {
2011
+ if (error.code === "ENOENT") {
2012
+ const source2 = this.sourceResolver.resolveSource(reference.filePluginSource);
2013
+ throw this.createFileNotFoundError(reference, source2);
2014
+ }
2015
+ throw error;
2016
+ }
2017
+ }
2018
+ /**
2019
+ * Check if a reference exists
2020
+ *
2021
+ * @param reference - Resolved reference
2022
+ * @param options - Fetch options (unused)
2023
+ * @returns True if file exists
2024
+ */
2025
+ async exists(reference, _options) {
2026
+ if (!reference.localPath) {
2027
+ return false;
2028
+ }
2029
+ const absolutePath = path3.isAbsolute(reference.localPath) ? path3.resolve(reference.localPath) : path3.resolve(this.baseDir, reference.localPath);
2030
+ if (reference.filePluginSource) {
2031
+ const source = this.sourceResolver.resolveSource(reference.filePluginSource);
2032
+ if (source) {
2033
+ const allowedDir = path3.resolve(this.baseDir, source.localPath);
2034
+ if (!absolutePath.startsWith(allowedDir + path3.sep) && absolutePath !== allowedDir) {
2035
+ return false;
2036
+ }
2037
+ }
2038
+ }
2039
+ try {
2040
+ await fs3.access(absolutePath);
2041
+ return true;
2042
+ } catch {
2043
+ return false;
2044
+ }
2045
+ }
2046
+ /**
2047
+ * Detect content type from file extension
2048
+ */
2049
+ detectContentType(filePath) {
2050
+ const ext = path3.extname(filePath).toLowerCase();
2051
+ const mimeTypes = {
2052
+ ".md": "text/markdown",
2053
+ ".txt": "text/plain",
2054
+ ".json": "application/json",
2055
+ ".yaml": "text/yaml",
2056
+ ".yml": "text/yaml",
2057
+ ".html": "text/html",
2058
+ ".xml": "application/xml",
2059
+ ".log": "text/plain",
2060
+ ".js": "application/javascript",
2061
+ ".ts": "application/typescript",
2062
+ ".py": "text/x-python",
2063
+ ".sh": "application/x-sh"
2064
+ };
2065
+ return mimeTypes[ext] || "application/octet-stream";
2066
+ }
2067
+ /**
2068
+ * Create a helpful error message when file is not found
2069
+ */
2070
+ createFileNotFoundError(reference, source) {
2071
+ const includeCloudSuggestions = this.options.enableS3Fallback !== false;
2072
+ const storageType = source?.config.type;
2073
+ return new FilePluginFileNotFoundError(
2074
+ reference.localPath || reference.path || "",
2075
+ reference.filePluginSource || "",
2076
+ {
2077
+ includeCloudSuggestions,
2078
+ storageType
2079
+ }
2080
+ );
2081
+ }
2082
+ };
2083
+ var execFileAsync = promisify(execFile);
2084
+ async function execFileNoThrow(command, args = [], options) {
2085
+ try {
2086
+ const { stdout, stderr } = await execFileAsync(command, args, {
2087
+ ...options,
2088
+ maxBuffer: options?.maxBuffer || 1024 * 1024 * 10
2089
+ // 10MB default
2090
+ });
2091
+ return {
2092
+ stdout: stdout || "",
2093
+ stderr: stderr || "",
2094
+ exitCode: 0
2095
+ };
2096
+ } catch (error) {
2097
+ const exitCode = typeof error.exitCode === "number" ? error.exitCode : 1;
2098
+ return {
2099
+ stdout: error.stdout || "",
2100
+ stderr: error.stderr || error.message || "",
2101
+ exitCode
2102
+ };
2103
+ }
2104
+ }
2105
+
2106
+ // src/storage/s3-archive.ts
2107
+ var S3ArchiveStorage = class {
2108
+ name = "s3-archive";
2109
+ type = "s3-archive";
2110
+ projects;
2111
+ fractaryCli;
2112
+ constructor(options = {}) {
2113
+ this.projects = options.projects || {};
2114
+ this.fractaryCli = options.fractaryCli || "fractary";
2115
+ }
2116
+ /**
2117
+ * Check if this provider can handle the reference
2118
+ *
2119
+ * S3 Archive provider handles references that:
2120
+ * 1. Are for the current project (same org/project)
2121
+ * 2. Have archive enabled in config
2122
+ * 3. Match configured patterns (if specified)
2123
+ */
2124
+ canHandle(reference) {
2125
+ if (!reference.isCurrentProject) {
2126
+ return false;
2127
+ }
2128
+ const projectKey = `${reference.org}/${reference.project}`;
2129
+ const config = this.projects[projectKey];
2130
+ if (!config || !config.enabled) {
2131
+ return false;
2132
+ }
2133
+ if (config.patterns && config.patterns.length > 0) {
2134
+ return this.matchesPatterns(reference.path, config.patterns);
2135
+ }
2136
+ return true;
2137
+ }
2138
+ /**
2139
+ * Fetch content from S3 archive via fractary-file CLI
2140
+ */
2141
+ async fetch(reference, options) {
2142
+ const opts = mergeFetchOptions(options);
2143
+ const projectKey = `${reference.org}/${reference.project}`;
2144
+ const config = this.projects[projectKey];
2145
+ if (!config) {
2146
+ throw new Error(`No archive config for project: ${projectKey}`);
2147
+ }
2148
+ const archivePath = this.calculateArchivePath(reference, config);
2149
+ try {
2150
+ const result = await execFileNoThrow(
2151
+ this.fractaryCli,
2152
+ [
2153
+ "file",
2154
+ "read",
2155
+ "--remote-path",
2156
+ archivePath,
2157
+ "--handler",
2158
+ config.handler,
2159
+ ...config.bucket ? ["--bucket", config.bucket] : []
2160
+ ],
2161
+ {
2162
+ timeout: opts.timeout
2163
+ }
2164
+ );
2165
+ if (result.exitCode !== 0) {
2166
+ throw new Error(`fractary-file read failed: ${result.stderr}`);
2167
+ }
2168
+ const content = Buffer.from(result.stdout);
2169
+ return {
2170
+ content,
2171
+ contentType: detectContentType(reference.path),
2172
+ size: content.length,
2173
+ source: "s3-archive",
2174
+ metadata: {
2175
+ archivePath,
2176
+ bucket: config.bucket,
2177
+ handler: config.handler
2178
+ }
2179
+ };
2180
+ } catch (error) {
2181
+ const message = error instanceof Error ? error.message : String(error);
2182
+ throw new Error(`Failed to fetch from archive: ${message}`);
2183
+ }
2184
+ }
2185
+ /**
2186
+ * Check if archived file exists
2187
+ *
2188
+ * Note: This currently downloads the file to check existence.
2189
+ * TODO: Optimize by using fractary-file 'stat' or 'head' command when available
2190
+ * to avoid downloading full file for existence checks.
2191
+ */
2192
+ async exists(reference, options) {
2193
+ const projectKey = `${reference.org}/${reference.project}`;
2194
+ const config = this.projects[projectKey];
2195
+ if (!config) {
2196
+ return false;
2197
+ }
2198
+ try {
2199
+ await this.fetch(reference, { ...options, timeout: 5e3 });
2200
+ return true;
2201
+ } catch {
2202
+ return false;
2203
+ }
2204
+ }
2205
+ /**
2206
+ * Calculate archive path from reference
2207
+ *
2208
+ * Pattern: {prefix}/{type}/{org}/{project}/{original-path}
2209
+ *
2210
+ * Examples (with default prefix "archive/"):
2211
+ * specs/WORK-123.md → archive/specs/org/project/specs/WORK-123.md
2212
+ * docs/api.md → archive/docs/org/project/docs/api.md
2213
+ *
2214
+ * Examples (with custom prefix "archived-docs/"):
2215
+ * specs/WORK-123.md → archived-docs/specs/org/project/specs/WORK-123.md
2216
+ */
2217
+ calculateArchivePath(reference, config) {
2218
+ const type = this.detectType(reference.path);
2219
+ const prefix = config.prefix || "archive/";
2220
+ const trimmedPrefix = prefix.trim();
2221
+ if (!trimmedPrefix) {
2222
+ throw new Error("Archive prefix cannot be empty or whitespace-only");
2223
+ }
2224
+ const normalizedPrefix = trimmedPrefix.endsWith("/") ? trimmedPrefix : `${trimmedPrefix}/`;
2225
+ return `${normalizedPrefix}${type}/${reference.org}/${reference.project}/${reference.path}`;
2226
+ }
2227
+ /**
2228
+ * Detect artifact type from path
2229
+ *
2230
+ * Used to organize archives by type
2231
+ */
2232
+ detectType(path7) {
2233
+ if (path7.startsWith("specs/")) return "specs";
2234
+ if (path7.startsWith("docs/")) return "docs";
2235
+ if (path7.includes("/logs/")) return "logs";
2236
+ return "misc";
2237
+ }
2238
+ /**
2239
+ * Check if path matches any of the patterns
2240
+ *
2241
+ * Supports glob-style patterns:
2242
+ * - specs/** (all files in specs/)
2243
+ * - *.md (all markdown files)
2244
+ * - docs/*.md (markdown files in docs/)
2245
+ */
2246
+ matchesPatterns(path7, patterns) {
2247
+ for (const pattern of patterns) {
2248
+ if (this.matchesPattern(path7, pattern)) {
2249
+ return true;
2250
+ }
2251
+ }
2252
+ return false;
2253
+ }
2254
+ /**
2255
+ * Check if path matches a single pattern
2256
+ */
2257
+ matchesPattern(path7, pattern) {
2258
+ const DOUBLE_STAR = "\0DOUBLE_STAR\0";
2259
+ let regexPattern = pattern.replace(/\*\*/g, DOUBLE_STAR);
2260
+ regexPattern = regexPattern.replace(/[.[\](){}+^$|\\]/g, "\\$&");
2261
+ regexPattern = regexPattern.replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
2262
+ regexPattern = regexPattern.replace(new RegExp(DOUBLE_STAR, "g"), ".*");
2263
+ const regex = new RegExp(`^${regexPattern}$`);
2264
+ return regex.test(path7);
2265
+ }
2266
+ };
2267
+
1698
2268
  // src/storage/manager.ts
1699
2269
  var StorageManager = class {
1700
2270
  providers = /* @__PURE__ */ new Map();
@@ -1703,7 +2273,13 @@ var StorageManager = class {
1703
2273
  this.providers.set("local", new LocalStorage(config.local));
1704
2274
  this.providers.set("github", new GitHubStorage(config.github));
1705
2275
  this.providers.set("http", new HttpStorage(config.http));
1706
- this.priority = config.priority || ["local", "github", "http"];
2276
+ if (config.s3Archive) {
2277
+ this.providers.set("s3-archive", new S3ArchiveStorage(config.s3Archive));
2278
+ }
2279
+ if (config.filePlugin) {
2280
+ this.providers.set("file-plugin", new FilePluginStorage(config.filePlugin));
2281
+ }
2282
+ this.priority = config.priority || (config.filePlugin && config.s3Archive ? ["file-plugin", "local", "s3-archive", "github", "http"] : config.filePlugin ? ["file-plugin", "local", "github", "http"] : config.s3Archive ? ["local", "s3-archive", "github", "http"] : ["local", "github", "http"]);
1707
2283
  }
1708
2284
  /**
1709
2285
  * Register a custom storage provider
@@ -1943,7 +2519,7 @@ var CachePersistence = class {
1943
2519
  }
1944
2520
  const [, org, project, filePath] = match;
1945
2521
  const relativePath = filePath || "index";
1946
- return path3.join(this.cacheDir, org, project, relativePath + this.extension);
2522
+ return path3__default.join(this.cacheDir, org, project, relativePath + this.extension);
1947
2523
  }
1948
2524
  /**
1949
2525
  * Get the metadata file path for a URI
@@ -1959,8 +2535,8 @@ var CachePersistence = class {
1959
2535
  const metadataPath = this.getMetadataPath(uri);
1960
2536
  try {
1961
2537
  const [metadataJson, content] = await Promise.all([
1962
- fs2.readFile(metadataPath, "utf-8"),
1963
- fs2.readFile(cachePath)
2538
+ fs3__default.readFile(metadataPath, "utf-8"),
2539
+ fs3__default.readFile(cachePath)
1964
2540
  ]);
1965
2541
  const metadata = JSON.parse(metadataJson);
1966
2542
  return {
@@ -1980,32 +2556,32 @@ var CachePersistence = class {
1980
2556
  async write(entry) {
1981
2557
  const cachePath = this.getCachePath(entry.metadata.uri);
1982
2558
  const metadataPath = this.getMetadataPath(entry.metadata.uri);
1983
- await fs2.mkdir(path3.dirname(cachePath), { recursive: true });
2559
+ await fs3__default.mkdir(path3__default.dirname(cachePath), { recursive: true });
1984
2560
  if (this.atomicWrites) {
1985
2561
  const tempCachePath = cachePath + ".tmp";
1986
2562
  const tempMetadataPath = metadataPath + ".tmp";
1987
2563
  try {
1988
2564
  await Promise.all([
1989
- fs2.writeFile(tempCachePath, entry.content),
1990
- fs2.writeFile(tempMetadataPath, JSON.stringify(entry.metadata, null, 2))
2565
+ fs3__default.writeFile(tempCachePath, entry.content),
2566
+ fs3__default.writeFile(tempMetadataPath, JSON.stringify(entry.metadata, null, 2))
1991
2567
  ]);
1992
2568
  await Promise.all([
1993
- fs2.rename(tempCachePath, cachePath),
1994
- fs2.rename(tempMetadataPath, metadataPath)
2569
+ fs3__default.rename(tempCachePath, cachePath),
2570
+ fs3__default.rename(tempMetadataPath, metadataPath)
1995
2571
  ]);
1996
2572
  } catch (error) {
1997
2573
  await Promise.all([
1998
- fs2.unlink(tempCachePath).catch(() => {
2574
+ fs3__default.unlink(tempCachePath).catch(() => {
1999
2575
  }),
2000
- fs2.unlink(tempMetadataPath).catch(() => {
2576
+ fs3__default.unlink(tempMetadataPath).catch(() => {
2001
2577
  })
2002
2578
  ]);
2003
2579
  throw error;
2004
2580
  }
2005
2581
  } else {
2006
2582
  await Promise.all([
2007
- fs2.writeFile(cachePath, entry.content),
2008
- fs2.writeFile(metadataPath, JSON.stringify(entry.metadata, null, 2))
2583
+ fs3__default.writeFile(cachePath, entry.content),
2584
+ fs3__default.writeFile(metadataPath, JSON.stringify(entry.metadata, null, 2))
2009
2585
  ]);
2010
2586
  }
2011
2587
  }
@@ -2016,7 +2592,7 @@ var CachePersistence = class {
2016
2592
  const cachePath = this.getCachePath(uri);
2017
2593
  const metadataPath = this.getMetadataPath(uri);
2018
2594
  try {
2019
- await Promise.all([fs2.unlink(cachePath), fs2.unlink(metadataPath)]);
2595
+ await Promise.all([fs3__default.unlink(cachePath), fs3__default.unlink(metadataPath)]);
2020
2596
  return true;
2021
2597
  } catch (error) {
2022
2598
  if (error.code === "ENOENT") {
@@ -2031,7 +2607,7 @@ var CachePersistence = class {
2031
2607
  async exists(uri) {
2032
2608
  const cachePath = this.getCachePath(uri);
2033
2609
  try {
2034
- await fs2.access(cachePath);
2610
+ await fs3__default.access(cachePath);
2035
2611
  return true;
2036
2612
  } catch {
2037
2613
  return false;
@@ -2043,20 +2619,20 @@ var CachePersistence = class {
2043
2619
  async list() {
2044
2620
  const uris = [];
2045
2621
  try {
2046
- const orgs = await fs2.readdir(this.cacheDir);
2622
+ const orgs = await fs3__default.readdir(this.cacheDir);
2047
2623
  for (const org of orgs) {
2048
- const orgPath = path3.join(this.cacheDir, org);
2049
- const orgStat = await fs2.stat(orgPath);
2624
+ const orgPath = path3__default.join(this.cacheDir, org);
2625
+ const orgStat = await fs3__default.stat(orgPath);
2050
2626
  if (!orgStat.isDirectory()) continue;
2051
- const projects = await fs2.readdir(orgPath);
2627
+ const projects = await fs3__default.readdir(orgPath);
2052
2628
  for (const project of projects) {
2053
- const projectPath = path3.join(orgPath, project);
2054
- const projectStat = await fs2.stat(projectPath);
2629
+ const projectPath = path3__default.join(orgPath, project);
2630
+ const projectStat = await fs3__default.stat(projectPath);
2055
2631
  if (!projectStat.isDirectory()) continue;
2056
2632
  const files = await this.listFilesRecursive(projectPath);
2057
2633
  for (const file of files) {
2058
2634
  if (file.endsWith(this.extension)) {
2059
- const relativePath = path3.relative(projectPath, file);
2635
+ const relativePath = path3__default.relative(projectPath, file);
2060
2636
  const filePath = relativePath.slice(0, -this.extension.length);
2061
2637
  uris.push(`codex://${org}/${project}/${filePath}`);
2062
2638
  }
@@ -2076,9 +2652,9 @@ var CachePersistence = class {
2076
2652
  */
2077
2653
  async listFilesRecursive(dir) {
2078
2654
  const files = [];
2079
- const entries = await fs2.readdir(dir, { withFileTypes: true });
2655
+ const entries = await fs3__default.readdir(dir, { withFileTypes: true });
2080
2656
  for (const entry of entries) {
2081
- const fullPath = path3.join(dir, entry.name);
2657
+ const fullPath = path3__default.join(dir, entry.name);
2082
2658
  if (entry.isDirectory()) {
2083
2659
  files.push(...await this.listFilesRecursive(fullPath));
2084
2660
  } else if (entry.isFile()) {
@@ -2093,12 +2669,12 @@ var CachePersistence = class {
2093
2669
  async clear() {
2094
2670
  let count = 0;
2095
2671
  try {
2096
- const orgs = await fs2.readdir(this.cacheDir);
2672
+ const orgs = await fs3__default.readdir(this.cacheDir);
2097
2673
  for (const org of orgs) {
2098
- const orgPath = path3.join(this.cacheDir, org);
2099
- const stat = await fs2.stat(orgPath);
2674
+ const orgPath = path3__default.join(this.cacheDir, org);
2675
+ const stat = await fs3__default.stat(orgPath);
2100
2676
  if (stat.isDirectory()) {
2101
- await fs2.rm(orgPath, { recursive: true });
2677
+ await fs3__default.rm(orgPath, { recursive: true });
2102
2678
  count++;
2103
2679
  }
2104
2680
  }
@@ -2159,7 +2735,7 @@ var CachePersistence = class {
2159
2735
  * Ensure cache directory exists
2160
2736
  */
2161
2737
  async ensureDir() {
2162
- await fs2.mkdir(this.cacheDir, { recursive: true });
2738
+ await fs3__default.mkdir(this.cacheDir, { recursive: true });
2163
2739
  }
2164
2740
  /**
2165
2741
  * Get cache directory path
@@ -2207,8 +2783,17 @@ var CacheManager = class {
2207
2783
  * Get content for a reference
2208
2784
  *
2209
2785
  * Implements cache-first strategy with stale-while-revalidate.
2786
+ *
2787
+ * EXCEPTION: File plugin sources (current project files) bypass cache entirely.
2788
+ * They are always read fresh from disk for optimal development experience.
2210
2789
  */
2211
2790
  async get(reference, options) {
2791
+ if (reference.isCurrentProject && reference.sourceType === "file-plugin") {
2792
+ if (!this.storage) {
2793
+ throw new Error("Storage manager not set");
2794
+ }
2795
+ return await this.storage.fetch(reference, options);
2796
+ }
2212
2797
  const ttl = options?.ttl ?? this.config.defaultTtl;
2213
2798
  let entry = this.memoryCache.get(reference.uri);
2214
2799
  if (!entry && this.persistence) {
@@ -2414,13 +2999,18 @@ var CacheManager = class {
2414
2999
  }
2415
3000
  /**
2416
3001
  * Fetch content and store in cache
3002
+ *
3003
+ * EXCEPTION: File plugin sources are not cached (should not reach here,
3004
+ * but added as safety check).
2417
3005
  */
2418
3006
  async fetchAndCache(reference, ttl, options) {
2419
3007
  if (!this.storage) {
2420
3008
  throw new Error("Storage manager not set");
2421
3009
  }
2422
3010
  const result = await this.storage.fetch(reference, options);
2423
- await this.set(reference.uri, result, ttl);
3011
+ if (!(reference.isCurrentProject && reference.sourceType === "file-plugin")) {
3012
+ await this.set(reference.uri, result, ttl);
3013
+ }
2424
3014
  return result;
2425
3015
  }
2426
3016
  /**
@@ -2539,11 +3129,11 @@ var DEFAULT_SYNC_CONFIG = {
2539
3129
  deleteOrphans: false,
2540
3130
  conflictStrategy: "newest"
2541
3131
  };
2542
- function evaluatePath(path6, rules, direction, defaultExcludes = []) {
3132
+ function evaluatePath(path7, rules, direction, defaultExcludes = []) {
2543
3133
  for (const pattern of defaultExcludes) {
2544
- if (micromatch3.isMatch(path6, pattern)) {
3134
+ if (micromatch3.isMatch(path7, pattern)) {
2545
3135
  return {
2546
- path: path6,
3136
+ path: path7,
2547
3137
  shouldSync: false,
2548
3138
  reason: `Excluded by default pattern: ${pattern}`
2549
3139
  };
@@ -2554,9 +3144,9 @@ function evaluatePath(path6, rules, direction, defaultExcludes = []) {
2554
3144
  if (rule.direction && rule.direction !== direction) {
2555
3145
  continue;
2556
3146
  }
2557
- if (micromatch3.isMatch(path6, rule.pattern)) {
3147
+ if (micromatch3.isMatch(path7, rule.pattern)) {
2558
3148
  return {
2559
- path: path6,
3149
+ path: path7,
2560
3150
  shouldSync: rule.include,
2561
3151
  matchedRule: rule,
2562
3152
  reason: rule.include ? `Included by rule: ${rule.pattern}` : `Excluded by rule: ${rule.pattern}`
@@ -2564,21 +3154,21 @@ function evaluatePath(path6, rules, direction, defaultExcludes = []) {
2564
3154
  }
2565
3155
  }
2566
3156
  return {
2567
- path: path6,
3157
+ path: path7,
2568
3158
  shouldSync: true,
2569
3159
  reason: "No matching rule, included by default"
2570
3160
  };
2571
3161
  }
2572
3162
  function evaluatePaths(paths, rules, direction, defaultExcludes = []) {
2573
3163
  const results = /* @__PURE__ */ new Map();
2574
- for (const path6 of paths) {
2575
- results.set(path6, evaluatePath(path6, rules, direction, defaultExcludes));
3164
+ for (const path7 of paths) {
3165
+ results.set(path7, evaluatePath(path7, rules, direction, defaultExcludes));
2576
3166
  }
2577
3167
  return results;
2578
3168
  }
2579
3169
  function filterSyncablePaths(paths, rules, direction, defaultExcludes = []) {
2580
3170
  return paths.filter(
2581
- (path6) => evaluatePath(path6, rules, direction, defaultExcludes).shouldSync
3171
+ (path7) => evaluatePath(path7, rules, direction, defaultExcludes).shouldSync
2582
3172
  );
2583
3173
  }
2584
3174
  function createRulesFromPatterns(include = [], exclude = []) {
@@ -2844,8 +3434,8 @@ async function scanCodexWithRouting(options) {
2844
3434
  for (const filePath of allFiles) {
2845
3435
  totalScanned++;
2846
3436
  try {
2847
- const fullPath = path3.join(codexDir, filePath);
2848
- const stats = await fs2.stat(fullPath);
3437
+ const fullPath = path3__default.join(codexDir, filePath);
3438
+ const stats = await fs3__default.stat(fullPath);
2849
3439
  if (stats.size > maxFileSize) {
2850
3440
  totalSkipped++;
2851
3441
  errors.push({
@@ -2941,10 +3531,10 @@ async function listAllFilesRecursive(dirPath) {
2941
3531
  const files = [];
2942
3532
  async function scanDirectory(currentPath, relativePath = "") {
2943
3533
  try {
2944
- const entries = await fs2.readdir(currentPath, { withFileTypes: true });
3534
+ const entries = await fs3__default.readdir(currentPath, { withFileTypes: true });
2945
3535
  for (const entry of entries) {
2946
- const entryPath = path3.join(currentPath, entry.name);
2947
- const entryRelativePath = relativePath ? path3.join(relativePath, entry.name) : entry.name;
3536
+ const entryPath = path3__default.join(currentPath, entry.name);
3537
+ const entryRelativePath = relativePath ? path3__default.join(relativePath, entry.name) : entry.name;
2948
3538
  if (entry.isDirectory()) {
2949
3539
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") {
2950
3540
  continue;
@@ -3183,17 +3773,17 @@ var SyncManager = class {
3183
3773
  if (file.operation === "create" || file.operation === "update") {
3184
3774
  const sourcePath = `${plan.source}/${file.path}`;
3185
3775
  const targetPath = `${plan.target}/${file.path}`;
3186
- const fs4 = await import('fs/promises');
3187
- const path6 = await import('path');
3188
- const targetDir = path6.dirname(targetPath);
3189
- await fs4.mkdir(targetDir, { recursive: true });
3190
- await fs4.copyFile(sourcePath, targetPath);
3776
+ const fs5 = await import('fs/promises');
3777
+ const path7 = await import('path');
3778
+ const targetDir = path7.dirname(targetPath);
3779
+ await fs5.mkdir(targetDir, { recursive: true });
3780
+ await fs5.copyFile(sourcePath, targetPath);
3191
3781
  synced++;
3192
3782
  } else if (file.operation === "delete") {
3193
3783
  const targetPath = `${plan.target}/${file.path}`;
3194
- const fs4 = await import('fs/promises');
3784
+ const fs5 = await import('fs/promises');
3195
3785
  try {
3196
- await fs4.unlink(targetPath);
3786
+ await fs5.unlink(targetPath);
3197
3787
  synced++;
3198
3788
  } catch (error) {
3199
3789
  if (error.code !== "ENOENT") {
@@ -3248,15 +3838,15 @@ var SyncManager = class {
3248
3838
  /**
3249
3839
  * Get sync status for a file
3250
3840
  */
3251
- async getFileStatus(path6) {
3841
+ async getFileStatus(path7) {
3252
3842
  const manifest = await this.loadManifest();
3253
- return manifest?.entries[path6] ?? null;
3843
+ return manifest?.entries[path7] ?? null;
3254
3844
  }
3255
3845
  /**
3256
3846
  * Check if a file is synced
3257
3847
  */
3258
- async isFileSynced(path6) {
3259
- const status = await this.getFileStatus(path6);
3848
+ async isFileSynced(path7) {
3849
+ const status = await this.getFileStatus(path7);
3260
3850
  return status !== null;
3261
3851
  }
3262
3852
  /**
@@ -3340,19 +3930,19 @@ function ruleMatchesContext(rule, context) {
3340
3930
  }
3341
3931
  return true;
3342
3932
  }
3343
- function ruleMatchesPath(rule, path6) {
3344
- return micromatch3.isMatch(path6, rule.pattern);
3933
+ function ruleMatchesPath(rule, path7) {
3934
+ return micromatch3.isMatch(path7, rule.pattern);
3345
3935
  }
3346
3936
  function ruleMatchesAction(rule, action) {
3347
3937
  return rule.actions.includes(action);
3348
3938
  }
3349
- function evaluatePermission(path6, action, context, config) {
3939
+ function evaluatePermission(path7, action, context, config) {
3350
3940
  const sortedRules = [...config.rules].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
3351
3941
  for (const rule of sortedRules) {
3352
3942
  if (!ruleMatchesContext(rule, context)) {
3353
3943
  continue;
3354
3944
  }
3355
- if (!ruleMatchesPath(rule, path6)) {
3945
+ if (!ruleMatchesPath(rule, path7)) {
3356
3946
  continue;
3357
3947
  }
3358
3948
  if (!ruleMatchesAction(rule, action)) {
@@ -3371,24 +3961,24 @@ function evaluatePermission(path6, action, context, config) {
3371
3961
  reason: config.defaultAllow ? "Allowed by default" : "Denied by default"
3372
3962
  };
3373
3963
  }
3374
- function isAllowed(path6, action, context, config) {
3375
- const result = evaluatePermission(path6, action, context, config);
3964
+ function isAllowed(path7, action, context, config) {
3965
+ const result = evaluatePermission(path7, action, context, config);
3376
3966
  return result.allowed;
3377
3967
  }
3378
- function hasPermission(path6, action, requiredLevel, context, config) {
3379
- const result = evaluatePermission(path6, action, context, config);
3968
+ function hasPermission(path7, action, requiredLevel, context, config) {
3969
+ const result = evaluatePermission(path7, action, context, config);
3380
3970
  return levelGrants(result.level, requiredLevel);
3381
3971
  }
3382
3972
  function evaluatePermissions(paths, action, context, config) {
3383
3973
  const results = /* @__PURE__ */ new Map();
3384
- for (const path6 of paths) {
3385
- results.set(path6, evaluatePermission(path6, action, context, config));
3974
+ for (const path7 of paths) {
3975
+ results.set(path7, evaluatePermission(path7, action, context, config));
3386
3976
  }
3387
3977
  return results;
3388
3978
  }
3389
3979
  function filterByPermission(paths, action, context, config, requiredLevel = "read") {
3390
- return paths.filter((path6) => {
3391
- const result = evaluatePermission(path6, action, context, config);
3980
+ return paths.filter((path7) => {
3981
+ const result = evaluatePermission(path7, action, context, config);
3392
3982
  return levelGrants(result.level, requiredLevel);
3393
3983
  });
3394
3984
  }
@@ -3479,21 +4069,21 @@ var PermissionManager = class {
3479
4069
  /**
3480
4070
  * Check if an action is allowed for a path
3481
4071
  */
3482
- isAllowed(path6, action, context) {
3483
- const result = this.evaluate(path6, action, context);
4072
+ isAllowed(path7, action, context) {
4073
+ const result = this.evaluate(path7, action, context);
3484
4074
  return result.allowed;
3485
4075
  }
3486
4076
  /**
3487
4077
  * Check if a permission level is granted
3488
4078
  */
3489
- hasPermission(path6, action, requiredLevel, context) {
3490
- const result = this.evaluate(path6, action, context);
4079
+ hasPermission(path7, action, requiredLevel, context) {
4080
+ const result = this.evaluate(path7, action, context);
3491
4081
  return levelGrants(result.level, requiredLevel);
3492
4082
  }
3493
4083
  /**
3494
4084
  * Evaluate permission for a path and action
3495
4085
  */
3496
- evaluate(path6, action, context) {
4086
+ evaluate(path7, action, context) {
3497
4087
  const mergedContext = { ...this.defaultContext, ...context };
3498
4088
  if (!this.config.enforced) {
3499
4089
  return {
@@ -3502,7 +4092,7 @@ var PermissionManager = class {
3502
4092
  reason: "Permissions not enforced"
3503
4093
  };
3504
4094
  }
3505
- return evaluatePermission(path6, action, mergedContext, this.config);
4095
+ return evaluatePermission(path7, action, mergedContext, this.config);
3506
4096
  }
3507
4097
  /**
3508
4098
  * Filter paths by permission
@@ -3605,12 +4195,12 @@ var PermissionManager = class {
3605
4195
  /**
3606
4196
  * Assert permission (throws if denied)
3607
4197
  */
3608
- assertPermission(path6, action, requiredLevel = "read", context) {
3609
- const result = this.evaluate(path6, action, context);
4198
+ assertPermission(path7, action, requiredLevel = "read", context) {
4199
+ const result = this.evaluate(path7, action, context);
3610
4200
  if (!levelGrants(result.level, requiredLevel)) {
3611
4201
  throw new PermissionDeniedError(
3612
- `Permission denied for ${action} on ${path6}: ${result.reason}`,
3613
- path6,
4202
+ `Permission denied for ${action} on ${path7}: ${result.reason}`,
4203
+ path7,
3614
4204
  action,
3615
4205
  result
3616
4206
  );
@@ -3618,9 +4208,9 @@ var PermissionManager = class {
3618
4208
  }
3619
4209
  };
3620
4210
  var PermissionDeniedError = class extends Error {
3621
- constructor(message, path6, action, result) {
4211
+ constructor(message, path7, action, result) {
3622
4212
  super(message);
3623
- this.path = path6;
4213
+ this.path = path7;
3624
4214
  this.action = action;
3625
4215
  this.result = result;
3626
4216
  this.name = "PermissionDeniedError";
@@ -4117,8 +4707,8 @@ function convertToUri(reference, options = {}) {
4117
4707
  const parts = parseReference2(trimmed);
4118
4708
  const org = parts.org || options.defaultOrg || "_";
4119
4709
  const project = parts.project || options.defaultProject || "_";
4120
- const path6 = parts.path;
4121
- return `codex://${org}/${project}/${path6}`;
4710
+ const path7 = parts.path;
4711
+ return `codex://${org}/${project}/${path7}`;
4122
4712
  }
4123
4713
  function parseReference2(reference) {
4124
4714
  const trimmed = reference.trim();
@@ -4198,6 +4788,6 @@ function generateReferenceMigrationSummary(results) {
4198
4788
  return lines.join("\n");
4199
4789
  }
4200
4790
 
4201
- export { AutoSyncPatternSchema, BUILT_IN_TYPES, CODEX_URI_PREFIX, CacheManager, CachePersistence, CodexConfigSchema, CodexError, CommonRules, ConfigurationError, CustomTypeSchema, DEFAULT_CACHE_DIR, DEFAULT_FETCH_OPTIONS, DEFAULT_MIGRATION_OPTIONS, DEFAULT_PERMISSION_CONFIG, DEFAULT_SYNC_CONFIG, DEFAULT_TYPE, GitHubStorage, HttpStorage, LEGACY_PATTERNS, LEGACY_REF_PREFIX, LocalStorage, MetadataSchema, PERMISSION_LEVEL_ORDER, PermissionDeniedError, PermissionManager, StorageManager, SyncManager, SyncRulesSchema, TTL, TypeRegistry, TypesConfigSchema, ValidationError, buildUri, calculateCachePath, calculateContentHash, convertLegacyReference, convertLegacyReferences, convertToUri, createCacheEntry, createCacheManager, createCachePersistence, createDefaultRegistry, createEmptyModernConfig, createEmptySyncPlan, createGitHubStorage, createHttpStorage, createLocalStorage, createPermissionManager, createRule, createRulesFromPatterns, createStorageManager, createSyncManager, createSyncPlan, deserializeCacheEntry, detectContentType, detectCurrentProject, detectVersion, estimateSyncTime, evaluatePath, evaluatePaths, evaluatePatterns, evaluatePermission, evaluatePermissions, extendType, extractOrgFromRepoName, extractRawFrontmatter, filterByPatterns, filterByPermission, filterPlanOperations, filterSyncablePaths, findLegacyReferences, formatPlanSummary, generateMigrationReport, generateReferenceMigrationSummary, getBuiltInType, getBuiltInTypeNames, getCacheEntryAge, getCacheEntryStatus, getCurrentContext, getCustomSyncDestinations, getDefaultCacheManager, getDefaultConfig, getDefaultDirectories, getDefaultPermissionManager, getDefaultRules, getDefaultStorageManager, getDirectory, getExtension, getFilename, getMigrationRequirements, getPlanStats, getRelativeCachePath, getRemainingTtl, getTargetRepos, hasContentChanged, hasFrontmatter, hasLegacyReferences, hasPermission as hasPermissionLevel, isBuiltInType, isCacheEntryFresh, isCacheEntryValid, isCurrentProjectUri, isLegacyConfig, isLegacyReference, isModernConfig, isAllowed as isPermissionAllowed, isValidUri, levelGrants, loadConfig, loadCustomTypes, matchAnyPattern, matchPattern, maxLevel, mergeFetchOptions, mergeRules, mergeTypes, migrateConfig, migrateFileReferences, minLevel, needsMigration, parseCustomDestination, parseMetadata, parseReference, parseTtl, resolveOrganization, resolveReference, resolveReferences, ruleMatchesAction, ruleMatchesContext, ruleMatchesPath, sanitizePath, serializeCacheEntry, setDefaultCacheManager, setDefaultPermissionManager, setDefaultStorageManager, shouldSyncToRepo, summarizeEvaluations, touchCacheEntry, validateCustomTypes, validateMetadata, validateMigratedConfig, validateOrg, validatePath, validateRules2 as validatePermissionRules, validateProject, validateRules, validateUri };
4791
+ export { AutoSyncPatternSchema, BUILT_IN_TYPES, CODEX_URI_PREFIX, CacheManager, CachePersistence, CodexConfigSchema, CodexError, CommonRules, ConfigurationError, CustomTypeSchema, DEFAULT_CACHE_DIR, DEFAULT_FETCH_OPTIONS, DEFAULT_MIGRATION_OPTIONS, DEFAULT_PERMISSION_CONFIG, DEFAULT_SYNC_CONFIG, DEFAULT_TYPE, FilePluginFileNotFoundError, FilePluginStorage, GitHubStorage, HttpStorage, LEGACY_PATTERNS, LEGACY_REF_PREFIX, LocalStorage, MetadataSchema, PERMISSION_LEVEL_ORDER, PermissionDeniedError, PermissionManager, StorageManager, SyncManager, SyncRulesSchema, TTL, TypeRegistry, TypesConfigSchema, ValidationError, buildUri, calculateCachePath, calculateContentHash, convertLegacyReference, convertLegacyReferences, convertToUri, createCacheEntry, createCacheManager, createCachePersistence, createDefaultRegistry, createEmptyModernConfig, createEmptySyncPlan, createGitHubStorage, createHttpStorage, createLocalStorage, createPermissionManager, createRule, createRulesFromPatterns, createStorageManager, createSyncManager, createSyncPlan, deserializeCacheEntry, detectContentType, detectCurrentProject, detectVersion, estimateSyncTime, evaluatePath, evaluatePaths, evaluatePatterns, evaluatePermission, evaluatePermissions, extendType, extractOrgFromRepoName, extractRawFrontmatter, filterByPatterns, filterByPermission, filterPlanOperations, filterSyncablePaths, findLegacyReferences, formatPlanSummary, generateMigrationReport, generateReferenceMigrationSummary, getBuiltInType, getBuiltInTypeNames, getCacheEntryAge, getCacheEntryStatus, getCurrentContext, getCustomSyncDestinations, getDefaultCacheManager, getDefaultConfig, getDefaultDirectories, getDefaultPermissionManager, getDefaultRules, getDefaultStorageManager, getDirectory, getExtension, getFilename, getMigrationRequirements, getPlanStats, getRelativeCachePath, getRemainingTtl, getTargetRepos, hasContentChanged, hasFrontmatter, hasLegacyReferences, hasPermission as hasPermissionLevel, isBuiltInType, isCacheEntryFresh, isCacheEntryValid, isCurrentProjectUri, isLegacyConfig, isLegacyReference, isModernConfig, isAllowed as isPermissionAllowed, isValidUri, levelGrants, loadConfig, loadCustomTypes, matchAnyPattern, matchPattern, maxLevel, mergeFetchOptions, mergeRules, mergeTypes, migrateConfig, migrateFileReferences, minLevel, needsMigration, parseCustomDestination, parseMetadata, parseReference, parseTtl, resolveOrganization, resolveReference, resolveReferences, ruleMatchesAction, ruleMatchesContext, ruleMatchesPath, sanitizePath, serializeCacheEntry, setDefaultCacheManager, setDefaultPermissionManager, setDefaultStorageManager, shouldSyncToRepo, summarizeEvaluations, touchCacheEntry, validateCustomTypes, validateMetadata, validateMigratedConfig, validateOrg, validatePath, validateRules2 as validatePermissionRules, validateProject, validateRules, validateUri };
4202
4792
  //# sourceMappingURL=index.js.map
4203
4793
  //# sourceMappingURL=index.js.map