@fractary/codex 0.4.0 → 0.5.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/dist/index.cjs CHANGED
@@ -1,19 +1,122 @@
1
1
  'use strict';
2
2
 
3
+ var micromatch3 = require('micromatch');
3
4
  var path3 = require('path');
4
5
  var child_process = require('child_process');
5
- var micromatch3 = require('micromatch');
6
6
  var zod = require('zod');
7
7
  var yaml = require('js-yaml');
8
8
  var fs2 = require('fs/promises');
9
9
 
10
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
11
 
12
- var path3__default = /*#__PURE__*/_interopDefault(path3);
13
12
  var micromatch3__default = /*#__PURE__*/_interopDefault(micromatch3);
13
+ var path3__default = /*#__PURE__*/_interopDefault(path3);
14
14
  var yaml__default = /*#__PURE__*/_interopDefault(yaml);
15
15
  var fs2__default = /*#__PURE__*/_interopDefault(fs2);
16
16
 
17
+ var __defProp = Object.defineProperty;
18
+ var __getOwnPropNames = Object.getOwnPropertyNames;
19
+ var __esm = (fn, res) => function __init() {
20
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
21
+ };
22
+ var __export = (target, all) => {
23
+ for (var name in all)
24
+ __defProp(target, name, { get: all[name], enumerable: true });
25
+ };
26
+ function matchPattern(pattern, value) {
27
+ if (pattern === value) return true;
28
+ return micromatch3__default.default.isMatch(value, pattern);
29
+ }
30
+ function matchAnyPattern(patterns, value) {
31
+ if (patterns.length === 1 && patterns[0] === "*") {
32
+ return true;
33
+ }
34
+ if (patterns.length === 0) {
35
+ return false;
36
+ }
37
+ return patterns.some((pattern) => matchPattern(pattern, value));
38
+ }
39
+ function filterByPatterns(patterns, values) {
40
+ return values.filter((value) => matchAnyPattern(patterns, value));
41
+ }
42
+ function evaluatePatterns(options) {
43
+ const { value, include = [], exclude = [] } = options;
44
+ if (exclude.length > 0 && matchAnyPattern(exclude, value)) {
45
+ return false;
46
+ }
47
+ if (include.length === 0) {
48
+ return true;
49
+ }
50
+ return matchAnyPattern(include, value);
51
+ }
52
+ var init_matcher = __esm({
53
+ "src/core/patterns/matcher.ts"() {
54
+ }
55
+ });
56
+
57
+ // src/sync/directional-patterns.ts
58
+ var directional_patterns_exports = {};
59
+ __export(directional_patterns_exports, {
60
+ expandPlaceholders: () => expandPlaceholders,
61
+ extractProjectFromCodexPath: () => extractProjectFromCodexPath,
62
+ getRelativePath: () => getRelativePath,
63
+ matchFromCodexPattern: () => matchFromCodexPattern,
64
+ matchToCodexPattern: () => matchToCodexPattern
65
+ });
66
+ function matchToCodexPattern(filePath, patterns) {
67
+ if (!patterns || patterns.length === 0) {
68
+ return false;
69
+ }
70
+ return patterns.some((pattern) => matchPattern(pattern, filePath));
71
+ }
72
+ function matchFromCodexPattern(codexFilePath, patterns, targetProject) {
73
+ if (!patterns || patterns.length === 0) {
74
+ return false;
75
+ }
76
+ return patterns.some((pattern) => {
77
+ if (pattern.startsWith("projects/")) {
78
+ return matchPattern(pattern, codexFilePath);
79
+ }
80
+ const projectSeparatorIndex = pattern.indexOf("/");
81
+ if (projectSeparatorIndex === -1) {
82
+ const fullPattern = `${targetProject}/${pattern}`;
83
+ return matchPattern(fullPattern, codexFilePath);
84
+ }
85
+ const firstSegment = pattern.substring(0, projectSeparatorIndex);
86
+ if (firstSegment.includes(".")) {
87
+ return matchPattern(pattern, codexFilePath);
88
+ } else {
89
+ const fullPattern = `${targetProject}/${pattern}`;
90
+ return matchPattern(fullPattern, codexFilePath);
91
+ }
92
+ });
93
+ }
94
+ function extractProjectFromCodexPath(codexFilePath) {
95
+ const firstSlashIndex = codexFilePath.indexOf("/");
96
+ if (firstSlashIndex === -1) {
97
+ return null;
98
+ }
99
+ return codexFilePath.substring(0, firstSlashIndex);
100
+ }
101
+ function getRelativePath(codexFilePath) {
102
+ const firstSlashIndex = codexFilePath.indexOf("/");
103
+ if (firstSlashIndex === -1) {
104
+ return null;
105
+ }
106
+ return codexFilePath.substring(firstSlashIndex + 1);
107
+ }
108
+ function expandPlaceholders(patterns, targetProject) {
109
+ if (!patterns) {
110
+ return patterns;
111
+ }
112
+ return patterns.map((pattern) => pattern.replace(/{project}/g, targetProject));
113
+ }
114
+ var init_directional_patterns = __esm({
115
+ "src/sync/directional-patterns.ts"() {
116
+ init_matcher();
117
+ }
118
+ });
119
+
17
120
  // src/errors/CodexError.ts
