agentel 0.2.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 +452 -0
- package/agentlog-spec.md +551 -0
- package/bin/agentlog-recall.js +8 -0
- package/bin/agentlog.js +14 -0
- package/docs/code-reference.md +1108 -0
- package/docs/history-source-handling.md +837 -0
- package/docs/release.md +69 -0
- package/package.json +57 -0
- package/src/archive.js +1130 -0
- package/src/autostart.js +182 -0
- package/src/canonical-events.js +575 -0
- package/src/cli.js +7928 -0
- package/src/collector.js +113 -0
- package/src/commands/logs.js +51 -0
- package/src/commands/server.js +11 -0
- package/src/config.js +240 -0
- package/src/doctor.js +102 -0
- package/src/importers/aider.js +553 -0
- package/src/importers/claude.js +349 -0
- package/src/importers/cline.js +471 -0
- package/src/importers/gemini.js +795 -0
- package/src/importers/providers.js +149 -0
- package/src/importers/shared.js +15 -0
- package/src/importers.js +7063 -0
- package/src/mcp.js +148 -0
- package/src/parser-versions.js +62 -0
- package/src/paths.js +61 -0
- package/src/redaction.js +228 -0
- package/src/repo.js +106 -0
- package/src/search.js +619 -0
- package/src/sources.js +86 -0
- package/src/supervisor.js +217 -0
- package/src/sync.js +677 -0
- package/src/version.js +7 -0
- package/src/web-accounts.js +122 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { execFileSync } = require("child_process");
|
|
7
|
+
|
|
8
|
+
const DEFAULT_MAX_DEPTH = 6;
|
|
9
|
+
const DEFAULT_MAX_FILES = 500;
|
|
10
|
+
const DEFAULT_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
11
|
+
const DEFAULT_GIT_COMMITS = 25;
|
|
12
|
+
|
|
13
|
+
function readClineCheckpointDiffs(taskDir, options = {}) {
|
|
14
|
+
const artifacts = discoverClineCheckpointArtifacts(taskDir, options);
|
|
15
|
+
const toolCalls = artifacts.map((artifact) => clineCheckpointToolCall(artifact)).filter(Boolean);
|
|
16
|
+
const messages = toolCalls.map((toolCall, index) => clineCheckpointMessage(toolCall, artifacts[index]));
|
|
17
|
+
return {
|
|
18
|
+
artifacts,
|
|
19
|
+
toolCalls,
|
|
20
|
+
messages,
|
|
21
|
+
sourceFiles: uniqueSorted(artifacts.flatMap((artifact) => artifact.sourceFiles || []))
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function discoverClineCheckpointArtifacts(taskDir, options = {}) {
|
|
26
|
+
const roots = clineCheckpointRoots(taskDir);
|
|
27
|
+
const artifacts = [];
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
for (const root of roots) {
|
|
30
|
+
for (const repo of findGitRepos(root, options)) {
|
|
31
|
+
for (const artifact of gitCheckpointArtifacts(repo, options)) {
|
|
32
|
+
pushUniqueArtifact(artifacts, seen, artifact);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const file of checkpointCandidateFiles(root, options)) {
|
|
36
|
+
for (const artifact of fileCheckpointArtifacts(file, taskDir, options)) {
|
|
37
|
+
pushUniqueArtifact(artifacts, seen, artifact);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return artifacts.sort((a, b) => String(a.timestamp || "").localeCompare(String(b.timestamp || "")) || String(a.sourcePath || "").localeCompare(String(b.sourcePath || "")));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function clineCheckpointRoots(taskDir) {
|
|
45
|
+
if (!taskDir || !safeStat(taskDir)?.isDirectory()) return [];
|
|
46
|
+
return uniqueSorted([
|
|
47
|
+
path.join(taskDir, "checkpoints"),
|
|
48
|
+
path.join(taskDir, "checkpoint"),
|
|
49
|
+
path.join(taskDir, "checkpointStore"),
|
|
50
|
+
path.join(taskDir, "checkpoint_store"),
|
|
51
|
+
path.join(taskDir, ".checkpoints")
|
|
52
|
+
].filter((dir) => safeStat(dir)?.isDirectory()));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function checkpointCandidateFiles(root, options = {}) {
|
|
56
|
+
const files = [];
|
|
57
|
+
const maxFiles = positiveInteger(options.maxFiles, DEFAULT_MAX_FILES);
|
|
58
|
+
walkLimited(root, {
|
|
59
|
+
maxDepth: positiveInteger(options.maxDepth, DEFAULT_MAX_DEPTH),
|
|
60
|
+
skipDirs: new Set([".git", "node_modules", "vendor", "dist", "build"]),
|
|
61
|
+
visitFile(file) {
|
|
62
|
+
if (files.length >= maxFiles) return;
|
|
63
|
+
if (isCheckpointArtifactFile(file)) files.push(file);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return files.sort((a, b) => a.localeCompare(b));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function findGitRepos(root, options = {}) {
|
|
70
|
+
const repos = [];
|
|
71
|
+
const maxFiles = positiveInteger(options.maxFiles, DEFAULT_MAX_FILES);
|
|
72
|
+
walkLimited(root, {
|
|
73
|
+
maxDepth: positiveInteger(options.maxDepth, DEFAULT_MAX_DEPTH),
|
|
74
|
+
skipDirs: new Set(["node_modules", "vendor", "dist", "build"]),
|
|
75
|
+
visitDir(dir, entryNames) {
|
|
76
|
+
if (repos.length >= maxFiles) return false;
|
|
77
|
+
if (entryNames.includes(".git") && safeStat(path.join(dir, ".git"))?.isDirectory()) {
|
|
78
|
+
repos.push(dir);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return repos.sort((a, b) => a.localeCompare(b));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isCheckpointArtifactFile(file) {
|
|
88
|
+
const name = path.basename(file).toLowerCase();
|
|
89
|
+
if (/\.(diff|patch)$/i.test(name)) return true;
|
|
90
|
+
if (/\.(json|jsonl)$/i.test(name)) return true;
|
|
91
|
+
return /diff|patch|change|edit|checkpoint/i.test(name) && /\.(txt|md)$/i.test(name);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function fileCheckpointArtifacts(file, taskDir, options = {}) {
|
|
95
|
+
const stat = safeStat(file);
|
|
96
|
+
if (!stat || stat.size > positiveInteger(options.maxFileBytes, DEFAULT_MAX_FILE_BYTES)) return [];
|
|
97
|
+
const text = readUtf8(file);
|
|
98
|
+
if (!text.trim()) return [];
|
|
99
|
+
const timestamp = stat.mtimeMs ? new Date(stat.mtimeMs).toISOString() : undefined;
|
|
100
|
+
if (/\.(diff|patch)$/i.test(file) || looksLikeUnifiedDiff(text) || looksLikeClineSearchReplace(text)) {
|
|
101
|
+
return [checkpointArtifact({
|
|
102
|
+
kind: "diff-file",
|
|
103
|
+
checkpointId: checkpointIdFromPath(file, taskDir),
|
|
104
|
+
diff: text.trim(),
|
|
105
|
+
timestamp,
|
|
106
|
+
sourcePath: file,
|
|
107
|
+
sourceFiles: [file]
|
|
108
|
+
})];
|
|
109
|
+
}
|
|
110
|
+
if (/\.jsonl$/i.test(file)) return jsonlCheckpointArtifacts(file, text, timestamp, taskDir);
|
|
111
|
+
if (/\.json$/i.test(file)) return jsonCheckpointArtifacts(file, text, timestamp, taskDir);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function jsonlCheckpointArtifacts(file, text, timestamp, taskDir) {
|
|
116
|
+
const artifacts = [];
|
|
117
|
+
for (const line of text.split(/\r?\n/)) {
|
|
118
|
+
if (!line.trim()) continue;
|
|
119
|
+
const data = parseJson(line);
|
|
120
|
+
if (data) artifacts.push(...jsonNodeCheckpointArtifacts(data, { file, timestamp, taskDir }));
|
|
121
|
+
}
|
|
122
|
+
return artifacts;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function jsonCheckpointArtifacts(file, text, timestamp, taskDir) {
|
|
126
|
+
const data = parseJson(text);
|
|
127
|
+
if (!data) return [];
|
|
128
|
+
return jsonNodeCheckpointArtifacts(data, { file, timestamp, taskDir });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function jsonNodeCheckpointArtifacts(node, context, trail = []) {
|
|
132
|
+
if (node == null) return [];
|
|
133
|
+
if (Array.isArray(node)) return node.flatMap((item, index) => jsonNodeCheckpointArtifacts(item, context, trail.concat(String(index))));
|
|
134
|
+
if (typeof node !== "object") return [];
|
|
135
|
+
|
|
136
|
+
const nextContext = jsonNodeContext(node, context);
|
|
137
|
+
const artifacts = [];
|
|
138
|
+
const direct = directJsonCheckpointArtifact(node, nextContext, trail);
|
|
139
|
+
if (direct) artifacts.push(direct);
|
|
140
|
+
|
|
141
|
+
for (const [key, value] of Object.entries(node)) {
|
|
142
|
+
if (!value || typeof value !== "object") continue;
|
|
143
|
+
if (isLikelyCheckpointContainerKey(key) || isLikelyEditContainerKey(key)) {
|
|
144
|
+
artifacts.push(...jsonNodeCheckpointArtifacts(value, nextContext, trail.concat(key)));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return artifacts;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function jsonNodeContext(node, context) {
|
|
151
|
+
return {
|
|
152
|
+
...context,
|
|
153
|
+
checkpointId: firstString(node.checkpointId, node.checkpoint_id, node.id, context.checkpointId),
|
|
154
|
+
timestamp: toIso(firstString(node.timestamp, node.createdAt, node.created_at, node.ts)) || context.timestamp,
|
|
155
|
+
path: firstString(node.path, node.file, node.filePath, node.file_path, node.filename, node.relativePath, node.relative_path, context.path)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function directJsonCheckpointArtifact(node, context, trail) {
|
|
160
|
+
const diff = firstString(
|
|
161
|
+
node.diff,
|
|
162
|
+
node.patch,
|
|
163
|
+
node.unifiedDiff,
|
|
164
|
+
node.unified_diff,
|
|
165
|
+
looksLikeUnifiedDiff(node.content) || looksLikeClineSearchReplace(node.content) ? node.content : "",
|
|
166
|
+
looksLikeUnifiedDiff(node.text) || looksLikeClineSearchReplace(node.text) ? node.text : ""
|
|
167
|
+
);
|
|
168
|
+
const pathValue = firstString(node.path, node.file, node.filePath, node.file_path, node.filename, node.relativePath, node.relative_path, context.path);
|
|
169
|
+
if (diff) {
|
|
170
|
+
return checkpointArtifact({
|
|
171
|
+
kind: "metadata-diff",
|
|
172
|
+
checkpointId: firstString(context.checkpointId, checkpointIdFromPath(context.file, context.taskDir)),
|
|
173
|
+
path: pathValue || undefined,
|
|
174
|
+
diff: normalizeDiffText(diff, pathValue),
|
|
175
|
+
timestamp: context.timestamp,
|
|
176
|
+
sourcePath: context.file,
|
|
177
|
+
sourceFiles: [context.file],
|
|
178
|
+
rawPath: trail.join(".") || undefined
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const oldString = firstString(node.old_string, node.oldString, node.oldText, node.previousText, node.beforeText, node.previous, node.before, node.search);
|
|
183
|
+
const newString = firstString(node.new_string, node.newString, node.newText, node.currentText, node.afterText, node.current, node.after, node.replace);
|
|
184
|
+
if ((oldString || newString) && (pathValue || oldString || newString)) {
|
|
185
|
+
return checkpointArtifact({
|
|
186
|
+
kind: "metadata-edit",
|
|
187
|
+
checkpointId: firstString(context.checkpointId, checkpointIdFromPath(context.file, context.taskDir)),
|
|
188
|
+
path: pathValue || undefined,
|
|
189
|
+
old_string: oldString,
|
|
190
|
+
new_string: newString,
|
|
191
|
+
timestamp: context.timestamp,
|
|
192
|
+
sourcePath: context.file,
|
|
193
|
+
sourceFiles: [context.file],
|
|
194
|
+
rawPath: trail.join(".") || undefined
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function gitCheckpointArtifacts(repoDir, options = {}) {
|
|
201
|
+
const commits = gitCheckpointCommits(repoDir, options);
|
|
202
|
+
const sourceFiles = checkpointGitSourceFiles(repoDir);
|
|
203
|
+
return commits
|
|
204
|
+
.map((commit) => {
|
|
205
|
+
const diff = gitShowDiff(repoDir, commit.hash, options);
|
|
206
|
+
if (!diff) return null;
|
|
207
|
+
return checkpointArtifact({
|
|
208
|
+
kind: "git-commit",
|
|
209
|
+
checkpointId: commit.hash.slice(0, 12),
|
|
210
|
+
commit: commit.hash,
|
|
211
|
+
summary: commit.subject,
|
|
212
|
+
diff,
|
|
213
|
+
timestamp: commit.timestamp,
|
|
214
|
+
sourcePath: repoDir,
|
|
215
|
+
sourceFiles
|
|
216
|
+
});
|
|
217
|
+
})
|
|
218
|
+
.filter(Boolean);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function gitCheckpointCommits(repoDir, options = {}) {
|
|
222
|
+
const limit = positiveInteger(options.gitCommitLimit, DEFAULT_GIT_COMMITS);
|
|
223
|
+
const output = git(repoDir, ["log", `--max-count=${limit}`, "--format=%H%x1f%ct%x1f%s"], options);
|
|
224
|
+
if (!output) return [];
|
|
225
|
+
return output
|
|
226
|
+
.split(/\r?\n/)
|
|
227
|
+
.map((line) => {
|
|
228
|
+
const [hash, unixSeconds, subject] = line.split("\x1f");
|
|
229
|
+
if (!hash) return null;
|
|
230
|
+
const ms = Number(unixSeconds) * 1000;
|
|
231
|
+
return {
|
|
232
|
+
hash,
|
|
233
|
+
timestamp: Number.isFinite(ms) ? new Date(ms).toISOString() : undefined,
|
|
234
|
+
subject: subject || ""
|
|
235
|
+
};
|
|
236
|
+
})
|
|
237
|
+
.filter(Boolean);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function gitShowDiff(repoDir, commit, options = {}) {
|
|
241
|
+
const diff = git(repoDir, ["show", "--format=", "--no-ext-diff", "--no-color", "--find-renames", "--find-copies", commit], options);
|
|
242
|
+
return looksLikeUnifiedDiff(diff) ? diff.trim() : "";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function git(repoDir, args, options = {}) {
|
|
246
|
+
try {
|
|
247
|
+
return execFileSync("git", ["-C", repoDir, ...args], {
|
|
248
|
+
encoding: "utf8",
|
|
249
|
+
timeout: positiveInteger(options.gitTimeoutMs, 5000),
|
|
250
|
+
maxBuffer: positiveInteger(options.gitMaxBuffer, DEFAULT_MAX_FILE_BYTES * 2),
|
|
251
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
252
|
+
});
|
|
253
|
+
} catch {
|
|
254
|
+
return "";
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function checkpointGitSourceFiles(repoDir) {
|
|
259
|
+
const gitDir = path.join(repoDir, ".git");
|
|
260
|
+
const files = [
|
|
261
|
+
path.join(gitDir, "HEAD"),
|
|
262
|
+
path.join(gitDir, "config"),
|
|
263
|
+
path.join(gitDir, "packed-refs"),
|
|
264
|
+
path.join(gitDir, "logs", "HEAD")
|
|
265
|
+
].filter((file) => safeStat(file)?.isFile());
|
|
266
|
+
for (const subdir of [path.join(gitDir, "refs", "heads"), path.join(gitDir, "refs", "tags")]) {
|
|
267
|
+
walkLimited(subdir, {
|
|
268
|
+
maxDepth: 4,
|
|
269
|
+
skipDirs: new Set(),
|
|
270
|
+
visitFile(file) {
|
|
271
|
+
files.push(file);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return uniqueSorted(files);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function clineCheckpointToolCall(artifact) {
|
|
279
|
+
const args = {};
|
|
280
|
+
if (artifact.path) args.path = artifact.path;
|
|
281
|
+
if (artifact.diff) args.diff = artifact.diff;
|
|
282
|
+
if (artifact.old_string || artifact.new_string) {
|
|
283
|
+
args.old_string = artifact.old_string || "";
|
|
284
|
+
args.new_string = artifact.new_string || "";
|
|
285
|
+
}
|
|
286
|
+
if (artifact.checkpointId) args.checkpointId = artifact.checkpointId;
|
|
287
|
+
if (artifact.commit) args.commit = artifact.commit;
|
|
288
|
+
if (artifact.sourcePath) args.sourcePath = artifact.sourcePath;
|
|
289
|
+
const summary = artifactSummary(artifact);
|
|
290
|
+
return {
|
|
291
|
+
id: artifact.checkpointId ? `cline-checkpoint-${artifact.checkpointId}` : `cline-checkpoint-${hashId(JSON.stringify(args))}`,
|
|
292
|
+
name: "cline_checkpoint_diff",
|
|
293
|
+
displayName: "Cline Checkpoint Diff",
|
|
294
|
+
category: "edit",
|
|
295
|
+
title: "Cline checkpoint diff",
|
|
296
|
+
status: "completed",
|
|
297
|
+
argument: summary,
|
|
298
|
+
rawInputSummary: summary,
|
|
299
|
+
inputPreview: summary,
|
|
300
|
+
target: artifact.path || undefined,
|
|
301
|
+
arguments: args,
|
|
302
|
+
provider: "cline"
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function clineCheckpointMessage(toolCall, artifact) {
|
|
307
|
+
return {
|
|
308
|
+
role: "assistant",
|
|
309
|
+
content: "",
|
|
310
|
+
timestamp: artifact.timestamp,
|
|
311
|
+
metadata: {
|
|
312
|
+
provider: "cline",
|
|
313
|
+
source: "cline_checkpoints",
|
|
314
|
+
supplementary: true,
|
|
315
|
+
toolCalls: [toolCall]
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function checkpointArtifact(value) {
|
|
321
|
+
return {
|
|
322
|
+
...value,
|
|
323
|
+
sourceFiles: uniqueSorted(value.sourceFiles || [])
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function pushUniqueArtifact(artifacts, seen, artifact) {
|
|
328
|
+
const key = hashId(JSON.stringify({
|
|
329
|
+
kind: artifact.kind,
|
|
330
|
+
path: artifact.path,
|
|
331
|
+
diff: artifact.diff,
|
|
332
|
+
old_string: artifact.old_string,
|
|
333
|
+
new_string: artifact.new_string,
|
|
334
|
+
commit: artifact.commit,
|
|
335
|
+
sourcePath: artifact.sourcePath
|
|
336
|
+
}));
|
|
337
|
+
if (seen.has(key)) return;
|
|
338
|
+
seen.add(key);
|
|
339
|
+
artifacts.push(artifact);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function artifactSummary(artifact) {
|
|
343
|
+
const label = artifact.path || artifact.summary || artifact.checkpointId || "checkpoint changes";
|
|
344
|
+
if (artifact.diff) return String(label).slice(0, 240);
|
|
345
|
+
if (artifact.old_string || artifact.new_string) return String(label).slice(0, 240);
|
|
346
|
+
return "checkpoint changes";
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function normalizeDiffText(diff, file) {
|
|
350
|
+
const text = String(diff || "").trim();
|
|
351
|
+
if (!text) return "";
|
|
352
|
+
if (looksLikeUnifiedDiff(text) || looksLikeClineSearchReplace(text)) return text;
|
|
353
|
+
return file ? `diff --git a/${file} b/${file}\n${text}` : text;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function looksLikeUnifiedDiff(value) {
|
|
357
|
+
const text = typeof value === "string" ? value : "";
|
|
358
|
+
return /^diff --git\s/m.test(text) || /^@@\s/m.test(text) || (/^---\s/m.test(text) && /^\+\+\+\s/m.test(text));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function looksLikeClineSearchReplace(value) {
|
|
362
|
+
return typeof value === "string" && /<{7}\s+SEARCH[\s\S]*={7}[\s\S]*>{7}\s+REPLACE/.test(value);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function checkpointIdFromPath(file, taskDir) {
|
|
366
|
+
const relative = path.relative(taskDir || path.dirname(file), file);
|
|
367
|
+
const parts = relative.split(path.sep).filter(Boolean);
|
|
368
|
+
const checkpointIndex = parts.findIndex((part) => /^\.?checkpoints?$/i.test(part) || /^checkpoint[-_]?store$/i.test(part));
|
|
369
|
+
if (checkpointIndex >= 0 && parts[checkpointIndex + 1]) return parts[checkpointIndex + 1].replace(/\.[^.]+$/, "");
|
|
370
|
+
return path.basename(path.dirname(file)) || path.basename(file);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function isLikelyCheckpointContainerKey(key) {
|
|
374
|
+
return /checkpoint|snapshot|restore|history/i.test(key);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function isLikelyEditContainerKey(key) {
|
|
378
|
+
return /diff|patch|change|edit|file/i.test(key);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function walkLimited(root, options) {
|
|
382
|
+
const stat = safeStat(root);
|
|
383
|
+
if (!stat || !stat.isDirectory()) return;
|
|
384
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
385
|
+
const skipDirs = options.skipDirs || new Set();
|
|
386
|
+
const walk = (dir, depth) => {
|
|
387
|
+
let entries;
|
|
388
|
+
try {
|
|
389
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
390
|
+
} catch {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const entryNames = entries.map((entry) => entry.name);
|
|
394
|
+
if (options.visitDir && options.visitDir(dir, entryNames) === false) return;
|
|
395
|
+
if (depth >= maxDepth) return;
|
|
396
|
+
for (const entry of entries) {
|
|
397
|
+
const full = path.join(dir, entry.name);
|
|
398
|
+
if (entry.isDirectory()) {
|
|
399
|
+
if (skipDirs.has(entry.name)) continue;
|
|
400
|
+
walk(full, depth + 1);
|
|
401
|
+
} else if (entry.isFile() && options.visitFile) {
|
|
402
|
+
options.visitFile(full);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
walk(root, 0);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function safeStat(file) {
|
|
410
|
+
try {
|
|
411
|
+
return fs.statSync(file);
|
|
412
|
+
} catch {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function readUtf8(file) {
|
|
418
|
+
try {
|
|
419
|
+
return fs.readFileSync(file, "utf8");
|
|
420
|
+
} catch {
|
|
421
|
+
return "";
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function parseJson(text) {
|
|
426
|
+
try {
|
|
427
|
+
return JSON.parse(text);
|
|
428
|
+
} catch {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function toIso(value) {
|
|
434
|
+
if (!value) return "";
|
|
435
|
+
if (value instanceof Date) return Number.isNaN(value.valueOf()) ? "" : value.toISOString();
|
|
436
|
+
if (typeof value === "number") {
|
|
437
|
+
const ms = value > 1e12 ? value : value * 1000;
|
|
438
|
+
const date = new Date(ms);
|
|
439
|
+
return Number.isNaN(date.valueOf()) ? "" : date.toISOString();
|
|
440
|
+
}
|
|
441
|
+
const date = new Date(value);
|
|
442
|
+
return Number.isNaN(date.valueOf()) ? "" : date.toISOString();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function firstString(...values) {
|
|
446
|
+
for (const value of values) {
|
|
447
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
448
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
449
|
+
}
|
|
450
|
+
return "";
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function positiveInteger(value, fallback) {
|
|
454
|
+
const number = Number(value);
|
|
455
|
+
return Number.isInteger(number) && number > 0 ? number : fallback;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function hashId(value) {
|
|
459
|
+
return crypto.createHash("sha256").update(String(value)).digest("hex").slice(0, 24);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function uniqueSorted(values) {
|
|
463
|
+
return [...new Set(values.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
module.exports = {
|
|
467
|
+
clineCheckpointRoots,
|
|
468
|
+
clineCheckpointToolCall,
|
|
469
|
+
discoverClineCheckpointArtifacts,
|
|
470
|
+
readClineCheckpointDiffs
|
|
471
|
+
};
|