ultraclaude-agent 0.0.21 → 0.0.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.
- package/__tests__/daemon-reconcile.test.ts +1 -0
- package/__tests__/daemon.test.ts +1 -0
- package/__tests__/hide-branches.test.ts +129 -0
- package/__tests__/logger-multistream.test.ts +151 -0
- package/__tests__/repl-reset.test.ts +3 -0
- package/__tests__/repl-status-account.test.ts +3 -0
- package/__tests__/repl.test.ts +7 -2
- package/__tests__/snapshot-sync.test.ts +6 -6
- package/__tests__/status-command.test.ts +479 -0
- package/__tests__/status-service-type.test.ts +177 -0
- package/__tests__/sync-bugs.test.ts +8 -7
- package/__tests__/sync-queue-credentials.test.ts +4 -4
- package/__tests__/sync-reorder.test.ts +8 -8
- package/__tests__/sync.test.ts +4 -3
- package/__tests__/version-check.test.ts +1 -1
- package/__tests__/version-watcher.test.ts +8 -2
- package/__tests__/watcher-branch.test.ts +68 -0
- package/dist/cli.js +6 -96
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +3 -70
- package/dist/daemon.js.map +1 -1
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +6 -143
- package/dist/repl.js.map +1 -1
- package/dist/sync.d.ts +13 -8
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +45 -21
- package/dist/sync.js.map +1 -1
- package/dist/watcher.d.ts +6 -0
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +92 -7
- package/dist/watcher.js.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.d.ts +11 -0
- package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.d.ts.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.js +8 -0
- package/node_modules/@ultra-claude/shared/dist/api/schemas/projects.js.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/api/schemas/sync.d.ts +11 -0
- package/node_modules/@ultra-claude/shared/dist/api/schemas/sync.d.ts.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/api/schemas/sync.js +11 -0
- package/node_modules/@ultra-claude/shared/dist/api/schemas/sync.js.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/index.d.ts +3 -3
- package/node_modules/@ultra-claude/shared/dist/index.d.ts.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/index.js +2 -2
- package/node_modules/@ultra-claude/shared/dist/index.js.map +1 -1
- package/node_modules/@ultra-claude/shared/dist/types.d.ts +0 -32
- package/node_modules/@ultra-claude/shared/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +6 -120
- package/src/daemon.ts +3 -82
- package/src/repl.ts +6 -166
- package/src/sync.ts +56 -14
- package/src/watcher.ts +101 -7
- package/__tests__/claude-profiles-ops.test.ts +0 -441
- package/__tests__/claude-profiles.test.ts +0 -407
- package/__tests__/credential-watcher.test.ts +0 -229
- package/dist/claude-profiles.d.ts +0 -83
- package/dist/claude-profiles.d.ts.map +0 -1
- package/dist/claude-profiles.js +0 -499
- package/dist/claude-profiles.js.map +0 -1
- package/src/claude-profiles.ts +0 -597
package/src/sync.ts
CHANGED
|
@@ -107,10 +107,11 @@ export async function apiRequestWithDiskCredentials(
|
|
|
107
107
|
|
|
108
108
|
type ManifestError = 'NOT_LOGGED_IN' | 'NETWORK_ERROR' | 'MANIFEST_FETCH_FAILED' | 'PROJECT_NOT_FOUND';
|
|
109
109
|
|
|
110
|
-
export async function fetchManifest(projectId: string, credentials: AgentCredentials): Promise<Result<ManifestEntry[], ManifestError>> {
|
|
110
|
+
export async function fetchManifest(projectId: string, credentials: AgentCredentials, branch?: string): Promise<Result<ManifestEntry[], ManifestError>> {
|
|
111
111
|
const log = logger.child({ projectId, op: 'fetchManifest' });
|
|
112
112
|
|
|
113
|
-
const
|
|
113
|
+
const branchParam = branch ? `&branch=${encodeURIComponent(branch)}` : '';
|
|
114
|
+
const result = await apiRequest('GET', `/api/sync/manifest?projectId=${projectId}${branchParam}`, undefined, credentials);
|
|
114
115
|
if (!result.success) return result;
|
|
115
116
|
|
|
116
117
|
if (result.data.status === 404) {
|
|
@@ -142,9 +143,9 @@ export async function fetchManifest(projectId: string, credentials: AgentCredent
|
|
|
142
143
|
/**
|
|
143
144
|
* Load manifest from server and populate local section hash cache.
|
|
144
145
|
*/
|
|
145
|
-
export async function loadManifestIntoCache(projectId: string, credentials: AgentCredentials): Promise<void> {
|
|
146
|
+
export async function loadManifestIntoCache(projectId: string, credentials: AgentCredentials, branch?: string): Promise<void> {
|
|
146
147
|
const state = getOrCreateState(projectId);
|
|
147
|
-
const result = await fetchManifest(projectId, credentials);
|
|
148
|
+
const result = await fetchManifest(projectId, credentials, branch);
|
|
148
149
|
|
|
149
150
|
if (!result.success) {
|
|
150
151
|
logger.warn({ projectId, error: result.error, message: result.message }, 'Could not load manifest — will push all sections');
|
|
@@ -172,6 +173,7 @@ export async function syncMarkdownFile(
|
|
|
172
173
|
projectPath: string,
|
|
173
174
|
absoluteFilePath: string,
|
|
174
175
|
credentials: AgentCredentials,
|
|
176
|
+
branch: string,
|
|
175
177
|
): Promise<number> {
|
|
176
178
|
const log = logger.child({ projectId, file: absoluteFilePath });
|
|
177
179
|
|
|
@@ -233,6 +235,7 @@ export async function syncMarkdownFile(
|
|
|
233
235
|
|
|
234
236
|
const payload = {
|
|
235
237
|
projectId,
|
|
238
|
+
branch,
|
|
236
239
|
file: relativePath,
|
|
237
240
|
file_hash: fileHash,
|
|
238
241
|
sections: sectionsToSend.map((s) => ({
|
|
@@ -279,6 +282,7 @@ export async function syncJsonFile(
|
|
|
279
282
|
projectPath: string,
|
|
280
283
|
absoluteFilePath: string,
|
|
281
284
|
credentials: AgentCredentials,
|
|
285
|
+
branch: string,
|
|
282
286
|
): Promise<boolean> {
|
|
283
287
|
const log = logger.child({ projectId, file: absoluteFilePath });
|
|
284
288
|
|
|
@@ -301,6 +305,7 @@ export async function syncJsonFile(
|
|
|
301
305
|
|
|
302
306
|
const payload = {
|
|
303
307
|
projectId,
|
|
308
|
+
branch,
|
|
304
309
|
files: [
|
|
305
310
|
{
|
|
306
311
|
path: relativePath,
|
|
@@ -336,11 +341,12 @@ export async function syncFile(
|
|
|
336
341
|
projectPath: string,
|
|
337
342
|
absoluteFilePath: string,
|
|
338
343
|
credentials: AgentCredentials,
|
|
344
|
+
branch: string,
|
|
339
345
|
): Promise<void> {
|
|
340
346
|
if (absoluteFilePath.endsWith('.md')) {
|
|
341
|
-
await syncMarkdownFile(projectId, projectPath, absoluteFilePath, credentials);
|
|
347
|
+
await syncMarkdownFile(projectId, projectPath, absoluteFilePath, credentials, branch);
|
|
342
348
|
} else if (absoluteFilePath.endsWith('.json')) {
|
|
343
|
-
await syncJsonFile(projectId, projectPath, absoluteFilePath, credentials);
|
|
349
|
+
await syncJsonFile(projectId, projectPath, absoluteFilePath, credentials, branch);
|
|
344
350
|
}
|
|
345
351
|
}
|
|
346
352
|
|
|
@@ -426,6 +432,7 @@ export async function createProjectOnServer(
|
|
|
426
432
|
export async function createSnapshot(
|
|
427
433
|
projectId: string,
|
|
428
434
|
name: string,
|
|
435
|
+
branch: string,
|
|
429
436
|
commitHash?: string,
|
|
430
437
|
trigger: string = 'manual',
|
|
431
438
|
credentials?: AgentCredentials,
|
|
@@ -433,11 +440,13 @@ export async function createSnapshot(
|
|
|
433
440
|
const log = logger.child({ projectId, op: 'snapshot' });
|
|
434
441
|
const idempotencyKey = crypto.randomUUID();
|
|
435
442
|
|
|
443
|
+
const body = { projectId, name, branch, commitHash, trigger };
|
|
444
|
+
|
|
436
445
|
const requestFn = credentials
|
|
437
|
-
? apiRequest('POST', '/api/sync/snapshot',
|
|
446
|
+
? apiRequest('POST', '/api/sync/snapshot', body, credentials, {
|
|
438
447
|
'Idempotency-Key': idempotencyKey,
|
|
439
448
|
})
|
|
440
|
-
: apiRequestWithDiskCredentials('POST', '/api/sync/snapshot',
|
|
449
|
+
: apiRequestWithDiskCredentials('POST', '/api/sync/snapshot', body, {
|
|
441
450
|
'Idempotency-Key': idempotencyKey,
|
|
442
451
|
});
|
|
443
452
|
const result = await requestFn;
|
|
@@ -467,13 +476,14 @@ export async function deleteFiles(
|
|
|
467
476
|
projectPath: string,
|
|
468
477
|
absoluteFilePaths: string[],
|
|
469
478
|
credentials: AgentCredentials,
|
|
479
|
+
branch: string,
|
|
470
480
|
): Promise<boolean> {
|
|
471
481
|
const log = logger.child({ projectId, op: 'deleteFiles' });
|
|
472
482
|
const docDir = join(projectPath, 'documentation');
|
|
473
483
|
|
|
474
484
|
const relativePaths = absoluteFilePaths.map((p) => relative(docDir, p));
|
|
475
485
|
|
|
476
|
-
const payload = { projectId, files: relativePaths };
|
|
486
|
+
const payload = { projectId, branch, files: relativePaths };
|
|
477
487
|
const idempotencyKey = crypto.randomUUID();
|
|
478
488
|
|
|
479
489
|
const result = await apiRequest('POST', '/api/sync/delete-files', payload, credentials, {
|
|
@@ -513,10 +523,11 @@ async function reconcile(
|
|
|
513
523
|
projectId: string,
|
|
514
524
|
activePaths: string[],
|
|
515
525
|
credentials: AgentCredentials,
|
|
526
|
+
branch: string,
|
|
516
527
|
): Promise<void> {
|
|
517
528
|
const log = logger.child({ projectId, op: 'reconcile' });
|
|
518
529
|
|
|
519
|
-
const payload = { projectId, filePaths: activePaths };
|
|
530
|
+
const payload = { projectId, branch, filePaths: activePaths };
|
|
520
531
|
const idempotencyKey = crypto.randomUUID();
|
|
521
532
|
|
|
522
533
|
const result = await apiRequest('POST', '/api/sync/reconcile', payload, credentials, {
|
|
@@ -541,17 +552,48 @@ async function reconcile(
|
|
|
541
552
|
}
|
|
542
553
|
}
|
|
543
554
|
|
|
555
|
+
// --- Branch hide ---
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Notify the server that branches have been deleted locally.
|
|
559
|
+
* Server marks them as hidden (not deleted — they can be revealed in the dashboard).
|
|
560
|
+
*/
|
|
561
|
+
export async function hideBranches(
|
|
562
|
+
projectId: string,
|
|
563
|
+
branches: string[],
|
|
564
|
+
credentials: AgentCredentials,
|
|
565
|
+
): Promise<boolean> {
|
|
566
|
+
const log = logger.child({ projectId, op: 'hideBranches' });
|
|
567
|
+
|
|
568
|
+
const payload = { projectId, branches };
|
|
569
|
+
const idempotencyKey = crypto.randomUUID();
|
|
570
|
+
|
|
571
|
+
const result = await apiRequest('POST', '/api/sync/branch-hide', payload, credentials, {
|
|
572
|
+
'Idempotency-Key': idempotencyKey,
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
if (!result.success || !result.data.ok) {
|
|
576
|
+
const reason = !result.success ? result.message : `HTTP ${result.data.status}`;
|
|
577
|
+
log.warn({ error: reason, branches }, 'Branch hide request failed');
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
log.info({ branches }, 'Branches hidden on server');
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
|
|
544
585
|
// --- Initial sync for a project ---
|
|
545
586
|
|
|
546
587
|
export async function initialSync(
|
|
547
588
|
projectId: string,
|
|
548
589
|
projectPath: string,
|
|
549
590
|
credentials: AgentCredentials,
|
|
591
|
+
branch: string,
|
|
550
592
|
): Promise<void> {
|
|
551
593
|
const log = logger.child({ projectId, op: 'initialSync' });
|
|
552
|
-
log.info('Starting initial sync');
|
|
594
|
+
log.info({ branch }, 'Starting initial sync');
|
|
553
595
|
|
|
554
|
-
await loadManifestIntoCache(projectId, credentials);
|
|
596
|
+
await loadManifestIntoCache(projectId, credentials, branch);
|
|
555
597
|
|
|
556
598
|
// Walk documentation/ and sync all files, collecting relative paths
|
|
557
599
|
const { glob } = await import('node:fs/promises');
|
|
@@ -567,11 +609,11 @@ export async function initialSync(
|
|
|
567
609
|
const relPath = relative(docDir, entry);
|
|
568
610
|
activePaths.push(relPath);
|
|
569
611
|
|
|
570
|
-
await syncFile(projectId, projectPath, entry, credentials);
|
|
612
|
+
await syncFile(projectId, projectPath, entry, credentials, branch);
|
|
571
613
|
}
|
|
572
614
|
|
|
573
615
|
// Reconcile: prune server-side data for files that no longer exist on disk
|
|
574
|
-
await reconcile(projectId, activePaths, credentials);
|
|
616
|
+
await reconcile(projectId, activePaths, credentials, branch);
|
|
575
617
|
|
|
576
618
|
log.info('Initial sync complete');
|
|
577
619
|
} catch (err) {
|
package/src/watcher.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// Watches documentation/ for changes, triggers section-level sync
|
|
3
3
|
|
|
4
4
|
import chokidar from 'chokidar';
|
|
5
|
+
import { execFile } from 'node:child_process';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
5
7
|
import { join, basename } from 'node:path';
|
|
6
|
-
import { syncFile, deleteFiles, initialSync, createSnapshot, pushVersionMetadata } from './sync.js';
|
|
8
|
+
import { syncFile, deleteFiles, initialSync, createSnapshot, pushVersionMetadata, hideBranches } from './sync.js';
|
|
7
9
|
import { logger } from './logger.js';
|
|
8
10
|
import type { AgentCredentials } from '@ultra-claude/shared';
|
|
9
11
|
|
|
@@ -22,6 +24,49 @@ export interface ProjectWatcher {
|
|
|
22
24
|
close(): Promise<void>;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Read the current branch name from .git/HEAD.
|
|
29
|
+
* Returns branch name for normal refs, "HEAD-{short-sha}" for detached HEAD,
|
|
30
|
+
* or "unknown" if .git/HEAD cannot be read.
|
|
31
|
+
*/
|
|
32
|
+
export function getCurrentBranch(projectPath: string): string {
|
|
33
|
+
const headPath = join(projectPath, '.git', 'HEAD');
|
|
34
|
+
try {
|
|
35
|
+
const content = readFileSync(headPath, 'utf8').trim();
|
|
36
|
+
if (content.startsWith('ref: refs/heads/')) {
|
|
37
|
+
return content.slice('ref: refs/heads/'.length);
|
|
38
|
+
}
|
|
39
|
+
// Detached HEAD — use short SHA
|
|
40
|
+
if (/^[0-9a-f]{40}$/.test(content)) {
|
|
41
|
+
return `HEAD-${content.slice(0, 8)}`;
|
|
42
|
+
}
|
|
43
|
+
return 'unknown';
|
|
44
|
+
} catch {
|
|
45
|
+
return 'unknown';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* List local branches using git for-each-ref (handles packed-refs).
|
|
51
|
+
* Returns a promise resolving to an array of branch names.
|
|
52
|
+
*/
|
|
53
|
+
function getLocalBranches(projectPath: string): Promise<string[]> {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
execFile(
|
|
56
|
+
'git',
|
|
57
|
+
['for-each-ref', '--format=%(refname:short)', 'refs/heads/'],
|
|
58
|
+
{ cwd: projectPath, encoding: 'utf8' },
|
|
59
|
+
(error, stdout) => {
|
|
60
|
+
if (error) {
|
|
61
|
+
resolve([]);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
resolve(stdout.trim().split('\n').filter(Boolean));
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
25
70
|
/**
|
|
26
71
|
* Start watching a project's documentation/ directory for changes.
|
|
27
72
|
* Also watches .git/HEAD for branch switches (triggers full resync).
|
|
@@ -36,6 +81,19 @@ export function startProjectWatcher(options: ProjectWatcherOptions): ProjectWatc
|
|
|
36
81
|
// Credentials are always provided by the daemon — no disk fallback needed
|
|
37
82
|
const credentialsPromise = Promise.resolve(credentials);
|
|
38
83
|
|
|
84
|
+
// Branch state — read on startup, updated on .git/HEAD change
|
|
85
|
+
let currentBranch = getCurrentBranch(projectPath);
|
|
86
|
+
log.info({ branch: currentBranch }, 'Initial branch detected');
|
|
87
|
+
|
|
88
|
+
// Track known branches for deletion detection
|
|
89
|
+
const knownBranches = new Set<string>();
|
|
90
|
+
knownBranches.add(currentBranch);
|
|
91
|
+
|
|
92
|
+
// Populate known branches from git on startup
|
|
93
|
+
getLocalBranches(projectPath).then((branches) => {
|
|
94
|
+
for (const b of branches) knownBranches.add(b);
|
|
95
|
+
}).catch(() => {});
|
|
96
|
+
|
|
39
97
|
// Documentation watcher
|
|
40
98
|
const docWatcher = chokidar.watch(docDir, {
|
|
41
99
|
ignored: (filePath: string, stats) => {
|
|
@@ -61,7 +119,7 @@ export function startProjectWatcher(options: ProjectWatcherOptions): ProjectWatc
|
|
|
61
119
|
const handleChange = (filePath: string) => {
|
|
62
120
|
log.debug({ file: filePath }, 'File changed');
|
|
63
121
|
credentialsPromise
|
|
64
|
-
.then(creds => syncFile(projectId, projectPath, filePath, creds))
|
|
122
|
+
.then(creds => syncFile(projectId, projectPath, filePath, creds, currentBranch))
|
|
65
123
|
.then(() => {
|
|
66
124
|
onPushSuccess?.(projectPath);
|
|
67
125
|
})
|
|
@@ -77,7 +135,7 @@ export function startProjectWatcher(options: ProjectWatcherOptions): ProjectWatc
|
|
|
77
135
|
.on('unlink', (filePath) => {
|
|
78
136
|
log.info({ file: filePath }, 'File deleted — notifying server');
|
|
79
137
|
credentialsPromise
|
|
80
|
-
.then(creds => deleteFiles(projectId, projectPath, [filePath], creds))
|
|
138
|
+
.then(creds => deleteFiles(projectId, projectPath, [filePath], creds, currentBranch))
|
|
81
139
|
.catch((deleteErr) => {
|
|
82
140
|
log.error({ err: deleteErr, file: filePath }, 'Delete notification failed for file');
|
|
83
141
|
});
|
|
@@ -93,9 +151,12 @@ export function startProjectWatcher(options: ProjectWatcherOptions): ProjectWatc
|
|
|
93
151
|
});
|
|
94
152
|
|
|
95
153
|
gitWatcher.on('change', () => {
|
|
96
|
-
|
|
154
|
+
const newBranch = getCurrentBranch(projectPath);
|
|
155
|
+
log.info({ oldBranch: currentBranch, newBranch }, 'Git HEAD changed — triggering full resync');
|
|
156
|
+
currentBranch = newBranch;
|
|
157
|
+
knownBranches.add(newBranch);
|
|
97
158
|
credentialsPromise
|
|
98
|
-
.then(creds => initialSync(projectId, projectPath, creds))
|
|
159
|
+
.then(creds => initialSync(projectId, projectPath, creds, currentBranch))
|
|
99
160
|
.catch((err) => {
|
|
100
161
|
log.error({ err }, 'Full resync after git HEAD change failed');
|
|
101
162
|
});
|
|
@@ -132,11 +193,11 @@ export function startProjectWatcher(options: ProjectWatcherOptions): ProjectWatc
|
|
|
132
193
|
log.info('Git commit detected — creating auto-snapshot');
|
|
133
194
|
credentialsPromise
|
|
134
195
|
.then(async (creds) => {
|
|
135
|
-
const success = await createSnapshot(projectId, `auto: commit`, undefined, 'git_commit', creds);
|
|
196
|
+
const success = await createSnapshot(projectId, `auto: commit`, currentBranch, undefined, 'git_commit', creds);
|
|
136
197
|
if (!success) {
|
|
137
198
|
log.warn('Auto-snapshot failed — retrying in 2s');
|
|
138
199
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
139
|
-
await createSnapshot(projectId, `auto: commit`, undefined, 'git_commit', creds);
|
|
200
|
+
await createSnapshot(projectId, `auto: commit`, currentBranch, undefined, 'git_commit', creds);
|
|
140
201
|
}
|
|
141
202
|
})
|
|
142
203
|
.catch((err) => {
|
|
@@ -145,11 +206,44 @@ export function startProjectWatcher(options: ProjectWatcherOptions): ProjectWatc
|
|
|
145
206
|
}
|
|
146
207
|
});
|
|
147
208
|
|
|
209
|
+
// Periodic branch deletion detection (every 5 minutes)
|
|
210
|
+
const branchCheckInterval = setInterval(() => {
|
|
211
|
+
credentialsPromise
|
|
212
|
+
.then(async (creds) => {
|
|
213
|
+
const localBranches = await getLocalBranches(projectPath);
|
|
214
|
+
const localSet = new Set(localBranches);
|
|
215
|
+
const deletedBranches: string[] = [];
|
|
216
|
+
|
|
217
|
+
for (const branch of knownBranches) {
|
|
218
|
+
// Skip detached HEAD entries and "unknown" — they aren't real branches
|
|
219
|
+
if (branch.startsWith('HEAD-') || branch === 'unknown') continue;
|
|
220
|
+
if (!localSet.has(branch)) {
|
|
221
|
+
deletedBranches.push(branch);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (deletedBranches.length > 0) {
|
|
226
|
+
log.info({ deletedBranches }, 'Detected deleted branches — notifying server');
|
|
227
|
+
const success = await hideBranches(projectId, deletedBranches, creds);
|
|
228
|
+
if (success) {
|
|
229
|
+
for (const b of deletedBranches) knownBranches.delete(b);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Update known branches from current local state
|
|
234
|
+
for (const b of localBranches) knownBranches.add(b);
|
|
235
|
+
})
|
|
236
|
+
.catch((checkErr) => {
|
|
237
|
+
log.error({ err: checkErr }, 'Branch deletion check failed');
|
|
238
|
+
});
|
|
239
|
+
}, 5 * 60 * 1000);
|
|
240
|
+
|
|
148
241
|
return {
|
|
149
242
|
projectId,
|
|
150
243
|
projectPath,
|
|
151
244
|
credentials,
|
|
152
245
|
async close() {
|
|
246
|
+
clearInterval(branchCheckInterval);
|
|
153
247
|
await Promise.all([docWatcher.close(), gitWatcher.close(), gitRefsWatcher.close(), versionWatcher.close()]);
|
|
154
248
|
log.info('Watchers closed');
|
|
155
249
|
},
|