@bluewombat/isolation-git 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +62 -0
  2. package/dist/branch-name.d.ts +2 -0
  3. package/dist/branch-name.d.ts.map +1 -0
  4. package/dist/branch-name.js +42 -0
  5. package/dist/branch-name.js.map +1 -0
  6. package/dist/child/run-child.d.ts +42 -0
  7. package/dist/child/run-child.d.ts.map +1 -0
  8. package/dist/child/run-child.js +121 -0
  9. package/dist/child/run-child.js.map +1 -0
  10. package/dist/copy-working-files.d.ts +40 -0
  11. package/dist/copy-working-files.d.ts.map +1 -0
  12. package/dist/copy-working-files.js +144 -0
  13. package/dist/copy-working-files.js.map +1 -0
  14. package/dist/fold.d.ts +6 -0
  15. package/dist/fold.d.ts.map +1 -0
  16. package/dist/fold.js +19 -0
  17. package/dist/fold.js.map +1 -0
  18. package/dist/git-merge.d.ts +37 -0
  19. package/dist/git-merge.d.ts.map +1 -0
  20. package/dist/git-merge.js +415 -0
  21. package/dist/git-merge.js.map +1 -0
  22. package/dist/git-worktree.d.ts +37 -0
  23. package/dist/git-worktree.d.ts.map +1 -0
  24. package/dist/git-worktree.js +68 -0
  25. package/dist/git-worktree.js.map +1 -0
  26. package/dist/index.d.ts +15 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +13 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/isolation.d.ts +13 -0
  31. package/dist/isolation.d.ts.map +1 -0
  32. package/dist/isolation.js +43 -0
  33. package/dist/isolation.js.map +1 -0
  34. package/dist/reference.d.ts +15 -0
  35. package/dist/reference.d.ts.map +1 -0
  36. package/dist/reference.js +196 -0
  37. package/dist/reference.js.map +1 -0
  38. package/dist/run-env.d.ts +30 -0
  39. package/dist/run-env.d.ts.map +1 -0
  40. package/dist/run-env.js +49 -0
  41. package/dist/run-env.js.map +1 -0
  42. package/dist/strategy.d.ts +19 -0
  43. package/dist/strategy.d.ts.map +1 -0
  44. package/dist/strategy.js +44 -0
  45. package/dist/strategy.js.map +1 -0
  46. package/dist/types.d.ts +63 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +2 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # Isolation: git
