@fractary/codex 0.1.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/LICENSE +21 -0
- package/README.md +280 -0
- package/dist/index.cjs +416 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +274 -0
- package/dist/index.d.ts +274 -0
- package/dist/index.js +388 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Fractary Engineering
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# @fractary/codex
|
|
2
|
+
|
|
3
|
+
Core SDK for Fractary Codex - a centralized knowledge management and distribution platform for organizations.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@fractary/codex)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
The Codex SDK provides the foundational business logic for the Fractary Codex system, which enables organizations to maintain a single source of truth for documentation, AI tools, and organizational knowledge across all projects.
|
|
11
|
+
|
|
12
|
+
### Key Features
|
|
13
|
+
|
|
14
|
+
- **Metadata Parsing**: Extract and validate YAML frontmatter from markdown files
|
|
15
|
+
- **Pattern Matching**: Glob-based pattern matching for intelligent file routing
|
|
16
|
+
- **Smart Routing**: Determine which files should sync to which repositories
|
|
17
|
+
- **Configuration Management**: Multi-source configuration with sensible defaults
|
|
18
|
+
- **Organization-Agnostic**: Works for any organization, not just Fractary
|
|
19
|
+
- **Type-Safe**: Full TypeScript support with strict typing
|
|
20
|
+
- **Well-Tested**: Comprehensive unit test coverage
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @fractary/codex
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import {
|
|
32
|
+
parseMetadata,
|
|
33
|
+
shouldSyncToRepo,
|
|
34
|
+
loadConfig
|
|
35
|
+
} from '@fractary/codex'
|
|
36
|
+
|
|
37
|
+
// 1. Load configuration
|
|
38
|
+
const config = loadConfig({
|
|
39
|
+
organizationSlug: 'fractary'
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// 2. Parse frontmatter from a markdown file
|
|
43
|
+
const fileContent = await readFile('docs/api-guide.md', 'utf-8')
|
|
44
|
+
const { metadata, content } = parseMetadata(fileContent)
|
|
45
|
+
|
|
46
|
+
// 3. Determine if file should sync to a target repository
|
|
47
|
+
const shouldSync = shouldSyncToRepo({
|
|
48
|
+
filePath: 'docs/api-guide.md',
|
|
49
|
+
fileMetadata: metadata,
|
|
50
|
+
targetRepo: 'api-gateway',
|
|
51
|
+
sourceRepo: 'codex.fractary.com',
|
|
52
|
+
rules: config.rules
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
console.log(`Sync to api-gateway: ${shouldSync}`)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Core Concepts
|
|
59
|
+
|
|
60
|
+
### Metadata Parsing
|
|
61
|
+
|
|
62
|
+
The SDK parses YAML frontmatter from markdown files to extract sync rules and metadata:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
---
|
|
66
|
+
org: fractary
|
|
67
|
+
system: api-gateway
|
|
68
|
+
codex_sync_include: ['api-*', 'core-*']
|
|
69
|
+
codex_sync_exclude: ['*-test', '*-dev']
|
|
70
|
+
visibility: internal
|
|
71
|
+
tags: [api, rest]
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
# API Documentation
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { parseMetadata } from '@fractary/codex'
|
|
79
|
+
|
|
80
|
+
const result = parseMetadata(markdown)
|
|
81
|
+
console.log(result.metadata.codex_sync_include) // ['api-*', 'core-*']
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Pattern Matching
|
|
85
|
+
|
|
86
|
+
Glob patterns determine which repositories receive which files:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { matchPattern, matchAnyPattern } from '@fractary/codex'
|
|
90
|
+
|
|
91
|
+
// Single pattern
|
|
92
|
+
matchPattern('api-*', 'api-gateway') // true
|
|
93
|
+
matchPattern('api-*', 'web-app') // false
|
|
94
|
+
|
|
95
|
+
// Multiple patterns
|
|
96
|
+
matchAnyPattern(['api-*', 'core-*'], 'api-gateway') // true
|
|
97
|
+
matchAnyPattern(['api-*', 'core-*'], 'web-app') // false
|
|
98
|
+
|
|
99
|
+
// Special: match all
|
|
100
|
+
matchAnyPattern(['*'], 'anything') // true
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Sync Routing
|
|
104
|
+
|
|
105
|
+
Determine which repositories should receive a file based on frontmatter rules:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { shouldSyncToRepo } from '@fractary/codex'
|
|
109
|
+
|
|
110
|
+
const shouldSync = shouldSyncToRepo({
|
|
111
|
+
filePath: 'docs/api-guide.md',
|
|
112
|
+
fileMetadata: {
|
|
113
|
+
codex_sync_include: ['api-*', 'core-*'],
|
|
114
|
+
codex_sync_exclude: ['*-test']
|
|
115
|
+
},
|
|
116
|
+
targetRepo: 'api-gateway',
|
|
117
|
+
sourceRepo: 'codex.fractary.com'
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Returns: true (matches 'api-*' and not excluded)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Configuration
|
|
124
|
+
|
|
125
|
+
The SDK supports multi-source configuration:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { loadConfig, resolveOrganization } from '@fractary/codex'
|
|
129
|
+
|
|
130
|
+
// Auto-detect organization from repo name
|
|
131
|
+
const org = resolveOrganization({
|
|
132
|
+
repoName: 'codex.fractary.com'
|
|
133
|
+
}) // Returns: 'fractary'
|
|
134
|
+
|
|
135
|
+
// Load configuration
|
|
136
|
+
const config = loadConfig({
|
|
137
|
+
organizationSlug: 'fractary'
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
console.log(config)
|
|
141
|
+
/*
|
|
142
|
+
{
|
|
143
|
+
organizationSlug: 'fractary',
|
|
144
|
+
directories: {
|
|
145
|
+
source: '.fractary',
|
|
146
|
+
target: '.fractary',
|
|
147
|
+
systems: '.fractary/systems'
|
|
148
|
+
},
|
|
149
|
+
rules: {
|
|
150
|
+
preventSelfSync: true,
|
|
151
|
+
preventCodexSync: true,
|
|
152
|
+
allowProjectOverrides: true,
|
|
153
|
+
autoSyncPatterns: []
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
*/
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## API Reference
|
|
160
|
+
|
|
161
|
+
### Metadata Parsing
|
|
162
|
+
|
|
163
|
+
- `parseMetadata(content, options?)` - Parse YAML frontmatter from markdown
|
|
164
|
+
- `hasFrontmatter(content)` - Check if content has frontmatter
|
|
165
|
+
- `validateMetadata(metadata)` - Validate metadata against schema
|
|
166
|
+
- `extractRawFrontmatter(content)` - Extract raw frontmatter string
|
|
167
|
+
|
|
168
|
+
### Pattern Matching
|
|
169
|
+
|
|
170
|
+
- `matchPattern(pattern, value)` - Match a single glob pattern
|
|
171
|
+
- `matchAnyPattern(patterns, value)` - Match against multiple patterns
|
|
172
|
+
- `filterByPatterns(patterns, values)` - Filter array by patterns
|
|
173
|
+
- `evaluatePatterns({ value, include, exclude })` - Evaluate include/exclude rules
|
|
174
|
+
|
|
175
|
+
### Configuration
|
|
176
|
+
|
|
177
|
+
- `loadConfig(options)` - Load configuration from all sources
|
|
178
|
+
- `resolveOrganization(options)` - Resolve organization slug
|
|
179
|
+
- `extractOrgFromRepoName(repoName)` - Extract org from repo name pattern
|
|
180
|
+
- `getDefaultConfig(orgSlug)` - Get default configuration
|
|
181
|
+
|
|
182
|
+
### Routing
|
|
183
|
+
|
|
184
|
+
- `shouldSyncToRepo(options)` - Determine if file should sync to repo
|
|
185
|
+
- `getTargetRepos(options)` - Get all repos that should receive a file
|
|
186
|
+
|
|
187
|
+
## Configuration
|
|
188
|
+
|
|
189
|
+
### Environment Variables
|
|
190
|
+
|
|
191
|
+
- `ORGANIZATION_SLUG` - Organization identifier
|
|
192
|
+
- `CODEX_ORG_SLUG` - Alternative organization identifier
|
|
193
|
+
- `CODEX_SOURCE_DIR` - Source directory (default: `.{org}`)
|
|
194
|
+
- `CODEX_TARGET_DIR` - Target directory (default: `.{org}`)
|
|
195
|
+
- `CODEX_PREVENT_SELF_SYNC` - Prevent self-sync (default: `true`)
|
|
196
|
+
- `CODEX_ALLOW_PROJECT_OVERRIDES` - Allow project overrides (default: `true`)
|
|
197
|
+
|
|
198
|
+
### Auto-Sync Patterns
|
|
199
|
+
|
|
200
|
+
Configure patterns that automatically sync to repositories:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const config = loadConfig({
|
|
204
|
+
organizationSlug: 'fractary'
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
config.rules.autoSyncPatterns = [
|
|
208
|
+
{
|
|
209
|
+
pattern: '*/docs/schema/*.json',
|
|
210
|
+
include: ['*'], // All repos
|
|
211
|
+
exclude: []
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
pattern: '*/security/**/*.md',
|
|
215
|
+
include: ['*'],
|
|
216
|
+
exclude: ['*-public']
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Development
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
# Install dependencies
|
|
225
|
+
npm install
|
|
226
|
+
|
|
227
|
+
# Build
|
|
228
|
+
npm run build
|
|
229
|
+
|
|
230
|
+
# Test
|
|
231
|
+
npm test
|
|
232
|
+
|
|
233
|
+
# Test with coverage
|
|
234
|
+
npm run test:coverage
|
|
235
|
+
|
|
236
|
+
# Type check
|
|
237
|
+
npm run typecheck
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Project Structure
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
src/
|
|
244
|
+
├── core/
|
|
245
|
+
│ ├── metadata/ # Frontmatter parsing
|
|
246
|
+
│ ├── patterns/ # Pattern matching
|
|
247
|
+
│ ├── routing/ # Sync routing logic
|
|
248
|
+
│ └── config/ # Configuration system
|
|
249
|
+
├── schemas/ # Zod schemas
|
|
250
|
+
├── errors/ # Error classes
|
|
251
|
+
└── index.ts # Main exports
|
|
252
|
+
|
|
253
|
+
tests/
|
|
254
|
+
├── unit/ # Unit tests
|
|
255
|
+
└── fixtures/ # Test fixtures
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Specifications
|
|
259
|
+
|
|
260
|
+
Detailed specifications are available in `/docs/specs/`:
|
|
261
|
+
|
|
262
|
+
- [SPEC-00001: Core SDK Overview](./docs/specs/SPEC-00001-core-sdk-overview.md)
|
|
263
|
+
- [SPEC-00002: Metadata Parsing](./docs/specs/SPEC-00002-metadata-parsing.md)
|
|
264
|
+
- [SPEC-00003: Pattern Matching](./docs/specs/SPEC-00003-pattern-matching.md)
|
|
265
|
+
- [SPEC-00004: Routing & Distribution](./docs/specs/SPEC-00004-routing-distribution.md)
|
|
266
|
+
- [SPEC-00005: Configuration System](./docs/specs/SPEC-00005-configuration-system.md)
|
|
267
|
+
|
|
268
|
+
## Related Projects
|
|
269
|
+
|
|
270
|
+
- **fractary-cli** - Unified CLI for all Fractary tools (coming soon)
|
|
271
|
+
- **forge-bundle-codex-github-core** - GitHub Actions workflows for codex sync
|
|
272
|
+
- **forge-bundle-codex-claude-agents** - Claude Code agents for codex management
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT © Fractary Engineering
|
|
277
|
+
|
|
278
|
+
## Contributing
|
|
279
|
+
|
|
280
|
+
This is part of the Fractary ecosystem. For issues or contributions, please refer to the [main repository](https://github.com/fractary/codex).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
var yaml = require('js-yaml');
|
|
5
|
+
var micromatch = require('micromatch');
|
|
6
|
+
|
|
7
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var yaml__default = /*#__PURE__*/_interopDefault(yaml);
|
|
10
|
+
|
|
11
|
+
// src/errors/CodexError.ts
|
|
12
|
+
var CodexError = class _CodexError extends Error {
|
|
13
|
+
constructor(message, options) {
|
|
14
|
+
super(message, options);
|
|
15
|
+
this.name = "CodexError";
|
|
16
|
+
Object.setPrototypeOf(this, _CodexError.prototype);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// src/errors/ConfigurationError.ts
|
|
21
|
+
var ConfigurationError = class _ConfigurationError extends CodexError {
|
|
22
|
+
constructor(message, options) {
|
|
23
|
+
super(message, options);
|
|
24
|
+
this.name = "ConfigurationError";
|
|
25
|
+
Object.setPrototypeOf(this, _ConfigurationError.prototype);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/errors/ValidationError.ts
|
|
30
|
+
var ValidationError = class _ValidationError extends CodexError {
|
|
31
|
+
constructor(message, options) {
|
|
32
|
+
super(message, options);
|
|
33
|
+
this.name = "ValidationError";
|
|
34
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var MetadataSchema = zod.z.object({
|
|
38
|
+
// Organizational
|
|
39
|
+
org: zod.z.string().optional(),
|
|
40
|
+
system: zod.z.string().optional(),
|
|
41
|
+
// Sync rules
|
|
42
|
+
codex_sync_include: zod.z.array(zod.z.string()).optional(),
|
|
43
|
+
codex_sync_exclude: zod.z.array(zod.z.string()).optional(),
|
|
44
|
+
// Metadata
|
|
45
|
+
title: zod.z.string().optional(),
|
|
46
|
+
description: zod.z.string().optional(),
|
|
47
|
+
visibility: zod.z.enum(["public", "internal", "private"]).optional(),
|
|
48
|
+
audience: zod.z.array(zod.z.string()).optional(),
|
|
49
|
+
tags: zod.z.array(zod.z.string()).optional(),
|
|
50
|
+
// Timestamps
|
|
51
|
+
created: zod.z.string().optional(),
|
|
52
|
+
// ISO 8601 date string
|
|
53
|
+
updated: zod.z.string().optional()
|
|
54
|
+
}).passthrough();
|
|
55
|
+
var AutoSyncPatternSchema = zod.z.object({
|
|
56
|
+
pattern: zod.z.string(),
|
|
57
|
+
include: zod.z.array(zod.z.string()),
|
|
58
|
+
exclude: zod.z.array(zod.z.string()).optional()
|
|
59
|
+
});
|
|
60
|
+
var SyncRulesSchema = zod.z.object({
|
|
61
|
+
autoSyncPatterns: zod.z.array(AutoSyncPatternSchema).optional(),
|
|
62
|
+
preventSelfSync: zod.z.boolean().optional(),
|
|
63
|
+
preventCodexSync: zod.z.boolean().optional(),
|
|
64
|
+
allowProjectOverrides: zod.z.boolean().optional(),
|
|
65
|
+
defaultInclude: zod.z.array(zod.z.string()).optional(),
|
|
66
|
+
defaultExclude: zod.z.array(zod.z.string()).optional()
|
|
67
|
+
});
|
|
68
|
+
var CodexConfigSchema = zod.z.object({
|
|
69
|
+
organizationSlug: zod.z.string(),
|
|
70
|
+
directories: zod.z.object({
|
|
71
|
+
source: zod.z.string().optional(),
|
|
72
|
+
target: zod.z.string().optional(),
|
|
73
|
+
systems: zod.z.string().optional()
|
|
74
|
+
}).optional(),
|
|
75
|
+
rules: SyncRulesSchema.optional()
|
|
76
|
+
}).strict();
|
|
77
|
+
function parseMetadata(content, options = {}) {
|
|
78
|
+
const { strict = true, normalize = true } = options;
|
|
79
|
+
const normalizedContent = normalize ? content.replace(/\r\n/g, "\n") : content;
|
|
80
|
+
const frontmatterMatch = normalizedContent.match(
|
|
81
|
+
/^---\n([\s\S]*?)\n---\n([\s\S]*)$/
|
|
82
|
+
);
|
|
83
|
+
if (!frontmatterMatch) {
|
|
84
|
+
return {
|
|
85
|
+
metadata: {},
|
|
86
|
+
content: normalizedContent,
|
|
87
|
+
raw: ""
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const rawFrontmatter = frontmatterMatch[1];
|
|
91
|
+
const documentContent = frontmatterMatch[2];
|
|
92
|
+
try {
|
|
93
|
+
const parsed = yaml__default.default.load(rawFrontmatter);
|
|
94
|
+
const normalized = normalizeLegacyMetadata(parsed);
|
|
95
|
+
const metadata = strict ? MetadataSchema.parse(normalized) : MetadataSchema.safeParse(normalized).data || {};
|
|
96
|
+
return {
|
|
97
|
+
metadata,
|
|
98
|
+
content: documentContent,
|
|
99
|
+
raw: rawFrontmatter
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (strict) {
|
|
103
|
+
throw new ValidationError(
|
|
104
|
+
`Invalid frontmatter: ${error instanceof Error ? error.message : String(error)}`,
|
|
105
|
+
{ cause: error }
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
metadata: {},
|
|
110
|
+
content: documentContent,
|
|
111
|
+
raw: rawFrontmatter
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function normalizeLegacyMetadata(parsed) {
|
|
116
|
+
const normalized = { ...parsed };
|
|
117
|
+
if (parsed.codex?.includes && !parsed.codex_sync_include) {
|
|
118
|
+
normalized.codex_sync_include = parsed.codex.includes;
|
|
119
|
+
}
|
|
120
|
+
if (parsed.codex?.excludes && !parsed.codex_sync_exclude) {
|
|
121
|
+
normalized.codex_sync_exclude = parsed.codex.excludes;
|
|
122
|
+
}
|
|
123
|
+
return normalized;
|
|
124
|
+
}
|
|
125
|
+
function hasFrontmatter(content) {
|
|
126
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
127
|
+
return /^---\n[\s\S]*?\n---\n/.test(normalized);
|
|
128
|
+
}
|
|
129
|
+
function validateMetadata(metadata) {
|
|
130
|
+
const result = MetadataSchema.safeParse(metadata);
|
|
131
|
+
if (result.success) {
|
|
132
|
+
return { valid: true };
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
valid: false,
|
|
136
|
+
errors: result.error.issues.map((issue) => issue.message)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function extractRawFrontmatter(content) {
|
|
140
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
141
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---\n/);
|
|
142
|
+
return match && match[1] ? match[1] : null;
|
|
143
|
+
}
|
|
144
|
+
function matchPattern(pattern, value) {
|
|
145
|
+
if (pattern === value) return true;
|
|
146
|
+
return micromatch.isMatch(value, pattern);
|
|
147
|
+
}
|
|
148
|
+
function matchAnyPattern(patterns, value) {
|
|
149
|
+
if (patterns.length === 1 && patterns[0] === "*") {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
if (patterns.length === 0) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return patterns.some((pattern) => matchPattern(pattern, value));
|
|
156
|
+
}
|
|
157
|
+
function filterByPatterns(patterns, values) {
|
|
158
|
+
return values.filter((value) => matchAnyPattern(patterns, value));
|
|
159
|
+
}
|
|
160
|
+
function evaluatePatterns(options) {
|
|
161
|
+
const { value, include = [], exclude = [] } = options;
|
|
162
|
+
if (exclude.length > 0 && matchAnyPattern(exclude, value)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
if (include.length === 0) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
return matchAnyPattern(include, value);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/core/config/organization.ts
|
|
172
|
+
function resolveOrganization(options = {}) {
|
|
173
|
+
const { orgSlug, repoName, autoDetect = true } = options;
|
|
174
|
+
if (orgSlug) {
|
|
175
|
+
return orgSlug;
|
|
176
|
+
}
|
|
177
|
+
if (autoDetect && repoName) {
|
|
178
|
+
const detected = extractOrgFromRepoName(repoName);
|
|
179
|
+
if (detected) {
|
|
180
|
+
return detected;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const envOrg = process.env["ORGANIZATION_SLUG"] || process.env["CODEX_ORG_SLUG"];
|
|
184
|
+
if (envOrg) {
|
|
185
|
+
return envOrg;
|
|
186
|
+
}
|
|
187
|
+
throw new ConfigurationError(
|
|
188
|
+
"Organization slug could not be determined. Set ORGANIZATION_SLUG environment variable or pass orgSlug option."
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
function extractOrgFromRepoName(repoName) {
|
|
192
|
+
const match = repoName.match(/^codex\.([^.]+)\.[^.]+$/);
|
|
193
|
+
if (match && match[1]) {
|
|
194
|
+
return match[1];
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/core/config/defaults.ts
|
|
200
|
+
function getDefaultDirectories(orgSlug) {
|
|
201
|
+
return {
|
|
202
|
+
source: `.${orgSlug}`,
|
|
203
|
+
target: `.${orgSlug}`,
|
|
204
|
+
systems: `.${orgSlug}/systems`
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function getDefaultRules() {
|
|
208
|
+
return {
|
|
209
|
+
autoSyncPatterns: [],
|
|
210
|
+
preventSelfSync: true,
|
|
211
|
+
preventCodexSync: true,
|
|
212
|
+
allowProjectOverrides: true,
|
|
213
|
+
defaultInclude: [],
|
|
214
|
+
defaultExclude: []
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function getDefaultConfig(orgSlug) {
|
|
218
|
+
return {
|
|
219
|
+
organizationSlug: orgSlug,
|
|
220
|
+
directories: getDefaultDirectories(orgSlug),
|
|
221
|
+
rules: getDefaultRules()
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/core/config/loader.ts
|
|
226
|
+
function loadConfig(options = {}) {
|
|
227
|
+
const orgSlug = resolveOrganization({
|
|
228
|
+
orgSlug: options.organizationSlug,
|
|
229
|
+
repoName: options.repoName
|
|
230
|
+
});
|
|
231
|
+
let config = {
|
|
232
|
+
organizationSlug: orgSlug,
|
|
233
|
+
directories: getDefaultDirectories(orgSlug),
|
|
234
|
+
rules: getDefaultRules()
|
|
235
|
+
};
|
|
236
|
+
const envConfig = loadConfigFromEnv(options.env || process.env);
|
|
237
|
+
config = mergeConfigs(config, envConfig);
|
|
238
|
+
return CodexConfigSchema.parse(config);
|
|
239
|
+
}
|
|
240
|
+
function loadConfigFromEnv(env) {
|
|
241
|
+
const config = {};
|
|
242
|
+
if (env["ORGANIZATION_SLUG"] || env["CODEX_ORG_SLUG"]) {
|
|
243
|
+
config.organizationSlug = env["ORGANIZATION_SLUG"] || env["CODEX_ORG_SLUG"];
|
|
244
|
+
}
|
|
245
|
+
if (env["CODEX_SOURCE_DIR"] || env["CODEX_TARGET_DIR"]) {
|
|
246
|
+
config.directories = {
|
|
247
|
+
source: env["CODEX_SOURCE_DIR"],
|
|
248
|
+
target: env["CODEX_TARGET_DIR"]
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const rules = {};
|
|
252
|
+
if (env["CODEX_PREVENT_SELF_SYNC"] !== void 0) {
|
|
253
|
+
rules.preventSelfSync = env["CODEX_PREVENT_SELF_SYNC"] === "true";
|
|
254
|
+
}
|
|
255
|
+
if (env["CODEX_PREVENT_CODEX_SYNC"] !== void 0) {
|
|
256
|
+
rules.preventCodexSync = env["CODEX_PREVENT_CODEX_SYNC"] === "true";
|
|
257
|
+
}
|
|
258
|
+
if (env["CODEX_ALLOW_PROJECT_OVERRIDES"] !== void 0) {
|
|
259
|
+
rules.allowProjectOverrides = env["CODEX_ALLOW_PROJECT_OVERRIDES"] === "true";
|
|
260
|
+
}
|
|
261
|
+
if (Object.keys(rules).length > 0) {
|
|
262
|
+
config.rules = rules;
|
|
263
|
+
}
|
|
264
|
+
return config;
|
|
265
|
+
}
|
|
266
|
+
function mergeConfigs(base, override) {
|
|
267
|
+
return {
|
|
268
|
+
organizationSlug: override.organizationSlug ?? base.organizationSlug,
|
|
269
|
+
directories: {
|
|
270
|
+
...base.directories,
|
|
271
|
+
...override.directories
|
|
272
|
+
},
|
|
273
|
+
rules: {
|
|
274
|
+
...base.rules,
|
|
275
|
+
...override.rules,
|
|
276
|
+
// Arrays are replaced, not merged
|
|
277
|
+
autoSyncPatterns: override.rules?.autoSyncPatterns ?? base.rules?.autoSyncPatterns,
|
|
278
|
+
defaultInclude: override.rules?.defaultInclude ?? base.rules?.defaultInclude,
|
|
279
|
+
defaultExclude: override.rules?.defaultExclude ?? base.rules?.defaultExclude
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/core/routing/evaluator.ts
|
|
285
|
+
function shouldSyncToRepo(options) {
|
|
286
|
+
const {
|
|
287
|
+
filePath,
|
|
288
|
+
fileMetadata,
|
|
289
|
+
targetRepo,
|
|
290
|
+
sourceRepo,
|
|
291
|
+
rules = getDefaultRules()
|
|
292
|
+
} = options;
|
|
293
|
+
const specialRuleResult = evaluateSpecialRules({
|
|
294
|
+
filePath,
|
|
295
|
+
targetRepo,
|
|
296
|
+
sourceRepo,
|
|
297
|
+
rules
|
|
298
|
+
});
|
|
299
|
+
if (specialRuleResult !== null) {
|
|
300
|
+
return specialRuleResult;
|
|
301
|
+
}
|
|
302
|
+
return evaluateFrontmatterRules({
|
|
303
|
+
metadata: fileMetadata,
|
|
304
|
+
targetRepo,
|
|
305
|
+
allowOverrides: rules.allowProjectOverrides ?? true
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function evaluateSpecialRules(options) {
|
|
309
|
+
const { filePath, targetRepo, sourceRepo, rules } = options;
|
|
310
|
+
if (rules.autoSyncPatterns?.length) {
|
|
311
|
+
const autoSyncResult = evaluateAutoSyncPatterns(
|
|
312
|
+
filePath,
|
|
313
|
+
targetRepo,
|
|
314
|
+
rules.autoSyncPatterns
|
|
315
|
+
);
|
|
316
|
+
if (autoSyncResult !== null) {
|
|
317
|
+
return autoSyncResult;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (rules.preventSelfSync) {
|
|
321
|
+
const selfSyncResult = preventSelfSync(filePath, targetRepo);
|
|
322
|
+
if (selfSyncResult !== null) {
|
|
323
|
+
return selfSyncResult;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (rules.preventCodexSync) {
|
|
327
|
+
const codexSyncResult = preventCodexSync(targetRepo, sourceRepo);
|
|
328
|
+
if (codexSyncResult !== null) {
|
|
329
|
+
return codexSyncResult;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
function evaluateAutoSyncPatterns(filePath, targetRepo, patterns) {
|
|
335
|
+
for (const autoPattern of patterns) {
|
|
336
|
+
if (matchPattern(autoPattern.pattern, filePath)) {
|
|
337
|
+
return evaluatePatterns({
|
|
338
|
+
value: targetRepo,
|
|
339
|
+
include: autoPattern.include,
|
|
340
|
+
...autoPattern.exclude && { exclude: autoPattern.exclude }
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
function preventSelfSync(filePath, targetRepo, _sourceRepo) {
|
|
347
|
+
const systemMatch = filePath.match(/systems\/([^/]+)\//);
|
|
348
|
+
if (systemMatch && systemMatch[1]) {
|
|
349
|
+
const systemName = systemMatch[1];
|
|
350
|
+
if (systemName === targetRepo) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
function preventCodexSync(targetRepo, sourceRepo) {
|
|
357
|
+
const isCodexRepo = targetRepo === sourceRepo || targetRepo.startsWith("codex.");
|
|
358
|
+
if (isCodexRepo) {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
function evaluateFrontmatterRules(options) {
|
|
364
|
+
const { metadata, targetRepo, allowOverrides } = options;
|
|
365
|
+
if (!allowOverrides) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
const include = metadata.codex_sync_include || [];
|
|
369
|
+
const exclude = metadata.codex_sync_exclude || [];
|
|
370
|
+
if (include.length === 0) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
return evaluatePatterns({
|
|
374
|
+
value: targetRepo,
|
|
375
|
+
include,
|
|
376
|
+
exclude
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function getTargetRepos(options) {
|
|
380
|
+
const { filePath, fileMetadata, sourceRepo, allRepos, rules } = options;
|
|
381
|
+
return allRepos.filter(
|
|
382
|
+
(targetRepo) => shouldSyncToRepo({
|
|
383
|
+
filePath,
|
|
384
|
+
fileMetadata,
|
|
385
|
+
targetRepo,
|
|
386
|
+
sourceRepo,
|
|
387
|
+
...rules && { rules }
|
|
388
|
+
})
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
exports.AutoSyncPatternSchema = AutoSyncPatternSchema;
|
|
393
|
+
exports.CodexConfigSchema = CodexConfigSchema;
|
|
394
|
+
exports.CodexError = CodexError;
|
|
395
|
+
exports.ConfigurationError = ConfigurationError;
|
|
396
|
+
exports.MetadataSchema = MetadataSchema;
|
|
397
|
+
exports.SyncRulesSchema = SyncRulesSchema;
|
|
398
|
+
exports.ValidationError = ValidationError;
|
|
399
|
+
exports.evaluatePatterns = evaluatePatterns;
|
|
400
|
+
exports.extractOrgFromRepoName = extractOrgFromRepoName;
|
|
401
|
+
exports.extractRawFrontmatter = extractRawFrontmatter;
|
|
402
|
+
exports.filterByPatterns = filterByPatterns;
|
|
403
|
+
exports.getDefaultConfig = getDefaultConfig;
|
|
404
|
+
exports.getDefaultDirectories = getDefaultDirectories;
|
|
405
|
+
exports.getDefaultRules = getDefaultRules;
|
|
406
|
+
exports.getTargetRepos = getTargetRepos;
|
|
407
|
+
exports.hasFrontmatter = hasFrontmatter;
|
|
408
|
+
exports.loadConfig = loadConfig;
|
|
409
|
+
exports.matchAnyPattern = matchAnyPattern;
|
|
410
|
+
exports.matchPattern = matchPattern;
|
|
411
|
+
exports.parseMetadata = parseMetadata;
|
|
412
|
+
exports.resolveOrganization = resolveOrganization;
|
|
413
|
+
exports.shouldSyncToRepo = shouldSyncToRepo;
|
|
414
|
+
exports.validateMetadata = validateMetadata;
|
|
415
|
+
//# sourceMappingURL=index.cjs.map
|
|
416
|
+
//# sourceMappingURL=index.cjs.map
|