@prover-coder-ai/context-doc 1.0.18 → 1.0.19

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.
@@ -1,54 +0,0 @@
1
- import { Context, Effect, Layer, Option } from "effect"
2
-
3
- export class RuntimeEnv extends Context.Tag("RuntimeEnv")<
4
- RuntimeEnv,
5
- {
6
- readonly argv: Effect.Effect<ReadonlyArray<string>>
7
- readonly cwd: Effect.Effect<string>
8
- readonly homedir: Effect.Effect<string>
9
- readonly envVar: (key: string) => Effect.Effect<Option.Option<string>>
10
- }
11
- >() {}
12
-
13
- const readProcess = (): NodeJS.Process | undefined => typeof process === "undefined" ? undefined : process
14
-
15
- const readEnv = (): NodeJS.ProcessEnv => readProcess()?.env ?? {}
16
-
17
- const resolveHomeDir = (env: NodeJS.ProcessEnv, cwdFallback: string): string => {
18
- const direct = env["HOME"] ?? env["USERPROFILE"]
19
- if (direct !== undefined) {
20
- return direct
21
- }
22
-
23
- const drive = env["HOMEDRIVE"]
24
- const path = env["HOMEPATH"]
25
- if (drive !== undefined && path !== undefined) {
26
- return `${drive}${path}`
27
- }
28
-
29
- return cwdFallback
30
- }
31
-
32
- // CHANGE: wrap process/os access behind a typed Effect service
33
- // WHY: keep shell dependencies injectable and testable
34
- // QUOTE(TZ): "Внешние зависимости: только через типизированные интерфейсы"
35
- // REF: user-2026-01-19-sync-rewrite
36
- // SOURCE: n/a
37
- // FORMAT THEOREM: forall k: env(k) -> Option<string>
38
- // PURITY: SHELL
39
- // EFFECT: Effect<RuntimeEnv, never, never>
40
- // INVARIANT: argv/cwd/homedir are read once per effect
41
- // COMPLEXITY: O(1)/O(1)
42
- export const RuntimeEnvLive = Layer.succeed(RuntimeEnv, {
43
- argv: Effect.sync(() => {
44
- const proc = readProcess()
45
- return proc === undefined ? [] : [...proc.argv]
46
- }),
47
- cwd: Effect.sync(() => readProcess()?.cwd() ?? "."),
48
- homedir: Effect.sync(() => {
49
- const proc = readProcess()
50
- const cwdFallback = proc?.cwd() ?? "."
51
- return resolveHomeDir(readEnv(), cwdFallback)
52
- }),
53
- envVar: (key) => Effect.sync(() => Option.fromNullable(readEnv()[key]))
54
- })
@@ -1,35 +0,0 @@
1
- import type * as Path from "@effect/platform/Path"
2
- import { Effect } from "effect"
3
-
4
- import type { CryptoService } from "../services/crypto.js"
5
- import type { FileSystemService } from "../services/file-system.js"
6
- import type { RuntimeEnv } from "../services/runtime-env.js"
7
- import { syncClaude } from "./sources/claude.js"
8
- import { syncCodex } from "./sources/codex.js"
9
- import { syncQwen } from "./sources/qwen.js"
10
- import type { SyncOptions } from "./types.js"
11
-
12
- type SyncProgramEnv = RuntimeEnv | FileSystemService | CryptoService | Path.Path
13
-
14
- // CHANGE: compose multi-source sync into a single Effect program
15
- // WHY: centralize orchestration while keeping each source isolated
16
- // QUOTE(TZ): "монодическая композиция"
17
- // REF: user-2026-01-19-sync-rewrite
18
- // SOURCE: n/a
19
- // FORMAT THEOREM: forall s in Sources: run(s) -> synced(s)
20
- // PURITY: SHELL
21
- // EFFECT: Effect<void, never, FileSystemService | RuntimeEnv | CryptoService | Path>
22
- // INVARIANT: sources run sequentially in fixed order
23
- // COMPLEXITY: O(n)/O(n)
24
- export const buildSyncProgram = (
25
- options: SyncOptions
26
- ): Effect.Effect<
27
- void,
28
- never,
29
- SyncProgramEnv
30
- > =>
31
- Effect.gen(function*(_) {
32
- yield* _(syncClaude(options))
33
- yield* _(syncCodex(options))
34
- yield* _(syncQwen(options))
35
- })
@@ -1,229 +0,0 @@
1
- import * as Path from "@effect/platform/Path"
2
- import { Console, Effect, Match, pipe } from "effect"
3
-
4
- import { type DirectoryEntry, FileSystemService } from "../services/file-system.js"
5
- import { type SyncError, syncError, type SyncOptions, type SyncSource } from "./types.js"
6
-
7
- const forEach = Effect.forEach
8
-
9
- type FilteredSourceEnv<R> = R | FileSystemService | Path.Path
10
-
11
- const ensureDirectory = (
12
- directory: string
13
- ): Effect.Effect<void, SyncError, FileSystemService> =>
14
- Effect.gen(function*(_) {
15
- const fs = yield* _(FileSystemService)
16
- yield* _(fs.makeDirectory(directory))
17
- })
18
-
19
- // CHANGE: expose recursive traversal for callers that filter by entry
20
- // WHY: reuse traversal logic across sources without duplicating Match logic
21
- // QUOTE(TZ): "минимальный корректный diff"
22
- // REF: user-2026-01-19-sync-rewrite
23
- // SOURCE: n/a
24
- // FORMAT THEOREM: forall f: collectFiles(root, p) -> p(f) => exists(f)
25
- // PURITY: SHELL
26
- // EFFECT: Effect<ReadonlyArray<string>, SyncError, FileSystemService>
27
- // INVARIANT: returned paths are absolute and exist in traversal
28
- // COMPLEXITY: O(n)/O(n)
29
- export const collectFiles = (
30
- root: string,
31
- isRelevant: (entry: DirectoryEntry) => boolean
32
- ): Effect.Effect<ReadonlyArray<string>, SyncError, FileSystemService> =>
33
- Effect.gen(function*(_) {
34
- const fs = yield* _(FileSystemService)
35
- const entries = yield* _(fs.readDirectory(root))
36
- const chunks = yield* _(
37
- forEach(entries, (entry) =>
38
- Match.value(entry.kind).pipe(
39
- Match.when("directory", () => collectFiles(entry.path, isRelevant)),
40
- Match.when("file", () => isRelevant(entry) ? Effect.succeed([entry.path]) : Effect.succeed([])),
41
- Match.when("other", () => Effect.succeed([])),
42
- Match.exhaustive
43
- ))
44
- )
45
- return chunks.flat()
46
- })
47
-
48
- // CHANGE: share relative path copy for multiple sync sources
49
- // WHY: avoid repeating path math in each source
50
- // QUOTE(TZ): "SHELL → CORE (but not наоборот)"
51
- // REF: user-2026-01-19-sync-rewrite
52
- // SOURCE: n/a
53
- // FORMAT THEOREM: forall f: copy(f) -> relative(f) preserved
54
- // PURITY: SHELL
55
- // EFFECT: Effect<void, SyncError, FileSystemService | Path>
56
- // INVARIANT: destination preserves source-relative path
57
- // COMPLEXITY: O(1)/O(1)
58
- export const copyFilePreservingRelativePath = (
59
- sourceRoot: string,
60
- destinationRoot: string,
61
- filePath: string
62
- ): Effect.Effect<void, SyncError, FileSystemService | Path.Path> =>
63
- Effect.gen(function*(_) {
64
- const fs = yield* _(FileSystemService)
65
- const path = yield* _(Path.Path)
66
- const relative = path.relative(sourceRoot, filePath)
67
- const targetPath = path.join(destinationRoot, relative)
68
- yield* _(fs.makeDirectory(path.dirname(targetPath)))
69
- yield* _(fs.copyFile(filePath, targetPath))
70
- })
71
-
72
- // CHANGE: expose reusable first-match search with effectful predicate
73
- // WHY: remove duplicated recursive search logic across sources
74
- // QUOTE(TZ): "минимальный корректный diff"
75
- // REF: user-2026-01-19-sync-rewrite
76
- // SOURCE: n/a
77
- // FORMAT THEOREM: forall c: match(c) -> returns first c
78
- // PURITY: SHELL
79
- // EFFECT: Effect<string | undefined, SyncError, R>
80
- // INVARIANT: result is undefined iff no candidate matches
81
- // COMPLEXITY: O(n)/O(1)
82
- export const findFirstMatching = <R>(
83
- candidates: ReadonlyArray<string>,
84
- matches: (candidate: string) => Effect.Effect<boolean, SyncError, R>
85
- ): Effect.Effect<string | undefined, SyncError, R> => {
86
- const loop = (
87
- remaining: ReadonlyArray<string>
88
- ): Effect.Effect<string | undefined, SyncError, R> =>
89
- Effect.gen(function*(_) {
90
- const [candidate, ...rest] = remaining
91
- if (candidate === undefined) {
92
- return
93
- }
94
- const matched = yield* _(matches(candidate))
95
- if (!matched) {
96
- return yield* _(loop(rest))
97
- }
98
- return candidate
99
- })
100
-
101
- return loop(candidates)
102
- }
103
-
104
- // CHANGE: ensure destination directory is created through Effect-typed fs
105
- // WHY: keep IO effects inside SHELL and reuse in sync flows
106
- // QUOTE(TZ): "SHELL: Все эффекты изолированы в тонкой оболочке"
107
- // REF: user-2026-01-19-sync-rewrite
108
- // SOURCE: n/a
109
- // FORMAT THEOREM: forall d: ensureDirectory(d) -> exists(d)
110
- // PURITY: SHELL
111
- // EFFECT: Effect<void, SyncError, FileSystemService>
112
- // INVARIANT: directory exists after successful effect
113
- // COMPLEXITY: O(1)/O(1)
114
- export const ensureDestination = ensureDirectory
115
-
116
- // CHANGE: resolve project root for cwd-based matching and destination paths
117
- // WHY: allow running from sub-packages while targeting the repo root
118
- // QUOTE(TZ): "передай root-path на основную папку"
119
- // REF: user-2026-01-19-project-root
120
- // SOURCE: n/a
121
- // FORMAT THEOREM: forall o: root(o) = resolve(o.projectRoot ?? o.cwd)
122
- // PURITY: SHELL
123
- // EFFECT: n/a
124
- // INVARIANT: resolved root is absolute
125
- // COMPLEXITY: O(1)/O(1)
126
- export const resolveProjectRoot = (
127
- path: Path.Path,
128
- options: SyncOptions
129
- ): string => path.resolve(options.projectRoot ?? options.cwd)
130
-
131
- // CHANGE: copy filtered files with typed errors and deterministic traversal
132
- // WHY: reuse shared traversal logic for Qwen/Claude syncs
133
- // QUOTE(TZ): "FUNCTIONAL CORE, IMPERATIVE SHELL"
134
- // REF: user-2026-01-19-sync-rewrite
135
- // SOURCE: n/a
136
- // FORMAT THEOREM: forall f: relevant(f) -> copied(f)
137
- // PURITY: SHELL
138
- // EFFECT: Effect<number, SyncError, FileSystemService | Path>
139
- // INVARIANT: copied count equals length of relevant files
140
- // COMPLEXITY: O(n)/O(n)
141
- export const copyFilteredFiles = (
142
- sourceRoot: string,
143
- destinationRoot: string,
144
- isRelevant: (entry: DirectoryEntry, fullPath: string) => boolean,
145
- errorReason: string
146
- ): Effect.Effect<number, SyncError, FileSystemService | Path.Path> =>
147
- pipe(
148
- Effect.gen(function*(_) {
149
- const files = yield* _(collectFiles(sourceRoot, (entry) => isRelevant(entry, entry.path)))
150
- yield* _(
151
- forEach(files, (filePath) => copyFilePreservingRelativePath(sourceRoot, destinationRoot, filePath))
152
- )
153
- return files.length
154
- }),
155
- Effect.mapError(() => syncError(sourceRoot, errorReason))
156
- )
157
-
158
- // CHANGE: build a SyncSource using shared filtered copy logic
159
- // WHY: eliminate repeated source definitions for file-extension based syncs
160
- // QUOTE(TZ): "минимальный корректный diff"
161
- // REF: user-2026-01-19-sync-rewrite
162
- // SOURCE: n/a
163
- // FORMAT THEOREM: forall s: createFilteredSource(s) -> copyFilteredFiles(s)
164
- // PURITY: SHELL
165
- // EFFECT: Effect<number, SyncError, R>
166
- // INVARIANT: copy uses shared filter predicate
167
- // COMPLEXITY: O(n)/O(n)
168
- export const createFilteredSource = <R>(params: {
169
- readonly name: SyncSource<FilteredSourceEnv<R>>["name"]
170
- readonly destSubdir: SyncSource<FilteredSourceEnv<R>>["destSubdir"]
171
- readonly resolveSource: SyncSource<FilteredSourceEnv<R>>["resolveSource"]
172
- readonly filter: (entry: DirectoryEntry, fullPath: string) => boolean
173
- readonly errorReason: string
174
- }): SyncSource<FilteredSourceEnv<R>> => ({
175
- name: params.name,
176
- destSubdir: params.destSubdir,
177
- resolveSource: params.resolveSource,
178
- copy: (sourceDir, destinationDir) => copyFilteredFiles(sourceDir, destinationDir, params.filter, params.errorReason)
179
- })
180
-
181
- // CHANGE: standardize per-source sync orchestration with skip-on-error logging
182
- // WHY: keep the shell thin and consistent for all sources
183
- // QUOTE(TZ): "SHELL → CORE (but not наоборот)"
184
- // REF: user-2026-01-19-sync-rewrite
185
- // SOURCE: n/a
186
- // FORMAT THEOREM: forall s: runSyncSource(s) -> logs(s)
187
- // PURITY: SHELL
188
- // EFFECT: Effect<void, never, FileSystemService | Path>
189
- // INVARIANT: source==destination implies no copy
190
- // COMPLEXITY: O(n)/O(n)
191
- export const runSyncSource = <R>(
192
- source: SyncSource<R>,
193
- options: SyncOptions
194
- ): Effect.Effect<void, never, R | FileSystemService | Path.Path> =>
195
- pipe(
196
- Effect.gen(function*(_) {
197
- const path = yield* _(Path.Path)
198
- const resolvedSource = yield* _(source.resolveSource(options))
199
- const destination = path.join(
200
- resolveProjectRoot(path, options),
201
- ".knowledge",
202
- source.destSubdir
203
- )
204
-
205
- if (path.resolve(resolvedSource) === path.resolve(destination)) {
206
- yield* _(
207
- Console.log(
208
- `${source.name}: source equals destination; skipping copy to avoid duplicates`
209
- )
210
- )
211
- return
212
- }
213
-
214
- yield* _(ensureDirectory(destination))
215
- const copied = yield* _(source.copy(resolvedSource, destination, options))
216
- yield* _(
217
- Console.log(
218
- `${source.name}: copied ${copied} files from ${resolvedSource} to ${destination}`
219
- )
220
- )
221
- }),
222
- Effect.matchEffect({
223
- onFailure: (error: SyncError) =>
224
- Console.log(
225
- `${source.name}: source not found; skipped syncing (${error.reason})`
226
- ),
227
- onSuccess: () => Effect.void
228
- })
229
- )
@@ -1,54 +0,0 @@
1
- import * as Path from "@effect/platform/Path"
2
- import { Effect } from "effect"
3
-
4
- import { FileSystemService } from "../../services/file-system.js"
5
- import { RuntimeEnv } from "../../services/runtime-env.js"
6
- import { createFilteredSource, resolveProjectRoot, runSyncSource } from "../shared.js"
7
- import { type SyncError, syncError, type SyncOptions, type SyncSource } from "../types.js"
8
-
9
- type ClaudeEnv = RuntimeEnv | FileSystemService | Path.Path
10
-
11
- const slugFromCwd = (cwd: string): string => `-${cwd.replace(/^\/+/, "").replaceAll("\\", "-").replaceAll("/", "-")}`
12
-
13
- const resolveClaudeProjectDir = (
14
- options: SyncOptions
15
- ): Effect.Effect<string, SyncError, ClaudeEnv> =>
16
- Effect.gen(function*(_) {
17
- const env = yield* _(RuntimeEnv)
18
- const homeDir = yield* _(env.homedir)
19
- const path = yield* _(Path.Path)
20
- const base = options.claudeProjectsRoot ??
21
- path.join(homeDir, ".claude", "projects")
22
- const candidate = path.join(base, slugFromCwd(resolveProjectRoot(path, options)))
23
- const fs = yield* _(FileSystemService)
24
- const exists = yield* _(fs.exists(candidate))
25
- if (!exists) {
26
- return yield* _(
27
- Effect.fail(syncError(".claude", "Claude project directory is missing"))
28
- )
29
- }
30
-
31
- return candidate
32
- })
33
-
34
- const claudeSource: SyncSource<ClaudeEnv> = createFilteredSource({
35
- name: "Claude",
36
- destSubdir: ".claude",
37
- resolveSource: resolveClaudeProjectDir,
38
- filter: (entry, fullPath) => entry.kind === "file" && fullPath.endsWith(".jsonl"),
39
- errorReason: "Cannot traverse Claude project"
40
- })
41
-
42
- // CHANGE: sync Claude dialog files through shared sync runner
43
- // WHY: keep Claude-specific path resolution isolated from other sources
44
- // QUOTE(TZ): "SHELL: Все эффекты (IO, сеть, БД, env/process) изолированы"
45
- // REF: user-2026-01-19-sync-rewrite
46
- // SOURCE: n/a
47
- // FORMAT THEOREM: forall f: jsonl(f) -> copied(f)
48
- // PURITY: SHELL
49
- // EFFECT: Effect<void, never, FileSystemService | RuntimeEnv | Path>
50
- // INVARIANT: only .jsonl files are copied
51
- // COMPLEXITY: O(n)/O(n)
52
- export const syncClaude = (
53
- options: SyncOptions
54
- ): Effect.Effect<void, never, ClaudeEnv> => runSyncSource(claudeSource, options)
@@ -1,261 +0,0 @@
1
- import * as Path from "@effect/platform/Path"
2
- import * as Schema from "@effect/schema/Schema"
3
- import { Console, Effect, Option, pipe } from "effect"
4
-
5
- import type { JsonValue, ProjectLocator } from "../../../core/knowledge.js"
6
- import { buildProjectLocator, valueMatchesProject } from "../../../core/knowledge.js"
7
- import { FileSystemService } from "../../services/file-system.js"
8
- import { RuntimeEnv } from "../../services/runtime-env.js"
9
- import {
10
- collectFiles,
11
- copyFilePreservingRelativePath,
12
- ensureDestination,
13
- findFirstMatching,
14
- resolveProjectRoot
15
- } from "../shared.js"
16
- import { type SyncError, syncError, type SyncOptions } from "../types.js"
17
-
18
- type CodexEnv = RuntimeEnv | FileSystemService | Path.Path
19
- type CodexFsEnv = FileSystemService | Path.Path
20
-
21
- const some = Option.some
22
- const forEach = Effect.forEach
23
-
24
- const JsonValueSchema: Schema.Schema<JsonValue> = Schema.suspend(() =>
25
- Schema.Union(
26
- Schema.String,
27
- Schema.Number,
28
- Schema.Boolean,
29
- Schema.Null,
30
- Schema.Array(JsonValueSchema),
31
- Schema.Record({ key: Schema.String, value: JsonValueSchema })
32
- )
33
- )
34
-
35
- const parseJsonLine = (
36
- line: string
37
- ): Effect.Effect<Option.Option<JsonValue>> =>
38
- pipe(
39
- Schema.decode(Schema.parseJson(JsonValueSchema))(line),
40
- Effect.match({
41
- onFailure: () => Option.none(),
42
- onSuccess: (value) => some(value)
43
- })
44
- )
45
-
46
- const resolveEnvValue = (envValue: Option.Option<string>): string | undefined => Option.getOrUndefined(envValue)
47
-
48
- const buildLocator = (path: Path.Path, projectRoot: string): ProjectLocator => {
49
- const normalizedRoot = path.resolve(projectRoot)
50
- const isWithinRoot = (candidate: string): boolean => {
51
- const normalizedCandidate = path.resolve(candidate)
52
- const relative = path.relative(normalizedRoot, normalizedCandidate)
53
- return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))
54
- }
55
- return buildProjectLocator(normalizedRoot, isWithinRoot)
56
- }
57
-
58
- const containsJsonl = (root: string): Effect.Effect<boolean, SyncError, FileSystemService> =>
59
- Effect.gen(function*(_) {
60
- const fs = yield* _(FileSystemService)
61
- const entries = yield* _(fs.readDirectory(root))
62
- for (const entry of entries) {
63
- if (entry.kind === "file" && entry.path.endsWith(".jsonl")) {
64
- return true
65
- }
66
- if (entry.kind === "directory") {
67
- const found = yield* _(containsJsonl(entry.path))
68
- if (found) {
69
- return true
70
- }
71
- }
72
- }
73
- return false
74
- })
75
-
76
- const hasJsonlInCandidate = (
77
- candidate: string
78
- ): Effect.Effect<boolean, SyncError, FileSystemService> =>
79
- Effect.gen(function*(_) {
80
- const fs = yield* _(FileSystemService)
81
- const exists = yield* _(fs.exists(candidate))
82
- if (!exists) {
83
- return false
84
- }
85
- return yield* _(containsJsonl(candidate))
86
- })
87
-
88
- const findFirstExistingWithJsonl = (
89
- candidates: ReadonlyArray<string>
90
- ): Effect.Effect<string | undefined, SyncError, FileSystemService> => findFirstMatching(candidates, hasJsonlInCandidate)
91
-
92
- const resolveSourceDir = (
93
- options: SyncOptions
94
- ): Effect.Effect<string, SyncError, CodexEnv> =>
95
- Effect.gen(function*(_) {
96
- const env = yield* _(RuntimeEnv)
97
- const path = yield* _(Path.Path)
98
- const envSource = resolveEnvValue(yield* _(env.envVar("CODEX_SOURCE_DIR")))
99
- const homeDir = yield* _(env.homedir)
100
- const projectRoot = resolveProjectRoot(path, options)
101
- let metaCandidate: string | undefined
102
- if (options.metaRoot !== undefined) {
103
- metaCandidate = options.metaRoot.endsWith(".codex")
104
- ? options.metaRoot
105
- : path.join(options.metaRoot, ".codex")
106
- }
107
- const localSource = path.join(projectRoot, ".codex")
108
- const localKnowledge = path.join(projectRoot, ".knowledge", ".codex")
109
- const homeSource = path.join(homeDir, ".codex")
110
- const homeKnowledge = path.join(homeDir, ".knowledge", ".codex")
111
-
112
- const candidates = [
113
- options.sourceDir,
114
- envSource,
115
- metaCandidate,
116
- localSource,
117
- homeSource,
118
- localKnowledge,
119
- homeKnowledge
120
- ].filter((candidate): candidate is string => candidate !== undefined)
121
-
122
- const existing = yield* _(findFirstExistingWithJsonl(candidates))
123
- if (existing === undefined) {
124
- return yield* _(
125
- Effect.fail(
126
- syncError(
127
- ".codex",
128
- `No .jsonl files found in .codex candidates; checked: ${candidates.join(", ")}`
129
- )
130
- )
131
- )
132
- }
133
-
134
- return existing
135
- })
136
-
137
- const resolveLocator = (
138
- options: SyncOptions
139
- ): Effect.Effect<ProjectLocator, never, Path.Path> =>
140
- Effect.gen(function*(_) {
141
- const path = yield* _(Path.Path)
142
- const projectRoot = resolveProjectRoot(path, options)
143
- return buildLocator(path, projectRoot)
144
- })
145
-
146
- const lineMatchesProject = (
147
- line: string,
148
- locator: ProjectLocator
149
- ): Effect.Effect<boolean> =>
150
- pipe(
151
- parseJsonLine(line),
152
- Effect.map((parsed) => Option.exists(parsed, (value) => valueMatchesProject(value, locator)))
153
- )
154
-
155
- const fileMatchesProject = (
156
- filePath: string,
157
- locator: ProjectLocator
158
- ): Effect.Effect<boolean, SyncError, FileSystemService> =>
159
- Effect.gen(function*(_) {
160
- const fs = yield* _(FileSystemService)
161
- const content = yield* _(fs.readFileString(filePath))
162
- const lines = content.split("\n")
163
- const matches = yield* _(
164
- forEach(lines, (line) => {
165
- const trimmed = line.trim()
166
- return trimmed.length === 0
167
- ? Effect.succeed(false)
168
- : lineMatchesProject(trimmed, locator)
169
- })
170
- )
171
- return matches.some(Boolean)
172
- })
173
-
174
- const selectRelevantFiles = (
175
- files: ReadonlyArray<string>,
176
- locator: ProjectLocator
177
- ): Effect.Effect<ReadonlyArray<string>, SyncError, FileSystemService> =>
178
- pipe(
179
- forEach(files, (filePath) =>
180
- pipe(
181
- fileMatchesProject(filePath, locator),
182
- Effect.map((matches) => ({ filePath, matches }))
183
- )),
184
- Effect.map((results) =>
185
- results
186
- .filter((result) => result.matches)
187
- .map((result) => result.filePath)
188
- )
189
- )
190
-
191
- const copyCodexFiles = (
192
- sourceDir: string,
193
- destinationDir: string,
194
- locator: ProjectLocator
195
- ): Effect.Effect<void, SyncError, CodexFsEnv> =>
196
- Effect.gen(function*(_) {
197
- yield* _(ensureDestination(destinationDir))
198
- const allJsonlFiles = yield* _(
199
- collectFiles(sourceDir, (entry) => entry.kind === "file" && entry.path.endsWith(".jsonl"))
200
- )
201
- const relevantFiles = yield* _(selectRelevantFiles(allJsonlFiles, locator))
202
- yield* _(
203
- forEach(relevantFiles, (filePath) => copyFilePreservingRelativePath(sourceDir, destinationDir, filePath))
204
- )
205
- yield* _(
206
- Console.log(
207
- `Codex: copied ${relevantFiles.length} files from ${sourceDir} to ${destinationDir}`
208
- )
209
- )
210
- })
211
-
212
- // CHANGE: extract Codex dialog sync into dedicated module
213
- // WHY: keep Codex-specific shell effects isolated from other sources
214
- // QUOTE(TZ): "FUNCTIONAL CORE, IMPERATIVE SHELL"
215
- // REF: user-2026-01-19-sync-rewrite
216
- // SOURCE: n/a
217
- // FORMAT THEOREM: forall f: relevant(f, locator) -> copied(f)
218
- // PURITY: SHELL
219
- // EFFECT: Effect<void, never, FileSystemService | RuntimeEnv | Path>
220
- // INVARIANT: copied files contain at least one project-matching line by projectRoot
221
- // COMPLEXITY: O(n)/O(n)
222
- export const syncCodex = (
223
- options: SyncOptions
224
- ): Effect.Effect<void, never, CodexEnv> =>
225
- Effect.gen(function*(_) {
226
- const locator = yield* _(resolveLocator(options))
227
- const sourceDir = yield* _(resolveSourceDir(options))
228
- const path = yield* _(Path.Path)
229
- const destinationDir = options.destinationDir ??
230
- path.join(resolveProjectRoot(path, options), ".knowledge", ".codex")
231
-
232
- if (path.resolve(sourceDir) === path.resolve(destinationDir)) {
233
- yield* _(
234
- Console.log(
235
- "Codex source equals destination; skipping copy to avoid duplicates"
236
- )
237
- )
238
- return
239
- }
240
-
241
- yield* _(copyCodexFiles(sourceDir, destinationDir, locator))
242
- }).pipe(
243
- Effect.matchEffect({
244
- onFailure: (error: SyncError) =>
245
- Console.log(
246
- `Codex source not found; skipped syncing Codex dialog files (${error.reason})`
247
- ),
248
- onSuccess: () => Effect.void
249
- })
250
- )
251
-
252
- // CHANGE: expose DirectoryEntry type for Codex traversal helpers
253
- // WHY: reuse typed filesystem entries without leaking Node types
254
- // QUOTE(TZ): "CORE never calls SHELL"
255
- // REF: user-2026-01-19-sync-rewrite
256
- // SOURCE: n/a
257
- // FORMAT THEOREM: forall e: DirectoryEntry -> shellOnly(e)
258
- // PURITY: SHELL
259
- // EFFECT: n/a
260
- // INVARIANT: entry.kind is one of file|directory|other
261
- // COMPLEXITY: O(1)/O(1)