18
121
  var CodexError = class _CodexError extends Error {
19
122
  constructor(message, options) {
@@ -730,6 +833,16 @@ var SyncRulesSchema = zod.z.object({
730
833
  defaultInclude: zod.z.array(zod.z.string()).optional(),
731
834
  defaultExclude: zod.z.array(zod.z.string()).optional()
732
835
  });
836
+ var DirectionalSyncSchema = zod.z.object({
837
+ // Patterns for files to push from this project to codex
838
+ to_codex: zod.z.array(zod.z.string()).optional(),
839
+ // Patterns for files to pull from codex to this project
840
+ // Format: "project-name/path/pattern" or "project-name/**"
841
+ from_codex: zod.z.array(zod.z.string()).optional(),
842
+ // Org-level defaults (only in codex repository config)
843
+ default_to_codex: zod.z.array(zod.z.string()).optional(),
844
+ default_from_codex: zod.z.array(zod.z.string()).optional()
845
+ });
733
846
  var CodexConfigSchema = zod.z.object({
734
847
  organizationSlug: zod.z.string(),
735
848
  directories: zod.z.object({
@@ -737,7 +850,9 @@ var CodexConfigSchema = zod.z.object({
737
850
  target: zod.z.string().optional(),
738
851
  systems: zod.z.string().optional()
739
852
  }).optional(),
740
- rules: SyncRulesSchema.optional()
853
+ rules: SyncRulesSchema.optional(),
854
+ // Directional sync configuration
855
+ sync: DirectionalSyncSchema.optional()
741
856
  }).strict();
742
857
  function parseMetadata(content, options = {}) {
743
858
  const { strict = true, normalize = true } = options;
@@ -812,32 +927,9 @@ function extractRawFrontmatter(content) {
812
927
  const match = normalized.match(/^---\n([\s\S]*?)\n---\n/);
813
928
  return match && match[1] ? match[1] : null;
814
929
  }
815
- function matchPattern(pattern, value) {
816
- if (pattern === value) return true;
817
- return micromatch3__default.default.isMatch(value, pattern);
818
- }
819
- function matchAnyPattern(patterns, value) {
820
- if (patterns.length === 1 && patterns[0] === "*") {
821
- return true;
822
- }
823
- if (patterns.length === 0) {
824
- return false;
825
- }
826
- return patterns.some((pattern) => matchPattern(pattern, value));
827
- }
828
- function filterByPatterns(patterns, values) {
829
- return values.filter((value) => matchAnyPattern(patterns, value));
830
- }
831
- function evaluatePatterns(options) {
832
- const { value, include = [], exclude = [] } = options;
833
- if (exclude.length > 0 && matchAnyPattern(exclude, value)) {
834
- return false;
835
- }
836
- if (include.length === 0) {
837
- return true;
838
- }
839
- return matchAnyPattern(include, value);
840
- }
930
+
931
+ // src/core/patterns/index.ts
932
+ init_matcher();
841
933
 
842
934
  // src/core/config/organization.ts
843
935
  function resolveOrganization(options = {}) {
@@ -948,11 +1040,24 @@ function mergeConfigs(base, override) {
948
1040
  autoSyncPatterns: override.rules?.autoSyncPatterns ?? base.rules?.autoSyncPatterns,
949
1041
  defaultInclude: override.rules?.defaultInclude ?? base.rules?.defaultInclude,
950
1042
  defaultExclude: override.rules?.defaultExclude ?? base.rules?.defaultExclude
951
- }
1043
+ },
1044
+ // Only include sync if either base or override has sync config
1045
+ ...base.sync || override.sync ? {
1046
+ sync: {
1047
+ ...base.sync,
1048
+ ...override.sync,
1049
+ // Arrays are replaced, not merged
1050
+ to_codex: override.sync?.to_codex ?? base.sync?.to_codex,
1051
+ from_codex: override.sync?.from_codex ?? base.sync?.from_codex,
1052
+ default_to_codex: override.sync?.default_to_codex ?? base.sync?.default_to_codex,
1053
+ default_from_codex: override.sync?.default_from_codex ?? base.sync?.default_from_codex
1054
+ }
1055
+ } : {}
952
1056
  };
953
1057
  }
954
1058
 
955
1059
  // src/core/routing/evaluator.ts
1060
+ init_matcher();
956
1061
  function shouldSyncToRepo(options) {
957
1062
  const {
958
1063
  filePath,
@@ -2727,8 +2832,9 @@ async function scanCodexWithRouting(options) {
2727
2832
  rules,
2728
2833
  storage,
2729
2834
  skipNoFrontmatter = false,
2730
- maxFileSize = 10 * 1024 * 1024
2835
+ maxFileSize = 10 * 1024 * 1024,
2731
2836
  // 10MB default
2837
+ fromCodexPatterns
2732
2838
  } = options;
2733
2839
  const startTime = Date.now();
2734
2840
  const routedFiles = [];
@@ -2736,6 +2842,13 @@ async function scanCodexWithRouting(options) {
2736
2842
  const errors = [];
2737
2843
  let totalScanned = 0;
2738
2844
  let totalSkipped = 0;
2845
+ let expandedFromCodexPatterns = fromCodexPatterns;
2846
+ let matchFromCodexPattern2 = null;
2847
+ if (fromCodexPatterns && fromCodexPatterns.length > 0) {
2848
+ const module = await Promise.resolve().then(() => (init_directional_patterns(), directional_patterns_exports));
2849
+ matchFromCodexPattern2 = module.matchFromCodexPattern;
2850
+ expandedFromCodexPatterns = module.expandPlaceholders(fromCodexPatterns, targetProject);
2851
+ }
2739
2852
  const allFiles = await listAllFilesRecursive(codexDir);
2740
2853
  for (const filePath of allFiles) {
2741
2854
  totalScanned++;
@@ -2752,18 +2865,23 @@ async function scanCodexWithRouting(options) {
2752
2865
  }
2753
2866
  const content = await storage.readText(fullPath);
2754
2867
  const parseResult = parseMetadata(content, { strict: false });
2755
- if (skipNoFrontmatter && Object.keys(parseResult.metadata).length === 0) {
2868
+ if (!matchFromCodexPattern2 && skipNoFrontmatter && Object.keys(parseResult.metadata).length === 0) {
2756
2869
  totalSkipped++;
2757
2870
  continue;
2758
2871
  }
2759
2872
  const sourceProject = extractProjectFromPath(filePath, org);
2760
- const shouldSync = shouldSyncToRepo({
2761
- filePath,
2762
- fileMetadata: parseResult.metadata,
2763
- targetRepo: targetProject,
2764
- sourceRepo: sourceProject,
2765
- rules
2766
- });
2873
+ let shouldSync = false;
2874
+ if (matchFromCodexPattern2 && expandedFromCodexPatterns && expandedFromCodexPatterns.length > 0) {
2875
+ shouldSync = matchFromCodexPattern2(filePath, expandedFromCodexPatterns, targetProject);
2876
+ } else {
2877
+ shouldSync = shouldSyncToRepo({
2878
+ filePath,
2879
+ fileMetadata: parseResult.metadata,
2880
+ targetRepo: targetProject,
2881
+ sourceRepo: sourceProject,
2882
+ rules
2883
+ });
2884
+ }
2767
2885
  if (shouldSync) {
2768
2886
  const buffer = Buffer.from(content);
2769
2887
  const hash = calculateContentHash(buffer);
@@ -2803,6 +2921,14 @@ async function scanCodexWithRouting(options) {
2803
2921
  function extractProjectFromPath(filePath, org) {
2804
2922
  const normalizedPath = filePath.replace(/\\/g, "/");
2805
2923
  const withoutOrg = normalizedPath.startsWith(`${org}/`) ? normalizedPath.slice(org.length + 1) : normalizedPath;
2924
+ if (withoutOrg.startsWith("projects/")) {
2925
+ const afterProjects = withoutOrg.slice("projects/".length);
2926
+ const firstSlash2 = afterProjects.indexOf("/");
2927
+ if (firstSlash2 === -1) {
2928
+ return afterProjects;
2929
+ }
2930
+ return afterProjects.slice(0, firstSlash2);
2931
+ }
2806
2932
  const firstSlash = withoutOrg.indexOf("/");
2807
2933
  if (firstSlash === -1) {
2808
2934
  return withoutOrg;
@@ -2914,7 +3040,16 @@ var SyncManager = class {
2914
3040
  * @param options - Sync options
2915
3041
  */
2916
3042
  async createPlan(_org, _project, sourceDir, targetFiles, options) {
2917
- const sourceFiles = await this.listLocalFiles(sourceDir);
3043
+ let sourceFiles = await this.listLocalFiles(sourceDir);
3044
+ if (options?.direction === "to-codex") {
3045
+ const toCodexPatterns = this.config.to_codex || this.config.default_to_codex;
3046
+ if (toCodexPatterns) {
3047
+ const { matchToCodexPattern: matchToCodexPattern2 } = await Promise.resolve().then(() => (init_directional_patterns(), directional_patterns_exports));
3048
+ sourceFiles = sourceFiles.filter(
3049
+ (file) => matchToCodexPattern2(file.path, toCodexPatterns)
3050
+ );
3051
+ }
3052
+ }
2918
3053
  const plan = createSyncPlan(
2919
3054
  sourceFiles,
2920
3055
  targetFiles,
@@ -2952,13 +3087,16 @@ var SyncManager = class {
2952
3087
  * ```
2953
3088
  */
2954
3089
  async createRoutingAwarePlan(org, project, codexDir, options) {
3090
+ const fromCodexPatterns = this.config.from_codex || this.config.default_from_codex;
2955
3091
  const routingScan = await scanCodexWithRouting({
2956
3092
  codexDir,
2957
3093
  targetProject: project,
2958
3094
  org,
2959
3095
  rules: void 0,
2960
3096
  // Use default routing rules (preventSelfSync, preventCodexSync, etc.)
2961
- storage: this.localStorage
3097
+ storage: this.localStorage,
3098
+ fromCodexPatterns
3099
+ // Use directional patterns if configured
2962
3100
  });
2963
3101
  const sourceFiles = routingScan.files.map((rf) => ({
2964
3102
  path: rf.path,
@@ -2967,7 +3105,15 @@ var SyncManager = class {
2967
3105
  hash: rf.hash
2968
3106
  }));
2969
3107
  const targetFiles = await this.listLocalFiles(process.cwd());
2970
- const plan = createSyncPlan(sourceFiles, targetFiles, options ?? {}, this.config);
3108
+ const planOptions = {
3109
+ direction: options?.direction,
3110
+ force: options?.force,
3111
+ dryRun: options?.dryRun
3112
+ // Explicitly exclude include/exclude to prevent double filtering
3113
+ // include: undefined,
3114
+ // exclude: undefined,
3115
+ };
3116
+ const plan = createSyncPlan(sourceFiles, targetFiles, planOptions, this.config);
2971
3117
  plan.estimatedTime = estimateSyncTime(plan);
2972
3118
  return {
2973
3119
  ...plan,