crewly 1.20.152 → 1.20.153

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 (71) hide show
  1. package/config/roles/orchestrator/prompt.md +7 -0
  2. package/config/roles/team-leader/prompt.md +6 -0
  3. package/config/sops/developer/git-workflow.md +1 -0
  4. package/dist/backend/backend/src/constants.d.ts +92 -0
  5. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  6. package/dist/backend/backend/src/constants.js +92 -0
  7. package/dist/backend/backend/src/constants.js.map +1 -1
  8. package/dist/backend/backend/src/controllers/worktree-janitor/worktree-janitor.routes.d.ts +29 -0
  9. package/dist/backend/backend/src/controllers/worktree-janitor/worktree-janitor.routes.d.ts.map +1 -0
  10. package/dist/backend/backend/src/controllers/worktree-janitor/worktree-janitor.routes.js +63 -0
  11. package/dist/backend/backend/src/controllers/worktree-janitor/worktree-janitor.routes.js.map +1 -0
  12. package/dist/backend/backend/src/index.d.ts +7 -0
  13. package/dist/backend/backend/src/index.d.ts.map +1 -1
  14. package/dist/backend/backend/src/index.js +114 -2
  15. package/dist/backend/backend/src/index.js.map +1 -1
  16. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  17. package/dist/backend/backend/src/routes/api.routes.js +3 -0
  18. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  19. package/dist/backend/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts +18 -3
  20. package/dist/backend/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts.map +1 -1
  21. package/dist/backend/backend/src/services/ai/prompt-modules/decision-rights.module.js +28 -4
  22. package/dist/backend/backend/src/services/ai/prompt-modules/decision-rights.module.js.map +1 -1
  23. package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
  24. package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
  25. package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +1 -0
  26. package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
  27. package/dist/backend/backend/src/services/cloud/waiting-actions-inbound.service.d.ts +128 -0
  28. package/dist/backend/backend/src/services/cloud/waiting-actions-inbound.service.d.ts.map +1 -0
  29. package/dist/backend/backend/src/services/cloud/waiting-actions-inbound.service.js +279 -0
  30. package/dist/backend/backend/src/services/cloud/waiting-actions-inbound.service.js.map +1 -0
  31. package/dist/backend/backend/src/services/cloud/waiting-items-sync.service.d.ts +122 -0
  32. package/dist/backend/backend/src/services/cloud/waiting-items-sync.service.d.ts.map +1 -0
  33. package/dist/backend/backend/src/services/cloud/waiting-items-sync.service.js +261 -0
  34. package/dist/backend/backend/src/services/cloud/waiting-items-sync.service.js.map +1 -0
  35. package/dist/backend/backend/src/services/cloud/waiting-items.contract.d.ts +101 -0
  36. package/dist/backend/backend/src/services/cloud/waiting-items.contract.d.ts.map +1 -0
  37. package/dist/backend/backend/src/services/cloud/waiting-items.contract.js +113 -0
  38. package/dist/backend/backend/src/services/cloud/waiting-items.contract.js.map +1 -0
  39. package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
  40. package/dist/backend/backend/src/services/slack/slack-initializer.js +2 -1
  41. package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
  42. package/dist/backend/backend/src/services/v3/request.service.d.ts +19 -0
  43. package/dist/backend/backend/src/services/v3/request.service.d.ts.map +1 -1
  44. package/dist/backend/backend/src/services/v3/request.service.js +36 -0
  45. package/dist/backend/backend/src/services/v3/request.service.js.map +1 -1
  46. package/dist/backend/backend/src/services/worktree/worktree-janitor.git.d.ts +131 -0
  47. package/dist/backend/backend/src/services/worktree/worktree-janitor.git.d.ts.map +1 -0
  48. package/dist/backend/backend/src/services/worktree/worktree-janitor.git.js +238 -0
  49. package/dist/backend/backend/src/services/worktree/worktree-janitor.git.js.map +1 -0
  50. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts +241 -0
  51. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts.map +1 -0
  52. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js +504 -0
  53. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js.map +1 -0
  54. package/dist/backend/build-info.json +2 -2
  55. package/dist/cli/backend/src/constants.d.ts +92 -0
  56. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  57. package/dist/cli/backend/src/constants.js +92 -0
  58. package/dist/cli/backend/src/constants.js.map +1 -1
  59. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts +18 -3
  60. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts.map +1 -1
  61. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js +28 -4
  62. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js.map +1 -1
  63. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts +1 -1
  64. package/dist/cli/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
  65. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js +1 -0
  66. package/dist/cli/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
  67. package/dist/cli/backend/src/services/v3/request.service.d.ts +19 -0
  68. package/dist/cli/backend/src/services/v3/request.service.d.ts.map +1 -1
  69. package/dist/cli/backend/src/services/v3/request.service.js +36 -0
  70. package/dist/cli/backend/src/services/v3/request.service.js.map +1 -1
  71. package/package.json +1 -1
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Worktree janitor — low-level helpers.
3
+ *
4
+ * A command runner that never throws, a parser for `git worktree list
5
+ * --porcelain`, path helpers, and the process-cwd probe used to tell whether
6
+ * anything is still working inside a worktree. Kept apart from the service so
7
+ * each piece can be tested on its own.
8
+ *
9
+ * @module services/worktree/worktree-janitor.git
10
+ */
11
+ import { execFile } from 'child_process';
12
+ import * as fs from 'fs';
13
+ import * as os from 'os';
14
+ import * as path from 'path';
15
+ import { WORKTREE_JANITOR_CONSTANTS } from '../../constants.js';
16
+ /**
17
+ * Run a command without a shell and never throw.
18
+ *
19
+ * Git and gh are told never to prompt (no credential dialogs, no pager), so a
20
+ * periodic background job cannot hang on input.
21
+ *
22
+ * @param file - Executable (e.g. `git`, `gh`, or an absolute path)
23
+ * @param args - Arguments
24
+ * @param options - cwd / timeout / env
25
+ * @returns Exit code and output; `code` is null on spawn failure or timeout
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const r = await runCommand('git', ['-C', repo, 'status', '--porcelain']);
30
+ * if (r.code === 0 && r.stdout === '') console.log('clean');
31
+ * ```
32
+ */
33
+ export function runCommand(file, args, options = {}) {
34
+ return new Promise((resolve) => {
35
+ try {
36
+ execFile(file, args, {
37
+ cwd: options.cwd,
38
+ timeout: options.timeoutMs ?? WORKTREE_JANITOR_CONSTANTS.GIT_TIMEOUT_MS,
39
+ maxBuffer: WORKTREE_JANITOR_CONSTANTS.MAX_OUTPUT_BYTES,
40
+ env: {
41
+ ...process.env,
42
+ GIT_TERMINAL_PROMPT: '0',
43
+ GIT_PAGER: 'cat',
44
+ GH_PROMPT_DISABLED: '1',
45
+ GH_NO_UPDATE_NOTIFIER: '1',
46
+ ...(options.env ?? {}),
47
+ },
48
+ }, (error, stdout, stderr) => {
49
+ const out = typeof stdout === 'string' ? stdout : String(stdout ?? '');
50
+ const err = typeof stderr === 'string' ? stderr : String(stderr ?? '');
51
+ if (!error) {
52
+ resolve({ code: 0, stdout: out, stderr: err });
53
+ return;
54
+ }
55
+ const errCode = error.code;
56
+ const code = typeof errCode === 'number' && !error.killed ? errCode : null;
57
+ resolve({ code, stdout: out, stderr: err || error.message });
58
+ });
59
+ }
60
+ catch (spawnErr) {
61
+ resolve({ code: null, stdout: '', stderr: spawnErr instanceof Error ? spawnErr.message : String(spawnErr) });
62
+ }
63
+ });
64
+ }
65
+ /**
66
+ * Parse `git worktree list --porcelain` output.
67
+ *
68
+ * Entries are separated by blank lines; the first entry is the main worktree.
69
+ *
70
+ * @param output - Raw porcelain output
71
+ * @returns Parsed entries in git's order
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * parsePorcelainWorktrees('worktree /r\nHEAD abc\nbranch refs/heads/main\n');
76
+ * // [{ path: '/r', head: 'abc', branch: 'main', isMain: true, ... }]
77
+ * ```
78
+ */
79
+ export function parsePorcelainWorktrees(output) {
80
+ const result = [];
81
+ let current = null;
82
+ for (const rawLine of output.split('\n')) {
83
+ const line = rawLine.replace(/\r$/, '');
84
+ if (line.startsWith('worktree ')) {
85
+ current = {
86
+ path: line.slice('worktree '.length),
87
+ head: null,
88
+ branch: null,
89
+ bare: false,
90
+ detached: false,
91
+ locked: false,
92
+ prunable: false,
93
+ isMain: result.length === 0,
94
+ };
95
+ result.push(current);
96
+ continue;
97
+ }
98
+ if (!current || line === '')
99
+ continue;
100
+ if (line.startsWith('HEAD '))
101
+ current.head = line.slice('HEAD '.length);
102
+ else if (line.startsWith('branch '))
103
+ current.branch = line.slice('branch '.length).replace(/^refs\/heads\//, '');
104
+ else if (line === 'bare')
105
+ current.bare = true;
106
+ else if (line === 'detached')
107
+ current.detached = true;
108
+ else if (line === 'locked' || line.startsWith('locked ')) {
109
+ current.locked = true;
110
+ const reason = line.slice('locked'.length).trim();
111
+ if (reason)
112
+ current.lockReason = reason;
113
+ }
114
+ else if (line === 'prunable' || line.startsWith('prunable '))
115
+ current.prunable = true;
116
+ }
117
+ return result;
118
+ }
119
+ /**
120
+ * Real path of `p`, or its resolved form when it does not exist.
121
+ *
122
+ * @param p - Path
123
+ * @returns Canonical path for comparisons
124
+ */
125
+ export function canonicalPath(p) {
126
+ try {
127
+ return fs.realpathSync(p);
128
+ }
129
+ catch {
130
+ return path.resolve(p);
131
+ }
132
+ }
133
+ /**
134
+ * Whether `child` is `parent` or inside it (both canonicalised first).
135
+ *
136
+ * @param child - Candidate path
137
+ * @param parent - Directory
138
+ * @returns True when child === parent or child is under parent
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * isPathInside('/r/.claude/worktrees/a/src', '/r/.claude/worktrees/a'); // true
143
+ * isPathInside('/r/.claude/worktrees/ab', '/r/.claude/worktrees/a'); // false
144
+ * ```
145
+ */
146
+ export function isPathInside(child, parent) {
147
+ const c = canonicalPath(child);
148
+ const p = canonicalPath(parent);
149
+ if (c === p)
150
+ return true;
151
+ const withSep = p.endsWith(path.sep) ? p : p + path.sep;
152
+ return c.startsWith(withSep);
153
+ }
154
+ /**
155
+ * Latest modification time (ms) of a worktree: the directory itself plus its
156
+ * private git dir's `index`, `HEAD` and `logs/HEAD` — every commit, checkout,
157
+ * stage or reset touches one of those. Uncommitted edits are caught by the
158
+ * dirty check instead.
159
+ *
160
+ * @param worktreePath - Worktree root
161
+ * @param gitDir - Its private git dir (`.git/worktrees/<name>`), when known
162
+ * @returns Newest mtime in ms, or null when nothing could be stat'ed
163
+ */
164
+ export function latestWorktreeMtimeMs(worktreePath, gitDir) {
165
+ const candidates = [worktreePath, path.join(worktreePath, '.git')];
166
+ if (gitDir) {
167
+ candidates.push(gitDir, path.join(gitDir, 'index'), path.join(gitDir, 'HEAD'), path.join(gitDir, 'logs', 'HEAD'));
168
+ }
169
+ let newest = null;
170
+ for (const c of candidates) {
171
+ try {
172
+ const m = fs.lstatSync(c).mtimeMs;
173
+ if (newest === null || m > newest)
174
+ newest = m;
175
+ }
176
+ catch {
177
+ // missing — ignore
178
+ }
179
+ }
180
+ return newest;
181
+ }
182
+ /**
183
+ * Current working directories of every process this user can see.
184
+ *
185
+ * macOS: `lsof -a -d cwd -u <uid> -Fn`. Linux: `/proc/<pid>/cwd`. Returns
186
+ * null when the probe cannot run, so the caller can treat "unknown" as busy.
187
+ *
188
+ * @returns Absolute cwd paths (deduplicated), or null when unavailable
189
+ */
190
+ export async function listProcessCwds() {
191
+ if (process.platform === 'linux') {
192
+ try {
193
+ const out = new Set();
194
+ for (const entry of fs.readdirSync('/proc')) {
195
+ if (!/^\d+$/.test(entry))
196
+ continue;
197
+ try {
198
+ out.add(fs.readlinkSync(path.join('/proc', entry, 'cwd')));
199
+ }
200
+ catch {
201
+ // other user's process or already gone
202
+ }
203
+ }
204
+ return [...out];
205
+ }
206
+ catch {
207
+ return null;
208
+ }
209
+ }
210
+ if (process.platform === 'win32')
211
+ return null;
212
+ const uid = typeof process.getuid === 'function' ? String(process.getuid()) : String(os.userInfo().uid);
213
+ const r = await runCommand('lsof', ['-a', '-d', 'cwd', '-u', uid, '-Fn', '-w'], {
214
+ timeoutMs: WORKTREE_JANITOR_CONSTANTS.CWD_PROBE_TIMEOUT_MS,
215
+ });
216
+ // lsof exits 1 when some listed item had no match; the output is still valid.
217
+ if (r.code === null || !r.stdout)
218
+ return null;
219
+ const out = new Set();
220
+ for (const line of r.stdout.split('\n')) {
221
+ if (line.startsWith('n/'))
222
+ out.add(line.slice(1));
223
+ }
224
+ return out.size > 0 ? [...out] : null;
225
+ }
226
+ /**
227
+ * Whether the kill switch disables the janitor.
228
+ *
229
+ * @param env - Environment (defaults to process.env)
230
+ * @returns True when `CREWLY_WORKTREE_JANITOR` is `0`/`off`/`false`/`no`
231
+ */
232
+ export function isJanitorDisabled(env = process.env) {
233
+ const raw = env[WORKTREE_JANITOR_CONSTANTS.ENV_VAR];
234
+ if (raw === undefined)
235
+ return false;
236
+ return WORKTREE_JANITOR_CONSTANTS.DISABLED_VALUES.includes(raw.trim().toLowerCase());
237
+ }
238
+ //# sourceMappingURL=worktree-janitor.git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree-janitor.git.js","sourceRoot":"","sources":["../../../../../../backend/src/services/worktree/worktree-janitor.git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAsBhE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,IAAc,EAAE,UAA6B,EAAE;IACvF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,IAAI,CAAC;YACJ,QAAQ,CACP,IAAI,EACJ,IAAI,EACJ;gBACC,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,OAAO,EAAE,OAAO,CAAC,SAAS,IAAI,0BAA0B,CAAC,cAAc;gBACvE,SAAS,EAAE,0BAA0B,CAAC,gBAAgB;gBACtD,GAAG,EAAE;oBACJ,GAAG,OAAO,CAAC,GAAG;oBACd,mBAAmB,EAAE,GAAG;oBACxB,SAAS,EAAE,KAAK;oBAChB,kBAAkB,EAAE,GAAG;oBACvB,qBAAqB,EAAE,GAAG;oBAC1B,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC;iBACtB;aACD,EACD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;gBACzB,MAAM,GAAG,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;gBACvE,MAAM,GAAG,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;gBACvE,IAAI,CAAC,KAAK,EAAE,CAAC;oBACZ,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;oBAC/C,OAAO;gBACR,CAAC;gBACD,MAAM,OAAO,GAAI,KAAoD,CAAC,IAAI,CAAC;gBAC3E,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAE,KAA8B,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;gBACrG,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9D,CAAC,CACD,CAAC;QACH,CAAC;QAAC,OAAO,QAAQ,EAAE,CAAC;YACnB,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,YAAY,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC9G,CAAC;IACF,CAAC,CAAC,CAAC;AACJ,CAAC;AAwBD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACrD,MAAM,MAAM,GAAwB,EAAE,CAAC;IACvC,IAAI,OAAO,GAA6B,IAAI,CAAC;IAC7C,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACxC,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,OAAO,GAAG;gBACT,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC;gBACpC,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,IAAI;gBACZ,IAAI,EAAE,KAAK;gBACX,QAAQ,EAAE,KAAK;gBACf,MAAM,EAAE,KAAK;gBACb,QAAQ,EAAE,KAAK;gBACf,MAAM,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC;aAC3B,CAAC;YACF,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrB,SAAS;QACV,CAAC;QACD,IAAI,CAAC,OAAO,IAAI,IAAI,KAAK,EAAE;YAAE,SAAS;QACtC,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;aACnE,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;aAC5G,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;aACzC,IAAI,IAAI,KAAK,UAAU;YAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;aACjD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1D,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;YACtB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YAClD,IAAI,MAAM;gBAAE,OAAO,CAAC,UAAU,GAAG,MAAM,CAAC;QACzC,CAAC;aAAM,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;YAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IACzF,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS;IACtC,IAAI,CAAC;QACJ,OAAO,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,MAAc;IACzD,MAAM,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,OAAO,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;IACxD,OAAO,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,YAAoB,EAAE,MAAqB;IAChF,MAAM,UAAU,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IACnE,IAAI,MAAM,EAAE,CAAC;QACZ,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnH,CAAC;IACD,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC5B,IAAI,CAAC;YACJ,MAAM,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YAClC,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,MAAM;gBAAE,MAAM,GAAG,CAAC,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACR,mBAAmB;QACpB,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACpC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClC,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;YAC9B,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC7C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;oBAAE,SAAS;gBACnC,IAAI,CAAC;oBACJ,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC5D,CAAC;gBAAC,MAAM,CAAC;oBACR,uCAAuC;gBACxC,CAAC;YACF,CAAC;YACD,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;QACjB,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,GAAG,GAAG,OAAO,OAAO,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IACxG,MAAM,CAAC,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE;QAC/E,SAAS,EAAE,0BAA0B,CAAC,oBAAoB;KAC1D,CAAC,CAAC;IACH,8EAA8E;IAC9E,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACrE,MAAM,GAAG,GAAG,GAAG,CAAC,0BAA0B,CAAC,OAAO,CAAC,CAAC;IACpD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACpC,OAAO,0BAA0B,CAAC,eAAe,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AACtF,CAAC"}
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Worktree janitor — removes git worktrees whose work has landed.
3
+ *
4
+ * Agents create a worktree per code task (`<repo>/.claude/worktrees/<name>`,
5
+ * `/tmp/crewly-worktrees/<session>-<slug>`) and never delete it; a busy Mac
6
+ * collected 30+ of them, tens of GB. Owner decision: once the PR is merged the
7
+ * worktree goes away by itself.
8
+ *
9
+ * ## Which repos
10
+ * Every registered project path (projects store) that is inside a git repo,
11
+ * plus the Crewly package root when it is a dev checkout. Repos are
12
+ * de-duplicated by their main worktree.
13
+ *
14
+ * ## Removal rules — a worktree is removed only when ALL hold
15
+ * 1. It is not the main worktree, not bare, and not `prunable` (a missing
16
+ * directory is left to `git worktree prune`).
17
+ * 2. It is not locked (`git worktree lock`; Claude Code locks the worktree of
18
+ * a running subagent).
19
+ * 3. It is not under `<repo>/.crewly/worktrees/` — that directory belongs to
20
+ * the per-WorkItem worktree feature (#814), which does its own
21
+ * WorkItem-aware cleanup.
22
+ * 4. It is an agent worktree: its path is under `<main>/.claude/worktrees/`
23
+ * or `<tmp>/crewly-worktrees/` (`/tmp` and `os.tmpdir()`), OR its branch
24
+ * starts with an agent-only prefix (`worktree-agent-`, Claude Code's
25
+ * automatic subagent branch). Human-made worktrees such as
26
+ * `../crewly-wt-805` on `fix/...` are never touched. Deliberately narrow:
27
+ * a false "keep" costs disk, a false "remove" costs work.
28
+ * 5. Nothing is working in it: no Crewly agent session was started in it and
29
+ * no process of this user (PTY shells, runtimes, editors, test runners)
30
+ * has its cwd inside it. If the process probe fails, everything is kept.
31
+ * 6. It was last touched more than 2 hours ago (newest mtime of the
32
+ * directory and its git dir's index / HEAD / logs/HEAD).
33
+ * 7. `git status --porcelain --untracked-files=all` is empty: no tracked
34
+ * change and no untracked file. Ignored files (node_modules, dist) do not
35
+ * count. A failing status keeps it.
36
+ * 8. Its work has landed: HEAD is an ancestor of `origin/<default branch>`
37
+ * after a quiet `git fetch` (fetch errors ignored), OR — when `gh` is
38
+ * installed and authenticated — a MERGED PR has this branch as head AND
39
+ * its head commit equals the worktree HEAD (squash merges; commits made
40
+ * after the merge keep the worktree).
41
+ *
42
+ * ## Removal
43
+ * `git worktree remove <path>` without `--force`. If git refuses although
44
+ * the status is still clean (only ignored output left) and the worktree has
45
+ * no submodules, it is retried once with `--force`. Then the local branch is
46
+ * deleted (never a protected name), and `git worktree prune` runs per repo.
47
+ *
48
+ * Runs every 30 minutes (first run 10 minutes after boot). Kill switch:
49
+ * `CREWLY_WORKTREE_JANITOR=0`. Never throws; logs one summary line per run.
50
+ *
51
+ * @module services/worktree/worktree-janitor.service
52
+ */
53
+ /** Why a worktree is kept, or why it may go. */
54
+ export type WorktreeReason = 'main-worktree' | 'bare' | 'prunable' | 'locked' | 'managed-by-workitem-worktrees' | 'not-agent-worktree' | 'agent-session-inside' | 'process-inside' | 'cwd-probe-failed' | 'recent' | 'dirty' | 'status-failed' | 'not-merged' | 'merged' | 'pr-merged';
55
+ /** Verdict for one worktree (the dry-run output). */
56
+ export interface WorktreeVerdict {
57
+ /** Main worktree of the repo this worktree belongs to */
58
+ repo: string;
59
+ /** Worktree path */
60
+ path: string;
61
+ /** Branch, or null when detached */
62
+ branch: string | null;
63
+ /** HEAD commit */
64
+ head: string | null;
65
+ /** What the janitor would do */
66
+ decision: 'remove' | 'keep';
67
+ /** The deciding rule */
68
+ reason: WorktreeReason;
69
+ /** Human-readable detail (lock reason, dirty file count, PR number, …) */
70
+ detail?: string;
71
+ /** Ms since last touched, when computed */
72
+ idleMs?: number;
73
+ }
74
+ /** Outcome of acting on one verdict. */
75
+ export interface WorktreeOutcome extends WorktreeVerdict {
76
+ /** True when the worktree directory was removed */
77
+ removed: boolean;
78
+ /** True when `--force` was needed (only ignored files were in the way) */
79
+ forced: boolean;
80
+ /** True when the local branch was deleted */
81
+ branchDeleted: boolean;
82
+ /** Error text when removal failed (the worktree is then counted as kept) */
83
+ error?: string;
84
+ }
85
+ /** Result of one janitor pass. */
86
+ export interface JanitorRunSummary {
87
+ /** ISO start time */
88
+ startedAt: string;
89
+ /** Duration in ms */
90
+ durationMs: number;
91
+ /** True when the kill switch stopped the pass */
92
+ disabled: boolean;
93
+ /** True when this was a dry run (nothing removed) */
94
+ dryRun: boolean;
95
+ /** Repos (main worktrees) examined */
96
+ repos: string[];
97
+ /** Worktrees removed */
98
+ removed: number;
99
+ /** Dry run only: worktrees a real pass would remove */
100
+ wouldRemove: number;
101
+ /** Worktrees kept */
102
+ kept: number;
103
+ /** Kept worktrees by reason (removal failures under `remove-failed`) */
104
+ keptReasons: Record<string, number>;
105
+ /** Per-worktree results */
106
+ worktrees: WorktreeOutcome[];
107
+ }
108
+ /** A place a live process or agent is working in. */
109
+ export interface BusyCwd {
110
+ /** Absolute directory */
111
+ cwd: string;
112
+ /** `agent-session` for Crewly sessions, `process` for anything else */
113
+ source: 'agent-session' | 'process';
114
+ }
115
+ /** Minimal logger surface (a ComponentLogger fits). */
116
+ export interface JanitorLogger {
117
+ info(message: string, context?: Record<string, unknown>): void;
118
+ warn(message: string, context?: Record<string, unknown>): void;
119
+ }
120
+ /** Injectable dependencies (defaults talk to the real backend). */
121
+ export interface WorktreeJanitorOptions {
122
+ /** Candidate repo/project paths (default: projects store + dev checkout) */
123
+ listRepoPaths?: () => Promise<string[]>;
124
+ /** Where Crewly agents and processes are working; null = probe failed (default: sessions + lsof) */
125
+ listBusyCwds?: () => Promise<BusyCwd[] | null>;
126
+ /** git executable (default `git`) */
127
+ gitBin?: string;
128
+ /** gh executable (default `gh`); set to null to never use gh */
129
+ ghBin?: string | null;
130
+ /** Clock (default Date.now) */
131
+ now?: () => number;
132
+ /** Minimum idle time before removal (default 2 h) */
133
+ minIdleMs?: number;
134
+ /** Temp roots whose `crewly-worktrees/` holds agent worktrees (default `/tmp` + os.tmpdir()) */
135
+ tmpRoots?: string[];
136
+ /** Environment for the kill switch (default process.env) */
137
+ env?: NodeJS.ProcessEnv;
138
+ /** Logger (default: component logger `WorktreeJanitor`) */
139
+ logger?: JanitorLogger;
140
+ }
141
+ /**
142
+ * Periodic cleaner of finished agent worktrees. See the module JSDoc for the
143
+ * exact rules.
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * const janitor = WorktreeJanitorService.getInstance();
148
+ * janitor.start(); // first pass in 10 min, then every 30 min
149
+ * const plan = await janitor.plan(); // dry run: verdict per worktree
150
+ * const summary = await janitor.run(); // one real pass now
151
+ * ```
152
+ */
153
+ export declare class WorktreeJanitorService {
154
+ private static instance;
155
+ private readonly opts;
156
+ private firstRunTimer;
157
+ private intervalTimer;
158
+ private inFlight;
159
+ private ghUsable;
160
+ private lastSummary;
161
+ private loggerInstance;
162
+ /**
163
+ * @param options - Injectable dependencies; defaults use the live backend
164
+ */
165
+ constructor(options?: WorktreeJanitorOptions);
166
+ /**
167
+ * Process-wide instance used by the server and the HTTP routes.
168
+ *
169
+ * @returns The singleton
170
+ */
171
+ static getInstance(): WorktreeJanitorService;
172
+ /** Reset the singleton (tests). */
173
+ static resetInstance(): void;
174
+ /**
175
+ * Whether the kill switch is on.
176
+ *
177
+ * @returns True when `CREWLY_WORKTREE_JANITOR` disables the janitor
178
+ */
179
+ isDisabled(): boolean;
180
+ /**
181
+ * Schedule the periodic pass: first after FIRST_RUN_DELAY_MS, then every
182
+ * INTERVAL_MS. No-op when disabled or already started. Timers are unref'd.
183
+ *
184
+ * @returns True when scheduled
185
+ */
186
+ start(): boolean;
187
+ /** Cancel scheduled passes. */
188
+ stop(): void;
189
+ /**
190
+ * Summary of the last completed real pass, if any.
191
+ *
192
+ * @returns Last summary or null
193
+ */
194
+ getLastSummary(): JanitorRunSummary | null;
195
+ /**
196
+ * Dry run: the verdict for every worktree of every known repo. Removes
197
+ * nothing (it may `git fetch`).
198
+ *
199
+ * @returns Summary with per-worktree verdicts
200
+ */
201
+ plan(): Promise<JanitorRunSummary>;
202
+ /**
203
+ * One real pass. Concurrent calls share the pass in flight. Never throws.
204
+ * Honours the kill switch.
205
+ *
206
+ * @returns Summary of what was removed and kept
207
+ */
208
+ run(): Promise<JanitorRunSummary>;
209
+ private get logger();
210
+ private git;
211
+ private pass;
212
+ private resolveRepos;
213
+ private safeBusyCwds;
214
+ private isAgentWorktree;
215
+ /**
216
+ * Apply the removal rules to one worktree (read-only apart from fetch).
217
+ */
218
+ private evaluate;
219
+ /** `origin/<default>` after one quiet fetch per repo per pass; null when there is no origin. */
220
+ private defaultRef;
221
+ /** Number of a MERGED PR whose head is `branch` at exactly `head`, or null. */
222
+ private mergedPrFor;
223
+ /** Remove one worktree whose verdict is `remove`, then its branch. Never throws. */
224
+ private remove;
225
+ }
226
+ /**
227
+ * Default repo candidates: every registered project path, plus the running
228
+ * Crewly package root when it is a git checkout.
229
+ *
230
+ * @returns Paths (may include non-git paths; those are skipped)
231
+ */
232
+ export declare function defaultListRepoPaths(): Promise<string[]>;
233
+ /**
234
+ * Default busy-cwd probe: the start directory of every live Crewly session,
235
+ * plus the cwd of every process of this user (which covers the PTY shells,
236
+ * the agent runtimes under them, and anything the owner has open).
237
+ *
238
+ * @returns Busy directories, or null when the process probe failed
239
+ */
240
+ export declare function defaultListBusyCwds(): Promise<BusyCwd[] | null>;
241
+ //# sourceMappingURL=worktree-janitor.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree-janitor.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/worktree/worktree-janitor.service.ts"],"names":[],"mappings":"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"}