@claudexor/workspace 1.0.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 +7 -0
- package/dist/artifact-paths.d.ts +40 -0
- package/dist/artifact-paths.d.ts.map +1 -0
- package/dist/artifact-paths.js +128 -0
- package/dist/artifact-paths.js.map +1 -0
- package/dist/git.d.ts +110 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +376 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/manager.d.ts +88 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +398 -0
- package/dist/manager.js.map +1 -0
- package/dist/self-ignore.d.ts +10 -0
- package/dist/self-ignore.d.ts.map +1 -0
- package/dist/self-ignore.js +24 -0
- package/dist/self-ignore.js.map +1 -0
- package/dist/thread-tree.d.ts +20 -0
- package/dist/thread-tree.d.ts.map +1 -0
- package/dist/thread-tree.js +69 -0
- package/dist/thread-tree.js.map +1 -0
- package/package.json +42 -0
package/dist/git.js
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { parseUnifiedDiff, runCaptureRaw, WorkspaceError } from "@claudexor/core";
|
|
5
|
+
import { claudexorArtifactPredicate, trackedArtifactDirPaths } from "./artifact-paths.js";
|
|
6
|
+
/** BYTE-FAITHFUL git capture: raw buffers, never readline — CR
|
|
7
|
+
* bytes in CRLF diff content survive, and no trailing newline is fabricated
|
|
8
|
+
* (trim-based consumers like revParse are unaffected: git ends its own
|
|
9
|
+
* output with \n). */
|
|
10
|
+
export async function git(repo, args, input) {
|
|
11
|
+
const r = await runCaptureRaw("git", ["-C", repo, ...args], { timeoutMs: 60_000, input });
|
|
12
|
+
return { code: r.code, stdout: r.stdout, stderr: r.stderr };
|
|
13
|
+
}
|
|
14
|
+
/** `git` with extra environment (e.g. a scratch GIT_INDEX_FILE for snapshots). */
|
|
15
|
+
async function gitEnv(repo, args, env) {
|
|
16
|
+
const r = await runCaptureRaw("git", ["-C", repo, ...args], { timeoutMs: 60_000, env });
|
|
17
|
+
return { code: r.code, stdout: r.stdout, stderr: r.stderr };
|
|
18
|
+
}
|
|
19
|
+
export async function isGitRepo(repo) {
|
|
20
|
+
const r = await git(repo, ["rev-parse", "--is-inside-work-tree"]);
|
|
21
|
+
return r.code === 0 && r.stdout.trim() === "true";
|
|
22
|
+
}
|
|
23
|
+
export async function revParse(repo, ref) {
|
|
24
|
+
const r = await git(repo, ["rev-parse", ref]);
|
|
25
|
+
if (r.code !== 0)
|
|
26
|
+
throw new WorkspaceError(`git rev-parse ${ref} failed: ${r.stderr.trim()}`);
|
|
27
|
+
return r.stdout.trim();
|
|
28
|
+
}
|
|
29
|
+
const GITIGNORE_SEED = ".claudexor/";
|
|
30
|
+
/**
|
|
31
|
+
* Make a project folder usable as a git boundary for write-mode runs.
|
|
32
|
+
*
|
|
33
|
+
* Comparator note: Codex CLI refuses to run outside a git repo (its official
|
|
34
|
+
* quick start is `mkdir && git init && codex`); Claudexor goes one step
|
|
35
|
+
* further and creates the boundary itself, announced via the
|
|
36
|
+
* `project.git.initialized` run event. The baseline commit makes worktree
|
|
37
|
+
* diffs honest from the very first run; `.claudexor/` is seeded into
|
|
38
|
+
* .gitignore FIRST so run artifacts never enter the baseline.
|
|
39
|
+
*
|
|
40
|
+
* The baseline commit is authored as "Claudexor" deterministically — it is a
|
|
41
|
+
* tool-created commit and must not depend on (or pollute) user git identity.
|
|
42
|
+
*/
|
|
43
|
+
export async function ensureGitRepository(repo) {
|
|
44
|
+
const isRepo = await isGitRepo(repo);
|
|
45
|
+
const hasHead = isRepo && (await git(repo, ["rev-parse", "--verify", "HEAD"])).code === 0;
|
|
46
|
+
if (isRepo && hasHead) {
|
|
47
|
+
return { initialized: false, baselineCommitted: false, gitignoreSeeded: false, headSha: await revParse(repo, "HEAD") };
|
|
48
|
+
}
|
|
49
|
+
let gitignoreSeeded = false;
|
|
50
|
+
const gitignorePath = join(repo, ".gitignore");
|
|
51
|
+
if (!existsSync(gitignorePath)) {
|
|
52
|
+
writeFileSync(gitignorePath, `${GITIGNORE_SEED}\n`, "utf8");
|
|
53
|
+
gitignoreSeeded = true;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const raw = readFileSync(gitignorePath, "utf8");
|
|
57
|
+
const lines = raw.split("\n").map((l) => l.trim());
|
|
58
|
+
if (!lines.includes(GITIGNORE_SEED) && !lines.includes(".claudexor")) {
|
|
59
|
+
// A missing trailing newline would concatenate the seed onto the last
|
|
60
|
+
// pattern (e.g. "node_modules.claudexor/"), silently un-ignoring run
|
|
61
|
+
// artifacts right before the baseline `git add -A`.
|
|
62
|
+
const separator = raw.length > 0 && !raw.endsWith("\n") ? "\n" : "";
|
|
63
|
+
appendFileSync(gitignorePath, `${separator}${GITIGNORE_SEED}\n`, "utf8");
|
|
64
|
+
gitignoreSeeded = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
let initialized = false;
|
|
68
|
+
if (!isRepo) {
|
|
69
|
+
const init = await git(repo, ["init"]);
|
|
70
|
+
if (init.code !== 0)
|
|
71
|
+
throw new WorkspaceError(`git init failed: ${init.stderr.trim()}`);
|
|
72
|
+
initialized = true;
|
|
73
|
+
}
|
|
74
|
+
const add = await git(repo, ["add", "-A"]);
|
|
75
|
+
if (add.code !== 0)
|
|
76
|
+
throw new WorkspaceError(`git add failed during repository initialization: ${add.stderr.trim()}`);
|
|
77
|
+
const commit = await git(repo, [
|
|
78
|
+
"-c",
|
|
79
|
+
"user.name=Claudexor",
|
|
80
|
+
"-c",
|
|
81
|
+
"user.email=noreply@claudexor.local",
|
|
82
|
+
"commit",
|
|
83
|
+
"--allow-empty",
|
|
84
|
+
"--no-verify",
|
|
85
|
+
"-m",
|
|
86
|
+
"claudexor: initialize repository baseline",
|
|
87
|
+
]);
|
|
88
|
+
if (commit.code !== 0)
|
|
89
|
+
throw new WorkspaceError(`baseline commit failed during repository initialization: ${commit.stderr.trim()}`);
|
|
90
|
+
return { initialized, baselineCommitted: true, gitignoreSeeded, headSha: await revParse(repo, "HEAD") };
|
|
91
|
+
}
|
|
92
|
+
export async function statusPorcelain(repo) {
|
|
93
|
+
return (await git(repo, ["status", "--porcelain"])).stdout;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Capture the full dirty working tree (tracked AND untracked) as a dangling
|
|
97
|
+
* commit; null if clean. Plain `git stash create` captures only TRACKED
|
|
98
|
+
* changes, silently dropping the user's new-but-unstaged files from the run's
|
|
99
|
+
* base. We instead seed a scratch index from HEAD, `add -A` into it (which
|
|
100
|
+
* respects .gitignore, so run artifacts stay out), and commit-tree the result.
|
|
101
|
+
*/
|
|
102
|
+
export async function stashCreate(repo) {
|
|
103
|
+
const status = await statusPorcelain(repo);
|
|
104
|
+
// Claudexor's own run/workspace artifacts are never part of the user's dirty
|
|
105
|
+
// state (concurrent envelope creation materializes `.claudexor/workspaces/...`
|
|
106
|
+
// inside the repo); snapshotting them would bake run artifacts into the base.
|
|
107
|
+
const isArtifact = await claudexorArtifactPredicate(repo);
|
|
108
|
+
const meaningful = status
|
|
109
|
+
.split("\n")
|
|
110
|
+
.map((l) => l.slice(3).trim().replace(/^"|"$/g, ""))
|
|
111
|
+
.filter((p) => p.length > 0 && !isArtifact(p));
|
|
112
|
+
if (meaningful.length === 0)
|
|
113
|
+
return null;
|
|
114
|
+
const head = await git(repo, ["rev-parse", "HEAD"]);
|
|
115
|
+
if (head.code !== 0)
|
|
116
|
+
throw new WorkspaceError(`snapshot rev-parse HEAD failed: ${head.stderr.trim()}`);
|
|
117
|
+
const headSha = head.stdout.trim();
|
|
118
|
+
// Unique per call: concurrent envelope creates (a best_of_n wave) must never
|
|
119
|
+
// collide on the scratch index (same pid + same millisecond is real). It lives
|
|
120
|
+
// in the OS temp dir, NOT under `<repo>/.git` — in a linked worktree `.git` is
|
|
121
|
+
// a FILE (gitdir pointer), so a scratch path there fails (review #8).
|
|
122
|
+
const tmpIndex = join(tmpdir(), `claudexor-snapshot-index-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
123
|
+
const env = {
|
|
124
|
+
GIT_INDEX_FILE: tmpIndex,
|
|
125
|
+
GIT_AUTHOR_NAME: "Claudexor",
|
|
126
|
+
GIT_AUTHOR_EMAIL: "noreply@claudexor.local",
|
|
127
|
+
GIT_COMMITTER_NAME: "Claudexor",
|
|
128
|
+
GIT_COMMITTER_EMAIL: "noreply@claudexor.local",
|
|
129
|
+
};
|
|
130
|
+
try {
|
|
131
|
+
const read = await gitEnv(repo, ["read-tree", "HEAD"], env);
|
|
132
|
+
if (read.code !== 0)
|
|
133
|
+
throw new WorkspaceError(`snapshot read-tree failed: ${read.stderr.trim()}`);
|
|
134
|
+
// Bare `git add -A` with NO pathspec. A pathspec — positive OR `:(exclude)` —
|
|
135
|
+
// that *names* an ignored path makes git hard-error ("paths are ignored ...
|
|
136
|
+
// use -f") when the project's own .gitignore lists `.claudexor` (the in-place
|
|
137
|
+
// bug the user hit). Bare `add -A` honors .gitignore and silently skips ignored
|
|
138
|
+
// paths instead. We then unstage `.claudexor` from the SCRATCH index so run
|
|
139
|
+
// artifacts never enter the snapshot even when `.claudexor` is NOT gitignored
|
|
140
|
+
// (concurrent envelope materialization); `--ignore-unmatch` makes the already-
|
|
141
|
+
// ignored case a clean no-op rather than an error.
|
|
142
|
+
const add = await gitEnv(repo, ["add", "-A"], env);
|
|
143
|
+
if (add.code !== 0)
|
|
144
|
+
throw new WorkspaceError(`snapshot add -A failed: ${add.stderr.trim()}`);
|
|
145
|
+
// TRACKED files under the artifact dirs are USER STATE (versioned config)
|
|
146
|
+
// — capture them (INDEX ∪ HEAD via the shared owner: index alone misses a
|
|
147
|
+
// user-STAGED deletion, HEAD alone misses a user-staged new config) and
|
|
148
|
+
// restore the still-present ones after the bulk unstage, so a tracked
|
|
149
|
+
// .claudexor/config.yaml edit survives into the snapshot TREE while a
|
|
150
|
+
// staged deletion stays deleted. Reads the REAL index (no scratch env):
|
|
151
|
+
// the scratch index just add -A'd runtime artifacts, which must not
|
|
152
|
+
// masquerade as tracked.
|
|
153
|
+
const trackedKeep = [...(await trackedArtifactDirPaths(repo))];
|
|
154
|
+
const unstage = await gitEnv(repo, ["rm", "-r", "--cached", "--quiet", "--ignore-unmatch", ".claudexor", ".claudexor-review-evidence"], env);
|
|
155
|
+
if (unstage.code !== 0)
|
|
156
|
+
throw new WorkspaceError(`snapshot exclude .claudexor failed: ${unstage.stderr.trim()}`);
|
|
157
|
+
// Only re-add paths still PRESENT in the worktree: a deleted tracked
|
|
158
|
+
// file stays absent from the scratch index — that IS the deletion.
|
|
159
|
+
const keepPresent = trackedKeep.filter((p) => existsSync(join(repo, p)));
|
|
160
|
+
if (keepPresent.length > 0) {
|
|
161
|
+
const readd = await gitEnv(repo, ["add", "-f", "--", ...keepPresent], env);
|
|
162
|
+
if (readd.code !== 0)
|
|
163
|
+
throw new WorkspaceError(`snapshot re-add of tracked artifact-dir files failed: ${readd.stderr.trim()}`);
|
|
164
|
+
}
|
|
165
|
+
const writeTree = await gitEnv(repo, ["write-tree"], env);
|
|
166
|
+
if (writeTree.code !== 0)
|
|
167
|
+
throw new WorkspaceError(`snapshot write-tree failed: ${writeTree.stderr.trim()}`);
|
|
168
|
+
const tree = writeTree.stdout.trim();
|
|
169
|
+
const commit = await gitEnv(repo, ["commit-tree", tree, "-p", headSha, "-m", "claudexor: dirty worktree snapshot (incl. untracked)"], env);
|
|
170
|
+
if (commit.code !== 0)
|
|
171
|
+
throw new WorkspaceError(`snapshot commit-tree failed: ${commit.stderr.trim()}`);
|
|
172
|
+
const sha = commit.stdout.trim();
|
|
173
|
+
return sha.length > 0 ? sha : null;
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
try {
|
|
177
|
+
rmSync(tmpIndex, { force: true });
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
/* scratch index cleanup is best-effort */
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Capture the CURRENT working-tree state (tracked + untracked, minus `.claudexor`)
|
|
186
|
+
* as a commit sha — always returns one. Dirty trees become a dangling snapshot
|
|
187
|
+
* commit (via `stashCreate`); a clean tree resolves to HEAD. This is the per-turn
|
|
188
|
+
* diff base for in-place threads: snapshot at turn start, snapshot at turn end,
|
|
189
|
+
* `diffTrees(base, end)` yields exactly that turn's net change (pre-existing dirty
|
|
190
|
+
* state is folded into `base`, so the reviewer never sees prior turns' edits).
|
|
191
|
+
*/
|
|
192
|
+
export async function snapshotTree(repo) {
|
|
193
|
+
const snap = await stashCreate(repo);
|
|
194
|
+
return snap ?? (await revParse(repo, "HEAD"));
|
|
195
|
+
}
|
|
196
|
+
/** Net diff between two tree-ish shas (used for in-place per-turn diffs).
|
|
197
|
+
* `--binary`: binary changes carry an APPLYABLE payload instead of
|
|
198
|
+
* degrading to a "Binary files differ" stub that silently loses the work. */
|
|
199
|
+
export async function diffTrees(repo, baseSha, endSha) {
|
|
200
|
+
const r = await git(repo, ["diff", "--binary", baseSha, endSha]);
|
|
201
|
+
if (r.code !== 0) {
|
|
202
|
+
throw new WorkspaceError(`git diff ${baseSha} ${endSha} failed: ${r.stderr.trim()}`);
|
|
203
|
+
}
|
|
204
|
+
assertNoBinaryStubs(r.stdout, `git diff ${baseSha} ${endSha}`);
|
|
205
|
+
return r.stdout;
|
|
206
|
+
}
|
|
207
|
+
/** Capture-time honesty: with --binary a payload-less "Binary files differ"
|
|
208
|
+
* stub should be impossible; if one appears anyway, fail AT CAPTURE with a
|
|
209
|
+
* typed error instead of shipping a patch that cannot apply. */
|
|
210
|
+
function assertNoBinaryStubs(diff, label) {
|
|
211
|
+
const stubs = parseUnifiedDiff(diff).files.filter((f) => f.binaryStub);
|
|
212
|
+
if (stubs.length > 0) {
|
|
213
|
+
const names = stubs.map((f) => f.newPath ?? f.oldPath ?? "(unknown)").join(", ");
|
|
214
|
+
throw new WorkspaceError(`${label} produced undeliverable binary stub(s) for: ${names}; the work product cannot be applied`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** Resolve the TREE object sha for a commit-ish. snapshotTree returns COMMIT
|
|
218
|
+
* shas whose ids vary by timestamp even for identical content; comparing the
|
|
219
|
+
* underlying tree shas is the content-stable equality test. */
|
|
220
|
+
async function treeOf(repo, sha) {
|
|
221
|
+
const r = await git(repo, ["rev-parse", `${sha}^{tree}`]);
|
|
222
|
+
if (r.code !== 0)
|
|
223
|
+
throw new WorkspaceError(`rev-parse ${sha}^{tree} failed: ${r.stderr.trim()}`);
|
|
224
|
+
return r.stdout.trim();
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Server-owned in-place revert: restore the live working tree to a recorded
|
|
228
|
+
* pre-turn snapshot, but ONLY when the tree still matches the recorded post-turn
|
|
229
|
+
* snapshot (i.e. the user hasn't edited since) — otherwise it fails loudly and
|
|
230
|
+
* touches nothing. Reverts tracked modifications/deletions AND removes files the
|
|
231
|
+
* turn added; `.claudexor` (run artifacts) is always preserved.
|
|
232
|
+
*/
|
|
233
|
+
export async function revertWorkingTreeTo(repo, preTurnSha, expectedPostSha) {
|
|
234
|
+
// Divergence fence (compare content-stable tree shas, never commit shas).
|
|
235
|
+
const current = await snapshotTree(repo);
|
|
236
|
+
const [currentTree, expectedTree] = await Promise.all([treeOf(repo, current), treeOf(repo, expectedPostSha)]);
|
|
237
|
+
if (currentTree !== expectedTree) {
|
|
238
|
+
return { reverted: false, removed: [], reason: "working tree diverged from the recorded post-turn state; refusing to revert" };
|
|
239
|
+
}
|
|
240
|
+
// Files the turn ADDED (in post, absent in pre) must be removed; restore alone
|
|
241
|
+
// only reverts tracked modifications/deletions, never deletes extra files.
|
|
242
|
+
// `--no-renames` forces a turn rename to surface as delete-old + ADD-new so the
|
|
243
|
+
// new path is caught here (rename detection would hide it under an R status).
|
|
244
|
+
// `-z`: NUL-separated RAW paths — git C-quotes special names on the
|
|
245
|
+
// newline format, and the quoted literal never exists on disk, so rmSync
|
|
246
|
+
// "removed" nothing while the old code still reported reverted:true.
|
|
247
|
+
const added = await git(repo, ["diff", "--no-renames", "--name-only", "--diff-filter=A", "-z", preTurnSha, expectedPostSha]);
|
|
248
|
+
if (added.code !== 0)
|
|
249
|
+
throw new WorkspaceError(`revert diff failed: ${added.stderr.trim()}`);
|
|
250
|
+
const isArtifact = await claudexorArtifactPredicate(repo);
|
|
251
|
+
const toRemove = added.stdout
|
|
252
|
+
.split("\0")
|
|
253
|
+
.map((l) => l.replace(/\n$/, ""))
|
|
254
|
+
.filter((p) => p.length > 0 && !isArtifact(p));
|
|
255
|
+
// Restore every path present in the pre-turn snapshot (mods + deletions).
|
|
256
|
+
const restore = await git(repo, ["checkout", preTurnSha, "--", "."]);
|
|
257
|
+
if (restore.code !== 0)
|
|
258
|
+
throw new WorkspaceError(`revert checkout failed: ${restore.stderr.trim()}`);
|
|
259
|
+
const removed = [];
|
|
260
|
+
const survivors = [];
|
|
261
|
+
for (const rel of toRemove) {
|
|
262
|
+
try {
|
|
263
|
+
rmSync(join(repo, rel), { force: true });
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
/* verified below: existence decides, not the rm call */
|
|
267
|
+
}
|
|
268
|
+
if (existsSync(join(repo, rel)))
|
|
269
|
+
survivors.push(rel);
|
|
270
|
+
else
|
|
271
|
+
removed.push(rel);
|
|
272
|
+
}
|
|
273
|
+
// Re-sync the index so `git status` reflects the restored tree (checkout left
|
|
274
|
+
// turn-added paths staged-for-delete handling to us).
|
|
275
|
+
await git(repo, ["add", "-A", "--", "."]);
|
|
276
|
+
await git(repo, ["reset", "--quiet"]);
|
|
277
|
+
if (survivors.length > 0) {
|
|
278
|
+
// A survivor means the tree does NOT match the pre-turn state: refusing
|
|
279
|
+
// to claim reverted:true is the honesty contract (INV-114 family).
|
|
280
|
+
return {
|
|
281
|
+
reverted: false,
|
|
282
|
+
removed,
|
|
283
|
+
reason: `failed to remove turn-added file(s): ${survivors.join(", ")}`,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return { reverted: true, removed };
|
|
287
|
+
}
|
|
288
|
+
export async function worktreeAdd(repo, path, branch, baseSha) {
|
|
289
|
+
const r = await git(repo, ["worktree", "add", "-b", branch, path, baseSha]);
|
|
290
|
+
if (r.code !== 0)
|
|
291
|
+
throw new WorkspaceError(`git worktree add failed: ${r.stderr.trim()}`);
|
|
292
|
+
}
|
|
293
|
+
/** Recreate a worktree for an EXISTING branch (recovery: dir lost, branch survived). */
|
|
294
|
+
export async function worktreeAddExisting(repo, path, branch) {
|
|
295
|
+
// A stale registration for the lost directory would fail the add; prune first.
|
|
296
|
+
await git(repo, ["worktree", "prune"]);
|
|
297
|
+
const r = await git(repo, ["worktree", "add", path, branch]);
|
|
298
|
+
if (r.code !== 0)
|
|
299
|
+
throw new WorkspaceError(`git worktree add (existing branch) failed: ${r.stderr.trim()}`);
|
|
300
|
+
}
|
|
301
|
+
export async function worktreeRemove(repo, path) {
|
|
302
|
+
await git(repo, ["worktree", "remove", "--force", path]);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The PROTECTED apply path: `git apply --check` first (clean typed
|
|
306
|
+
* refusal, tree untouched), then `--3way`; if 3way still fails (race with a
|
|
307
|
+
* concurrent edit), RESTORE — `checkout -- .` for tracked content plus
|
|
308
|
+
* removal of files the partial apply CREATED (from the shared diff parser's
|
|
309
|
+
* added-paths) — and verify with `status --porcelain`. `ok:false,
|
|
310
|
+
* treeMutated:false` is a GUARANTEE the tree is byte-identical (INV-114);
|
|
311
|
+
* a failed restoration reports `treeMutated:true` honestly instead.
|
|
312
|
+
*/
|
|
313
|
+
export async function applyPatchProtected(repo, diff) {
|
|
314
|
+
if (!diff.trim())
|
|
315
|
+
return { ok: true, treeMutated: false, detail: "empty patch; no-op" };
|
|
316
|
+
const check = await git(repo, ["apply", "--check", "--whitespace=nowarn", "-"], diff);
|
|
317
|
+
if (check.code !== 0) {
|
|
318
|
+
return { ok: false, treeMutated: false, detail: `apply --check refused: ${check.stderr.trim()}` };
|
|
319
|
+
}
|
|
320
|
+
const before = await statusPorcelain(repo);
|
|
321
|
+
const ap = await git(repo, ["apply", "--3way", "--whitespace=nowarn", "-"], diff);
|
|
322
|
+
if (ap.code === 0)
|
|
323
|
+
return { ok: true, treeMutated: true };
|
|
324
|
+
// 3way failed after a passing --check (concurrent mutation): restore.
|
|
325
|
+
const added = parseUnifiedDiff(diff)
|
|
326
|
+
.files.filter((f) => f.added && f.newPath)
|
|
327
|
+
.map((f) => f.newPath);
|
|
328
|
+
for (const rel of added) {
|
|
329
|
+
try {
|
|
330
|
+
rmSync(join(repo, rel), { force: true });
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
/* verified below via the status fingerprint */
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
await git(repo, ["checkout", "--", "."]);
|
|
337
|
+
const after = await statusPorcelain(repo);
|
|
338
|
+
const restored = after === before;
|
|
339
|
+
return {
|
|
340
|
+
ok: false,
|
|
341
|
+
treeMutated: !restored,
|
|
342
|
+
detail: `apply --3way failed: ${ap.stderr.trim()}` +
|
|
343
|
+
(restored ? "; tree restored to its pre-apply state" : "; RESTORE FAILED — tree left mutated, inspect manually"),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
export async function worktreePrune(repo) {
|
|
347
|
+
await git(repo, ["worktree", "prune"]);
|
|
348
|
+
}
|
|
349
|
+
/** Delete a local branch (best-effort GC of per-attempt claudexor/* branches). */
|
|
350
|
+
export async function branchDelete(repo, branch) {
|
|
351
|
+
await git(repo, ["branch", "-D", branch]);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Stage everything (so untracked files appear) and return the diff vs the
|
|
355
|
+
* recorded BASE sha — NOT the worktree HEAD. A harness that commits inside the
|
|
356
|
+
* worktree (Claude Code does this routinely) advances HEAD; diffing vs HEAD
|
|
357
|
+
* would then hide the committed work and report an empty no-op diff, silently
|
|
358
|
+
* losing the candidate's real output. Diffing the staged tree vs base_sha
|
|
359
|
+
* captures all net change since the run started regardless of intermediate
|
|
360
|
+
* commits. Git op failures throw loudly instead of masquerading as "no changes".
|
|
361
|
+
*/
|
|
362
|
+
export async function diffStaged(worktreePath, baseSha) {
|
|
363
|
+
await runCaptureRaw("rm", ["-rf", ".claudexor-review-evidence"], { cwd: worktreePath, timeoutMs: 10_000 }).catch(() => null);
|
|
364
|
+
const add = await git(worktreePath, ["add", "-A"]);
|
|
365
|
+
if (add.code !== 0) {
|
|
366
|
+
throw new WorkspaceError(`git add -A failed during diff capture: ${add.stderr.trim()}`);
|
|
367
|
+
}
|
|
368
|
+
const target = baseSha && baseSha.length > 0 ? baseSha : "HEAD";
|
|
369
|
+
const diff = await git(worktreePath, ["diff", "--binary", "--cached", target]);
|
|
370
|
+
if (diff.code !== 0) {
|
|
371
|
+
throw new WorkspaceError(`git diff --cached ${target} failed during diff capture: ${diff.stderr.trim()}`);
|
|
372
|
+
}
|
|
373
|
+
assertNoBinaryStubs(diff.stdout, `git diff --cached ${target}`);
|
|
374
|
+
return diff.stdout;
|
|
375
|
+
}
|
|
376
|
+
//# sourceMappingURL=git.js.map
|
package/dist/git.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { AccessProfile, DirtyPolicy, WorkspaceEnvelope } from "@claudexor/schema";
|
|
2
|
+
export interface CreateEnvelopeOptions {
|
|
3
|
+
taskId: string;
|
|
4
|
+
attemptId: string;
|
|
5
|
+
baseRef?: string;
|
|
6
|
+
accessProfile?: AccessProfile;
|
|
7
|
+
dirtyPolicy?: DirtyPolicy;
|
|
8
|
+
/**
|
|
9
|
+
* Run against the live `repoRoot` directly instead of an isolated git worktree.
|
|
10
|
+
* Used for external stateful environments that may not be git repositories
|
|
11
|
+
* and whose runtime STATE, not a patch, is the deliverable.
|
|
12
|
+
* `dispose()` never deletes the live tree in this mode; a best-effort baseline
|
|
13
|
+
* snapshot backs `diff()` and reviewers also read the live tree directly.
|
|
14
|
+
*/
|
|
15
|
+
inPlace?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Manages WorkspaceEnvelopes: an isolated git worktree plus scoped HOME and
|
|
19
|
+
* per-harness config dirs, and dirty-tree handling. Claudexor owns these
|
|
20
|
+
* envelopes (it does not rely on a harness's native --worktree).
|
|
21
|
+
*/
|
|
22
|
+
/** `ps` start time for a pid, or null when unavailable. Pid+start-time
|
|
23
|
+
* equality is the recycling-proof liveness identity for envelope owners
|
|
24
|
+
* (command names/titles mutate; the kernel start time never does). */
|
|
25
|
+
export declare function processStartTime(pid: number): string | null;
|
|
26
|
+
export declare class WorkspaceManager {
|
|
27
|
+
private readonly repoRoot;
|
|
28
|
+
constructor(repoRoot: string);
|
|
29
|
+
private workspacesDir;
|
|
30
|
+
/**
|
|
31
|
+
* The scoped envelope base for a task/attempt. The sole on-disk root we
|
|
32
|
+
* delete on dispose — so the ids MUST be path-safe segments and the resolved
|
|
33
|
+
* base MUST stay inside the workspaces dir (a crafted `../` id could
|
|
34
|
+
* otherwise turn dispose() into an arbitrary recursive delete).
|
|
35
|
+
*/
|
|
36
|
+
private envelopeBase;
|
|
37
|
+
create(opts: CreateEnvelopeOptions): Promise<WorkspaceEnvelope>;
|
|
38
|
+
/**
|
|
39
|
+
* Best-effort baseline copy of the live tree for in-place diff(). Copies each
|
|
40
|
+
* top-level entry individually (skipping heavy/ephemeral dirs, notably `.claudexor`
|
|
41
|
+
* which holds this base) — this both prunes noise and avoids Node's "cannot copy
|
|
42
|
+
* a directory into its own subdirectory" guard, since the baseline lives under
|
|
43
|
+
* `.claudexor`. On any failure the baseline is simply absent and diff() returns
|
|
44
|
+
* empty; reviewers still read the live tree directly.
|
|
45
|
+
*/
|
|
46
|
+
private snapshotBaseline;
|
|
47
|
+
/** Env vars that scope a child harness to this envelope (HOME + per-harness config dirs). */
|
|
48
|
+
envFor(env: WorkspaceEnvelope): Record<string, string>;
|
|
49
|
+
/**
|
|
50
|
+
* Provision a SCOPED, worktree-less harness HOME for READ-ONLY routes (plan,
|
|
51
|
+
* ask, audit, orchestrate, reviewers). Read-only modes build no git worktree,
|
|
52
|
+
* but a harness still writes native state — claude-code plan files, codex
|
|
53
|
+
* session rollouts, transcripts — into `$HOME/.claude`, `$CODEX_HOME`, etc.
|
|
54
|
+
* Without this, those land in the operator's REAL home (a live-caught leak: a
|
|
55
|
+
* read-only `plan` wrote into `~/.claude/plans`). Same env shape as `envFor`,
|
|
56
|
+
* so the adapters seed auth (subscription creds / api key) into these scoped
|
|
57
|
+
* dirs exactly as they do for a write envelope (CLAUDEXOR_BIBLE §6). Caller
|
|
58
|
+
* disposes when the run ends.
|
|
59
|
+
*/
|
|
60
|
+
readOnlyHomeEnv(): {
|
|
61
|
+
env: Record<string, string>;
|
|
62
|
+
dispose: () => void;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Header-only rewrite of a plain GNU `diff -ruN <baseline> <live>` document:
|
|
66
|
+
* absolute baseline/live prefixes become git-style `a/<rel>` / `b/<rel>` so
|
|
67
|
+
* repo-relative protected-path and risk globs see the SAME shape they see
|
|
68
|
+
* for git diffs. STATEFUL, mirroring the shared plain-diff parser's
|
|
69
|
+
* structural rules: only the GNU `diff …` command echo, the `--- `/`+++ `
|
|
70
|
+
* pair of a real file-header triple (`--- ` + `+++ ` + `@@` on consecutive
|
|
71
|
+
* lines), and structural `Binary files … differ` lines are rewritten.
|
|
72
|
+
* Hunk CONTENT is never touched — a removed/added content line that merely
|
|
73
|
+
* starts with `-- `/`++ ` (rendering as `--- `/`+++ ` in the diff) keeps
|
|
74
|
+
* its bytes, per the diff-fidelity contract (INV-041).
|
|
75
|
+
*/
|
|
76
|
+
private static relativizePlainDiffHeadersFor;
|
|
77
|
+
diff(env: WorkspaceEnvelope): Promise<string>;
|
|
78
|
+
dispose(env: WorkspaceEnvelope): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Dispose an ORPHANED envelope by ids alone (crash GC): a daemon
|
|
81
|
+
* crash leaves envelopes with no live job. Reconstructs the disposal
|
|
82
|
+
* surface (worktree path, branch name, envelope base) from the same id
|
|
83
|
+
* derivation create() used, so the safety invariants (id-validated base,
|
|
84
|
+
* never the live tree) hold without a persisted envelope record.
|
|
85
|
+
*/
|
|
86
|
+
disposeOrphan(taskId: string, attemptId: string): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAmBvF,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH;;sEAEsE;AACtE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO3D;AAED,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAE7C,OAAO,CAAC,aAAa;IAIrB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAcd,MAAM,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA6GrE;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAcxB,6FAA6F;IAC7F,MAAM,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAYtD;;;;;;;;;;OAUG;IACH,eAAe,IAAI;QAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE;IAsBvE;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,MAAM,CAAC,6BAA6B;IAiDtC,IAAI,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;IAkC7C,OAAO,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDpD;;;;;;OAMG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA0BtE"}
|