teamshare-bridge 0.26.22 → 0.26.23

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 (58) hide show
  1. package/dist/bridge/daemon.js +32 -0
  2. package/dist/bridge/daemon.js.map +1 -1
  3. package/dist/cli/commands/blog.js +31 -16
  4. package/dist/cli/commands/blog.js.map +1 -1
  5. package/dist/cli/commands/build.js +2 -2
  6. package/dist/cli/commands/build.js.map +1 -1
  7. package/dist/cli/commands/chat.js +2 -3
  8. package/dist/cli/commands/chat.js.map +1 -1
  9. package/dist/cli/commands/cleanup.js +98 -27
  10. package/dist/cli/commands/cleanup.js.map +1 -1
  11. package/dist/cli/commands/plan.d.ts +4 -0
  12. package/dist/cli/commands/plan.js +33 -10
  13. package/dist/cli/commands/plan.js.map +1 -1
  14. package/dist/cli/commands/planner.js +15 -5
  15. package/dist/cli/commands/planner.js.map +1 -1
  16. package/dist/cli/commands/research.js +26 -20
  17. package/dist/cli/commands/research.js.map +1 -1
  18. package/dist/cli/commands/run.js +2 -5
  19. package/dist/cli/commands/run.js.map +1 -1
  20. package/dist/cli/commands/worker.js +2 -3
  21. package/dist/cli/commands/worker.js.map +1 -1
  22. package/dist/cli/index.js +2 -0
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/lib/cache-cleaner.js +226 -205
  25. package/dist/lib/cache-cleaner.js.map +1 -1
  26. package/dist/lib/git/git-auto.js +46 -4
  27. package/dist/lib/git/git-auto.js.map +1 -1
  28. package/dist/lib/git/git-core.d.ts +11 -0
  29. package/dist/lib/git/git-core.js +14 -0
  30. package/dist/lib/git/git-core.js.map +1 -1
  31. package/dist/lib/git/git-finalize.d.ts +4 -0
  32. package/dist/lib/git/git-finalize.js +46 -10
  33. package/dist/lib/git/git-finalize.js.map +1 -1
  34. package/dist/lib/git/git-runner.js +2 -5
  35. package/dist/lib/git/git-runner.js.map +1 -1
  36. package/dist/lib/git/index.d.ts +2 -1
  37. package/dist/lib/git/index.js +5 -4
  38. package/dist/lib/git/index.js.map +1 -1
  39. package/dist/lib/git/pr-token.d.ts +29 -0
  40. package/dist/lib/git/pr-token.js +102 -0
  41. package/dist/lib/git/pr-token.js.map +1 -0
  42. package/dist/lib/harness/run-scope.d.ts +23 -4
  43. package/dist/lib/harness/run-scope.js +237 -48
  44. package/dist/lib/harness/run-scope.js.map +1 -1
  45. package/dist/lib/llm/usage-reader.js +2 -1
  46. package/dist/lib/llm/usage-reader.js.map +1 -1
  47. package/dist/lib/orchestrator/worktree.d.ts +7 -1
  48. package/dist/lib/orchestrator/worktree.js +18 -1
  49. package/dist/lib/orchestrator/worktree.js.map +1 -1
  50. package/dist/lib/workspace/mirror.js +51 -5
  51. package/dist/lib/workspace/mirror.js.map +1 -1
  52. package/dist/lib/workspace/state-root.d.ts +44 -0
  53. package/dist/lib/workspace/state-root.js +266 -0
  54. package/dist/lib/workspace/state-root.js.map +1 -0
  55. package/dist/lib/workspace/workspace-audit.d.ts +20 -0
  56. package/dist/lib/workspace/workspace-audit.js +339 -0
  57. package/dist/lib/workspace/workspace-audit.js.map +1 -0
  58. package/package.json +1 -1
