@telora/daemon 0.20.96 → 0.20.104

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 (54) hide show
  1. package/build-info.json +2 -2
  2. package/dist/cli/connect.d.ts.map +1 -1
  3. package/dist/cli/connect.js +12 -12
  4. package/dist/cli/connect.js.map +1 -1
  5. package/dist/crash-recovery.d.ts +12 -2
  6. package/dist/crash-recovery.d.ts.map +1 -1
  7. package/dist/crash-recovery.js +43 -4
  8. package/dist/crash-recovery.js.map +1 -1
  9. package/dist/delivery-advance-block.d.ts.map +1 -1
  10. package/dist/delivery-advance-block.js +21 -4
  11. package/dist/delivery-advance-block.js.map +1 -1
  12. package/dist/gitignore-convergence.d.ts +47 -0
  13. package/dist/gitignore-convergence.d.ts.map +1 -0
  14. package/dist/gitignore-convergence.js +75 -0
  15. package/dist/gitignore-convergence.js.map +1 -0
  16. package/dist/state-cascade.d.ts +23 -3
  17. package/dist/state-cascade.d.ts.map +1 -1
  18. package/dist/state-cascade.js +33 -9
  19. package/dist/state-cascade.js.map +1 -1
  20. package/dist/unified-init.d.ts.map +1 -1
  21. package/dist/unified-init.js +11 -19
  22. package/dist/unified-init.js.map +1 -1
  23. package/dist/unified-shell-provisioning.d.ts +22 -0
  24. package/dist/unified-shell-provisioning.d.ts.map +1 -1
  25. package/dist/unified-shell-provisioning.js +55 -0
  26. package/dist/unified-shell-provisioning.js.map +1 -1
  27. package/dist/unified-shell.d.ts.map +1 -1
  28. package/dist/unified-shell.js +9 -1
  29. package/dist/unified-shell.js.map +1 -1
  30. package/dist/worktree/identity.d.ts +63 -0
  31. package/dist/worktree/identity.d.ts.map +1 -0
  32. package/dist/worktree/identity.js +79 -0
  33. package/dist/worktree/identity.js.map +1 -0
  34. package/dist/worktree/index.d.ts +3 -0
  35. package/dist/worktree/index.d.ts.map +1 -1
  36. package/dist/worktree/index.js +3 -0
  37. package/dist/worktree/index.js.map +1 -1
  38. package/dist/worktree/reap.d.ts +60 -0
  39. package/dist/worktree/reap.d.ts.map +1 -0
  40. package/dist/worktree/reap.js +175 -0
  41. package/dist/worktree/reap.js.map +1 -0
  42. package/dist/worktree/safety.d.ts +15 -4
  43. package/dist/worktree/safety.d.ts.map +1 -1
  44. package/dist/worktree/safety.js +72 -39
  45. package/dist/worktree/safety.js.map +1 -1
  46. package/dist/worktree/scratch-guard.d.ts +126 -0
  47. package/dist/worktree/scratch-guard.d.ts.map +1 -0
  48. package/dist/worktree/scratch-guard.js +196 -0
  49. package/dist/worktree/scratch-guard.js.map +1 -0
  50. package/dist/worktree.d.ts +7 -39
  51. package/dist/worktree.d.ts.map +1 -1
  52. package/dist/worktree.js +8 -103
  53. package/dist/worktree.js.map +1 -1
  54. package/package.json +3 -3
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Worktree identity assertion.
3
+ *
4
+ * "The daemon owns the boundary of every path it creates." The daemon hands a
5
+ * worktree path to git only as a working directory -- it never checks that git
6
+ * agrees the directory IS that worktree. When a checkout has lost its `.git`
7
+ * file, git resolves the command against the ENCLOSING repository instead of
8
+ * failing: `git status` reports the parent's tree, `git add -A` stages it, and
9
+ * a "safety commit" for one agent's worktree lands in the parent repo's tree
10
+ * with a hash that belongs to a commit the agent never made. That silent
11
+ * redirection is the shared root beneath both the 15,684-file sweep (a blind
12
+ * stage becomes repo-wide) and the un-reapable residue (the pre-removal
13
+ * dirty-check sees the parent's untracked `.telora/` and refuses to reap).
14
+ *
15
+ * assertWorktreeIdentity is the single primitive every daemon git WRITE that
16
+ * names a worktree consults first: it confirms `git rev-parse --show-toplevel`
17
+ * (run inside the directory) resolves to the worktree the daemon named. When it
18
+ * does not, the caller refuses the operation rather than performing it against
19
+ * a different repository.
20
+ */
21
+ import { realpathSync } from 'node:fs';
22
+ import { resolve } from 'node:path';
23
+ import { runGitSync } from '../git/index.js';
24
+ import { createLogger } from '../log.js';
25
+ const log = createLogger({ module: 'worktree' });
26
+ /**
27
+ * Canonicalize a path for comparison: resolve symlinks via realpath so a
28
+ * symlinked or non-canonical worktree path does not false-positive against
29
+ * git's already-canonical `--show-toplevel`. Falls back to `path.resolve` when
30
+ * the path cannot be realpath'd (e.g. it does not exist).
31
+ */
32
+ function canonicalize(p) {
33
+ try {
34
+ return realpathSync(p);
35
+ }
36
+ catch {
37
+ return resolve(p);
38
+ }
39
+ }
40
+ /**
41
+ * Confirm that `worktreePath` is the git worktree it names: run
42
+ * `git rev-parse --show-toplevel` with the directory as cwd and compare
43
+ * (canonical/realpath form) against the directory itself.
44
+ *
45
+ * A healthy linked worktree reports its OWN root as the toplevel, so it passes.
46
+ * A `.git`-less checkout resolves to the enclosing repository -- ok is false and
47
+ * resolvedRepo carries that enclosing repo, so the caller can refuse and name
48
+ * exactly where the operation would otherwise have landed.
49
+ */
50
+ export function assertWorktreeIdentity(worktreePath) {
51
+ const top = runGitSync(['rev-parse', '--show-toplevel'], worktreePath);
52
+ if (!top.success || top.output.trim().length === 0) {
53
+ return {
54
+ ok: false,
55
+ resolvedRepo: null,
56
+ reason: `git could not resolve a repository at ${worktreePath}`,
57
+ };
58
+ }
59
+ const resolvedRepo = canonicalize(top.output.trim());
60
+ const intended = canonicalize(worktreePath);
61
+ if (resolvedRepo === intended) {
62
+ return { ok: true, resolvedRepo };
63
+ }
64
+ return {
65
+ ok: false,
66
+ resolvedRepo,
67
+ reason: `worktree path ${worktreePath} resolves to a different git toplevel ` +
68
+ `(${resolvedRepo}); the checkout may have lost its .git file, so a git ` +
69
+ `command run here would act on the enclosing repository`,
70
+ };
71
+ }
72
+ /** Default reporter: loudly name the worktree, the operation, and the repo git resolved. */
73
+ export const defaultWorktreeIdentityViolationReporter = (v) => {
74
+ log.error(`REFUSING daemon git ${v.operation} at ${v.worktreePath}: it is not its own ` +
75
+ `worktree (git resolves it to ${v.resolvedRepo ?? 'no repository'}). A checkout ` +
76
+ `that lost its .git file would redirect this operation at the enclosing ` +
77
+ `repository. Operation refused; no git write performed.`);
78
+ };
79
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/worktree/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AAEjD;;;;;GAKG;AACH,SAAS,YAAY,CAAC,CAAS;IAC7B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAgBD;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,YAAoB;IACzD,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAAE,YAAY,CAAC,CAAC;IACvE,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,YAAY,EAAE,IAAI;YAClB,MAAM,EAAE,yCAAyC,YAAY,EAAE;SAChE,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAE5C,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IACpC,CAAC;IAED,OAAO;QACL,EAAE,EAAE,KAAK;QACT,YAAY;QACZ,MAAM,EACJ,iBAAiB,YAAY,wCAAwC;YACrE,IAAI,YAAY,wDAAwD;YACxE,wDAAwD;KAC3D,CAAC;AACJ,CAAC;AAoBD,4FAA4F;AAC5F,MAAM,CAAC,MAAM,wCAAwC,GAAsC,CAAC,CAAC,EAAE,EAAE;IAC/F,GAAG,CAAC,KAAK,CACP,uBAAuB,CAAC,CAAC,SAAS,OAAO,CAAC,CAAC,YAAY,sBAAsB;QAC7E,gCAAgC,CAAC,CAAC,YAAY,IAAI,eAAe,gBAAgB;QACjF,yEAAyE;QACzE,wDAAwD,CACzD,CAAC;AACJ,CAAC,CAAC"}
@@ -13,4 +13,7 @@
13
13
  export * from './safety.js';
14
14
  export * from './focus.js';
15
15
  export * from './state.js';
16
+ export * from './scratch-guard.js';
17
+ export * from './reap.js';
18
+ export * from './identity.js';
16
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worktree/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worktree/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC"}
@@ -13,4 +13,7 @@
13
13
  export * from './safety.js';
14
14
  export * from './focus.js';
15
15
  export * from './state.js';
16
+ export * from './scratch-guard.js';
17
+ export * from './reap.js';
18
+ export * from './identity.js';
16
19
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/worktree/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/worktree/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Atomic worktree reaping.
3
+ *
4
+ * "The daemon owns the boundary of every path it creates." A worktree checkout
5
+ * on disk and its `.git/worktrees/<id>` registration must live and die together
6
+ * -- a directory left behind WITHOUT its registration is invisible to
7
+ * `git worktree list`/`prune` and accumulates as unbounded residue (the 4.9 GB
8
+ * BELL leak). This module wraps daemon-core's base removal with an explicit
9
+ * post-condition check so a reap can never silently leave that residue.
10
+ */
11
+ /**
12
+ * Cap on the number of files we blob-verify for an UNREGISTERED, .git-less
13
+ * residue checkout. Above this we cannot cheaply prove reachability, so we
14
+ * preserve (safe) rather than stall crash recovery hashing a huge tree.
15
+ */
16
+ export declare const MAX_RESIDUE_FILES_TO_VERIFY = 2000;
17
+ /**
18
+ * True iff `worktreePath` is still listed in `repoPath`'s
19
+ * `git worktree list --porcelain` (i.e. git still has a registration for it).
20
+ */
21
+ export declare function worktreeIsRegistered(repoPath: string, worktreePath: string): boolean;
22
+ /**
23
+ * Remove a worktree ATOMICALLY: after this returns `true`, the directory is
24
+ * gone AND no `git worktree` registration for it remains.
25
+ *
26
+ * Delegates the removal to daemon-core's `removeWorktreeBase` (which runs
27
+ * `git worktree remove --force`, or an `rmSync` fallback + `git worktree
28
+ * prune`), then VERIFIES the post-condition. If a stale registration lingers
29
+ * (the fallback path can leave one until prune), it prunes and re-checks. Any
30
+ * remaining inconsistency -- a surviving directory or registration -- is logged
31
+ * loudly and returns `false`, so an unregistered leftover can never pass
32
+ * silently as a clean reap.
33
+ *
34
+ * Signature matches daemon-core `removeWorktreeBase` so it is a drop-in.
35
+ */
36
+ export declare function reapWorktreeAtomically(repoPath: string, worktreePath: string): boolean;
37
+ /** Verdict on whether an unregistered residue checkout is safe to reap. */
38
+ export interface ResidueClassification {
39
+ /** True iff the residue's content is already reachable from integration. */
40
+ reapSafe: boolean;
41
+ /** Human-readable justification (for the crash-recovery log). */
42
+ reason: string;
43
+ }
44
+ /**
45
+ * Classify whether an UNREGISTERED residue checkout (a directory under the
46
+ * worktree dir with no `git worktree list` entry) is safe to reap. Because
47
+ * there is no branch ref to diff, safety is judged by content reachability from
48
+ * integration:
49
+ *
50
+ * - Functional checkout (its HEAD still resolves): reap-safe iff it has no
51
+ * uncommitted changes AND its HEAD commit is an ancestor of integration.
52
+ * - Broken/.git-less checkout: reap-safe iff every file matches the same
53
+ * path's blob in integration ({@link classifyResidueByContent}).
54
+ *
55
+ * Every uncertain case resolves to `reapSafe: false` (preserve) so unmerged
56
+ * work is never destroyed -- the atomic-reap half of this focus ensures no NEW
57
+ * residue accumulates while preserved residue awaits manual review.
58
+ */
59
+ export declare function classifyUnregisteredResidue(residuePath: string, repoPath: string, integrationBranch: string): ResidueClassification;
60
+ //# sourceMappingURL=reap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reap.d.ts","sourceRoot":"","sources":["../../src/worktree/reap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAUH;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAEhD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAUpF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAuBtF;AAED,2EAA2E;AAC3E,MAAM,WAAW,qBAAqB;IACpC,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,CAAC;IAClB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;CAChB;AAwED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,MAAM,GACxB,qBAAqB,CAsBvB"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Atomic worktree reaping.
3
+ *
4
+ * "The daemon owns the boundary of every path it creates." A worktree checkout
5
+ * on disk and its `.git/worktrees/<id>` registration must live and die together
6
+ * -- a directory left behind WITHOUT its registration is invisible to
7
+ * `git worktree list`/`prune` and accumulates as unbounded residue (the 4.9 GB
8
+ * BELL leak). This module wraps daemon-core's base removal with an explicit
9
+ * post-condition check so a reap can never silently leave that residue.
10
+ */
11
+ import { existsSync, readdirSync } from 'node:fs';
12
+ import { resolve, join } from 'node:path';
13
+ import { runGitSync } from '../git/index.js';
14
+ import { removeWorktreeBase } from '@telora/daemon-core';
15
+ import { createLogger } from '../log.js';
16
+ const log = createLogger({ module: 'worktree' });
17
+ /**
18
+ * Cap on the number of files we blob-verify for an UNREGISTERED, .git-less
19
+ * residue checkout. Above this we cannot cheaply prove reachability, so we
20
+ * preserve (safe) rather than stall crash recovery hashing a huge tree.
21
+ */
22
+ export const MAX_RESIDUE_FILES_TO_VERIFY = 2000;
23
+ /**
24
+ * True iff `worktreePath` is still listed in `repoPath`'s
25
+ * `git worktree list --porcelain` (i.e. git still has a registration for it).
26
+ */
27
+ export function worktreeIsRegistered(repoPath, worktreePath) {
28
+ const result = runGitSync(['worktree', 'list', '--porcelain'], repoPath);
29
+ if (!result.success)
30
+ return false;
31
+ const target = resolve(worktreePath);
32
+ for (const line of result.output.split('\n')) {
33
+ if (line.startsWith('worktree ')) {
34
+ if (resolve(line.slice('worktree '.length).trim()) === target)
35
+ return true;
36
+ }
37
+ }
38
+ return false;
39
+ }
40
+ /**
41
+ * Remove a worktree ATOMICALLY: after this returns `true`, the directory is
42
+ * gone AND no `git worktree` registration for it remains.
43
+ *
44
+ * Delegates the removal to daemon-core's `removeWorktreeBase` (which runs
45
+ * `git worktree remove --force`, or an `rmSync` fallback + `git worktree
46
+ * prune`), then VERIFIES the post-condition. If a stale registration lingers
47
+ * (the fallback path can leave one until prune), it prunes and re-checks. Any
48
+ * remaining inconsistency -- a surviving directory or registration -- is logged
49
+ * loudly and returns `false`, so an unregistered leftover can never pass
50
+ * silently as a clean reap.
51
+ *
52
+ * Signature matches daemon-core `removeWorktreeBase` so it is a drop-in.
53
+ */
54
+ export function reapWorktreeAtomically(repoPath, worktreePath) {
55
+ const removed = removeWorktreeBase(repoPath, worktreePath);
56
+ if (!removed)
57
+ return false;
58
+ let dirGone = !existsSync(worktreePath);
59
+ let registered = worktreeIsRegistered(repoPath, worktreePath);
60
+ if (registered) {
61
+ // The rmSync-fallback path drops the directory but leaves the registration
62
+ // until a prune; converge it here so the two never diverge.
63
+ runGitSync(['worktree', 'prune'], repoPath);
64
+ registered = worktreeIsRegistered(repoPath, worktreePath);
65
+ dirGone = !existsSync(worktreePath);
66
+ }
67
+ if (!dirGone || registered) {
68
+ log.error(`Worktree reap left an inconsistent state at ${worktreePath}: ` +
69
+ `dirGone=${dirGone}, stillRegistered=${registered}. Manual cleanup may be required.`);
70
+ return false;
71
+ }
72
+ return true;
73
+ }
74
+ /**
75
+ * Recursively list repo-relative file paths under `dir`, EXCLUDING any `.git`
76
+ * entry (the residue's dangling gitdir pointer). Returns null on a read error,
77
+ * or once the count exceeds {@link MAX_RESIDUE_FILES_TO_VERIFY} (caller then
78
+ * preserves rather than verify an unbounded tree).
79
+ */
80
+ function listResidueFiles(dir) {
81
+ const out = [];
82
+ const walk = (abs, rel) => {
83
+ let entries;
84
+ try {
85
+ entries = readdirSync(abs, { withFileTypes: true });
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ for (const e of entries) {
91
+ if (e.name === '.git')
92
+ continue;
93
+ const childRel = rel ? `${rel}/${e.name}` : e.name;
94
+ if (e.isDirectory()) {
95
+ if (!walk(join(abs, e.name), childRel))
96
+ return false;
97
+ }
98
+ else {
99
+ out.push(childRel);
100
+ if (out.length > MAX_RESIDUE_FILES_TO_VERIFY)
101
+ return false;
102
+ }
103
+ }
104
+ return true;
105
+ };
106
+ return walk(dir, '') ? out : null;
107
+ }
108
+ /**
109
+ * Blob-level reachability check for a broken (.git-less) residue checkout: it is
110
+ * reap-safe ONLY when EVERY file it holds matches the same path's blob in the
111
+ * integration branch (content already reachable). Any new or modified file --
112
+ * i.e. potential unmerged work -- makes it unsafe (preserve). An unreadable or
113
+ * over-large tree is likewise preserved. Errs strictly toward preserve so a
114
+ * reap never loses work.
115
+ */
116
+ function classifyResidueByContent(residuePath, repoPath, integrationBranch) {
117
+ const files = listResidueFiles(residuePath);
118
+ if (files === null) {
119
+ return { reapSafe: false, reason: 'residue too large or unreadable to verify; preserved for manual review' };
120
+ }
121
+ if (files.length === 0) {
122
+ return { reapSafe: true, reason: 'residue holds no files' };
123
+ }
124
+ for (const rel of files) {
125
+ const abs = join(residuePath, rel);
126
+ const blob = runGitSync(['hash-object', abs], repoPath);
127
+ if (!blob.success || blob.output.trim().length === 0) {
128
+ return { reapSafe: false, reason: `could not hash residue file ${rel}; preserved` };
129
+ }
130
+ const intBlob = runGitSync(['rev-parse', '--verify', '--quiet', `${integrationBranch}:${rel}`], repoPath);
131
+ if (!intBlob.success || intBlob.output.trim().length === 0) {
132
+ return { reapSafe: false, reason: `${rel} is absent from ${integrationBranch} (potential unmerged work)` };
133
+ }
134
+ if (blob.output.trim() !== intBlob.output.trim()) {
135
+ return { reapSafe: false, reason: `${rel} differs from ${integrationBranch} (potential unmerged work)` };
136
+ }
137
+ }
138
+ return { reapSafe: true, reason: `all ${files.length} residue file(s) match ${integrationBranch}` };
139
+ }
140
+ /**
141
+ * Classify whether an UNREGISTERED residue checkout (a directory under the
142
+ * worktree dir with no `git worktree list` entry) is safe to reap. Because
143
+ * there is no branch ref to diff, safety is judged by content reachability from
144
+ * integration:
145
+ *
146
+ * - Functional checkout (its HEAD still resolves): reap-safe iff it has no
147
+ * uncommitted changes AND its HEAD commit is an ancestor of integration.
148
+ * - Broken/.git-less checkout: reap-safe iff every file matches the same
149
+ * path's blob in integration ({@link classifyResidueByContent}).
150
+ *
151
+ * Every uncertain case resolves to `reapSafe: false` (preserve) so unmerged
152
+ * work is never destroyed -- the atomic-reap half of this focus ensures no NEW
153
+ * residue accumulates while preserved residue awaits manual review.
154
+ */
155
+ export function classifyUnregisteredResidue(residuePath, repoPath, integrationBranch) {
156
+ const head = runGitSync(['rev-parse', '--verify', 'HEAD'], residuePath);
157
+ if (head.success && head.output.trim().length > 0) {
158
+ const sha = head.output.trim();
159
+ const status = runGitSync(['status', '--porcelain'], residuePath);
160
+ if (status.success && status.output.trim().length > 0) {
161
+ return { reapSafe: false, reason: 'functional checkout has uncommitted changes (potential unmerged work)' };
162
+ }
163
+ const ancestor = runGitSync(['merge-base', '--is-ancestor', sha, integrationBranch], repoPath);
164
+ if (ancestor.success) {
165
+ return { reapSafe: true, reason: `HEAD ${sha.slice(0, 8)} is reachable from ${integrationBranch}` };
166
+ }
167
+ return {
168
+ reapSafe: false,
169
+ reason: `HEAD ${sha.slice(0, 8)} holds commit(s) not reachable from ${integrationBranch}`,
170
+ };
171
+ }
172
+ // No resolvable HEAD -- the classic .git-less residue. Fall back to blobs.
173
+ return classifyResidueByContent(residuePath, repoPath, integrationBranch);
174
+ }
175
+ //# sourceMappingURL=reap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reap.js","sourceRoot":"","sources":["../../src/worktree/reap.ts"],"names":[],"mappings":"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"}
@@ -4,8 +4,16 @@
4
4
  * Uncommitted change detection, unmerged commit checks, safety commits,
5
5
  * guarded worktree removal, and WIP commit after agent exit.
6
6
  */
7
+ import { type SweepRefusalReporter } from './scratch-guard.js';
8
+ import { type WorktreeIdentityViolationReporter } from './identity.js';
7
9
  /**
8
10
  * Check if a worktree has uncommitted changes (dirty working tree).
11
+ *
12
+ * Identity-guarded: if the directory is not the worktree it names (a `.git`-less
13
+ * checkout whose `git status` would report the ENCLOSING repo's tree), the
14
+ * enclosing repo's state is never attributed to this worktree -- the mismatch is
15
+ * logged loudly and the function returns false. The write paths (guardedCommit,
16
+ * removeWorktree) are the hard gate; this keeps the read from lying.
9
17
  */
10
18
  export declare function worktreeHasUncommittedChanges(worktreePath: string): boolean;
11
19
  /**
@@ -16,7 +24,7 @@ export declare function branchHasUnmergedCommits(branchName: string, targetBranc
16
24
  * Commit all uncommitted changes in a worktree as a safety net.
17
25
  * Returns the commit hash if successful, null otherwise.
18
26
  */
19
- export declare function safetyCommitWorktree(worktreePath: string, deliveryName: string): string | null;
27
+ export declare function safetyCommitWorktree(worktreePath: string, deliveryName: string, reporter?: SweepRefusalReporter): string | null;
20
28
  /**
21
29
  * Generated lockfile basenames the daemon may safely auto-commit. These are
22
30
  * machine-regenerated (never hand-authored), so a worktree whose ONLY dirt is
@@ -52,7 +60,7 @@ export declare function dirtyChangesAreGeneratedLockfilesOnly(worktreePath: stri
52
60
  * agent work is never silently committed here. Returns the commit hash, or
53
61
  * null if staging/committing failed (e.g. nothing staged).
54
62
  */
55
- export declare function commitGeneratedLockfiles(worktreePath: string): string | null;
63
+ export declare function commitGeneratedLockfiles(worktreePath: string, reporter?: SweepRefusalReporter): string | null;
56
64
  /**
57
65
  * Remove a worktree when an agent session ends.
58
66
  * The branch is preserved for PR creation.
@@ -68,6 +76,7 @@ export declare function removeWorktree(repoPath: string, worktreePath: string, o
68
76
  integrationBranch?: string;
69
77
  deliveryName?: string;
70
78
  mergeSucceeded?: boolean;
79
+ identityReporter?: WorktreeIdentityViolationReporter;
71
80
  }): boolean;
72
81
  /**
73
82
  * Commit any uncommitted changes in a worktree as a WIP commit.
@@ -76,9 +85,11 @@ export declare function removeWorktree(repoPath: string, worktreePath: string, o
76
85
  * removal. Captures in-flight work that the agent didn't commit before
77
86
  * being terminated.
78
87
  *
79
- * Delegates to daemon-core's commitWip for the actual git operations.
88
+ * Stages via the scoped-staging guard (scratch-excluded, sweep-refused) and
89
+ * commits in the daemon package -- it no longer delegates to daemon-core's
90
+ * blind-`git add -A` commitWip, so the WIP path is guarded like every other.
80
91
  *
81
92
  * @returns The commit hash if a WIP commit was created, null if no changes or failure.
82
93
  */
83
- export declare function commitWipChanges(worktreePath: string, contextName: string): string | null;
94
+ export declare function commitWipChanges(worktreePath: string, contextName: string, reporter?: SweepRefusalReporter): string | null;
84
95
  //# sourceMappingURL=safety.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../src/worktree/safety.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAI3E;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,OAAO,CAMT;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB9F;AAED;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAMzD,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qCAAqC,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAWnF;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgB5E;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IACP,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;CACrB,GACL,OAAO,CAgDT;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGzF"}
1
+ {"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../src/worktree/safety.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAGL,KAAK,iCAAiC,EACvC,MAAM,eAAe,CAAC;AAMvB;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAa3E;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,OAAO,CAMT;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,MAAM,GAAG,IAAI,CAUf;AAED;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAMzD,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qCAAqC,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAWnF;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,MAAM,GAAG,IAAI,CAUf;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IACP,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gBAAgB,CAAC,EAAE,iCAAiC,CAAC;CACjD,GACL,OAAO,CAsET;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,MAAM,GAAG,IAAI,CAQf"}
@@ -6,15 +6,30 @@
6
6
  */
7
7
  import { existsSync } from 'node:fs';
8
8
  import { runGitSync } from '../git/index.js';
9
- import { commitWip, removeWorktreeBase, } from '@telora/daemon-core';
9
+ import { guardedCommit, worktreeHasNonScratchChanges, } from './scratch-guard.js';
10
+ import { reapWorktreeAtomically } from './reap.js';
11
+ import { assertWorktreeIdentity, defaultWorktreeIdentityViolationReporter, } from './identity.js';
10
12
  import { createLogger } from '../log.js';
11
13
  const log = createLogger({ module: 'worktree' });
12
14
  /**
13
15
  * Check if a worktree has uncommitted changes (dirty working tree).
16
+ *
17
+ * Identity-guarded: if the directory is not the worktree it names (a `.git`-less
18
+ * checkout whose `git status` would report the ENCLOSING repo's tree), the
19
+ * enclosing repo's state is never attributed to this worktree -- the mismatch is
20
+ * logged loudly and the function returns false. The write paths (guardedCommit,
21
+ * removeWorktree) are the hard gate; this keeps the read from lying.
14
22
  */
15
23
  export function worktreeHasUncommittedChanges(worktreePath) {
16
24
  if (!existsSync(worktreePath))
17
25
  return false;
26
+ const identity = assertWorktreeIdentity(worktreePath);
27
+ if (!identity.ok) {
28
+ log.error(`worktreeHasUncommittedChanges: ${worktreePath} is not its own worktree ` +
29
+ `(git resolves it to ${identity.resolvedRepo ?? 'no repository'}); refusing ` +
30
+ `to attribute the enclosing repo's status to it.`);
31
+ return false;
32
+ }
18
33
  const result = runGitSync(['status', '--porcelain'], worktreePath);
19
34
  return result.success && result.output.trim().length > 0;
20
35
  }
@@ -29,22 +44,16 @@ export function branchHasUnmergedCommits(branchName, targetBranch, repoPath) {
29
44
  * Commit all uncommitted changes in a worktree as a safety net.
30
45
  * Returns the commit hash if successful, null otherwise.
31
46
  */
32
- export function safetyCommitWorktree(worktreePath, deliveryName) {
33
- // Stage everything
34
- const addResult = runGitSync(['add', '-A'], worktreePath);
35
- if (!addResult.success) {
36
- log.error(`Safety commit: failed to stage changes: ${addResult.error}`);
37
- return null;
38
- }
39
- // Commit
40
- const commitResult = runGitSync(['commit', '-m', `wip: uncommitted agent work for "${deliveryName}" (safety commit)`], worktreePath);
41
- if (!commitResult.success) {
42
- log.warn(`Safety commit: commit failed (may be empty): ${commitResult.error}`);
43
- return null;
44
- }
45
- // Return the new commit hash
46
- const hashResult = runGitSync(['rev-parse', 'HEAD'], worktreePath);
47
- return hashResult.success ? hashResult.output : null;
47
+ export function safetyCommitWorktree(worktreePath, deliveryName, reporter) {
48
+ // Routes through the scoped-staging guard: daemon scratch (`.telora/`) is
49
+ // never staged, and a mass sweep is refused (working tree left untouched,
50
+ // reporter invoked) rather than committed.
51
+ return guardedCommit({
52
+ worktreePath,
53
+ message: `wip: uncommitted agent work for "${deliveryName}" (safety commit)`,
54
+ context: deliveryName,
55
+ reporter,
56
+ });
48
57
  }
49
58
  /**
50
59
  * Generated lockfile basenames the daemon may safely auto-commit. These are
@@ -101,19 +110,16 @@ export function dirtyChangesAreGeneratedLockfilesOnly(worktreePath) {
101
110
  * agent work is never silently committed here. Returns the commit hash, or
102
111
  * null if staging/committing failed (e.g. nothing staged).
103
112
  */
104
- export function commitGeneratedLockfiles(worktreePath) {
105
- const addResult = runGitSync(['add', '-A'], worktreePath);
106
- if (!addResult.success) {
107
- log.error(`lockfile auto-commit: failed to stage changes: ${addResult.error}`);
108
- return null;
109
- }
110
- const commitResult = runGitSync(['commit', '-m', 'chore: auto-commit generated lockfile churn (daemon remediation)'], worktreePath);
111
- if (!commitResult.success) {
112
- log.warn(`lockfile auto-commit: commit failed (may be empty): ${commitResult.error}`);
113
- return null;
114
- }
115
- const hashResult = runGitSync(['rev-parse', 'HEAD'], worktreePath);
116
- return hashResult.success ? hashResult.output : null;
113
+ export function commitGeneratedLockfiles(worktreePath, reporter) {
114
+ // Callers gate this behind dirtyChangesAreGeneratedLockfilesOnly, so the
115
+ // staged set is a handful of lockfiles; the guard is pure defense-in-depth
116
+ // (scratch exclusion + sweep refusal) shared with every other commit path.
117
+ return guardedCommit({
118
+ worktreePath,
119
+ message: 'chore: auto-commit generated lockfile churn (daemon remediation)',
120
+ context: 'generated-lockfile churn',
121
+ reporter,
122
+ });
117
123
  }
118
124
  /**
119
125
  * Remove a worktree when an agent session ends.
@@ -133,8 +139,27 @@ export function removeWorktree(repoPath, worktreePath, options = {}) {
133
139
  runGitSync(['worktree', 'prune'], repoPath);
134
140
  return true;
135
141
  }
136
- // Guard 1: Check for uncommitted changes and safety-commit them
137
- if (worktreeHasUncommittedChanges(worktreePath)) {
142
+ // Guard 0: Identity. If the directory is not the worktree it names (a
143
+ // `.git`-less checkout whose git commands redirect at the enclosing repo),
144
+ // refuse before the dirty-check/safety-commit guards run -- otherwise the
145
+ // dirty-check would read the parent's tree and the safety commit would stage
146
+ // it. Unregistered residue is reaped via the crash-recovery residue path,
147
+ // not here.
148
+ const identity = assertWorktreeIdentity(worktreePath);
149
+ if (!identity.ok) {
150
+ const identityReporter = options.identityReporter ?? defaultWorktreeIdentityViolationReporter;
151
+ identityReporter({
152
+ worktreePath,
153
+ resolvedRepo: identity.resolvedRepo,
154
+ operation: 'remove',
155
+ });
156
+ return false;
157
+ }
158
+ // Guard 1: Safety-commit real (non-scratch) uncommitted work. Daemon
159
+ // scratch (`.telora/`) is disposable, so a worktree dirty only with scratch
160
+ // is treated as clean here -- it is discarded with the worktree, never
161
+ // committed and never a reason to refuse removal.
162
+ if (worktreeHasNonScratchChanges(worktreePath)) {
138
163
  log.warn(`Worktree has uncommitted changes: ${worktreePath}. ` +
139
164
  `Creating safety commit to preserve work...`);
140
165
  const hash = safetyCommitWorktree(worktreePath, deliveryName ?? 'unknown');
@@ -142,9 +167,10 @@ export function removeWorktree(repoPath, worktreePath, options = {}) {
142
167
  log.info(`Safety commit created: ${hash.slice(0, 8)}`);
143
168
  }
144
169
  else {
145
- // Could not commit -- refuse to delete
170
+ // Could not commit (guard refused a mass sweep, or the commit failed) --
171
+ // refuse to delete so the work stays recoverable.
146
172
  log.error(`REFUSING to remove worktree ${worktreePath}: ` +
147
- `has uncommitted changes and safety commit failed. ` +
173
+ `has uncommitted changes and safety commit failed or was refused. ` +
148
174
  `Manual cleanup required.`);
149
175
  return false;
150
176
  }
@@ -158,8 +184,8 @@ export function removeWorktree(repoPath, worktreePath, options = {}) {
158
184
  return false;
159
185
  }
160
186
  }
161
- // Safe to remove -- delegate to daemon-core's base removal
162
- return removeWorktreeBase(repoPath, worktreePath);
187
+ // Safe to remove -- atomic reap (dir + registration gone together, verified)
188
+ return reapWorktreeAtomically(repoPath, worktreePath);
163
189
  }
164
190
  catch (err) {
165
191
  log.error('Error removing worktree:', err instanceof Error ? err.message : String(err));
@@ -173,12 +199,19 @@ export function removeWorktree(repoPath, worktreePath, options = {}) {
173
199
  * removal. Captures in-flight work that the agent didn't commit before
174
200
  * being terminated.
175
201
  *
176
- * Delegates to daemon-core's commitWip for the actual git operations.
202
+ * Stages via the scoped-staging guard (scratch-excluded, sweep-refused) and
203
+ * commits in the daemon package -- it no longer delegates to daemon-core's
204
+ * blind-`git add -A` commitWip, so the WIP path is guarded like every other.
177
205
  *
178
206
  * @returns The commit hash if a WIP commit was created, null if no changes or failure.
179
207
  */
180
- export function commitWipChanges(worktreePath, contextName) {
208
+ export function commitWipChanges(worktreePath, contextName, reporter) {
181
209
  log.info(`Committing WIP changes in ${worktreePath} for "${contextName}"`);
182
- return commitWip(worktreePath, `Uncommitted work captured by daemon on agent exit (${contextName})`);
210
+ return guardedCommit({
211
+ worktreePath,
212
+ message: `WIP: Uncommitted work captured by daemon on agent exit (${contextName})`,
213
+ context: contextName,
214
+ reporter,
215
+ });
183
216
  }
184
217
  //# sourceMappingURL=safety.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/worktree/safety.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/worktree/safety.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,aAAa,EACb,4BAA4B,GAE7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EACL,sBAAsB,EACtB,wCAAwC,GAEzC,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AAGjD;;;;;;;;GAQG;AACH,MAAM,UAAU,6BAA6B,CAAC,YAAoB;IAChE,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,QAAQ,GAAG,sBAAsB,CAAC,YAAY,CAAC,CAAC;IACtD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,GAAG,CAAC,KAAK,CACP,kCAAkC,YAAY,2BAA2B;YACzE,uBAAuB,QAAQ,CAAC,YAAY,IAAI,eAAe,cAAc;YAC7E,iDAAiD,CAClD,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,YAAY,CAAC,CAAC;IACnE,OAAO,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB,CACtC,UAAkB,EAClB,YAAoB,EACpB,QAAgB;IAEhB,MAAM,MAAM,GAAG,UAAU,CACvB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,YAAY,KAAK,UAAU,EAAE,EAAE,IAAI,CAAC,EAC5D,QAAQ,CACT,CAAC;IACF,OAAO,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,YAAoB,EACpB,YAAoB,EACpB,QAA+B;IAE/B,0EAA0E;IAC1E,0EAA0E;IAC1E,2CAA2C;IAC3C,OAAO,aAAa,CAAC;QACnB,YAAY;QACZ,OAAO,EAAE,oCAAoC,YAAY,mBAAmB;QAC5E,OAAO,EAAE,YAAY;QACrB,QAAQ;KACT,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAsB;IAC7D,mBAAmB;IACnB,qBAAqB;IACrB,WAAW;IACX,gBAAgB;IAChB,WAAW;CACZ,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,qCAAqC,CAAC,YAAoB;IACxE,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC;IAC1E,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,oBAAoB,CAAC,EAAE,YAAY,CAAC,CAAC;IAC3F,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IACzD,MAAM,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SAC3E,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,uCAAuC;IAC7E,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,4BAA4B,CAAC,CAAC;IACxD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CACtC,YAAoB,EACpB,QAA+B;IAE/B,yEAAyE;IACzE,2EAA2E;IAC3E,2EAA2E;IAC3E,OAAO,aAAa,CAAC;QACnB,YAAY;QACZ,OAAO,EAAE,kEAAkE;QAC3E,OAAO,EAAE,0BAA0B;QACnC,QAAQ;KACT,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAgB,EAChB,YAAoB,EACpB,UAMI,EAAE;IAEN,MAAM,EAAE,UAAU,EAAE,iBAAiB,EAAE,YAAY,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;IAEhF,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAC9B,GAAG,CAAC,IAAI,CAAC,6BAA6B,YAAY,EAAE,CAAC,CAAC;YACtD,UAAU,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;YAC5C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,sEAAsE;QACtE,2EAA2E;QAC3E,0EAA0E;QAC1E,6EAA6E;QAC7E,0EAA0E;QAC1E,YAAY;QACZ,MAAM,QAAQ,GAAG,sBAAsB,CAAC,YAAY,CAAC,CAAC;QACtD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,gBAAgB,GACpB,OAAO,CAAC,gBAAgB,IAAI,wCAAwC,CAAC;YACvE,gBAAgB,CAAC;gBACf,YAAY;gBACZ,YAAY,EAAE,QAAQ,CAAC,YAAY;gBACnC,SAAS,EAAE,QAAQ;aACpB,CAAC,CAAC;YACH,OAAO,KAAK,CAAC;QACf,CAAC;QAED,qEAAqE;QACrE,4EAA4E;QAC5E,uEAAuE;QACvE,kDAAkD;QAClD,IAAI,4BAA4B,CAAC,YAAY,CAAC,EAAE,CAAC;YAC/C,GAAG,CAAC,IAAI,CACN,qCAAqC,YAAY,IAAI;gBACrD,4CAA4C,CAC7C,CAAC;YACF,MAAM,IAAI,GAAG,oBAAoB,CAAC,YAAY,EAAE,YAAY,IAAI,SAAS,CAAC,CAAC;YAC3E,IAAI,IAAI,EAAE,CAAC;gBACT,GAAG,CAAC,IAAI,CAAC,0BAA0B,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACN,yEAAyE;gBACzE,kDAAkD;gBAClD,GAAG,CAAC,KAAK,CACP,+BAA+B,YAAY,IAAI;oBAC/C,mEAAmE;oBACnE,0BAA0B,CAC3B,CAAC;gBACF,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAED,8EAA8E;QAC9E,IAAI,UAAU,IAAI,iBAAiB,IAAI,CAAC,cAAc,EAAE,CAAC;YACvD,IAAI,wBAAwB,CAAC,UAAU,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,CAAC;gBACtE,GAAG,CAAC,KAAK,CACP,+BAA+B,YAAY,IAAI;oBAC/C,UAAU,UAAU,8BAA8B,iBAAiB,IAAI;oBACvE,2CAA2C,CAC5C,CAAC;gBACF,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAED,6EAA6E;QAC7E,OAAO,sBAAsB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACxF,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,YAAoB,EACpB,WAAmB,EACnB,QAA+B;IAE/B,GAAG,CAAC,IAAI,CAAC,6BAA6B,YAAY,SAAS,WAAW,GAAG,CAAC,CAAC;IAC3E,OAAO,aAAa,CAAC;QACnB,YAAY;QACZ,OAAO,EAAE,2DAA2D,WAAW,GAAG;QAClF,OAAO,EAAE,WAAW;QACpB,QAAQ;KACT,CAAC,CAAC;AACL,CAAC"}