@fractary/codex 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -7
- package/dist/index.cjs +215 -48
- 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 +215 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ console.log(ref.path) // 'docs/api-guide.md'
|
|
|
40
40
|
|
|
41
41
|
// Create storage and cache managers
|
|
42
42
|
const storage = StorageManager.create()
|
|
43
|
-
const cache = CacheManager.create({ cacheDir: '.codex
|
|
43
|
+
const cache = CacheManager.create({ cacheDir: '.fractary/codex/cache' })
|
|
44
44
|
|
|
45
45
|
// Fetch content with caching
|
|
46
46
|
const content = await cache.get('codex://myorg/docs/api-guide.md')
|
|
@@ -80,7 +80,7 @@ const result = await storage.fetch('codex://org/project/file.md')
|
|
|
80
80
|
import { createCacheManager } from '@fractary/codex'
|
|
81
81
|
|
|
82
82
|
const cache = createCacheManager({
|
|
83
|
-
cacheDir: '.codex
|
|
83
|
+
cacheDir: '.fractary/codex/cache',
|
|
84
84
|
maxMemorySize: 50 * 1024 * 1024,
|
|
85
85
|
defaultTtl: 3600
|
|
86
86
|
})
|
|
@@ -96,7 +96,7 @@ import { createMcpServer } from '@fractary/codex'
|
|
|
96
96
|
const server = createMcpServer({
|
|
97
97
|
name: 'codex',
|
|
98
98
|
version: '1.0.0',
|
|
99
|
-
cacheDir: '.codex
|
|
99
|
+
cacheDir: '.fractary/codex/cache'
|
|
100
100
|
})
|
|
101
101
|
|
|
102
102
|
await server.start()
|
|
@@ -226,12 +226,9 @@ MIT - see [LICENSE](LICENSE)
|
|
|
226
226
|
|
|
227
227
|
## Documentation
|
|
228
228
|
|
|
229
|
-
- [Command Reference](../../docs/guides/command-reference.md) - Complete command reference for all interfaces
|
|
230
|
-
- [API Reference](../../docs/guides/api-reference.md) - Complete API documentation
|
|
231
229
|
- [CLI Integration Guide](../../docs/guides/cli-integration.md) - How to integrate into CLI applications
|
|
230
|
+
- [API Reference](../../docs/guides/api-reference.md) - Complete API documentation
|
|
232
231
|
- [Configuration Guide](../../docs/guides/configuration.md) - Configuration reference
|
|
233
|
-
- [Naming Conventions](../../docs/guides/naming-conventions.md) - Naming standards across all interfaces
|
|
234
|
-
- [MCP Migration Guide](../../docs/guides/mcp-migration-guide.md) - Migrating to new MCP tool names
|
|
235
232
|
- [Troubleshooting](../../docs/guides/troubleshooting.md) - Common issues and solutions
|
|
236
233
|
- [Examples](../../docs/examples/) - Real-world usage patterns
|
|
237
234
|
|
package/dist/index.cjs
CHANGED
|
@@ -185,10 +185,10 @@ function parseReference(uri, options = {}) {
|
|
|
185
185
|
path: filePath
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
|
-
function buildUri(org, project,
|
|
188
|
+
function buildUri(org, project, path6) {
|
|
189
189
|
const base = `${CODEX_URI_PREFIX}${org}/${project}`;
|
|
190
|
-
if (
|
|
191
|
-
const cleanPath =
|
|
190
|
+
if (path6) {
|
|
191
|
+
const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
|
|
192
192
|
return `${base}/${cleanPath}`;
|
|
193
193
|
}
|
|
194
194
|
return base;
|
|
@@ -226,7 +226,7 @@ function getDirectory(uri) {
|
|
|
226
226
|
}
|
|
227
227
|
return parsed.path.slice(0, lastSlash);
|
|
228
228
|
}
|
|
229
|
-
var DEFAULT_CACHE_DIR = ".fractary/
|
|
229
|
+
var DEFAULT_CACHE_DIR = ".fractary/codex/cache";
|
|
230
230
|
function detectCurrentProject(cwd) {
|
|
231
231
|
try {
|
|
232
232
|
const options = cwd ? { cwd, encoding: "utf-8" } : { encoding: "utf-8" };
|
|
@@ -1066,18 +1066,18 @@ function parseCustomDestination(value) {
|
|
|
1066
1066
|
);
|
|
1067
1067
|
}
|
|
1068
1068
|
const repo = value.substring(0, colonIndex).trim();
|
|
1069
|
-
const
|
|
1069
|
+
const path6 = value.substring(colonIndex + 1).trim();
|
|
1070
1070
|
if (!repo) {
|
|
1071
1071
|
throw new ValidationError(
|
|
1072
1072
|
`Invalid custom destination: repository name cannot be empty in "${value}"`
|
|
1073
1073
|
);
|
|
1074
1074
|
}
|
|
1075
|
-
if (!
|
|
1075
|
+
if (!path6) {
|
|
1076
1076
|
throw new ValidationError(
|
|
1077
1077
|
`Invalid custom destination: path cannot be empty in "${value}"`
|
|
1078
1078
|
);
|
|
1079
1079
|
}
|
|
1080
|
-
return { repo, path:
|
|
1080
|
+
return { repo, path: path6 };
|
|
1081
1081
|
}
|
|
1082
1082
|
function getCustomSyncDestinations(metadata) {
|
|
1083
1083
|
const customDestinations = metadata.codex_sync_custom;
|
|
@@ -1113,8 +1113,8 @@ function mergeFetchOptions(options) {
|
|
|
1113
1113
|
...options
|
|
1114
1114
|
};
|
|
1115
1115
|
}
|
|
1116
|
-
function detectContentType(
|
|
1117
|
-
const ext =
|
|
1116
|
+
function detectContentType(path6) {
|
|
1117
|
+
const ext = path6.split(".").pop()?.toLowerCase();
|
|
1118
1118
|
const mimeTypes = {
|
|
1119
1119
|
md: "text/markdown",
|
|
1120
1120
|
markdown: "text/markdown",
|
|
@@ -2437,11 +2437,11 @@ var DEFAULT_SYNC_CONFIG = {
|
|
|
2437
2437
|
deleteOrphans: false,
|
|
2438
2438
|
conflictStrategy: "newest"
|
|
2439
2439
|
};
|
|
2440
|
-
function evaluatePath(
|
|
2440
|
+
function evaluatePath(path6, rules, direction, defaultExcludes = []) {
|
|
2441
2441
|
for (const pattern of defaultExcludes) {
|
|
2442
|
-
if (micromatch2__default.default.isMatch(
|
|
2442
|
+
if (micromatch2__default.default.isMatch(path6, pattern)) {
|
|
2443
2443
|
return {
|
|
2444
|
-
path:
|
|
2444
|
+
path: path6,
|
|
2445
2445
|
shouldSync: false,
|
|
2446
2446
|
reason: `Excluded by default pattern: ${pattern}`
|
|
2447
2447
|
};
|
|
@@ -2452,9 +2452,9 @@ function evaluatePath(path5, rules, direction, defaultExcludes = []) {
|
|
|
2452
2452
|
if (rule.direction && rule.direction !== direction) {
|
|
2453
2453
|
continue;
|
|
2454
2454
|
}
|
|
2455
|
-
if (micromatch2__default.default.isMatch(
|
|
2455
|
+
if (micromatch2__default.default.isMatch(path6, rule.pattern)) {
|
|
2456
2456
|
return {
|
|
2457
|
-
path:
|
|
2457
|
+
path: path6,
|
|
2458
2458
|
shouldSync: rule.include,
|
|
2459
2459
|
matchedRule: rule,
|
|
2460
2460
|
reason: rule.include ? `Included by rule: ${rule.pattern}` : `Excluded by rule: ${rule.pattern}`
|
|
@@ -2462,21 +2462,21 @@ function evaluatePath(path5, rules, direction, defaultExcludes = []) {
|
|
|
2462
2462
|
}
|
|
2463
2463
|
}
|
|
2464
2464
|
return {
|
|
2465
|
-
path:
|
|
2465
|
+
path: path6,
|
|
2466
2466
|
shouldSync: true,
|
|
2467
2467
|
reason: "No matching rule, included by default"
|
|
2468
2468
|
};
|
|
2469
2469
|
}
|
|
2470
2470
|
function evaluatePaths(paths, rules, direction, defaultExcludes = []) {
|
|
2471
2471
|
const results = /* @__PURE__ */ new Map();
|
|
2472
|
-
for (const
|
|
2473
|
-
results.set(
|
|
2472
|
+
for (const path6 of paths) {
|
|
2473
|
+
results.set(path6, evaluatePath(path6, rules, direction, defaultExcludes));
|
|
2474
2474
|
}
|
|
2475
2475
|
return results;
|
|
2476
2476
|
}
|
|
2477
2477
|
function filterSyncablePaths(paths, rules, direction, defaultExcludes = []) {
|
|
2478
2478
|
return paths.filter(
|
|
2479
|
-
(
|
|
2479
|
+
(path6) => evaluatePath(path6, rules, direction, defaultExcludes).shouldSync
|
|
2480
2480
|
);
|
|
2481
2481
|
}
|
|
2482
2482
|
function createRulesFromPatterns(include = [], exclude = []) {
|
|
@@ -2713,6 +2713,123 @@ function formatBytes(bytes) {
|
|
|
2713
2713
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
2714
2714
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
2715
2715
|
}
|
|
2716
|
+
async function scanCodexWithRouting(options) {
|
|
2717
|
+
const {
|
|
2718
|
+
codexDir,
|
|
2719
|
+
targetProject,
|
|
2720
|
+
org,
|
|
2721
|
+
rules,
|
|
2722
|
+
storage,
|
|
2723
|
+
skipNoFrontmatter = true,
|
|
2724
|
+
maxFileSize = 10 * 1024 * 1024
|
|
2725
|
+
// 10MB default
|
|
2726
|
+
} = options;
|
|
2727
|
+
const startTime = Date.now();
|
|
2728
|
+
const routedFiles = [];
|
|
2729
|
+
const sourceProjectsSet = /* @__PURE__ */ new Set();
|
|
2730
|
+
const errors = [];
|
|
2731
|
+
let totalScanned = 0;
|
|
2732
|
+
let totalSkipped = 0;
|
|
2733
|
+
const allFiles = await listAllFilesRecursive(codexDir);
|
|
2734
|
+
for (const filePath of allFiles) {
|
|
2735
|
+
totalScanned++;
|
|
2736
|
+
try {
|
|
2737
|
+
if (!filePath.endsWith(".md")) {
|
|
2738
|
+
totalSkipped++;
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
const fullPath = path3__default.default.join(codexDir, filePath);
|
|
2742
|
+
const stats = await fs2__default.default.stat(fullPath);
|
|
2743
|
+
if (stats.size > maxFileSize) {
|
|
2744
|
+
totalSkipped++;
|
|
2745
|
+
errors.push({
|
|
2746
|
+
path: filePath,
|
|
2747
|
+
error: `File too large (${stats.size} bytes, max: ${maxFileSize})`
|
|
2748
|
+
});
|
|
2749
|
+
continue;
|
|
2750
|
+
}
|
|
2751
|
+
const content = await storage.readText(fullPath);
|
|
2752
|
+
const parseResult = parseMetadata(content, { strict: false });
|
|
2753
|
+
if (skipNoFrontmatter && Object.keys(parseResult.metadata).length === 0) {
|
|
2754
|
+
totalSkipped++;
|
|
2755
|
+
continue;
|
|
2756
|
+
}
|
|
2757
|
+
const sourceProject = extractProjectFromPath(filePath, org);
|
|
2758
|
+
const shouldSync = shouldSyncToRepo({
|
|
2759
|
+
filePath,
|
|
2760
|
+
fileMetadata: parseResult.metadata,
|
|
2761
|
+
targetRepo: targetProject,
|
|
2762
|
+
sourceRepo: sourceProject,
|
|
2763
|
+
rules
|
|
2764
|
+
});
|
|
2765
|
+
if (shouldSync) {
|
|
2766
|
+
const buffer = Buffer.from(content);
|
|
2767
|
+
const hash = calculateContentHash(buffer);
|
|
2768
|
+
routedFiles.push({
|
|
2769
|
+
path: filePath,
|
|
2770
|
+
size: buffer.length,
|
|
2771
|
+
mtime: stats.mtimeMs,
|
|
2772
|
+
hash,
|
|
2773
|
+
metadata: parseResult.metadata,
|
|
2774
|
+
sourceProject
|
|
2775
|
+
});
|
|
2776
|
+
sourceProjectsSet.add(sourceProject);
|
|
2777
|
+
} else {
|
|
2778
|
+
totalSkipped++;
|
|
2779
|
+
}
|
|
2780
|
+
} catch (error) {
|
|
2781
|
+
totalSkipped++;
|
|
2782
|
+
errors.push({
|
|
2783
|
+
path: filePath,
|
|
2784
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
const durationMs = Date.now() - startTime;
|
|
2789
|
+
return {
|
|
2790
|
+
files: routedFiles,
|
|
2791
|
+
stats: {
|
|
2792
|
+
totalScanned,
|
|
2793
|
+
totalMatched: routedFiles.length,
|
|
2794
|
+
totalSkipped,
|
|
2795
|
+
sourceProjects: Array.from(sourceProjectsSet).sort(),
|
|
2796
|
+
durationMs,
|
|
2797
|
+
errors
|
|
2798
|
+
}
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2801
|
+
function extractProjectFromPath(filePath, org) {
|
|
2802
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
2803
|
+
const withoutOrg = normalizedPath.startsWith(`${org}/`) ? normalizedPath.slice(org.length + 1) : normalizedPath;
|
|
2804
|
+
const firstSlash = withoutOrg.indexOf("/");
|
|
2805
|
+
if (firstSlash === -1) {
|
|
2806
|
+
return withoutOrg;
|
|
2807
|
+
}
|
|
2808
|
+
return withoutOrg.slice(0, firstSlash);
|
|
2809
|
+
}
|
|
2810
|
+
async function listAllFilesRecursive(dirPath) {
|
|
2811
|
+
const files = [];
|
|
2812
|
+
async function scanDirectory(currentPath, relativePath = "") {
|
|
2813
|
+
try {
|
|
2814
|
+
const entries = await fs2__default.default.readdir(currentPath, { withFileTypes: true });
|
|
2815
|
+
for (const entry of entries) {
|
|
2816
|
+
const entryPath = path3__default.default.join(currentPath, entry.name);
|
|
2817
|
+
const entryRelativePath = relativePath ? path3__default.default.join(relativePath, entry.name) : entry.name;
|
|
2818
|
+
if (entry.isDirectory()) {
|
|
2819
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") {
|
|
2820
|
+
continue;
|
|
2821
|
+
}
|
|
2822
|
+
await scanDirectory(entryPath, entryRelativePath);
|
|
2823
|
+
} else if (entry.isFile()) {
|
|
2824
|
+
files.push(entryRelativePath);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
} catch {
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
await scanDirectory(dirPath);
|
|
2831
|
+
return files;
|
|
2832
|
+
}
|
|
2716
2833
|
|
|
2717
2834
|
// src/sync/manager.ts
|
|
2718
2835
|
var SyncManager = class {
|
|
@@ -2805,6 +2922,56 @@ var SyncManager = class {
|
|
|
2805
2922
|
plan.estimatedTime = estimateSyncTime(plan);
|
|
2806
2923
|
return plan;
|
|
2807
2924
|
}
|
|
2925
|
+
/**
|
|
2926
|
+
* Create a routing-aware sync plan
|
|
2927
|
+
*
|
|
2928
|
+
* Scans entire codex repository and evaluates routing rules to find all files
|
|
2929
|
+
* that should sync to the target project based on codex_sync_include patterns.
|
|
2930
|
+
*
|
|
2931
|
+
* This enables cross-project knowledge sharing where files from multiple
|
|
2932
|
+
* projects can be synced to the target based on their frontmatter metadata.
|
|
2933
|
+
*
|
|
2934
|
+
* @param org - Organization name (e.g., "corthosai")
|
|
2935
|
+
* @param project - Target project name (e.g., "lake.corthonomy.ai")
|
|
2936
|
+
* @param codexDir - Path to codex repository directory
|
|
2937
|
+
* @param options - Sync options
|
|
2938
|
+
* @returns Sync plan with routing metadata
|
|
2939
|
+
*
|
|
2940
|
+
* @example
|
|
2941
|
+
* ```typescript
|
|
2942
|
+
* const plan = await manager.createRoutingAwarePlan(
|
|
2943
|
+
* 'corthosai',
|
|
2944
|
+
* 'lake.corthonomy.ai',
|
|
2945
|
+
* '/path/to/codex.corthos.ai',
|
|
2946
|
+
* { direction: 'from-codex' }
|
|
2947
|
+
* )
|
|
2948
|
+
*
|
|
2949
|
+
* console.log(`Found ${plan.totalFiles} files from ${plan.metadata.scannedProjects.length} projects`)
|
|
2950
|
+
* ```
|
|
2951
|
+
*/
|
|
2952
|
+
async createRoutingAwarePlan(org, project, codexDir, options) {
|
|
2953
|
+
const routingScan = await scanCodexWithRouting({
|
|
2954
|
+
codexDir,
|
|
2955
|
+
targetProject: project,
|
|
2956
|
+
org,
|
|
2957
|
+
rules: void 0,
|
|
2958
|
+
// Use default routing rules (preventSelfSync, preventCodexSync, etc.)
|
|
2959
|
+
storage: this.localStorage
|
|
2960
|
+
});
|
|
2961
|
+
const sourceFiles = routingScan.files.map((rf) => ({
|
|
2962
|
+
path: rf.path,
|
|
2963
|
+
size: rf.size,
|
|
2964
|
+
mtime: rf.mtime,
|
|
2965
|
+
hash: rf.hash
|
|
2966
|
+
}));
|
|
2967
|
+
const targetFiles = await this.listLocalFiles(process.cwd());
|
|
2968
|
+
const plan = createSyncPlan(sourceFiles, targetFiles, options ?? {}, this.config);
|
|
2969
|
+
plan.estimatedTime = estimateSyncTime(plan);
|
|
2970
|
+
return {
|
|
2971
|
+
...plan,
|
|
2972
|
+
routingScan
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2808
2975
|
/**
|
|
2809
2976
|
* Execute a sync plan (dry run)
|
|
2810
2977
|
*
|
|
@@ -2880,15 +3047,15 @@ var SyncManager = class {
|
|
|
2880
3047
|
/**
|
|
2881
3048
|
* Get sync status for a file
|
|
2882
3049
|
*/
|
|
2883
|
-
async getFileStatus(
|
|
3050
|
+
async getFileStatus(path6) {
|
|
2884
3051
|
const manifest = await this.loadManifest();
|
|
2885
|
-
return manifest?.entries[
|
|
3052
|
+
return manifest?.entries[path6] ?? null;
|
|
2886
3053
|
}
|
|
2887
3054
|
/**
|
|
2888
3055
|
* Check if a file is synced
|
|
2889
3056
|
*/
|
|
2890
|
-
async isFileSynced(
|
|
2891
|
-
const status = await this.getFileStatus(
|
|
3057
|
+
async isFileSynced(path6) {
|
|
3058
|
+
const status = await this.getFileStatus(path6);
|
|
2892
3059
|
return status !== null;
|
|
2893
3060
|
}
|
|
2894
3061
|
/**
|
|
@@ -2972,19 +3139,19 @@ function ruleMatchesContext(rule, context) {
|
|
|
2972
3139
|
}
|
|
2973
3140
|
return true;
|
|
2974
3141
|
}
|
|
2975
|
-
function ruleMatchesPath(rule,
|
|
2976
|
-
return micromatch2__default.default.isMatch(
|
|
3142
|
+
function ruleMatchesPath(rule, path6) {
|
|
3143
|
+
return micromatch2__default.default.isMatch(path6, rule.pattern);
|
|
2977
3144
|
}
|
|
2978
3145
|
function ruleMatchesAction(rule, action) {
|
|
2979
3146
|
return rule.actions.includes(action);
|
|
2980
3147
|
}
|
|
2981
|
-
function evaluatePermission(
|
|
3148
|
+
function evaluatePermission(path6, action, context, config) {
|
|
2982
3149
|
const sortedRules = [...config.rules].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
2983
3150
|
for (const rule of sortedRules) {
|
|
2984
3151
|
if (!ruleMatchesContext(rule, context)) {
|
|
2985
3152
|
continue;
|
|
2986
3153
|
}
|
|
2987
|
-
if (!ruleMatchesPath(rule,
|
|
3154
|
+
if (!ruleMatchesPath(rule, path6)) {
|
|
2988
3155
|
continue;
|
|
2989
3156
|
}
|
|
2990
3157
|
if (!ruleMatchesAction(rule, action)) {
|
|
@@ -3003,24 +3170,24 @@ function evaluatePermission(path5, action, context, config) {
|
|
|
3003
3170
|
reason: config.defaultAllow ? "Allowed by default" : "Denied by default"
|
|
3004
3171
|
};
|
|
3005
3172
|
}
|
|
3006
|
-
function isAllowed(
|
|
3007
|
-
const result = evaluatePermission(
|
|
3173
|
+
function isAllowed(path6, action, context, config) {
|
|
3174
|
+
const result = evaluatePermission(path6, action, context, config);
|
|
3008
3175
|
return result.allowed;
|
|
3009
3176
|
}
|
|
3010
|
-
function hasPermission(
|
|
3011
|
-
const result = evaluatePermission(
|
|
3177
|
+
function hasPermission(path6, action, requiredLevel, context, config) {
|
|
3178
|
+
const result = evaluatePermission(path6, action, context, config);
|
|
3012
3179
|
return levelGrants(result.level, requiredLevel);
|
|
3013
3180
|
}
|
|
3014
3181
|
function evaluatePermissions(paths, action, context, config) {
|
|
3015
3182
|
const results = /* @__PURE__ */ new Map();
|
|
3016
|
-
for (const
|
|
3017
|
-
results.set(
|
|
3183
|
+
for (const path6 of paths) {
|
|
3184
|
+
results.set(path6, evaluatePermission(path6, action, context, config));
|
|
3018
3185
|
}
|
|
3019
3186
|
return results;
|
|
3020
3187
|
}
|
|
3021
3188
|
function filterByPermission(paths, action, context, config, requiredLevel = "read") {
|
|
3022
|
-
return paths.filter((
|
|
3023
|
-
const result = evaluatePermission(
|
|
3189
|
+
return paths.filter((path6) => {
|
|
3190
|
+
const result = evaluatePermission(path6, action, context, config);
|
|
3024
3191
|
return levelGrants(result.level, requiredLevel);
|
|
3025
3192
|
});
|
|
3026
3193
|
}
|
|
@@ -3111,21 +3278,21 @@ var PermissionManager = class {
|
|
|
3111
3278
|
/**
|
|
3112
3279
|
* Check if an action is allowed for a path
|
|
3113
3280
|
*/
|
|
3114
|
-
isAllowed(
|
|
3115
|
-
const result = this.evaluate(
|
|
3281
|
+
isAllowed(path6, action, context) {
|
|
3282
|
+
const result = this.evaluate(path6, action, context);
|
|
3116
3283
|
return result.allowed;
|
|
3117
3284
|
}
|
|
3118
3285
|
/**
|
|
3119
3286
|
* Check if a permission level is granted
|
|
3120
3287
|
*/
|
|
3121
|
-
hasPermission(
|
|
3122
|
-
const result = this.evaluate(
|
|
3288
|
+
hasPermission(path6, action, requiredLevel, context) {
|
|
3289
|
+
const result = this.evaluate(path6, action, context);
|
|
3123
3290
|
return levelGrants(result.level, requiredLevel);
|
|
3124
3291
|
}
|
|
3125
3292
|
/**
|
|
3126
3293
|
* Evaluate permission for a path and action
|
|
3127
3294
|
*/
|
|
3128
|
-
evaluate(
|
|
3295
|
+
evaluate(path6, action, context) {
|
|
3129
3296
|
const mergedContext = { ...this.defaultContext, ...context };
|
|
3130
3297
|
if (!this.config.enforced) {
|
|
3131
3298
|
return {
|
|
@@ -3134,7 +3301,7 @@ var PermissionManager = class {
|
|
|
3134
3301
|
reason: "Permissions not enforced"
|
|
3135
3302
|
};
|
|
3136
3303
|
}
|
|
3137
|
-
return evaluatePermission(
|
|
3304
|
+
return evaluatePermission(path6, action, mergedContext, this.config);
|
|
3138
3305
|
}
|
|
3139
3306
|
/**
|
|
3140
3307
|
* Filter paths by permission
|
|
@@ -3237,12 +3404,12 @@ var PermissionManager = class {
|
|
|
3237
3404
|
/**
|
|
3238
3405
|
* Assert permission (throws if denied)
|
|
3239
3406
|
*/
|
|
3240
|
-
assertPermission(
|
|
3241
|
-
const result = this.evaluate(
|
|
3407
|
+
assertPermission(path6, action, requiredLevel = "read", context) {
|
|
3408
|
+
const result = this.evaluate(path6, action, context);
|
|
3242
3409
|
if (!levelGrants(result.level, requiredLevel)) {
|
|
3243
3410
|
throw new PermissionDeniedError(
|
|
3244
|
-
`Permission denied for ${action} on ${
|
|
3245
|
-
|
|
3411
|
+
`Permission denied for ${action} on ${path6}: ${result.reason}`,
|
|
3412
|
+
path6,
|
|
3246
3413
|
action,
|
|
3247
3414
|
result
|
|
3248
3415
|
);
|
|
@@ -3250,9 +3417,9 @@ var PermissionManager = class {
|
|
|
3250
3417
|
}
|
|
3251
3418
|
};
|
|
3252
3419
|
var PermissionDeniedError = class extends Error {
|
|
3253
|
-
constructor(message,
|
|
3420
|
+
constructor(message, path6, action, result) {
|
|
3254
3421
|
super(message);
|
|
3255
|
-
this.path =
|
|
3422
|
+
this.path = path6;
|
|
3256
3423
|
this.action = action;
|
|
3257
3424
|
this.result = result;
|
|
3258
3425
|
this.name = "PermissionDeniedError";
|
|
@@ -3749,8 +3916,8 @@ function convertToUri(reference, options = {}) {
|
|
|
3749
3916
|
const parts = parseReference2(trimmed);
|
|
3750
3917
|
const org = parts.org || options.defaultOrg || "_";
|
|
3751
3918
|
const project = parts.project || options.defaultProject || "_";
|
|
3752
|
-
const
|
|
3753
|
-
return `codex://${org}/${project}/${
|
|
3919
|
+
const path6 = parts.path;
|
|
3920
|
+
return `codex://${org}/${project}/${path6}`;
|
|
3754
3921
|
}
|
|
3755
3922
|
function parseReference2(reference) {
|
|
3756
3923
|
const trimmed = reference.trim();
|