@prover-coder-ai/context-doc 1.0.17 → 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.
- package/README.md +18 -0
- package/dist/main.js +553 -0
- package/dist/main.js.map +1 -0
- package/package.json +6 -1
- package/.jscpd.json +0 -16
- package/CHANGELOG.md +0 -37
- package/biome.json +0 -37
- package/eslint.config.mts +0 -305
- package/eslint.effect-ts-check.config.mjs +0 -220
- package/linter.config.json +0 -33
- package/src/app/main.ts +0 -29
- package/src/app/program.ts +0 -32
- package/src/core/knowledge.ts +0 -115
- package/src/shell/cli.ts +0 -75
- package/src/shell/services/crypto.ts +0 -50
- package/src/shell/services/file-system.ts +0 -142
- package/src/shell/services/runtime-env.ts +0 -54
- package/src/shell/sync/index.ts +0 -35
- package/src/shell/sync/shared.ts +0 -229
- package/src/shell/sync/sources/claude.ts +0 -54
- package/src/shell/sync/sources/codex.ts +0 -261
- package/src/shell/sync/sources/qwen.ts +0 -97
- package/src/shell/sync/types.ts +0 -47
- package/tests/core/knowledge.test.ts +0 -95
- package/tests/shell/cli-pack.test.ts +0 -176
- package/tests/shell/sync-knowledge.test.ts +0 -203
- package/tests/support/fs-helpers.ts +0 -50
- package/tsconfig.json +0 -19
- package/vite.config.ts +0 -32
- package/vitest.config.ts +0 -85
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @prover-coder-ai/context-doc
|
|
2
|
+
|
|
3
|
+
Syncs AI assistant conversation history (Claude, Codex, Qwen) to your project's `.knowledge` folder.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @prover-coder-ai/context-doc
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
user@arch ~/effect-template (main)> npx @prover-coder-ai/context-doc
|
|
15
|
+
Claude: source not found; skipped syncing (Claude project directory is missing)
|
|
16
|
+
Codex: copied 9 files from /home/user/.codex to /home/user/effect-template/.knowledge/.codex
|
|
17
|
+
Qwen: copied 1 files from /home/user/.qwen/tmp/040644799b0a7c05ac8df9cf26da0eab978287161b20ee2addc8105393cf98dd to /home/user/effect-template/.knowledge/.qwen
|
|
18
|
+
```
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
2
|
+
import { Context, Layer, pipe, Effect, Option, Match, Console } from "effect";
|
|
3
|
+
import * as FileSystem from "@effect/platform/FileSystem";
|
|
4
|
+
import * as Path from "@effect/platform/Path";
|
|
5
|
+
import * as Schema from "@effect/schema/Schema";
|
|
6
|
+
class CryptoService extends Context.Tag("CryptoService")() {
|
|
7
|
+
}
|
|
8
|
+
const cryptoError = (reason) => ({
|
|
9
|
+
_tag: "CryptoError",
|
|
10
|
+
reason
|
|
11
|
+
});
|
|
12
|
+
const toHex = (buffer) => [...new Uint8Array(buffer)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
13
|
+
const digestSha256 = (value) => pipe(
|
|
14
|
+
Effect.tryPromise({
|
|
15
|
+
try: () => {
|
|
16
|
+
const crypto = globalThis.crypto;
|
|
17
|
+
const bytes = new TextEncoder().encode(value);
|
|
18
|
+
return crypto.subtle.digest("SHA-256", bytes);
|
|
19
|
+
},
|
|
20
|
+
catch: (error) => cryptoError(error instanceof Error ? error.message : "Crypto digest failed")
|
|
21
|
+
}),
|
|
22
|
+
Effect.map((buffer) => toHex(buffer))
|
|
23
|
+
);
|
|
24
|
+
const CryptoServiceLive = Layer.succeed(CryptoService, {
|
|
25
|
+
sha256: (value) => digestSha256(value)
|
|
26
|
+
});
|
|
27
|
+
const syncError = (pathValue, reason) => ({
|
|
28
|
+
_tag: "SyncError",
|
|
29
|
+
path: pathValue,
|
|
30
|
+
reason
|
|
31
|
+
});
|
|
32
|
+
class FileSystemService extends Context.Tag("FileSystemService")() {
|
|
33
|
+
}
|
|
34
|
+
const forEach$2 = Effect.forEach;
|
|
35
|
+
const resolveEntryPath = (path, root, entry) => path.isAbsolute(entry) ? entry : path.join(root, entry);
|
|
36
|
+
const entryKindFromInfo = (info) => {
|
|
37
|
+
if (info.type === "Directory") {
|
|
38
|
+
return "directory";
|
|
39
|
+
}
|
|
40
|
+
if (info.type === "File") {
|
|
41
|
+
return "file";
|
|
42
|
+
}
|
|
43
|
+
return "other";
|
|
44
|
+
};
|
|
45
|
+
const toDirectoryEntry = (path, entryPath, info) => ({
|
|
46
|
+
name: path.basename(entryPath),
|
|
47
|
+
path: entryPath,
|
|
48
|
+
kind: entryKindFromInfo(info)
|
|
49
|
+
});
|
|
50
|
+
const missingEntry = (path, entryPath) => ({
|
|
51
|
+
name: path.basename(entryPath),
|
|
52
|
+
path: entryPath,
|
|
53
|
+
kind: "other"
|
|
54
|
+
});
|
|
55
|
+
const isNotFoundError = (error) => error._tag === "SystemError" && error.reason === "NotFound";
|
|
56
|
+
const readEntry = (fs, path, entryPath) => pipe(
|
|
57
|
+
fs.stat(entryPath),
|
|
58
|
+
Effect.map((info) => toDirectoryEntry(path, entryPath, info)),
|
|
59
|
+
Effect.catchIf(isNotFoundError, () => Effect.succeed(missingEntry(path, entryPath))),
|
|
60
|
+
Effect.mapError(() => syncError(entryPath, "Cannot read directory entry"))
|
|
61
|
+
);
|
|
62
|
+
const resolveEntry = (fs, path, root, entry) => {
|
|
63
|
+
const entryPath = resolveEntryPath(path, root, entry);
|
|
64
|
+
return readEntry(fs, path, entryPath);
|
|
65
|
+
};
|
|
66
|
+
const FileSystemLive = Layer.effect(
|
|
67
|
+
FileSystemService,
|
|
68
|
+
Effect.gen(function* (_) {
|
|
69
|
+
const fs = yield* _(FileSystem.FileSystem);
|
|
70
|
+
const path = yield* _(Path.Path);
|
|
71
|
+
const readFileString = (pathValue) => pipe(
|
|
72
|
+
fs.readFileString(pathValue, "utf8"),
|
|
73
|
+
Effect.mapError(() => syncError(pathValue, "Cannot read file"))
|
|
74
|
+
);
|
|
75
|
+
const readDirectory = (pathValue) => pipe(
|
|
76
|
+
fs.readDirectory(pathValue),
|
|
77
|
+
Effect.mapError(() => syncError(pathValue, "Cannot read directory")),
|
|
78
|
+
Effect.flatMap((entries) => forEach$2(entries, (entry) => resolveEntry(fs, path, pathValue, entry)))
|
|
79
|
+
);
|
|
80
|
+
const makeDirectory = (pathValue) => pipe(
|
|
81
|
+
fs.makeDirectory(pathValue, { recursive: true }),
|
|
82
|
+
Effect.mapError(() => syncError(pathValue, "Cannot create destination directory structure"))
|
|
83
|
+
);
|
|
84
|
+
const copyFile = (sourcePath, destinationPath) => pipe(
|
|
85
|
+
fs.copyFile(sourcePath, destinationPath),
|
|
86
|
+
Effect.mapError(() => syncError(sourcePath, "Cannot copy file into destination"))
|
|
87
|
+
);
|
|
88
|
+
const exists = (pathValue) => pipe(
|
|
89
|
+
fs.exists(pathValue),
|
|
90
|
+
Effect.mapError(() => syncError(pathValue, "Cannot check path existence"))
|
|
91
|
+
);
|
|
92
|
+
return {
|
|
93
|
+
readFileString,
|
|
94
|
+
readDirectory,
|
|
95
|
+
makeDirectory,
|
|
96
|
+
copyFile,
|
|
97
|
+
exists
|
|
98
|
+
};
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
class RuntimeEnv extends Context.Tag("RuntimeEnv")() {
|
|
102
|
+
}
|
|
103
|
+
const readProcess = () => typeof process === "undefined" ? void 0 : process;
|
|
104
|
+
const readEnv = () => readProcess()?.env ?? {};
|
|
105
|
+
const resolveHomeDir = (env, cwdFallback) => {
|
|
106
|
+
const direct = env["HOME"] ?? env["USERPROFILE"];
|
|
107
|
+
if (direct !== void 0) {
|
|
108
|
+
return direct;
|
|
109
|
+
}
|
|
110
|
+
const drive = env["HOMEDRIVE"];
|
|
111
|
+
const path = env["HOMEPATH"];
|
|
112
|
+
if (drive !== void 0 && path !== void 0) {
|
|
113
|
+
return `${drive}${path}`;
|
|
114
|
+
}
|
|
115
|
+
return cwdFallback;
|
|
116
|
+
};
|
|
117
|
+
const RuntimeEnvLive = Layer.succeed(RuntimeEnv, {
|
|
118
|
+
argv: Effect.sync(() => {
|
|
119
|
+
const proc = readProcess();
|
|
120
|
+
return proc === void 0 ? [] : [...proc.argv];
|
|
121
|
+
}),
|
|
122
|
+
cwd: Effect.sync(() => readProcess()?.cwd() ?? "."),
|
|
123
|
+
homedir: Effect.sync(() => {
|
|
124
|
+
const proc = readProcess();
|
|
125
|
+
const cwdFallback = proc?.cwd() ?? ".";
|
|
126
|
+
return resolveHomeDir(readEnv(), cwdFallback);
|
|
127
|
+
}),
|
|
128
|
+
envVar: (key) => Effect.sync(() => Option.fromNullable(readEnv()[key]))
|
|
129
|
+
});
|
|
130
|
+
const flagMap = /* @__PURE__ */ new Map([
|
|
131
|
+
["--project-root", "projectRoot"],
|
|
132
|
+
["-r", "projectRoot"],
|
|
133
|
+
["--source", "sourceDir"],
|
|
134
|
+
["-s", "sourceDir"],
|
|
135
|
+
["--dest", "destinationDir"],
|
|
136
|
+
["-d", "destinationDir"],
|
|
137
|
+
["--project-url", "repositoryUrlOverride"],
|
|
138
|
+
["--project-name", "repositoryUrlOverride"],
|
|
139
|
+
["--meta-root", "metaRoot"],
|
|
140
|
+
["--qwen-source", "qwenSourceDir"],
|
|
141
|
+
["--claude-projects", "claudeProjectsRoot"]
|
|
142
|
+
]);
|
|
143
|
+
const parseArgs = (args, cwd) => {
|
|
144
|
+
let result = { cwd };
|
|
145
|
+
let index = 0;
|
|
146
|
+
while (index < args.length) {
|
|
147
|
+
const arg = args[index];
|
|
148
|
+
if (arg === void 0) {
|
|
149
|
+
index += 1;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const key = flagMap.get(arg);
|
|
153
|
+
if (key === void 0) {
|
|
154
|
+
index += 1;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const value = args[index + 1];
|
|
158
|
+
if (value !== void 0) {
|
|
159
|
+
result = { ...result, [key]: value };
|
|
160
|
+
index += 2;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
index += 1;
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
};
|
|
167
|
+
const readSyncOptions = Effect.gen(function* (_) {
|
|
168
|
+
const env = yield* _(RuntimeEnv);
|
|
169
|
+
const argv = yield* _(env.argv);
|
|
170
|
+
const cwd = yield* _(env.cwd);
|
|
171
|
+
return parseArgs(argv.slice(2), cwd);
|
|
172
|
+
});
|
|
173
|
+
const forEach$1 = Effect.forEach;
|
|
174
|
+
const ensureDirectory = (directory) => Effect.gen(function* (_) {
|
|
175
|
+
const fs = yield* _(FileSystemService);
|
|
176
|
+
yield* _(fs.makeDirectory(directory));
|
|
177
|
+
});
|
|
178
|
+
const collectFiles = (root, isRelevant) => Effect.gen(function* (_) {
|
|
179
|
+
const fs = yield* _(FileSystemService);
|
|
180
|
+
const entries = yield* _(fs.readDirectory(root));
|
|
181
|
+
const chunks = yield* _(
|
|
182
|
+
forEach$1(entries, (entry) => Match.value(entry.kind).pipe(
|
|
183
|
+
Match.when("directory", () => collectFiles(entry.path, isRelevant)),
|
|
184
|
+
Match.when("file", () => isRelevant(entry) ? Effect.succeed([entry.path]) : Effect.succeed([])),
|
|
185
|
+
Match.when("other", () => Effect.succeed([])),
|
|
186
|
+
Match.exhaustive
|
|
187
|
+
))
|
|
188
|
+
);
|
|
189
|
+
return chunks.flat();
|
|
190
|
+
});
|
|
191
|
+
const copyFilePreservingRelativePath = (sourceRoot, destinationRoot, filePath) => Effect.gen(function* (_) {
|
|
192
|
+
const fs = yield* _(FileSystemService);
|
|
193
|
+
const path = yield* _(Path.Path);
|
|
194
|
+
const relative = path.relative(sourceRoot, filePath);
|
|
195
|
+
const targetPath = path.join(destinationRoot, relative);
|
|
196
|
+
yield* _(fs.makeDirectory(path.dirname(targetPath)));
|
|
197
|
+
yield* _(fs.copyFile(filePath, targetPath));
|
|
198
|
+
});
|
|
199
|
+
const findFirstMatching = (candidates, matches) => {
|
|
200
|
+
const loop = (remaining) => Effect.gen(function* (_) {
|
|
201
|
+
const [candidate, ...rest] = remaining;
|
|
202
|
+
if (candidate === void 0) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const matched = yield* _(matches(candidate));
|
|
206
|
+
if (!matched) {
|
|
207
|
+
return yield* _(loop(rest));
|
|
208
|
+
}
|
|
209
|
+
return candidate;
|
|
210
|
+
});
|
|
211
|
+
return loop(candidates);
|
|
212
|
+
};
|
|
213
|
+
const ensureDestination = ensureDirectory;
|
|
214
|
+
const resolveProjectRoot = (path, options) => path.resolve(options.projectRoot ?? options.cwd);
|
|
215
|
+
const copyFilteredFiles = (sourceRoot, destinationRoot, isRelevant, errorReason) => pipe(
|
|
216
|
+
Effect.gen(function* (_) {
|
|
217
|
+
const files = yield* _(collectFiles(sourceRoot, (entry) => isRelevant(entry, entry.path)));
|
|
218
|
+
yield* _(
|
|
219
|
+
forEach$1(files, (filePath) => copyFilePreservingRelativePath(sourceRoot, destinationRoot, filePath))
|
|
220
|
+
);
|
|
221
|
+
return files.length;
|
|
222
|
+
}),
|
|
223
|
+
Effect.mapError(() => syncError(sourceRoot, errorReason))
|
|
224
|
+
);
|
|
225
|
+
const createFilteredSource = (params) => ({
|
|
226
|
+
name: params.name,
|
|
227
|
+
destSubdir: params.destSubdir,
|
|
228
|
+
resolveSource: params.resolveSource,
|
|
229
|
+
copy: (sourceDir, destinationDir) => copyFilteredFiles(sourceDir, destinationDir, params.filter, params.errorReason)
|
|
230
|
+
});
|
|
231
|
+
const runSyncSource = (source, options) => pipe(
|
|
232
|
+
Effect.gen(function* (_) {
|
|
233
|
+
const path = yield* _(Path.Path);
|
|
234
|
+
const resolvedSource = yield* _(source.resolveSource(options));
|
|
235
|
+
const destination = path.join(
|
|
236
|
+
resolveProjectRoot(path, options),
|
|
237
|
+
".knowledge",
|
|
238
|
+
source.destSubdir
|
|
239
|
+
);
|
|
240
|
+
if (path.resolve(resolvedSource) === path.resolve(destination)) {
|
|
241
|
+
yield* _(
|
|
242
|
+
Console.log(
|
|
243
|
+
`${source.name}: source equals destination; skipping copy to avoid duplicates`
|
|
244
|
+
)
|
|
245
|
+
);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
yield* _(ensureDirectory(destination));
|
|
249
|
+
const copied = yield* _(source.copy(resolvedSource, destination, options));
|
|
250
|
+
yield* _(
|
|
251
|
+
Console.log(
|
|
252
|
+
`${source.name}: copied ${copied} files from ${resolvedSource} to ${destination}`
|
|
253
|
+
)
|
|
254
|
+
);
|
|
255
|
+
}),
|
|
256
|
+
Effect.matchEffect({
|
|
257
|
+
onFailure: (error) => Console.log(
|
|
258
|
+
`${source.name}: source not found; skipped syncing (${error.reason})`
|
|
259
|
+
),
|
|
260
|
+
onSuccess: () => Effect.void
|
|
261
|
+
})
|
|
262
|
+
);
|
|
263
|
+
const slugFromCwd = (cwd) => `-${cwd.replace(/^\/+/, "").replaceAll("\\", "-").replaceAll("/", "-")}`;
|
|
264
|
+
const resolveClaudeProjectDir = (options) => Effect.gen(function* (_) {
|
|
265
|
+
const env = yield* _(RuntimeEnv);
|
|
266
|
+
const homeDir = yield* _(env.homedir);
|
|
267
|
+
const path = yield* _(Path.Path);
|
|
268
|
+
const base = options.claudeProjectsRoot ?? path.join(homeDir, ".claude", "projects");
|
|
269
|
+
const candidate = path.join(base, slugFromCwd(resolveProjectRoot(path, options)));
|
|
270
|
+
const fs = yield* _(FileSystemService);
|
|
271
|
+
const exists = yield* _(fs.exists(candidate));
|
|
272
|
+
if (!exists) {
|
|
273
|
+
return yield* _(
|
|
274
|
+
Effect.fail(syncError(".claude", "Claude project directory is missing"))
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return candidate;
|
|
278
|
+
});
|
|
279
|
+
const claudeSource = createFilteredSource({
|
|
280
|
+
name: "Claude",
|
|
281
|
+
destSubdir: ".claude",
|
|
282
|
+
resolveSource: resolveClaudeProjectDir,
|
|
283
|
+
filter: (entry, fullPath) => entry.kind === "file" && fullPath.endsWith(".jsonl"),
|
|
284
|
+
errorReason: "Cannot traverse Claude project"
|
|
285
|
+
});
|
|
286
|
+
const syncClaude = (options) => runSyncSource(claudeSource, options);
|
|
287
|
+
const isJsonRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
288
|
+
const pickString = (record, key) => {
|
|
289
|
+
const candidate = record[key];
|
|
290
|
+
return Option.fromNullable(typeof candidate === "string" ? candidate : null);
|
|
291
|
+
};
|
|
292
|
+
const pickRecord = (record, key) => pipe(
|
|
293
|
+
record[key],
|
|
294
|
+
Option.fromNullable,
|
|
295
|
+
Option.filter((value) => isJsonRecord(value))
|
|
296
|
+
);
|
|
297
|
+
const extractCwd = (record) => pipe(
|
|
298
|
+
pickString(record, "cwd"),
|
|
299
|
+
Option.orElse(
|
|
300
|
+
() => pipe(
|
|
301
|
+
pickRecord(record, "payload"),
|
|
302
|
+
Option.flatMap((payload) => pickString(payload, "cwd"))
|
|
303
|
+
)
|
|
304
|
+
)
|
|
305
|
+
);
|
|
306
|
+
const toMetadata = (value) => {
|
|
307
|
+
if (!isJsonRecord(value)) {
|
|
308
|
+
return { cwd: Option.none() };
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
cwd: extractCwd(value)
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
const cwdMatches = (metadata, locator) => Option.exists(metadata.cwd, (cwdValue) => locator.isWithinRoot(cwdValue));
|
|
315
|
+
const metadataMatches = (metadata, locator) => cwdMatches(metadata, locator);
|
|
316
|
+
const buildProjectLocator = (normalizedCwd, isWithinRoot) => ({
|
|
317
|
+
normalizedCwd,
|
|
318
|
+
isWithinRoot
|
|
319
|
+
});
|
|
320
|
+
const valueMatchesProject = (value, locator) => metadataMatches(toMetadata(value), locator);
|
|
321
|
+
const some = Option.some;
|
|
322
|
+
const forEach = Effect.forEach;
|
|
323
|
+
const JsonValueSchema = Schema.suspend(
|
|
324
|
+
() => Schema.Union(
|
|
325
|
+
Schema.String,
|
|
326
|
+
Schema.Number,
|
|
327
|
+
Schema.Boolean,
|
|
328
|
+
Schema.Null,
|
|
329
|
+
Schema.Array(JsonValueSchema),
|
|
330
|
+
Schema.Record({ key: Schema.String, value: JsonValueSchema })
|
|
331
|
+
)
|
|
332
|
+
);
|
|
333
|
+
const parseJsonLine = (line) => pipe(
|
|
334
|
+
Schema.decode(Schema.parseJson(JsonValueSchema))(line),
|
|
335
|
+
Effect.match({
|
|
336
|
+
onFailure: () => Option.none(),
|
|
337
|
+
onSuccess: (value) => some(value)
|
|
338
|
+
})
|
|
339
|
+
);
|
|
340
|
+
const resolveEnvValue$1 = (envValue) => Option.getOrUndefined(envValue);
|
|
341
|
+
const buildLocator = (path, projectRoot) => {
|
|
342
|
+
const normalizedRoot = path.resolve(projectRoot);
|
|
343
|
+
const isWithinRoot = (candidate) => {
|
|
344
|
+
const normalizedCandidate = path.resolve(candidate);
|
|
345
|
+
const relative = path.relative(normalizedRoot, normalizedCandidate);
|
|
346
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
347
|
+
};
|
|
348
|
+
return buildProjectLocator(normalizedRoot, isWithinRoot);
|
|
349
|
+
};
|
|
350
|
+
const containsJsonl = (root) => Effect.gen(function* (_) {
|
|
351
|
+
const fs = yield* _(FileSystemService);
|
|
352
|
+
const entries = yield* _(fs.readDirectory(root));
|
|
353
|
+
for (const entry of entries) {
|
|
354
|
+
if (entry.kind === "file" && entry.path.endsWith(".jsonl")) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
if (entry.kind === "directory") {
|
|
358
|
+
const found = yield* _(containsJsonl(entry.path));
|
|
359
|
+
if (found) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
});
|
|
366
|
+
const hasJsonlInCandidate = (candidate) => Effect.gen(function* (_) {
|
|
367
|
+
const fs = yield* _(FileSystemService);
|
|
368
|
+
const exists = yield* _(fs.exists(candidate));
|
|
369
|
+
if (!exists) {
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
return yield* _(containsJsonl(candidate));
|
|
373
|
+
});
|
|
374
|
+
const findFirstExistingWithJsonl = (candidates) => findFirstMatching(candidates, hasJsonlInCandidate);
|
|
375
|
+
const resolveSourceDir = (options) => Effect.gen(function* (_) {
|
|
376
|
+
const env = yield* _(RuntimeEnv);
|
|
377
|
+
const path = yield* _(Path.Path);
|
|
378
|
+
const envSource = resolveEnvValue$1(yield* _(env.envVar("CODEX_SOURCE_DIR")));
|
|
379
|
+
const homeDir = yield* _(env.homedir);
|
|
380
|
+
const projectRoot = resolveProjectRoot(path, options);
|
|
381
|
+
let metaCandidate;
|
|
382
|
+
if (options.metaRoot !== void 0) {
|
|
383
|
+
metaCandidate = options.metaRoot.endsWith(".codex") ? options.metaRoot : path.join(options.metaRoot, ".codex");
|
|
384
|
+
}
|
|
385
|
+
const localSource = path.join(projectRoot, ".codex");
|
|
386
|
+
const localKnowledge = path.join(projectRoot, ".knowledge", ".codex");
|
|
387
|
+
const homeSource = path.join(homeDir, ".codex");
|
|
388
|
+
const homeKnowledge = path.join(homeDir, ".knowledge", ".codex");
|
|
389
|
+
const candidates = [
|
|
390
|
+
options.sourceDir,
|
|
391
|
+
envSource,
|
|
392
|
+
metaCandidate,
|
|
393
|
+
localSource,
|
|
394
|
+
homeSource,
|
|
395
|
+
localKnowledge,
|
|
396
|
+
homeKnowledge
|
|
397
|
+
].filter((candidate) => candidate !== void 0);
|
|
398
|
+
const existing = yield* _(findFirstExistingWithJsonl(candidates));
|
|
399
|
+
if (existing === void 0) {
|
|
400
|
+
return yield* _(
|
|
401
|
+
Effect.fail(
|
|
402
|
+
syncError(
|
|
403
|
+
".codex",
|
|
404
|
+
`No .jsonl files found in .codex candidates; checked: ${candidates.join(", ")}`
|
|
405
|
+
)
|
|
406
|
+
)
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
return existing;
|
|
410
|
+
});
|
|
411
|
+
const resolveLocator = (options) => Effect.gen(function* (_) {
|
|
412
|
+
const path = yield* _(Path.Path);
|
|
413
|
+
const projectRoot = resolveProjectRoot(path, options);
|
|
414
|
+
return buildLocator(path, projectRoot);
|
|
415
|
+
});
|
|
416
|
+
const lineMatchesProject = (line, locator) => pipe(
|
|
417
|
+
parseJsonLine(line),
|
|
418
|
+
Effect.map((parsed) => Option.exists(parsed, (value) => valueMatchesProject(value, locator)))
|
|
419
|
+
);
|
|
420
|
+
const fileMatchesProject = (filePath, locator) => Effect.gen(function* (_) {
|
|
421
|
+
const fs = yield* _(FileSystemService);
|
|
422
|
+
const content = yield* _(fs.readFileString(filePath));
|
|
423
|
+
const lines = content.split("\n");
|
|
424
|
+
const matches = yield* _(
|
|
425
|
+
forEach(lines, (line) => {
|
|
426
|
+
const trimmed = line.trim();
|
|
427
|
+
return trimmed.length === 0 ? Effect.succeed(false) : lineMatchesProject(trimmed, locator);
|
|
428
|
+
})
|
|
429
|
+
);
|
|
430
|
+
return matches.some(Boolean);
|
|
431
|
+
});
|
|
432
|
+
const selectRelevantFiles = (files, locator) => pipe(
|
|
433
|
+
forEach(files, (filePath) => pipe(
|
|
434
|
+
fileMatchesProject(filePath, locator),
|
|
435
|
+
Effect.map((matches) => ({ filePath, matches }))
|
|
436
|
+
)),
|
|
437
|
+
Effect.map(
|
|
438
|
+
(results) => results.filter((result) => result.matches).map((result) => result.filePath)
|
|
439
|
+
)
|
|
440
|
+
);
|
|
441
|
+
const copyCodexFiles = (sourceDir, destinationDir, locator) => Effect.gen(function* (_) {
|
|
442
|
+
yield* _(ensureDestination(destinationDir));
|
|
443
|
+
const allJsonlFiles = yield* _(
|
|
444
|
+
collectFiles(sourceDir, (entry) => entry.kind === "file" && entry.path.endsWith(".jsonl"))
|
|
445
|
+
);
|
|
446
|
+
const relevantFiles = yield* _(selectRelevantFiles(allJsonlFiles, locator));
|
|
447
|
+
yield* _(
|
|
448
|
+
forEach(relevantFiles, (filePath) => copyFilePreservingRelativePath(sourceDir, destinationDir, filePath))
|
|
449
|
+
);
|
|
450
|
+
yield* _(
|
|
451
|
+
Console.log(
|
|
452
|
+
`Codex: copied ${relevantFiles.length} files from ${sourceDir} to ${destinationDir}`
|
|
453
|
+
)
|
|
454
|
+
);
|
|
455
|
+
});
|
|
456
|
+
const syncCodex = (options) => Effect.gen(function* (_) {
|
|
457
|
+
const locator = yield* _(resolveLocator(options));
|
|
458
|
+
const sourceDir = yield* _(resolveSourceDir(options));
|
|
459
|
+
const path = yield* _(Path.Path);
|
|
460
|
+
const destinationDir = options.destinationDir ?? path.join(resolveProjectRoot(path, options), ".knowledge", ".codex");
|
|
461
|
+
if (path.resolve(sourceDir) === path.resolve(destinationDir)) {
|
|
462
|
+
yield* _(
|
|
463
|
+
Console.log(
|
|
464
|
+
"Codex source equals destination; skipping copy to avoid duplicates"
|
|
465
|
+
)
|
|
466
|
+
);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
yield* _(copyCodexFiles(sourceDir, destinationDir, locator));
|
|
470
|
+
}).pipe(
|
|
471
|
+
Effect.matchEffect({
|
|
472
|
+
onFailure: (error) => Console.log(
|
|
473
|
+
`Codex source not found; skipped syncing Codex dialog files (${error.reason})`
|
|
474
|
+
),
|
|
475
|
+
onSuccess: () => Effect.void
|
|
476
|
+
})
|
|
477
|
+
);
|
|
478
|
+
const resolveEnvValue = (envValue) => Option.getOrUndefined(envValue);
|
|
479
|
+
const findFirstExisting = (candidates) => findFirstMatching(candidates, (candidate) => Effect.gen(function* (_) {
|
|
480
|
+
const fs = yield* _(FileSystemService);
|
|
481
|
+
return yield* _(fs.exists(candidate));
|
|
482
|
+
}));
|
|
483
|
+
const resolveQwenSourceDir = (options) => Effect.gen(function* (_) {
|
|
484
|
+
const env = yield* _(RuntimeEnv);
|
|
485
|
+
const crypto = yield* _(CryptoService);
|
|
486
|
+
const path = yield* _(Path.Path);
|
|
487
|
+
const projectRoot = resolveProjectRoot(path, options);
|
|
488
|
+
const hash = yield* _(
|
|
489
|
+
pipe(
|
|
490
|
+
crypto.sha256(projectRoot),
|
|
491
|
+
Effect.mapError((error) => syncError(".qwen", error.reason))
|
|
492
|
+
)
|
|
493
|
+
);
|
|
494
|
+
const envSource = resolveEnvValue(yield* _(env.envVar("QWEN_SOURCE_DIR")));
|
|
495
|
+
const homeDir = yield* _(env.homedir);
|
|
496
|
+
let baseFromMeta;
|
|
497
|
+
if (options.metaRoot !== void 0) {
|
|
498
|
+
baseFromMeta = options.metaRoot.endsWith(".qwen") ? options.metaRoot : path.join(options.metaRoot, ".qwen");
|
|
499
|
+
}
|
|
500
|
+
const metaKnowledge = options.metaRoot === void 0 ? void 0 : path.join(options.metaRoot, ".knowledge", ".qwen");
|
|
501
|
+
const homeBase = path.join(homeDir, ".qwen");
|
|
502
|
+
const homeKnowledge = path.join(homeDir, ".knowledge", ".qwen");
|
|
503
|
+
const candidates = [
|
|
504
|
+
options.qwenSourceDir,
|
|
505
|
+
envSource,
|
|
506
|
+
baseFromMeta ? path.join(baseFromMeta, "tmp", hash) : void 0,
|
|
507
|
+
path.join(projectRoot, ".qwen", "tmp", hash),
|
|
508
|
+
path.join(projectRoot, ".knowledge", ".qwen", "tmp", hash),
|
|
509
|
+
metaKnowledge ? path.join(metaKnowledge, "tmp", hash) : void 0,
|
|
510
|
+
path.join(homeBase, "tmp", hash),
|
|
511
|
+
path.join(homeKnowledge, "tmp", hash)
|
|
512
|
+
].filter((candidate) => candidate !== void 0);
|
|
513
|
+
const found = yield* _(findFirstExisting(candidates));
|
|
514
|
+
if (found === void 0) {
|
|
515
|
+
return yield* _(
|
|
516
|
+
Effect.fail(
|
|
517
|
+
syncError(
|
|
518
|
+
".qwen",
|
|
519
|
+
`Qwen source directory is missing for hash ${hash}`
|
|
520
|
+
)
|
|
521
|
+
)
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
return found;
|
|
525
|
+
});
|
|
526
|
+
const qwenSource = createFilteredSource({
|
|
527
|
+
name: "Qwen",
|
|
528
|
+
destSubdir: ".qwen",
|
|
529
|
+
resolveSource: resolveQwenSourceDir,
|
|
530
|
+
filter: (entry, fullPath) => entry.kind === "file" && fullPath.endsWith(".json"),
|
|
531
|
+
errorReason: "Cannot traverse Qwen directory"
|
|
532
|
+
});
|
|
533
|
+
const syncQwen = (options) => runSyncSource(qwenSource, options);
|
|
534
|
+
const buildSyncProgram = (options) => Effect.gen(function* (_) {
|
|
535
|
+
yield* _(syncClaude(options));
|
|
536
|
+
yield* _(syncCodex(options));
|
|
537
|
+
yield* _(syncQwen(options));
|
|
538
|
+
});
|
|
539
|
+
const program = pipe(
|
|
540
|
+
readSyncOptions,
|
|
541
|
+
Effect.flatMap((options) => buildSyncProgram(options))
|
|
542
|
+
);
|
|
543
|
+
const main = pipe(
|
|
544
|
+
program,
|
|
545
|
+
Effect.provide(
|
|
546
|
+
Layer.provideMerge(
|
|
547
|
+
Layer.mergeAll(RuntimeEnvLive, FileSystemLive, CryptoServiceLive),
|
|
548
|
+
NodeContext.layer
|
|
549
|
+
)
|
|
550
|
+
)
|
|
551
|
+
);
|
|
552
|
+
NodeRuntime.runMain(main);
|
|
553
|
+
//# sourceMappingURL=main.js.map
|