@yoavsh/treekeeper 0.0.0-stage → 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.
- package/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/README.md +237 -2
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +165 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/archive.d.ts +3 -0
- package/dist/commands/archive.d.ts.map +1 -0
- package/dist/commands/archive.js +55 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +54 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/context.d.ts +34 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +53 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +81 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +119 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/gc.d.ts +3 -0
- package/dist/commands/gc.d.ts.map +1 -0
- package/dist/commands/gc.js +98 -0
- package/dist/commands/gc.js.map +1 -0
- package/dist/commands/status.d.ts +4 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +52 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/watch.d.ts +10 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +41 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/core/apply.d.ts +47 -0
- package/dist/core/apply.d.ts.map +1 -0
- package/dist/core/apply.js +223 -0
- package/dist/core/apply.js.map +1 -0
- package/dist/core/archive.d.ts +69 -0
- package/dist/core/archive.d.ts.map +1 -0
- package/dist/core/archive.js +202 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/config.d.ts +69 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +261 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/decide.d.ts +102 -0
- package/dist/core/decide.d.ts.map +1 -0
- package/dist/core/decide.js +148 -0
- package/dist/core/decide.js.map +1 -0
- package/dist/core/forge.d.ts +39 -0
- package/dist/core/forge.d.ts.map +1 -0
- package/dist/core/forge.js +190 -0
- package/dist/core/forge.js.map +1 -0
- package/dist/core/git.d.ts +63 -0
- package/dist/core/git.d.ts.map +1 -0
- package/dist/core/git.js +194 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/plan.d.ts +75 -0
- package/dist/core/plan.d.ts.map +1 -0
- package/dist/core/plan.js +288 -0
- package/dist/core/plan.js.map +1 -0
- package/dist/core/render.d.ts +14 -0
- package/dist/core/render.d.ts.map +1 -0
- package/dist/core/render.js +128 -0
- package/dist/core/render.js.map +1 -0
- package/dist/core/scan.d.ts +22 -0
- package/dist/core/scan.d.ts.map +1 -0
- package/dist/core/scan.js +99 -0
- package/dist/core/scan.js.map +1 -0
- package/dist/core/sessions.d.ts +37 -0
- package/dist/core/sessions.d.ts.map +1 -0
- package/dist/core/sessions.js +300 -0
- package/dist/core/sessions.js.map +1 -0
- package/dist/core/size.d.ts +3 -0
- package/dist/core/size.d.ts.map +1 -0
- package/dist/core/size.js +11 -0
- package/dist/core/size.js.map +1 -0
- package/dist/daemon/lock.d.ts +10 -0
- package/dist/daemon/lock.d.ts.map +1 -0
- package/dist/daemon/lock.js +64 -0
- package/dist/daemon/lock.js.map +1 -0
- package/dist/daemon/service.d.ts +34 -0
- package/dist/daemon/service.d.ts.map +1 -0
- package/dist/daemon/service.js +257 -0
- package/dist/daemon/service.js.map +1 -0
- package/dist/daemon/state.d.ts +19 -0
- package/dist/daemon/state.d.ts.map +1 -0
- package/dist/daemon/state.js +32 -0
- package/dist/daemon/state.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/util/bytes.d.ts +4 -0
- package/dist/util/bytes.d.ts.map +1 -0
- package/dist/util/bytes.js +14 -0
- package/dist/util/bytes.js.map +1 -0
- package/dist/util/color.d.ts +16 -0
- package/dist/util/color.d.ts.map +1 -0
- package/dist/util/color.js +32 -0
- package/dist/util/color.js.map +1 -0
- package/dist/util/exec.d.ts +27 -0
- package/dist/util/exec.d.ts.map +1 -0
- package/dist/util/exec.js +57 -0
- package/dist/util/exec.js.map +1 -0
- package/dist/util/log.d.ts +29 -0
- package/dist/util/log.d.ts.map +1 -0
- package/dist/util/log.js +72 -0
- package/dist/util/log.js.map +1 -0
- package/dist/util/paths.d.ts +18 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +87 -0
- package/dist/util/paths.js.map +1 -0
- package/dist/util/prompt.d.ts +3 -0
- package/dist/util/prompt.d.ts.map +1 -0
- package/dist/util/prompt.js +15 -0
- package/dist/util/prompt.js.map +1 -0
- package/dist/util/redact.d.ts +15 -0
- package/dist/util/redact.d.ts.map +1 -0
- package/dist/util/redact.js +44 -0
- package/dist/util/redact.js.map +1 -0
- package/dist/util/time.d.ts +13 -0
- package/dist/util/time.d.ts.map +1 -0
- package/dist/util/time.js +77 -0
- package/dist/util/time.js.map +1 -0
- package/docs/integrations.md +128 -0
- package/docs/safety.md +62 -0
- package/package.json +69 -4
- package/src/cli.ts +175 -0
- package/src/commands/archive.ts +72 -0
- package/src/commands/config.ts +57 -0
- package/src/commands/context.ts +86 -0
- package/src/commands/daemon.ts +86 -0
- package/src/commands/doctor.ts +131 -0
- package/src/commands/gc.ts +108 -0
- package/src/commands/status.ts +68 -0
- package/src/commands/watch.ts +48 -0
- package/src/core/apply.ts +276 -0
- package/src/core/archive.ts +269 -0
- package/src/core/config.ts +339 -0
- package/src/core/decide.ts +230 -0
- package/src/core/forge.ts +243 -0
- package/src/core/git.ts +254 -0
- package/src/core/plan.ts +431 -0
- package/src/core/render.ts +168 -0
- package/src/core/scan.ts +105 -0
- package/src/core/sessions.ts +339 -0
- package/src/core/size.ts +10 -0
- package/src/daemon/lock.ts +65 -0
- package/src/daemon/service.ts +321 -0
- package/src/daemon/state.ts +49 -0
- package/src/index.ts +36 -0
- package/src/util/bytes.ts +11 -0
- package/src/util/color.ts +33 -0
- package/src/util/exec.ts +80 -0
- package/src/util/log.ts +88 -0
- package/src/util/paths.ts +84 -0
- package/src/util/prompt.ts +13 -0
- package/src/util/redact.ts +43 -0
- package/src/util/time.ts +75 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { loadRepoConfig } from "./config.js";
|
|
3
|
+
import { archiveWork, pruneArchives } from "./archive.js";
|
|
4
|
+
import * as git from "./git.js";
|
|
5
|
+
import { parseDuration } from "../util/time.js";
|
|
6
|
+
/**
|
|
7
|
+
* Carry out a plan.
|
|
8
|
+
*
|
|
9
|
+
* Each worktree is re-checked immediately before removal, because a plan is a
|
|
10
|
+
* snapshot: with a daemon, minutes can pass between deciding and acting, and in
|
|
11
|
+
* that time an agent may have started working in the directory again.
|
|
12
|
+
*/
|
|
13
|
+
export async function applyPlan(opts) {
|
|
14
|
+
const { plan, logger } = opts;
|
|
15
|
+
const result = {
|
|
16
|
+
removedWorktrees: [],
|
|
17
|
+
deletedBranches: [],
|
|
18
|
+
reclaimedKb: 0,
|
|
19
|
+
archives: [],
|
|
20
|
+
failures: [],
|
|
21
|
+
prunedArchives: [],
|
|
22
|
+
};
|
|
23
|
+
for (const repoPlan of plan.repos) {
|
|
24
|
+
if (repoPlan.skipped)
|
|
25
|
+
continue;
|
|
26
|
+
const { config: cfg } = await loadRepoConfig(repoPlan.repo, opts.config);
|
|
27
|
+
const base = await preferredBase(repoPlan.repo, repoPlan.defaultBranch);
|
|
28
|
+
for (const candidate of repoPlan.worktrees) {
|
|
29
|
+
if (candidate.verdict.action !== "reclaim")
|
|
30
|
+
continue;
|
|
31
|
+
await removeWorktree(candidate, { cfg, base, result, logger, opts });
|
|
32
|
+
}
|
|
33
|
+
for (const branch of repoPlan.branches) {
|
|
34
|
+
await deleteBranch(branch, { cfg, base, result, logger });
|
|
35
|
+
}
|
|
36
|
+
await git.pruneWorktrees(repoPlan.repo);
|
|
37
|
+
if (cfg.pruneRemotes)
|
|
38
|
+
await git.remotePrune(repoPlan.repo);
|
|
39
|
+
}
|
|
40
|
+
if (opts.config.archive.enabled) {
|
|
41
|
+
const pruned = await pruneArchives(opts.config.archive.dir, parseDuration(opts.config.archive.retention), plan.now);
|
|
42
|
+
result.prunedArchives = pruned.removed;
|
|
43
|
+
if (pruned.removed.length > 0) {
|
|
44
|
+
logger.event("archive-pruned", { count: pruned.removed.length, freedKb: pruned.freedKb });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
async function removeWorktree(c, ctx) {
|
|
50
|
+
const { logger, result } = ctx;
|
|
51
|
+
if (!(ctx.opts?.skipReverify ?? false)) {
|
|
52
|
+
const problem = await reverify(c);
|
|
53
|
+
if (problem) {
|
|
54
|
+
logger.warn(`skipping ${c.path}: ${problem}`);
|
|
55
|
+
logger.event("skip", { path: c.path, branch: c.branch, stage: "reverify", reason: problem });
|
|
56
|
+
result.failures.push({ target: c.path, stage: "reverify", message: problem });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
let archive = null;
|
|
61
|
+
try {
|
|
62
|
+
archive = await archiveWork({
|
|
63
|
+
repo: c.repo,
|
|
64
|
+
worktreePath: c.path,
|
|
65
|
+
branch: c.branch,
|
|
66
|
+
head: c.head,
|
|
67
|
+
reason: c.verdict.reason,
|
|
68
|
+
risks: c.verdict.risks,
|
|
69
|
+
merge: c.merge,
|
|
70
|
+
base: ctx.base,
|
|
71
|
+
config: ctx.cfg.archive,
|
|
72
|
+
logger,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
const message = err.message;
|
|
77
|
+
// Never remove work we failed to back up.
|
|
78
|
+
logger.error(`archive failed for ${c.path}, keeping it: ${message}`);
|
|
79
|
+
result.failures.push({ target: c.path, stage: "archive", message });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (archive)
|
|
83
|
+
result.archives.push(archive);
|
|
84
|
+
const archived = archive !== null;
|
|
85
|
+
let removal = await git.removeWorktree(c.repo, c.path, false);
|
|
86
|
+
if (!removal.ok && (archived || ctx.opts?.force)) {
|
|
87
|
+
removal = await git.removeWorktree(c.repo, c.path, true);
|
|
88
|
+
}
|
|
89
|
+
if (!removal.ok) {
|
|
90
|
+
const message = removal.error ?? "git worktree remove failed";
|
|
91
|
+
logger.error(`could not remove ${c.path}: ${message}`);
|
|
92
|
+
result.failures.push({ target: c.path, stage: "remove", message });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const sizeKb = c.sizeKb ?? 0;
|
|
96
|
+
result.removedWorktrees.push({
|
|
97
|
+
path: c.path,
|
|
98
|
+
branch: c.branch,
|
|
99
|
+
repo: c.repo,
|
|
100
|
+
sizeKb,
|
|
101
|
+
reason: c.verdict.reason,
|
|
102
|
+
archive: archive?.dir ?? null,
|
|
103
|
+
});
|
|
104
|
+
result.reclaimedKb += sizeKb;
|
|
105
|
+
logger.event("reclaim", {
|
|
106
|
+
path: c.path,
|
|
107
|
+
repo: c.repo,
|
|
108
|
+
branch: c.branch,
|
|
109
|
+
sizeKb,
|
|
110
|
+
reason: c.verdict.reason,
|
|
111
|
+
risks: c.verdict.risks,
|
|
112
|
+
archive: archive?.dir ?? null,
|
|
113
|
+
});
|
|
114
|
+
if (c.branch) {
|
|
115
|
+
const ok = await git.deleteBranch(c.repo, c.branch, true);
|
|
116
|
+
if (ok) {
|
|
117
|
+
result.deletedBranches.push({
|
|
118
|
+
repo: c.repo,
|
|
119
|
+
name: c.branch,
|
|
120
|
+
reason: c.verdict.reason,
|
|
121
|
+
archive: archive?.dir ?? null,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
logger.warn(`removed ${c.path} but could not delete branch ${c.branch}`);
|
|
126
|
+
result.failures.push({
|
|
127
|
+
target: `${c.repo}#${c.branch}`,
|
|
128
|
+
stage: "branch",
|
|
129
|
+
message: "git branch -D failed",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function deleteBranch(b, ctx) {
|
|
135
|
+
const { logger, result } = ctx;
|
|
136
|
+
// The tip may have moved since the plan was built.
|
|
137
|
+
const tip = await git.revParse(b.repo, `refs/heads/${b.name}`);
|
|
138
|
+
if (tip !== b.head) {
|
|
139
|
+
const message = "branch tip moved since the plan was built";
|
|
140
|
+
logger.warn(`skipping branch ${b.name}: ${message}`);
|
|
141
|
+
result.failures.push({ target: `${b.repo}#${b.name}`, stage: "reverify", message });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
let archive = null;
|
|
145
|
+
if (b.verdict.risks.length > 0) {
|
|
146
|
+
archive = await archiveWork({
|
|
147
|
+
repo: b.repo,
|
|
148
|
+
worktreePath: null,
|
|
149
|
+
branch: b.name,
|
|
150
|
+
head: b.head,
|
|
151
|
+
reason: b.verdict.reason,
|
|
152
|
+
risks: b.verdict.risks,
|
|
153
|
+
merge: b.merge,
|
|
154
|
+
base: ctx.base,
|
|
155
|
+
config: ctx.cfg.archive,
|
|
156
|
+
logger,
|
|
157
|
+
});
|
|
158
|
+
if (archive)
|
|
159
|
+
result.archives.push(archive);
|
|
160
|
+
}
|
|
161
|
+
if (await git.deleteBranch(b.repo, b.name, true)) {
|
|
162
|
+
result.deletedBranches.push({
|
|
163
|
+
repo: b.repo,
|
|
164
|
+
name: b.name,
|
|
165
|
+
reason: b.verdict.reason,
|
|
166
|
+
archive: archive?.dir ?? null,
|
|
167
|
+
});
|
|
168
|
+
logger.event("branch-delete", {
|
|
169
|
+
repo: b.repo,
|
|
170
|
+
branch: b.name,
|
|
171
|
+
reason: b.verdict.reason,
|
|
172
|
+
archive: archive?.dir ?? null,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
result.failures.push({
|
|
177
|
+
target: `${b.repo}#${b.name}`,
|
|
178
|
+
stage: "branch",
|
|
179
|
+
message: "git branch -D failed",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Confirm the worktree is still exactly what the plan described. Anything that
|
|
185
|
+
* moved — a new commit, a switched branch, a fresh file — means the snapshot is
|
|
186
|
+
* stale and the worktree is left alone until the next sweep.
|
|
187
|
+
*/
|
|
188
|
+
async function reverify(c) {
|
|
189
|
+
try {
|
|
190
|
+
const st = await stat(c.path);
|
|
191
|
+
if (!st.isDirectory())
|
|
192
|
+
return "no longer a directory";
|
|
193
|
+
if (st.mtimeMs > c.lastTouched + 1_000)
|
|
194
|
+
return "directory changed since the plan was built";
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return "directory no longer exists";
|
|
198
|
+
}
|
|
199
|
+
const worktrees = await git.listWorktrees(c.repo);
|
|
200
|
+
const current = worktrees.find((w) => w.path === c.path);
|
|
201
|
+
if (!current)
|
|
202
|
+
return "no longer a registered worktree";
|
|
203
|
+
if (current.branch !== c.branch)
|
|
204
|
+
return `branch changed to ${current.branch ?? "(detached)"}`;
|
|
205
|
+
if (current.head !== c.head)
|
|
206
|
+
return "new commits since the plan was built";
|
|
207
|
+
if (current.locked)
|
|
208
|
+
return "worktree was locked";
|
|
209
|
+
if (!c.dirty) {
|
|
210
|
+
const dirty = await git.dirtyState(c.path);
|
|
211
|
+
if (dirty.dirty)
|
|
212
|
+
return "uncommitted changes appeared since the plan was built";
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
async function preferredBase(repo, defaultBranch) {
|
|
217
|
+
for (const ref of [`refs/remotes/origin/${defaultBranch}`, `refs/heads/${defaultBranch}`]) {
|
|
218
|
+
if (await git.refExists(repo, ref))
|
|
219
|
+
return ref;
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/core/apply.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ArchiveConfig } from "./config.js";
|
|
2
|
+
import type { MergeStatus, Risk } from "./decide.js";
|
|
3
|
+
import type { Logger } from "../util/log.js";
|
|
4
|
+
export interface ArchiveRecord {
|
|
5
|
+
version: 1;
|
|
6
|
+
id: string;
|
|
7
|
+
dir: string;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
repo: string;
|
|
10
|
+
repoName: string;
|
|
11
|
+
worktree: string | null;
|
|
12
|
+
branch: string | null;
|
|
13
|
+
head: string | null;
|
|
14
|
+
reason: string;
|
|
15
|
+
risks: Risk[];
|
|
16
|
+
pr: {
|
|
17
|
+
number: number;
|
|
18
|
+
url: string;
|
|
19
|
+
state: string;
|
|
20
|
+
} | null;
|
|
21
|
+
/** Commits that were not reachable from the default branch. */
|
|
22
|
+
bundle: {
|
|
23
|
+
file: string;
|
|
24
|
+
commits: number;
|
|
25
|
+
revRange: string;
|
|
26
|
+
} | null;
|
|
27
|
+
/** Uncommitted tracked changes. */
|
|
28
|
+
patch: {
|
|
29
|
+
file: string;
|
|
30
|
+
bytes: number;
|
|
31
|
+
} | null;
|
|
32
|
+
untracked: {
|
|
33
|
+
count: number;
|
|
34
|
+
bytes: number;
|
|
35
|
+
file: string | null;
|
|
36
|
+
truncated: boolean;
|
|
37
|
+
} | null;
|
|
38
|
+
}
|
|
39
|
+
export interface ArchiveInput {
|
|
40
|
+
repo: string;
|
|
41
|
+
worktreePath: string | null;
|
|
42
|
+
branch: string | null;
|
|
43
|
+
head: string | null;
|
|
44
|
+
reason: string;
|
|
45
|
+
risks: Risk[];
|
|
46
|
+
merge: MergeStatus;
|
|
47
|
+
/** Base ref to diff history against, e.g. `refs/remotes/origin/main`. */
|
|
48
|
+
base: string | null;
|
|
49
|
+
config: ArchiveConfig;
|
|
50
|
+
logger?: Logger;
|
|
51
|
+
now?: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Save everything that would be lost, *before* anything is removed.
|
|
55
|
+
*
|
|
56
|
+
* A cleanly merged worktree has nothing unique in it, so only a small metadata
|
|
57
|
+
* record is written. Anything with unmerged commits, uncommitted changes or
|
|
58
|
+
* untracked files gets a bundle, a patch and a tarball — which is what makes
|
|
59
|
+
* an unattended daemon a reasonable thing to run.
|
|
60
|
+
*/
|
|
61
|
+
export declare function archiveWork(input: ArchiveInput): Promise<ArchiveRecord | null>;
|
|
62
|
+
export declare function listArchives(dir: string): Promise<ArchiveRecord[]>;
|
|
63
|
+
export interface PruneResult {
|
|
64
|
+
removed: string[];
|
|
65
|
+
freedKb: number;
|
|
66
|
+
}
|
|
67
|
+
/** Delete archives older than the retention window. */
|
|
68
|
+
export declare function pruneArchives(dir: string, retentionMs: number, now?: number): Promise<PruneResult>;
|
|
69
|
+
//# sourceMappingURL=archive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/core/archive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAIrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAI7C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,CAAC,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,EAAE,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1D,+DAA+D;IAC/D,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACnE,mCAAmC;IACnC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9C,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CAC7F;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,WAAW,CAAC;IACnB,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAID;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAoEpF;AAwFD,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA6BxE;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,uDAAuD;AACvD,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,GAAG,SAAa,GACf,OAAO,CAAC,WAAW,CAAC,CAetB"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import * as git from "./git.js";
|
|
4
|
+
import { dirSizeKb } from "./size.js";
|
|
5
|
+
import { run } from "../util/exec.js";
|
|
6
|
+
import { formatBytes } from "../util/bytes.js";
|
|
7
|
+
import { formatStamp } from "../util/time.js";
|
|
8
|
+
const META = "meta.json";
|
|
9
|
+
/**
|
|
10
|
+
* Save everything that would be lost, *before* anything is removed.
|
|
11
|
+
*
|
|
12
|
+
* A cleanly merged worktree has nothing unique in it, so only a small metadata
|
|
13
|
+
* record is written. Anything with unmerged commits, uncommitted changes or
|
|
14
|
+
* untracked files gets a bundle, a patch and a tarball — which is what makes
|
|
15
|
+
* an unattended daemon a reasonable thing to run.
|
|
16
|
+
*/
|
|
17
|
+
export async function archiveWork(input) {
|
|
18
|
+
if (!input.config.enabled)
|
|
19
|
+
return null;
|
|
20
|
+
const now = input.now ?? Date.now();
|
|
21
|
+
const repoName = basename(input.repo);
|
|
22
|
+
const slug = (input.branch ?? "detached").replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
23
|
+
const id = join(repoName, `${slug}-${formatStamp(now)}`);
|
|
24
|
+
const dir = join(input.config.dir, id);
|
|
25
|
+
await mkdir(dir, { recursive: true });
|
|
26
|
+
const record = {
|
|
27
|
+
version: 1,
|
|
28
|
+
id,
|
|
29
|
+
dir,
|
|
30
|
+
createdAt: new Date(now).toISOString(),
|
|
31
|
+
repo: input.repo,
|
|
32
|
+
repoName,
|
|
33
|
+
worktree: input.worktreePath,
|
|
34
|
+
branch: input.branch,
|
|
35
|
+
head: input.head,
|
|
36
|
+
reason: input.reason,
|
|
37
|
+
risks: input.risks,
|
|
38
|
+
pr: prOf(input.merge),
|
|
39
|
+
bundle: null,
|
|
40
|
+
patch: null,
|
|
41
|
+
untracked: null,
|
|
42
|
+
};
|
|
43
|
+
// 1. Commits that exist nowhere else.
|
|
44
|
+
if (input.branch) {
|
|
45
|
+
const revRange = input.base ? `${input.base}..${input.branch}` : input.branch;
|
|
46
|
+
const commits = input.base
|
|
47
|
+
? await git.commitsNotIn(input.repo, input.base, input.branch)
|
|
48
|
+
: [];
|
|
49
|
+
if (!input.base || commits.length > 0) {
|
|
50
|
+
const file = join(dir, "commits.bundle");
|
|
51
|
+
const ok = await git.createBundle(input.repo, file, [revRange]);
|
|
52
|
+
if (ok) {
|
|
53
|
+
record.bundle = { file, commits: commits.length, revRange };
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
input.logger?.warn(`archive: could not bundle ${revRange} for ${input.branch}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 2. Uncommitted tracked changes.
|
|
61
|
+
if (input.worktreePath) {
|
|
62
|
+
const diff = await git.diffAgainstHead(input.worktreePath);
|
|
63
|
+
if (diff) {
|
|
64
|
+
const file = join(dir, "uncommitted.patch");
|
|
65
|
+
await writeFile(file, diff);
|
|
66
|
+
record.patch = { file, bytes: Buffer.byteLength(diff) };
|
|
67
|
+
}
|
|
68
|
+
// 3. Untracked (but not ignored) files.
|
|
69
|
+
const untracked = await git.untrackedFiles(input.worktreePath);
|
|
70
|
+
if (untracked.length > 0) {
|
|
71
|
+
record.untracked = await saveUntracked(input.worktreePath, untracked, dir, input.config, input.logger);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
await writeFile(join(dir, META), JSON.stringify(record, null, 2) + "\n");
|
|
75
|
+
await writeFile(join(dir, "RESTORE.md"), restoreDoc(record));
|
|
76
|
+
input.logger?.debug(`archive: wrote ${dir}`);
|
|
77
|
+
return record;
|
|
78
|
+
}
|
|
79
|
+
async function saveUntracked(worktree, files, dir, cfg, logger) {
|
|
80
|
+
let bytes = 0;
|
|
81
|
+
for (const f of files) {
|
|
82
|
+
try {
|
|
83
|
+
const st = await stat(join(worktree, f));
|
|
84
|
+
bytes += st.size;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
/* raced away */
|
|
88
|
+
}
|
|
89
|
+
if (bytes > cfg.maxUntrackedBytes)
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
const listFile = join(dir, "untracked.txt");
|
|
93
|
+
await writeFile(listFile, files.join("\n") + "\n");
|
|
94
|
+
if (bytes > cfg.maxUntrackedBytes) {
|
|
95
|
+
logger?.warn(`archive: ${files.length} untracked files total ${formatBytes(bytes)}, over the ` +
|
|
96
|
+
`${formatBytes(cfg.maxUntrackedBytes)} limit — saved the file list only`);
|
|
97
|
+
return { count: files.length, bytes, file: listFile, truncated: true };
|
|
98
|
+
}
|
|
99
|
+
const tarball = join(dir, "untracked.tar.gz");
|
|
100
|
+
const r = await run("tar", ["-czf", tarball, "-C", worktree, "-T", listFile], {
|
|
101
|
+
timeoutMs: 300_000,
|
|
102
|
+
});
|
|
103
|
+
if (r.code !== 0) {
|
|
104
|
+
logger?.warn(`archive: tar failed (${(r.stderr || "").trim().split("\n")[0] ?? ""}) — kept the list`);
|
|
105
|
+
return { count: files.length, bytes, file: listFile, truncated: true };
|
|
106
|
+
}
|
|
107
|
+
return { count: files.length, bytes, file: tarball, truncated: false };
|
|
108
|
+
}
|
|
109
|
+
function prOf(merge) {
|
|
110
|
+
if ("pr" in merge && merge.pr) {
|
|
111
|
+
return { number: merge.pr.number, url: merge.pr.url, state: merge.pr.state };
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function restoreDoc(r) {
|
|
116
|
+
const lines = [
|
|
117
|
+
`# Archive: ${r.branch ?? "(detached)"}`,
|
|
118
|
+
"",
|
|
119
|
+
`Removed from \`${r.worktree ?? r.repo}\` on ${r.createdAt}.`,
|
|
120
|
+
`Reason: ${r.reason}.`,
|
|
121
|
+
r.risks.length > 0 ? `Flagged: ${r.risks.join(", ")}.` : "Nothing unique was at risk.",
|
|
122
|
+
"",
|
|
123
|
+
"## Restore",
|
|
124
|
+
"",
|
|
125
|
+
"```bash",
|
|
126
|
+
`cd ${r.repo}`,
|
|
127
|
+
];
|
|
128
|
+
if (r.bundle) {
|
|
129
|
+
lines.push(`# ${r.bundle.commits} commit(s) that were not on the default branch`, `git fetch ${r.bundle.file} '*:*'`, `git branch ${r.branch ?? "restored"} ${r.head ?? "FETCH_HEAD"}`);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
lines.push(`# all commits were already on the default branch`, `git branch ${r.branch ?? "restored"} ${r.head ?? ""}`);
|
|
133
|
+
}
|
|
134
|
+
lines.push(`git worktree add ../${basename(r.worktree ?? r.repo)} ${r.branch ?? "restored"}`);
|
|
135
|
+
if (r.patch) {
|
|
136
|
+
lines.push("", "# uncommitted changes", `git apply ${r.patch.file}`);
|
|
137
|
+
}
|
|
138
|
+
if (r.untracked && !r.untracked.truncated && r.untracked.file) {
|
|
139
|
+
lines.push("", "# untracked files", `tar -xzf ${r.untracked.file} -C .`);
|
|
140
|
+
}
|
|
141
|
+
else if (r.untracked) {
|
|
142
|
+
lines.push("", `# ${r.untracked.count} untracked files were too large to copy; see untracked.txt`);
|
|
143
|
+
}
|
|
144
|
+
lines.push("```", "");
|
|
145
|
+
return lines.join("\n");
|
|
146
|
+
}
|
|
147
|
+
export async function listArchives(dir) {
|
|
148
|
+
const out = [];
|
|
149
|
+
let repoDirs;
|
|
150
|
+
try {
|
|
151
|
+
repoDirs = await readdir(dir, { withFileTypes: true });
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
for (const repoDir of repoDirs) {
|
|
157
|
+
if (!repoDir.isDirectory())
|
|
158
|
+
continue;
|
|
159
|
+
let entries;
|
|
160
|
+
try {
|
|
161
|
+
entries = await readdir(join(dir, repoDir.name), { withFileTypes: true });
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
for (const e of entries) {
|
|
167
|
+
if (!e.isDirectory())
|
|
168
|
+
continue;
|
|
169
|
+
const metaPath = join(dir, repoDir.name, e.name, META);
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(await readFile(metaPath, "utf8"));
|
|
172
|
+
if (parsed && typeof parsed === "object")
|
|
173
|
+
out.push(parsed);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
/* an archive without readable metadata is not listed */
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
out.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
/** Delete archives older than the retention window. */
|
|
184
|
+
export async function pruneArchives(dir, retentionMs, now = Date.now()) {
|
|
185
|
+
const result = { removed: [], freedKb: 0 };
|
|
186
|
+
for (const record of await listArchives(dir)) {
|
|
187
|
+
const age = now - Date.parse(record.createdAt);
|
|
188
|
+
if (!Number.isFinite(age) || age < retentionMs)
|
|
189
|
+
continue;
|
|
190
|
+
const kb = (await dirSizeKb(record.dir)) ?? 0;
|
|
191
|
+
try {
|
|
192
|
+
await rm(record.dir, { recursive: true, force: true });
|
|
193
|
+
result.removed.push(record.id);
|
|
194
|
+
result.freedKb += kb;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
/* leave it for next time */
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../../src/core/archive.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export type DirtyPolicy = "archive" | "keep";
|
|
2
|
+
export type ForgeKind = "github" | "none";
|
|
3
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
4
|
+
export interface SessionCommand {
|
|
5
|
+
name: string;
|
|
6
|
+
/** Shell command; exit code 0 means "an agent is still working here". */
|
|
7
|
+
command: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SessionConfig {
|
|
10
|
+
/** A worktree touched by any detector inside this window is left alone. */
|
|
11
|
+
grace: string;
|
|
12
|
+
detectors: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Extra files/dirs whose mtime counts as agent activity.
|
|
15
|
+
* `{worktree}` is substituted with the worktree path.
|
|
16
|
+
*/
|
|
17
|
+
extraPaths: string[];
|
|
18
|
+
commands: SessionCommand[];
|
|
19
|
+
}
|
|
20
|
+
export interface ArchiveConfig {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
dir: string;
|
|
23
|
+
retention: string;
|
|
24
|
+
/** Untracked files above this total are listed instead of copied. */
|
|
25
|
+
maxUntrackedBytes: number;
|
|
26
|
+
}
|
|
27
|
+
export interface Config {
|
|
28
|
+
/** Directories scanned for git repos (and therefore for their worktrees). */
|
|
29
|
+
roots: string[];
|
|
30
|
+
maxDepth: number;
|
|
31
|
+
/** A worktree must be untouched this long before it can be reclaimed. */
|
|
32
|
+
minAge: string;
|
|
33
|
+
/** A worktree whose PR was closed unmerged waits this long on top of minAge. */
|
|
34
|
+
closedPrGrace: string;
|
|
35
|
+
onDirty: DirtyPolicy;
|
|
36
|
+
onClosedPr: DirtyPolicy;
|
|
37
|
+
/** PR merged but the local branch tip has commits the PR never had. */
|
|
38
|
+
onExtraCommits: DirtyPolicy;
|
|
39
|
+
protectBranches: string[];
|
|
40
|
+
protectPaths: string[];
|
|
41
|
+
/** Also delete merged local branches that have no worktree. */
|
|
42
|
+
pruneBranches: boolean;
|
|
43
|
+
pruneRemotes: boolean;
|
|
44
|
+
forge: ForgeKind;
|
|
45
|
+
forgeCacheTtl: string;
|
|
46
|
+
/** Daemon sweep interval. */
|
|
47
|
+
interval: string;
|
|
48
|
+
sessions: SessionConfig;
|
|
49
|
+
archive: ArchiveConfig;
|
|
50
|
+
logFile: string;
|
|
51
|
+
logLevel: LogLevel;
|
|
52
|
+
/** Set to false in a repo's .treekeeper.json to opt that repo out entirely. */
|
|
53
|
+
enabled: boolean;
|
|
54
|
+
}
|
|
55
|
+
export declare const CONFIG_FILENAME = "config.json";
|
|
56
|
+
export declare const REPO_CONFIG_FILENAME = ".treekeeper.json";
|
|
57
|
+
export declare function defaultConfig(): Config;
|
|
58
|
+
export interface LoadedConfig {
|
|
59
|
+
config: Config;
|
|
60
|
+
/** Files that contributed, in precedence order. */
|
|
61
|
+
sources: string[];
|
|
62
|
+
warnings: string[];
|
|
63
|
+
}
|
|
64
|
+
export declare function globalConfigPath(): string;
|
|
65
|
+
/** Load the user-level config, falling back to defaults when absent. */
|
|
66
|
+
export declare function loadConfig(path?: string): Promise<LoadedConfig>;
|
|
67
|
+
/** Layer a repo-local `.treekeeper.json` on top of an already-loaded config. */
|
|
68
|
+
export declare function loadRepoConfig(repoPath: string, parent: Config): Promise<LoadedConfig>;
|
|
69
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;AAC7C,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC1C,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;OAGG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,MAAM;IACrB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,WAAW,CAAC;IACrB,UAAU,EAAE,WAAW,CAAC;IACxB,uEAAuE;IACvE,cAAc,EAAE,WAAW,CAAC;IAC5B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,+DAA+D;IAC/D,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,SAAS,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,+EAA+E;IAC/E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAC7C,eAAO,MAAM,oBAAoB,qBAAqB,CAAC;AAEvD,wBAAgB,aAAa,IAAI,MAAM,CAiCtC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAIzC;AAED,wEAAwE;AACxE,wBAAsB,UAAU,CAAC,IAAI,SAAqB,GAAG,OAAO,CAAC,YAAY,CAAC,CAYjF;AAED,gFAAgF;AAChF,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAS5F"}
|