2
+
3
+ Git worktree attach and three-way merge fold. Host (or a Transformer CLI) loads
4
+ this package when `workLine.isolation` is `@bluewombat/isolation-git`.
5
+
6
+ ```ts
7
+ import { branchNameOf, strategy } from "@bluewombat/isolation-git";
8
+ // strategy.isolation → Isolator
9
+ // strategy.fold → Integrator
10
+ // strategy.refOf(id) → issue/<item>, what the Authority hands the Publisher:
11
+ // issue/9 for github:owner/repo#9, issue/9-s1 for its Subtask s1,
12
+ // issue/<slug of the id> for an id in any other shape, cut at 80 with a
13
+ // 7-hex digest of the whole id when it was cut
14
+ // branchNameOf(id) → the same, for anyone holding an id
15
+ ```
16
+
17
+ The Child is a worktree of the Parent, then wiped and filled with the Parent's
18
+ working files so dirty and untracked content matches — `.cursor/`, `CLAUDE.md`,
19
+ `node_modules/`, tracked or not, all of it.
20
+
21
+ ## What a Child does not get
22
+
23
+ `.env` and `.env.*`, at any depth, unless the Project says otherwise: a secret
24
+ in the clear has no business in a directory an agent reads and a pull request
25
+ may carry. A Project names its own list in the config, and that list
26
+ **replaces** the default:
27
+
28
+ ```json
29
+ "workLine": {
30
+ "isolation": "@bluewombat/isolation-git",
31
+ "isolationOptions": { "exclude": [".env", ".env.*", "**/*.pem", "secrets/**"] }
32
+ }
33
+ ```
34
+
35
+ A pattern with no `/` is matched against the last path segment, at any depth
36
+ (`.env` also leaves out `packages/api/.env`); one with a `/` against the whole
37
+ path from the root (`build/out`). `*` is any run of characters within one
38
+ segment, `**` anything, and a leading `**/` means "at any depth". An unknown
39
+ key, or an `exclude` that is not a list of non-empty strings, stops `mason
40
+ run` before it isolates anything. Nothing here reads `.gitignore`: what git
41
+ ignores is very often exactly what a Builder needs (its dependencies, the
42
+ Project's own agent configuration).
43
+
44
+ Each root entry is copied by `cp` — `-c` on macOS, `--reflink=auto` elsewhere
45
+ — so on a file system that clones (APFS, btrfs, XFS) the Child shares its
46
+ bytes with the Parent instead of doubling them; where `cp` cannot, the entry
47
+ is copied by hand. Excluded paths are removed from the Child afterwards.
48
+
49
+ ## The reference, and who the system is
50
+
51
+ `strategy.reference.parse` reads what a manager names as the reference work
52
+ line: `remote` and `branch`, required; `credentialEnv` and `author`, optional.
53
+ Anything else is refused. From the optional two it builds the git environment
54
+ of the run — `GIT_AUTHOR_*` / `GIT_COMMITTER_*`, and a credential helper that
55
+ reads the token from the named variable at the moment git asks, after
56
+ clearing the machine's own helpers so a keychain never answers first. Nothing
57
+ is written to disk. Every git this package spawns carries that environment,
58
+ and the parsed reference exposes it as `env` so Host can hand it to the slots
59
+ that touch the work line (Publisher, Refresher) — and to nothing else: a
60
+ Builder never inherits a token.
61
+
62
+ Without `author`, commits carry the identity of whoever runs the process.
@@ -0,0 +1,2 @@
1
+ export declare function branchNameOf(id: string): string;
2
+ //# sourceMappingURL=branch-name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-name.d.ts","sourceRoot":"","sources":["../src/branch-name.ts"],"names":[],"mappings":"AA4BA,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAiB/C"}
@@ -0,0 +1,42 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * A ref name for one Isolation, from its id.
4
+ *
5
+ * An unnamed Child leaves nothing behind: once its directory is gone, no one
6
+ * can see what the work was. The name is derived rather than asked for, so the
7
+ * contract keeps one id and gains no second identity to keep in step.
8
+ */
9
+ /**
10
+ * What the branch is named after: the tracked item it works on, not the tool
11
+ * that made it. The tool is one git user among others on the repository; the
12
+ * branch outlives its run and is read by people who never heard of it.
13
+ */
14
+ const REF_PREFIX = "issue";
15
+ /**
16
+ * `<scheme>:<path>#<number>[:<rest>]` is how a tracker names an item — a
17
+ * GitHub issue, a GitLab one — and inside one Project the work line belongs to
18
+ * one repository, so the number alone tells the branches apart. An id in any
19
+ * other shape keeps its whole slug.
20
+ */
21
+ const TRACKED_ITEM = /^[a-z][a-z0-9+.-]*:(?:[^#]*\/)?[^#/]*#(\d+)(?::(.+))?$/i;
22
+ /** Where a slug is cut; past it, a digest of the whole id keeps two long ids apart. */
23
+ const MAX_SLUG = 80;
24
+ const DIGEST_LENGTH = 7;
25
+ export function branchNameOf(id) {
26
+ const item = TRACKED_ITEM.exec(id);
27
+ const short = item?.[1] === undefined ? id : item[2] === undefined ? item[1] : `${item[1]}:${item[2]}`;
28
+ const slug = short
29
+ .toLowerCase()
30
+ .replace(/[^a-z0-9]+/g, "-")
31
+ .replace(/^-+|-+$/g, "");
32
+ if (slug.length <= MAX_SLUG) {
33
+ return `${REF_PREFIX}/${slug.length > 0 ? slug : "isolation"}`;
34
+ }
35
+ // Two ids that differ only past the cut would otherwise share a name, and
36
+ // `-B` would reset one Isolation's branch onto the other's. The digest is
37
+ // of the id as given, so the name stays a function of the id alone.
38
+ const digest = createHash("sha256").update(id).digest("hex").slice(0, DIGEST_LENGTH);
39
+ const head = slug.slice(0, MAX_SLUG - DIGEST_LENGTH - 1).replace(/-+$/, "");
40
+ return `${REF_PREFIX}/${head}-${digest}`;
41
+ }
42
+ //# sourceMappingURL=branch-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-name.js","sourceRoot":"","sources":["../src/branch-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;GAMG;AACH;;;;GAIG;AACH,MAAM,UAAU,GAAG,OAAO,CAAC;AAE3B;;;;;GAKG;AACH,MAAM,YAAY,GAAG,yDAAyD,CAAC;AAE/E,uFAAuF;AACvF,MAAM,QAAQ,GAAG,EAAE,CAAC;AACpB,MAAM,aAAa,GAAG,CAAC,CAAC;AAExB,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnC,MAAM,KAAK,GACT,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3F,MAAM,IAAI,GAAG,KAAK;SACf,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC5B,OAAO,GAAG,UAAU,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACjE,CAAC;IACD,0EAA0E;IAC1E,0EAA0E;IAC1E,oEAAoE;IACpE,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;IACrF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC5E,OAAO,GAAG,UAAU,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,42 @@
1
+ type ClockName = "isolation" | "integration";
2
+ export type SpawnRequest = {
3
+ argv: string[];
4
+ /** Undefined lets the child inherit the caller's directory. */
5
+ cwd?: string | undefined;
6
+ /** Wall-clock budget for this child (ms). */
7
+ timeoutMs: number;
8
+ /** Which clock owns the nearer deadline when timeoutMs hits. */
9
+ timeoutClock: ClockName;
10
+ /** When true, treat kill as interrupted (stop signal). */
11
+ shouldInterrupt: () => boolean;
12
+ /** Undefined lets the child inherit the caller's environment. */
13
+ env?: NodeJS.ProcessEnv | undefined;
14
+ /** Layered on top of the inherited environment. Ignored when `env` is set. */
15
+ envOverrides?: NodeJS.ProcessEnv | undefined;
16
+ };
17
+ export type SpawnOutcome = {
18
+ kind: "exited";
19
+ exitCode: number | null;
20
+ signal: NodeJS.Signals | null;
21
+ stdout: string;
22
+ stderr: string;
23
+ } | {
24
+ kind: "timed_out";
25
+ clock: ClockName;
26
+ stdout: string;
27
+ stderr: string;
28
+ } | {
29
+ kind: "interrupted";
30
+ stdout: string;
31
+ stderr: string;
32
+ } | {
33
+ kind: "spawn_error";
34
+ detail: string;
35
+ };
36
+ /**
37
+ * Run one child with a wall-clock timeout. Does not inherit the caller's stdin.
38
+ */
39
+ export declare function runChild(request: SpawnRequest): Promise<SpawnOutcome>;
40
+ export declare function combinedOutput(stdout: string, stderr: string): string;
41
+ export {};
42
+ //# sourceMappingURL=run-child.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-child.d.ts","sourceRoot":"","sources":["../../src/child/run-child.ts"],"names":[],"mappings":"AAIA,KAAK,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;AAE7C,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,YAAY,EAAE,SAAS,CAAC;IACxB,0DAA0D;IAC1D,eAAe,EAAE,MAAM,OAAO,CAAC;IAC/B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;IACpC,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;CAC9C,CAAC;AAgBF,MAAM,MAAM,YAAY,GACpB;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAiBN;;GAEG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA+FrE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGrE"}
@@ -0,0 +1,121 @@
1
+ import { spawn } from "node:child_process";
2
+ const OUTPUT_TRUNCATE = 8_192;
3
+ /**
4
+ * The one place ambient environment enters a child. `env` replaces it outright;
5
+ * `envOverrides` layers on top of what the child would inherit anyway.
6
+ */
7
+ function childEnv(request) {
8
+ if (request.env !== undefined) {
9
+ return request.env;
10
+ }
11
+ if (request.envOverrides !== undefined) {
12
+ return { ...process.env, ...request.envOverrides };
13
+ }
14
+ return undefined;
15
+ }
16
+ function truncate(text) {
17
+ if (text.length <= OUTPUT_TRUNCATE) {
18
+ return text;
19
+ }
20
+ return `${text.slice(0, OUTPUT_TRUNCATE)}…[truncated]`;
21
+ }
22
+ function killProcessTree(child) {
23
+ try {
24
+ child.kill("SIGKILL");
25
+ }
26
+ catch {
27
+ // already gone
28
+ }
29
+ }
30
+ /**
31
+ * Run one child with a wall-clock timeout. Does not inherit the caller's stdin.
32
+ */
33
+ export function runChild(request) {
34
+ return new Promise((resolve) => {
35
+ const [file, ...args] = request.argv;
36
+ if (file === undefined) {
37
+ resolve({ kind: "spawn_error", detail: "Empty command." });
38
+ return;
39
+ }
40
+ let settled = false;
41
+ let timedOut = false;
42
+ let interrupted = false;
43
+ let stdout = "";
44
+ let stderr = "";
45
+ let timer;
46
+ let interruptPoll;
47
+ const finish = (outcome) => {
48
+ if (settled) {
49
+ return;
50
+ }
51
+ settled = true;
52
+ if (timer !== undefined) {
53
+ clearTimeout(timer);
54
+ }
55
+ if (interruptPoll !== undefined) {
56
+ clearInterval(interruptPoll);
57
+ }
58
+ resolve(outcome);
59
+ };
60
+ let child;
61
+ try {
62
+ child = spawn(file, args, {
63
+ cwd: request.cwd,
64
+ env: childEnv(request),
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ });
67
+ }
68
+ catch (error) {
69
+ const detail = error instanceof Error ? error.message : String(error);
70
+ finish({ kind: "spawn_error", detail });
71
+ return;
72
+ }
73
+ if (!child.stdout || !child.stderr) {
74
+ finish({ kind: "spawn_error", detail: "Child stdout/stderr pipes unavailable." });
75
+ return;
76
+ }
77
+ child.stdout.setEncoding("utf8");
78
+ child.stderr.setEncoding("utf8");
79
+ child.stdout.on("data", (chunk) => {
80
+ stdout += chunk;
81
+ });
82
+ child.stderr.on("data", (chunk) => {
83
+ stderr += chunk;
84
+ });
85
+ child.on("error", (error) => {
86
+ finish({ kind: "spawn_error", detail: error.message });
87
+ });
88
+ timer = setTimeout(() => {
89
+ timedOut = true;
90
+ killProcessTree(child);
91
+ }, Math.max(1, request.timeoutMs));
92
+ interruptPoll = setInterval(() => {
93
+ if (request.shouldInterrupt()) {
94
+ interrupted = true;
95
+ killProcessTree(child);
96
+ }
97
+ }, 20);
98
+ if (request.shouldInterrupt()) {
99
+ interrupted = true;
100
+ killProcessTree(child);
101
+ }
102
+ child.on("close", (exitCode, signal) => {
103
+ const out = truncate(stdout);
104
+ const err = truncate(stderr);
105
+ if (interrupted || request.shouldInterrupt()) {
106
+ finish({ kind: "interrupted", stdout: out, stderr: err });
107
+ return;
108
+ }
109
+ if (timedOut) {
110
+ finish({ kind: "timed_out", clock: request.timeoutClock, stdout: out, stderr: err });
111
+ return;
112
+ }
113
+ finish({ kind: "exited", exitCode, signal, stdout: out, stderr: err });
114
+ });
115
+ });
116
+ }
117
+ export function combinedOutput(stdout, stderr) {
118
+ const parts = [stdout, stderr].filter((p) => p.length > 0);
119
+ return truncate(parts.join("\n"));
120
+ }
121
+ //# sourceMappingURL=run-child.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-child.js","sourceRoot":"","sources":["../../src/child/run-child.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { type IsolationStop, IsolationStoppedError } from "@bluewombat/isolator";
2
+ /**
3
+ * What never leaves the Parent for a Child, unless a Project says otherwise:
4
+ * a secret in the clear has no business in a workspace an agent reads and a
5
+ * pull request may carry.
6
+ */
7
+ export declare const DEFAULT_EXCLUDES: readonly string[];
8
+ /**
9
+ * Copy working files from `from` into an existing `to` directory.
10
+ * Skips `.git` only at the root of `from` when `skipGitAtRoot` is set, and
11
+ * every path `exclude` names (see `isExcluded`).
12
+ *
13
+ * Each root entry is cloned by `cp` where the file system can — APFS, btrfs,
14
+ * XFS: same bytes on disk, no second copy — and copied by hand where it
15
+ * cannot. Excluded paths are then removed from the Child; a clone shares
16
+ * blocks that were already on the disk, so nothing was written in the clear
17
+ * that was not there before.
18
+ */
19
+ export declare function copyWorkingFiles(input: {
20
+ from: string;
21
+ to: string;
22
+ skipGitAtRoot: boolean;
23
+ exclude?: readonly string[];
24
+ shouldStop: () => IsolationStop | undefined;
25
+ }): Promise<void>;
26
+ export declare function wipeWorkingFiles(directory: string, shouldStop: () => IsolationStop | undefined): Promise<void>;
27
+ export { IsolationStoppedError };
28
+ /**
29
+ * Whether `path` (relative to the copied root, `/`-separated) is one a
30
+ * Project asked to leave behind.
31
+ *
32
+ * A pattern with no `/` is matched against the last segment, at any depth:
33
+ * `.env` leaves out `.env` and `packages/api/.env`. A pattern with a `/` is
34
+ * matched against the whole relative path from the root. `*` stands for any
35
+ * run of characters within one segment, `**` for anything, `/` included; a
36
+ * pattern opening with two stars and a slash means "at any depth", like a
37
+ * bare name does.
38
+ */
39
+ export declare function isExcluded(path: string, patterns: readonly string[]): boolean;
40
+ //# sourceMappingURL=copy-working-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-working-files.d.ts","sourceRoot":"","sources":["../src/copy-working-files.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,aAAa,EAAE,qBAAqB,EAAkB,MAAM,sBAAsB,CAAC;AAMjG;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAAuB,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,UAAU,EAAE,MAAM,aAAa,GAAG,SAAS,CAAC;CAC7C,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBhB;AAED,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,aAAa,GAAG,SAAS,GAC1C,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,OAAO,EAAE,qBAAqB,EAAE,CAAC;AAEjC;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAQ7E"}
@@ -0,0 +1,144 @@
1
+ import { copyFile, lstat, mkdir, readdir, readlink, rm, symlink } from "node:fs/promises";
2
+ import { platform } from "node:os";
3
+ import { basename, join, relative, sep } from "node:path";
4
+ import { IsolationStoppedError, throwIfStopped } from "@bluewombat/isolator";
5
+ import { runChild } from "./child/run-child.js";
6
+ /** How long one `cp` of one root entry may take before the walk takes over. */
7
+ const CLONE_TIMEOUT_MS = 120_000;
8
+ /**
9
+ * What never leaves the Parent for a Child, unless a Project says otherwise:
10
+ * a secret in the clear has no business in a workspace an agent reads and a
11
+ * pull request may carry.
12
+ */
13
+ export const DEFAULT_EXCLUDES = [".env", ".env.*"];
14
+ /**
15
+ * Copy working files from `from` into an existing `to` directory.
16
+ * Skips `.git` only at the root of `from` when `skipGitAtRoot` is set, and
17
+ * every path `exclude` names (see `isExcluded`).
18
+ *
19
+ * Each root entry is cloned by `cp` where the file system can — APFS, btrfs,
20
+ * XFS: same bytes on disk, no second copy — and copied by hand where it
21
+ * cannot. Excluded paths are then removed from the Child; a clone shares
22
+ * blocks that were already on the disk, so nothing was written in the clear
23
+ * that was not there before.
24
+ */
25
+ export async function copyWorkingFiles(input) {
26
+ const exclude = input.exclude ?? DEFAULT_EXCLUDES;
27
+ await throwIfStopped(input.shouldStop);
28
+ const entries = await readdir(input.from, { withFileTypes: true });
29
+ for (const entry of entries) {
30
+ await throwIfStopped(input.shouldStop);
31
+ if (input.skipGitAtRoot && entry.name === ".git") {
32
+ continue;
33
+ }
34
+ if (isExcluded(entry.name, exclude)) {
35
+ continue;
36
+ }
37
+ const src = join(input.from, entry.name);
38
+ const dest = join(input.to, entry.name);
39
+ const cloned = await clone(src, dest, input.shouldStop);
40
+ if (!cloned) {
41
+ await rm(dest, { recursive: true, force: true });
42
+ await copyEntry(src, dest, entry, input.shouldStop);
43
+ }
44
+ }
45
+ await pruneExcluded(input.to, input.to, exclude, input.shouldStop);
46
+ }
47
+ export async function wipeWorkingFiles(directory, shouldStop) {
48
+ await throwIfStopped(shouldStop);
49
+ const entries = await readdir(directory);
50
+ for (const name of entries) {
51
+ await throwIfStopped(shouldStop);
52
+ if (name === ".git") {
53
+ continue;
54
+ }
55
+ await rm(join(directory, name), { recursive: true, force: true });
56
+ }
57
+ }
58
+ export { IsolationStoppedError };
59
+ /**
60
+ * Whether `path` (relative to the copied root, `/`-separated) is one a
61
+ * Project asked to leave behind.
62
+ *
63
+ * A pattern with no `/` is matched against the last segment, at any depth:
64
+ * `.env` leaves out `.env` and `packages/api/.env`. A pattern with a `/` is
65
+ * matched against the whole relative path from the root. `*` stands for any
66
+ * run of characters within one segment, `**` for anything, `/` included; a
67
+ * pattern opening with two stars and a slash means "at any depth", like a
68
+ * bare name does.
69
+ */
70
+ export function isExcluded(path, patterns) {
71
+ const normalized = path.split(sep).join("/");
72
+ return patterns.some((pattern) => {
73
+ // A leading `**/` says "at any depth", which is what a bare name already means.
74
+ const bare = pattern.startsWith("**/") ? pattern.slice(3) : pattern;
75
+ const subject = bare.includes("/") ? normalized : basename(normalized);
76
+ return toRegExp(bare).test(subject);
77
+ });
78
+ }
79
+ function toRegExp(pattern) {
80
+ const source = pattern
81
+ .split("**")
82
+ .map((part) => part
83
+ .split("*")
84
+ .map((literal) => literal.replace(/[.+?^${}()|[\]\\]/g, "\\$&"))
85
+ .join("[^/]*"))
86
+ .join(".*");
87
+ return new RegExp(`^${source}$`);
88
+ }
89
+ async function clone(src, dest, shouldStop) {
90
+ const flag = platform() === "darwin" ? "-c" : "--reflink=auto";
91
+ const outcome = await runChild({
92
+ argv: ["cp", "-R", "-P", flag, src, dest],
93
+ timeoutMs: CLONE_TIMEOUT_MS,
94
+ timeoutClock: "isolation",
95
+ shouldInterrupt: () => shouldStop() === "interrupt",
96
+ });
97
+ if (outcome.kind === "interrupted") {
98
+ throw new IsolationStoppedError("interrupt");
99
+ }
100
+ return outcome.kind === "exited" && outcome.exitCode === 0;
101
+ }
102
+ async function copyEntry(src, dest, entry, shouldStop) {
103
+ await throwIfStopped(shouldStop);
104
+ if (entry.isDirectory()) {
105
+ await mkdir(dest);
106
+ const entries = await readdir(src, { withFileTypes: true });
107
+ for (const child of entries) {
108
+ await copyEntry(join(src, child.name), join(dest, child.name), child, shouldStop);
109
+ }
110
+ return;
111
+ }
112
+ if (entry.isSymbolicLink()) {
113
+ const target = await readlink(src);
114
+ await symlink(target, dest);
115
+ return;
116
+ }
117
+ if (entry.isFile()) {
118
+ await copyFile(src, dest);
119
+ return;
120
+ }
121
+ throw new Error(`Cannot isolate "${src}": unsupported file type.`);
122
+ }
123
+ /** Remove from `directory` every path below `root` that `patterns` name. Never follows a link. */
124
+ async function pruneExcluded(root, directory, patterns, shouldStop) {
125
+ if (patterns.length === 0) {
126
+ return;
127
+ }
128
+ await throwIfStopped(shouldStop);
129
+ const entries = await readdir(directory, { withFileTypes: true });
130
+ for (const entry of entries) {
131
+ const path = join(directory, entry.name);
132
+ if (directory === root && entry.name === ".git") {
133
+ continue;
134
+ }
135
+ if (isExcluded(relative(root, path), patterns)) {
136
+ await rm(path, { recursive: true, force: true });
137
+ continue;
138
+ }
139
+ if (entry.isDirectory() && !(await lstat(path)).isSymbolicLink()) {
140
+ await pruneExcluded(root, path, patterns, shouldStop);
141
+ }
142
+ }
143
+ }
144
+ //# sourceMappingURL=copy-working-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-working-files.js","sourceRoot":"","sources":["../src/copy-working-files.ts"],"names":[],"mappings":"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"}
package/dist/fold.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { FoldBackend } from "@bluewombat/integrator";
2
+ /**
3
+ * Fold by three-way git merge. Never forced.
4
+ */
5
+ export declare const gitFold: FoldBackend;
6
+ //# sourceMappingURL=fold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold.d.ts","sourceRoot":"","sources":["../src/fold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG1D;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,WAarB,CAAC"}
package/dist/fold.js ADDED
@@ -0,0 +1,19 @@
1
+ import { gitMerge } from "./git-merge.js";
2
+ /**
3
+ * Fold by three-way git merge. Never forced.
4
+ */
5
+ export const gitFold = {
6
+ async fold(input) {
7
+ return await gitMerge({
8
+ parent: input.parent,
9
+ child: input.child,
10
+ ...(input.id === undefined ? {} : { id: input.id }),
11
+ ...(input.subject === undefined ? {} : { subject: input.subject }),
12
+ ...(input.mergeSubject === undefined ? {} : { mergeSubject: input.mergeSubject }),
13
+ deadlineMs: input.deadlineMs,
14
+ now: input.now,
15
+ shouldInterrupt: input.shouldInterrupt,
16
+ });
17
+ },
18
+ };
19
+ //# sourceMappingURL=fold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold.js","sourceRoot":"","sources":["../src/fold.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAgB;IAClC,KAAK,CAAC,IAAI,CAAC,KAAK;QACd,OAAO,MAAM,QAAQ,CAAC;YACpB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;YACnD,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;YAClE,GAAG,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;YACjF,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,eAAe,EAAE,KAAK,CAAC,eAAe;SACvC,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { type FoldStop } from "@bluewombat/integrator";
2
+ export type GitMergeResult = {
3
+ ok: true;
4
+ } | {
5
+ ok: false;
6
+ stop: FoldStop;
7
+ } | {
8
+ ok: false;
9
+ conflict: true;
10
+ report: string;
11
+ } | {
12
+ ok: false;
13
+ detail: string;
14
+ };
15
+ /**
16
+ * Three-way git merge of Child working files into Parent. Never forced.
17
+ * Does not fall back to copy.
18
+ *
19
+ * History is left only where something happened: a side with nothing loose
20
+ * gets no snapshot commit, a Parent that never moved is fast-forwarded, and a
21
+ * merge commit exists only when both sides moved. What is written is named
22
+ * after the work (`subject`), not after this mechanism.
23
+ */
24
+ export declare function gitMerge(input: {
25
+ parent: string;
26
+ child: string;
27
+ /** Names this Integration in what it writes, so history says what was folded. */
28
+ id?: string;
29
+ /** What the folded work is; the commit that carries it says this. */
30
+ subject?: string;
31
+ /** What a merge commit says, when both sides moved. */
32
+ mergeSubject?: string;
33
+ deadlineMs: number;
34
+ now: () => number;
35
+ shouldInterrupt: () => boolean;
36
+ }): Promise<GitMergeResult>;
37
+ //# sourceMappingURL=git-merge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-merge.d.ts","sourceRoot":"","sources":["../src/git-merge.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,QAAQ,EAA2C,MAAM,wBAAwB,CAAC;AAOhG,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAwFlC;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,OAAO,CAAC;CAChC,GAAG,OAAO,CAAC,cAAc,CAAC,CAmO1B"}