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