@@ -0,0 +1,44 @@
1
+ /** Stable key for a linked folder. Never throws. */
2
+ export declare function workspaceKeyFor(folder: string): string;
3
+ /** The workspace root for a linked folder. Creates it. */
4
+ export declare function workspaceRootFor(folder: string): string;
5
+ /** All workspace roots (for sweeps). Empty when the base does not exist. */
6
+ export declare function allWorkspaceRoots(): string[];
7
+ /**
8
+ * Scratch dir for transient run files (briefs, post.json, plan prompts).
9
+ * Created on call. MUST be disposed with disposeDir() on TERMINAL run end
10
+ * only (completed / failed / cancelled) - never on a pause or an idle kill,
11
+ * because a question resume reuses the same files.
12
+ */
13
+ export declare function scratchDirFor(folder: string, kind: 'research' | 'blog' | 'plans' | 'planner' | 'workflow' | 'insight' | 'misc', id: string): string;
14
+ /** Headless scratch (no linked folder - blog generations without a project). */
15
+ export declare function headlessScratchDir(kind: string, id: string): string;
16
+ /** Staging dir for files built for upload / attachFile. */
17
+ export declare function stagingDirFor(folder: string, runId: string): string;
18
+ /**
19
+ * Worktree root for isolation mirrors. This is `<workspaceRoot>`'s OWN
20
+ * `.teamshare-worktrees` directory - the NAME is the safety boundary:
21
+ * `removeWorktreeSafeAsync` refuses to delete anything outside a
22
+ * `.teamshare-worktrees` root, and that data-loss guard must keep working
23
+ * unchanged. What changed is only the PARENT: outside every tracked folder
24
+ * instead of `<folder>/..`.
25
+ */
26
+ export declare function worktreeRootFor(folder: string): string;
27
+ /** Legacy sibling worktree root. Read-only - for sweeps and migration. */
28
+ export declare function legacyWorktreeRootFor(folder: string): string;
29
+ /** --separate-git-dir target for a non-repo linked folder. */
30
+ export declare function containerGitDirFor(folder: string): string;
31
+ /** True unless TEAMSHARE_KEEP_SCRATCH=1 (local debugging). */
32
+ export declare function shouldDisposeScratch(): boolean;
33
+ /**
34
+ * Remove a scratch/staging dir. Links-first is unnecessary here (no junctions
35
+ * to the project), but we still never follow symlinks into the project:
36
+ * entries that are symlinks are unlinked, never traversed.
37
+ */
38
+ export declare function disposeDir(dir: string): void;
39
+ /**
40
+ * Reap crash-leftover scratch dirs older than `maxAgeMs` (default 7 days -
41
+ * the same horizon run secrets live). Never touches a dir younger than the
42
+ * horizon: a paused run (question wait, idle kill) resumes with its files.
43
+ */
44
+ export declare function sweepStaleScratch(maxAgeMs?: number): string[];
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.workspaceKeyFor = workspaceKeyFor;
4
+ exports.workspaceRootFor = workspaceRootFor;
5
+ exports.allWorkspaceRoots = allWorkspaceRoots;
6
+ exports.scratchDirFor = scratchDirFor;
7
+ exports.headlessScratchDir = headlessScratchDir;
8
+ exports.stagingDirFor = stagingDirFor;
9
+ exports.worktreeRootFor = worktreeRootFor;
10
+ exports.legacyWorktreeRootFor = legacyWorktreeRootFor;
11
+ exports.containerGitDirFor = containerGitDirFor;
12
+ exports.shouldDisposeScratch = shouldDisposeScratch;
13
+ exports.disposeDir = disposeDir;
14
+ exports.sweepStaleScratch = sweepStaleScratch;
15
+ /**
16
+ * Bridge workspace state root (2026-10).
17
+ *
18
+ * INVARIANT: the bridge creates zero entries inside a linked folder except
19
+ * the agent's own edits to real files. Everything the bridge owns -
20
+ * isolation mirrors, run scopes' backing data, scratch (briefs, post.json,
21
+ * plan prompts), staging, container gitdirs - lives under ONE per-machine
22
+ * root:
23
+ *
24
+ * ~/.teamshare/workspace/<key>/
25
+ * worktrees/taskrun-<uuid>/ isolation mirrors (were the sibling .teamshare-worktrees dir)
26
+ * scope/<runId>/ (reserved - run scopes stay in-folder transiently, see run-scope.ts)
27
+ * scratch/<kind>/<id>/ briefs, post.json, plan prompts - deleted on terminal end
28
+ * staging/<runId>/ files built for upload / attachFile
29
+ * container-git/ --separate-git-dir for non-repo linked folders
30
+ * bare-repos/ (legacy location stays - not moved)
31
+ *
32
+ * Key derivation: <sanitized-basename>-<8 hex of sha256(resolved absolute path)>.
33
+ * A rename orphans the old key (reaped by the sweeper); it never collides.
34
+ *
35
+ * Env overrides:
36
+ * TEAMSHARE_WORKSPACE_DIR - whole-root override (tests).
37
+ * TEAMSHARE_KEEP_SCRATCH=1 - never delete scratch (local debugging).
38
+ */
39
+ const node_crypto_1 = require("node:crypto");
40
+ const node_os_1 = require("node:os");
41
+ const node_path_1 = require("node:path");
42
+ const node_fs_1 = require("node:fs");
43
+ function rootBase() {
44
+ const override = process.env.TEAMSHARE_WORKSPACE_DIR?.trim();
45
+ if (override)
46
+ return override;
47
+ return (0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'workspace');
48
+ }
49
+ /** Filesystem-safe basename, max 40 chars. */
50
+ function safeBase(folder) {
51
+ const base = (0, node_path_1.basename)((0, node_path_1.resolve)(folder))
52
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
53
+ .slice(0, 40);
54
+ return base || 'project';
55
+ }
56
+ /** Stable key for a linked folder. Never throws. */
57
+ function workspaceKeyFor(folder) {
58
+ try {
59
+ const abs = (0, node_path_1.resolve)(folder);
60
+ const hash = (0, node_crypto_1.createHash)('sha256').update(abs).digest('hex').slice(0, 8);
61
+ return `${safeBase(abs)}-${hash}`;
62
+ }
63
+ catch {
64
+ return `${safeBase(folder)}-unknown`;
65
+ }
66
+ }
67
+ /** The workspace root for a linked folder. Creates it. */
68
+ function workspaceRootFor(folder) {
69
+ const dir = (0, node_path_1.join)(rootBase(), workspaceKeyFor(folder));
70
+ try {
71
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
72
+ }
73
+ catch {
74
+ /* best-effort - callers tolerate a missing dir */
75
+ }
76
+ return dir;
77
+ }
78
+ /** All workspace roots (for sweeps). Empty when the base does not exist. */
79
+ function allWorkspaceRoots() {
80
+ try {
81
+ const { readdirSync } = require('node:fs');
82
+ const base = rootBase();
83
+ if (!(0, node_fs_1.existsSync)(base))
84
+ return [];
85
+ return readdirSync(base, { withFileTypes: true })
86
+ .filter((e) => e.isDirectory())
87
+ .map((e) => (0, node_path_1.join)(base, e.name));
88
+ }
89
+ catch {
90
+ return [];
91
+ }
92
+ }
93
+ /**
94
+ * Scratch dir for transient run files (briefs, post.json, plan prompts).
95
+ * Created on call. MUST be disposed with disposeDir() on TERMINAL run end
96
+ * only (completed / failed / cancelled) - never on a pause or an idle kill,
97
+ * because a question resume reuses the same files.
98
+ */
99
+ function scratchDirFor(folder, kind, id) {
100
+ const clean = String(id)
101
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
102
+ .slice(0, 80) || 'run';
103
+ const dir = (0, node_path_1.join)(workspaceRootFor(folder), 'scratch', kind, clean);
104
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
105
+ return dir;
106
+ }
107
+ /** Headless scratch (no linked folder - blog generations without a project). */
108
+ function headlessScratchDir(kind, id) {
109
+ const clean = String(id)
110
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
111
+ .slice(0, 80) || 'run';
112
+ const dir = (0, node_path_1.join)(rootBase(), 'headless', kind, clean);
113
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
114
+ return dir;
115
+ }
116
+ /** Staging dir for files built for upload / attachFile. */
117
+ function stagingDirFor(folder, runId) {
118
+ const clean = String(runId)
119
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
120
+ .slice(0, 80) || 'run';
121
+ const dir = (0, node_path_1.join)(workspaceRootFor(folder), 'staging', clean);
122
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
123
+ return dir;
124
+ }
125
+ /**
126
+ * Worktree root for isolation mirrors. This is `<workspaceRoot>`'s OWN
127
+ * `.teamshare-worktrees` directory - the NAME is the safety boundary:
128
+ * `removeWorktreeSafeAsync` refuses to delete anything outside a
129
+ * `.teamshare-worktrees` root, and that data-loss guard must keep working
130
+ * unchanged. What changed is only the PARENT: outside every tracked folder
131
+ * instead of `<folder>/..`.
132
+ */
133
+ function worktreeRootFor(folder) {
134
+ const dir = (0, node_path_1.join)(workspaceRootFor(folder), '.teamshare-worktrees');
135
+ try {
136
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
137
+ }
138
+ catch {
139
+ /* best-effort */
140
+ }
141
+ return dir;
142
+ }
143
+ /** Legacy sibling worktree root. Read-only - for sweeps and migration. */
144
+ function legacyWorktreeRootFor(folder) {
145
+ return (0, node_path_1.join)((0, node_path_1.resolve)(folder, '..'), '.teamshare-worktrees');
146
+ }
147
+ /** --separate-git-dir target for a non-repo linked folder. */
148
+ function containerGitDirFor(folder) {
149
+ const dir = (0, node_path_1.join)(workspaceRootFor(folder), 'container-git');
150
+ try {
151
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
152
+ }
153
+ catch {
154
+ /* best-effort */
155
+ }
156
+ return dir;
157
+ }
158
+ /** True unless TEAMSHARE_KEEP_SCRATCH=1 (local debugging). */
159
+ function shouldDisposeScratch() {
160
+ return process.env.TEAMSHARE_KEEP_SCRATCH !== '1';
161
+ }
162
+ /**
163
+ * Remove a scratch/staging dir. Links-first is unnecessary here (no junctions
164
+ * to the project), but we still never follow symlinks into the project:
165
+ * entries that are symlinks are unlinked, never traversed.
166
+ */
167
+ function disposeDir(dir) {
168
+ if (!shouldDisposeScratch())
169
+ return;
170
+ if (!dir)
171
+ return;
172
+ try {
173
+ const { lstatSync, readdirSync, unlinkSync, rmdirSync } = require('node:fs');
174
+ if (!(0, node_fs_1.existsSync)(dir))
175
+ return;
176
+ for (const entry of readdirSync(dir)) {
177
+ const p = (0, node_path_1.join)(dir, entry);
178
+ try {
179
+ const st = lstatSync(p);
180
+ if (st.isSymbolicLink())
181
+ unlinkSync(p);
182
+ else if (st.isDirectory())
183
+ (0, node_fs_1.rmSync)(p, { recursive: true, force: true });
184
+ else
185
+ unlinkSync(p);
186
+ }
187
+ catch {
188
+ /* best-effort per entry */
189
+ }
190
+ }
191
+ rmdirSync(dir, { recursive: true });
192
+ }
193
+ catch {
194
+ /* best-effort: a leftover is reaped by the sweeper */
195
+ }
196
+ }
197
+ /**
198
+ * Reap crash-leftover scratch dirs older than `maxAgeMs` (default 7 days -
199
+ * the same horizon run secrets live). Never touches a dir younger than the
200
+ * horizon: a paused run (question wait, idle kill) resumes with its files.
201
+ */
202
+ function sweepStaleScratch(maxAgeMs = 7 * 24 * 3600_000) {
203
+ const removed = [];
204
+ const now = Date.now();
205
+ let bases = [];
206
+ try {
207
+ const base = rootBase();
208
+ const { readdirSync: rs, statSync: ss } = require('node:fs');
209
+ if (!(0, node_fs_1.existsSync)(base))
210
+ return removed;
211
+ bases = rs(base, { withFileTypes: true })
212
+ .filter((e) => e.isDirectory())
213
+ .map((e) => (0, node_path_1.join)(base, e.name));
214
+ void ss;
215
+ }
216
+ catch {
217
+ return removed;
218
+ }
219
+ const visit = (dir) => {
220
+ let entries = [];
221
+ try {
222
+ const { readdirSync: rs2 } = require('node:fs');
223
+ entries = rs2(dir);
224
+ }
225
+ catch {
226
+ return;
227
+ }
228
+ for (const name of entries) {
229
+ const p = (0, node_path_1.join)(dir, name);
230
+ try {
231
+ const { statSync: ss2 } = require('node:fs');
232
+ const st = ss2(p);
233
+ if (!st.isDirectory())
234
+ continue;
235
+ if (now - st.mtimeMs > maxAgeMs) {
236
+ disposeDir(p);
237
+ if (!(0, node_fs_1.existsSync)(p))
238
+ removed.push(p);
239
+ }
240
+ }
241
+ catch {
242
+ /* best-effort per dir */
243
+ }
244
+ }
245
+ };
246
+ for (const key of bases) {
247
+ // <key>/scratch/<kind>/<id> + headless trees
248
+ for (const sub of ['scratch', 'headless']) {
249
+ const subDir = (0, node_path_1.join)(key, sub);
250
+ if (!(0, node_fs_1.existsSync)(subDir))
251
+ continue;
252
+ let kinds = [];
253
+ try {
254
+ const { readdirSync: rs3 } = require('node:fs');
255
+ kinds = rs3(subDir);
256
+ }
257
+ catch {
258
+ continue;
259
+ }
260
+ for (const kind of kinds)
261
+ visit((0, node_path_1.join)(subDir, kind));
262
+ }
263
+ }
264
+ return removed;
265
+ }
266
+ //# sourceMappingURL=state-root.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-root.js","sourceRoot":"","sources":["../../../src/lib/workspace/state-root.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface WorkspaceFinding {
2
+ kind: 'legacy-scratch-file' | 'leftover-scope' | 'legacy-worktree' | 'exclude-block';
3
+ path: string;
4
+ detail?: string;
5
+ /** True when `--apply` will remove this (worktrees are never auto-removed). */
6
+ removable: boolean;
7
+ }
8
+ export interface WorkspaceAudit {
9
+ folders: number;
10
+ findings: WorkspaceFinding[];
11
+ truncated: boolean;
12
+ }
13
+ export declare function auditWorkspaceState(): WorkspaceAudit;
14
+ export interface WorkspaceCleanupResult {
15
+ removed: number;
16
+ kept: number;
17
+ keptReason: string;
18
+ errors: string[];
19
+ }
20
+ export declare function applyWorkspaceStateCleanup(report: WorkspaceAudit): WorkspaceCleanupResult;
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.auditWorkspaceState = auditWorkspaceState;
4
+ exports.applyWorkspaceStateCleanup = applyWorkspaceStateCleanup;
5
+ /**
6
+ * Workspace-state audit (2026-10-27): report (and optionally remove) every
7
+ * bridge-owned entry left INSIDE linked folders.
8
+ *
9
+ * The invariant is that the bridge creates zero entries in a linked folder
10
+ * except the agent's own edits. These findings are all pre-migration
11
+ * leftovers or crash survivors:
12
+ * - legacy-scratch-file: `<folder>/teamshare/...` (research/blog/plan mirrors)
13
+ * - leftover-scope: `<folder>/.teamshare-runs/<runId>/` (transient scope)
14
+ * - legacy-worktree: `<folder>/../.teamshare-worktrees/*` (pre-migration mirrors)
15
+ * - exclude-block: auto-added `.git/info/exclude` lines
16
+ *
17
+ * Removal policy (`--apply`): scopes go through the live-guarded sweep
18
+ * (a scope claimed by a live/resumable run is kept); scratch files are
19
+ * deleted only under the three bridge-owned subdirs
20
+ * (`research/`, `blog/`, `plans/`); exclude cleanup strips only lines the
21
+ * bridge wrote; sibling worktrees are NEVER removed here (the 6h sweep owns
22
+ * them with rescue-first) and are reported as kept.
23
+ */
24
+ const node_fs_1 = require("node:fs");
25
+ const node_path_1 = require("node:path");
26
+ const node_os_1 = require("node:os");
27
+ const config_1 = require("../config");
28
+ const MAX_FILES_PER_TREE = 2000;
29
+ /** Scope dirs still claimed by a live/resumable harness run. */
30
+ function liveScopeDirs() {
31
+ const live = new Set();
32
+ try {
33
+ const sessionsDir = (0, node_path_1.join)((0, node_os_1.homedir)(), '.teamshare', 'sessions');
34
+ if (!(0, node_fs_1.existsSync)(sessionsDir))
35
+ return live;
36
+ for (const name of (0, node_fs_1.readdirSync)(sessionsDir)) {
37
+ if (!name.startsWith('harness-') || !name.endsWith('.json'))
38
+ continue;
39
+ try {
40
+ const rec = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(sessionsDir, name), 'utf8'));
41
+ if (rec?.scopeDir)
42
+ live.add(rec.scopeDir.replace(/\\/g, '/').toLowerCase());
43
+ }
44
+ catch {
45
+ /* corrupt record - ignore */
46
+ }
47
+ }
48
+ }
49
+ catch {
50
+ /* best-effort */
51
+ }
52
+ return live;
53
+ }
54
+ function walkFiles(root, out) {
55
+ const stack = [root];
56
+ while (stack.length && out.length < MAX_FILES_PER_TREE) {
57
+ const dir = stack.pop();
58
+ let entries;
59
+ try {
60
+ entries = (0, node_fs_1.readdirSync)(dir);
61
+ }
62
+ catch {
63
+ continue;
64
+ }
65
+ for (const name of entries) {
66
+ if (out.length >= MAX_FILES_PER_TREE)
67
+ break;
68
+ const p = (0, node_path_1.join)(dir, name);
69
+ try {
70
+ const st = (0, node_fs_1.statSync)(p);
71
+ if (st.isDirectory())
72
+ stack.push(p);
73
+ else if (st.isFile())
74
+ out.push(p);
75
+ }
76
+ catch {
77
+ /* best-effort per entry */
78
+ }
79
+ }
80
+ }
81
+ }
82
+ /** Lines the bridge ever auto-added to an exclude file (exact trimmed match). */
83
+ const BRIDGE_EXCLUDE_LINES = new Set([
84
+ 'teamshare/',
85
+ '.teamshare-runs/',
86
+ '# TeamShare local scratch (research/tool outputs) - added automatically',
87
+ '# TeamShare run scopes (per-run opencode projects) - added automatically',
88
+ '# teamshare isolation (auto) - do not edit',
89
+ '# >>> TeamShare run scope (auto, transient) - do not edit',
90
+ '# <<< TeamShare run scope (auto, transient)',
91
+ ]);
92
+ function excludeFileFor(folder) {
93
+ try {
94
+ const { execFileSync } = require('node:child_process');
95
+ const { envWithoutShims } = require('./shims');
96
+ const out = execFileSync('git', ['rev-parse', '--git-path', 'info/exclude'], {
97
+ cwd: folder,
98
+ encoding: 'utf-8',
99
+ timeout: 5_000,
100
+ stdio: ['pipe', 'pipe', 'pipe'],
101
+ env: envWithoutShims(),
102
+ })
103
+ .toString()
104
+ .trim();
105
+ if (!out)
106
+ return null;
107
+ const path = require('node:path');
108
+ return path.isAbsolute(out) ? out : (0, node_path_1.resolve)(folder, out);
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ }
114
+ function auditWorkspaceState() {
115
+ const findings = [];
116
+ let truncated = false;
117
+ let folders = 0;
118
+ let links = {};
119
+ try {
120
+ links = ((0, config_1.loadConfig)().projects ?? {});
121
+ }
122
+ catch {
123
+ return { folders: 0, findings, truncated };
124
+ }
125
+ const live = liveScopeDirs();
126
+ for (const link of Object.values(links)) {
127
+ const folder = link?.path;
128
+ if (!folder || !(0, node_fs_1.existsSync)(folder))
129
+ continue;
130
+ folders++;
131
+ // 1. Legacy in-folder scratch.
132
+ const scratchRoot = (0, node_path_1.join)(folder, 'teamshare');
133
+ if ((0, node_fs_1.existsSync)(scratchRoot)) {
134
+ const files = [];
135
+ try {
136
+ walkFiles(scratchRoot, files);
137
+ }
138
+ catch {
139
+ /* best-effort */
140
+ }
141
+ for (const f of files) {
142
+ const rel = f.slice(scratchRoot.length + 1).replace(/\\/g, '/');
143
+ const top = rel.split('/')[0];
144
+ findings.push({
145
+ kind: 'legacy-scratch-file',
146
+ path: f,
147
+ detail: 'legacy in-folder scratch (bridge mirror)',
148
+ removable: top === 'research' || top === 'blog' || top === 'plans',
149
+ });
150
+ }
151
+ if (files.length >= MAX_FILES_PER_TREE)
152
+ truncated = true;
153
+ }
154
+ // 2. Leftover run scopes.
155
+ const scopeBase = (0, node_path_1.join)(folder, '.teamshare-runs');
156
+ if ((0, node_fs_1.existsSync)(scopeBase)) {
157
+ let names = [];
158
+ try {
159
+ names = (0, node_fs_1.readdirSync)(scopeBase);
160
+ }
161
+ catch {
162
+ names = [];
163
+ }
164
+ for (const name of names) {
165
+ const dir = (0, node_path_1.join)(scopeBase, name);
166
+ try {
167
+ if (!(0, node_fs_1.lstatSync)(dir).isDirectory() && !(0, node_fs_1.lstatSync)(dir).isSymbolicLink())
168
+ continue;
169
+ }
170
+ catch {
171
+ continue;
172
+ }
173
+ const isLive = live.has(dir.replace(/\\/g, '/').toLowerCase());
174
+ findings.push({
175
+ kind: 'leftover-scope',
176
+ path: dir,
177
+ detail: isLive ? 'claimed by a live/resumable run - kept' : undefined,
178
+ removable: !isLive,
179
+ });
180
+ }
181
+ }
182
+ // 3. Legacy sibling worktrees (report-only).
183
+ let legacy = '';
184
+ try {
185
+ const { legacyWorktreeRootFor } = require('./state-root');
186
+ legacy = legacyWorktreeRootFor(folder);
187
+ }
188
+ catch {
189
+ legacy = (0, node_path_1.join)((0, node_path_1.resolve)(folder, '..'), '.teamshare-worktrees');
190
+ }
191
+ if ((0, node_fs_1.existsSync)(legacy)) {
192
+ let names = [];
193
+ try {
194
+ names = (0, node_fs_1.readdirSync)(legacy);
195
+ }
196
+ catch {
197
+ names = [];
198
+ }
199
+ for (const name of names) {
200
+ findings.push({
201
+ kind: 'legacy-worktree',
202
+ path: (0, node_path_1.join)(legacy, name),
203
+ detail: 'pre-migration mirror - owned by the 6h sweep (rescue-first), never removed here',
204
+ removable: false,
205
+ });
206
+ }
207
+ }
208
+ // 4. Auto-added exclude lines.
209
+ try {
210
+ const file = excludeFileFor(folder);
211
+ if (file && (0, node_fs_1.existsSync)(file)) {
212
+ const lines = (0, node_fs_1.readFileSync)(file, 'utf8').split('\n');
213
+ const hits = lines.filter((l) => BRIDGE_EXCLUDE_LINES.has(l.trim()));
214
+ if (hits.length) {
215
+ findings.push({
216
+ kind: 'exclude-block',
217
+ path: file,
218
+ detail: `${hits.length} auto-added line(s): ${hits
219
+ .map((h) => h.trim())
220
+ .slice(0, 4)
221
+ .join(' | ')}${hits.length > 4 ? ' …' : ''}`,
222
+ removable: true,
223
+ });
224
+ }
225
+ }
226
+ }
227
+ catch {
228
+ /* best-effort */
229
+ }
230
+ }
231
+ return { folders, findings, truncated };
232
+ }
233
+ function removeEmptyDirsUp(root, start) {
234
+ let dir = start;
235
+ while (dir.startsWith(root) && dir !== root) {
236
+ try {
237
+ if ((0, node_fs_1.existsSync)(dir) && (0, node_fs_1.readdirSync)(dir).length === 0)
238
+ (0, node_fs_1.rmdirSync)(dir);
239
+ else
240
+ break;
241
+ }
242
+ catch {
243
+ break;
244
+ }
245
+ const path = require('node:path');
246
+ const parent = path.dirname(dir);
247
+ if (parent === dir)
248
+ break;
249
+ dir = parent;
250
+ }
251
+ }
252
+ function applyWorkspaceStateCleanup(report) {
253
+ const res = {
254
+ removed: 0,
255
+ kept: 0,
256
+ keptReason: '',
257
+ errors: [],
258
+ };
259
+ const keptNotes = [];
260
+ for (const f of report.findings) {
261
+ try {
262
+ if (f.kind === 'leftover-scope') {
263
+ // Counted in bulk below: scopes go through the live-guarded sweep.
264
+ continue;
265
+ }
266
+ if (f.kind === 'legacy-worktree') {
267
+ res.kept++;
268
+ keptNotes.push('sibling worktrees (6h sweep owns them)');
269
+ continue;
270
+ }
271
+ if (f.kind === 'legacy-scratch-file') {
272
+ if (!f.removable) {
273
+ res.kept++;
274
+ keptNotes.push('unknown files under teamshare/');
275
+ continue;
276
+ }
277
+ (0, node_fs_1.rmSync)(f.path, { force: true });
278
+ if (!(0, node_fs_1.existsSync)(f.path)) {
279
+ res.removed++;
280
+ // Prune newly-empty parents up to (and including, when empty) the
281
+ // `teamshare/` root. Located by the LAST `/teamshare/` segment so a
282
+ // project folder that itself contains "teamshare" still resolves.
283
+ const norm = f.path.replace(/\\/g, '/');
284
+ const idx = norm.toLowerCase().lastIndexOf('/teamshare/');
285
+ if (idx !== -1) {
286
+ const path = require('node:path');
287
+ const root = f.path.slice(0, idx + '/teamshare'.length);
288
+ removeEmptyDirsUp(root, path.dirname(f.path));
289
+ }
290
+ }
291
+ else {
292
+ res.errors.push(`could not remove ${f.path}`);
293
+ }
294
+ continue;
295
+ }
296
+ if (f.kind === 'exclude-block') {
297
+ const lines = (0, node_fs_1.readFileSync)(f.path, 'utf8').split('\n');
298
+ const keptLines = lines.filter((l) => !BRIDGE_EXCLUDE_LINES.has(l.trim()));
299
+ // Drop a leftover blank line the append introduced, never other content.
300
+ const cleaned = keptLines.join('\n').replace(/\n{3,}/g, '\n\n');
301
+ (0, node_fs_1.writeFileSync)(f.path, cleaned, 'utf8');
302
+ res.removed++;
303
+ continue;
304
+ }
305
+ }
306
+ catch (err) {
307
+ res.errors.push(`${f.path}: ${err instanceof Error ? err.message : String(err)}`);
308
+ }
309
+ }
310
+ // Scopes go through the live-guarded sweep in bulk (one pass per folder set
311
+ // is cheaper and the guard is authoritative there).
312
+ try {
313
+ const scopeFindings = report.findings.filter((f) => f.kind === 'leftover-scope' && f.removable);
314
+ if (scopeFindings.length) {
315
+ const { sweepStaleScopes } = require('../harness/run-scope');
316
+ const links = ((0, config_1.loadConfig)().projects ?? {});
317
+ const folders = Object.values(links)
318
+ .map((l) => l?.path)
319
+ .filter((p) => typeof p === 'string' && p.length > 0);
320
+ const swept = sweepStaleScopes(folders);
321
+ res.removed += swept.length;
322
+ res.kept += Math.max(0, scopeFindings.length - swept.length);
323
+ if (scopeFindings.length - swept.length > 0)
324
+ keptNotes.push('scopes still claimed or locked');
325
+ }
326
+ else if (report.findings.some((f) => f.kind === 'leftover-scope')) {
327
+ res.kept += report.findings.filter((f) => f.kind === 'leftover-scope').length;
328
+ keptNotes.push('live/resumable scopes');
329
+ }
330
+ }
331
+ catch (err) {
332
+ res.errors.push(`scope sweep: ${err instanceof Error ? err.message : String(err)}`);
333
+ }
334
+ res.keptReason = keptNotes.length
335
+ ? [...new Set(keptNotes)].join('; ')
336
+ : 'none';
337
+ return res;
338
+ }
339
+ //# sourceMappingURL=workspace-audit.js.map