@renseiai/agentfactory-cli 0.8.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/LICENSE +21 -0
- package/README.md +123 -0
- package/dist/src/agent.d.ts +20 -0
- package/dist/src/agent.d.ts.map +1 -0
- package/dist/src/agent.js +109 -0
- package/dist/src/analyze-logs.d.ts +26 -0
- package/dist/src/analyze-logs.d.ts.map +1 -0
- package/dist/src/analyze-logs.js +152 -0
- package/dist/src/cleanup.d.ts +17 -0
- package/dist/src/cleanup.d.ts.map +1 -0
- package/dist/src/cleanup.js +111 -0
- package/dist/src/governor.d.ts +26 -0
- package/dist/src/governor.d.ts.map +1 -0
- package/dist/src/governor.js +305 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +76 -0
- package/dist/src/lib/agent-runner.d.ts +28 -0
- package/dist/src/lib/agent-runner.d.ts.map +1 -0
- package/dist/src/lib/agent-runner.js +272 -0
- package/dist/src/lib/analyze-logs-runner.d.ts +47 -0
- package/dist/src/lib/analyze-logs-runner.d.ts.map +1 -0
- package/dist/src/lib/analyze-logs-runner.js +216 -0
- package/dist/src/lib/auto-updater.d.ts +40 -0
- package/dist/src/lib/auto-updater.d.ts.map +1 -0
- package/dist/src/lib/auto-updater.js +109 -0
- package/dist/src/lib/cleanup-runner.d.ts +29 -0
- package/dist/src/lib/cleanup-runner.d.ts.map +1 -0
- package/dist/src/lib/cleanup-runner.js +295 -0
- package/dist/src/lib/governor-dependencies.d.ts +23 -0
- package/dist/src/lib/governor-dependencies.d.ts.map +1 -0
- package/dist/src/lib/governor-dependencies.js +361 -0
- package/dist/src/lib/governor-logger.d.ts +30 -0
- package/dist/src/lib/governor-logger.d.ts.map +1 -0
- package/dist/src/lib/governor-logger.js +210 -0
- package/dist/src/lib/governor-runner.d.ts +103 -0
- package/dist/src/lib/governor-runner.d.ts.map +1 -0
- package/dist/src/lib/governor-runner.js +210 -0
- package/dist/src/lib/linear-runner.d.ts +8 -0
- package/dist/src/lib/linear-runner.d.ts.map +1 -0
- package/dist/src/lib/linear-runner.js +7 -0
- package/dist/src/lib/orchestrator-runner.d.ts +51 -0
- package/dist/src/lib/orchestrator-runner.d.ts.map +1 -0
- package/dist/src/lib/orchestrator-runner.js +151 -0
- package/dist/src/lib/queue-admin-runner.d.ts +30 -0
- package/dist/src/lib/queue-admin-runner.d.ts.map +1 -0
- package/dist/src/lib/queue-admin-runner.js +378 -0
- package/dist/src/lib/sync-routes-runner.d.ts +28 -0
- package/dist/src/lib/sync-routes-runner.d.ts.map +1 -0
- package/dist/src/lib/sync-routes-runner.js +110 -0
- package/dist/src/lib/version.d.ts +35 -0
- package/dist/src/lib/version.d.ts.map +1 -0
- package/dist/src/lib/version.js +168 -0
- package/dist/src/lib/worker-fleet-runner.d.ts +32 -0
- package/dist/src/lib/worker-fleet-runner.d.ts.map +1 -0
- package/dist/src/lib/worker-fleet-runner.js +256 -0
- package/dist/src/lib/worker-runner.d.ts +33 -0
- package/dist/src/lib/worker-runner.d.ts.map +1 -0
- package/dist/src/lib/worker-runner.js +781 -0
- package/dist/src/linear.d.ts +37 -0
- package/dist/src/linear.d.ts.map +1 -0
- package/dist/src/linear.js +118 -0
- package/dist/src/orchestrator.d.ts +21 -0
- package/dist/src/orchestrator.d.ts.map +1 -0
- package/dist/src/orchestrator.js +190 -0
- package/dist/src/queue-admin.d.ts +25 -0
- package/dist/src/queue-admin.d.ts.map +1 -0
- package/dist/src/queue-admin.js +96 -0
- package/dist/src/sync-routes.d.ts +17 -0
- package/dist/src/sync-routes.d.ts.map +1 -0
- package/dist/src/sync-routes.js +100 -0
- package/dist/src/worker-fleet.d.ts +25 -0
- package/dist/src/worker-fleet.d.ts.map +1 -0
- package/dist/src/worker-fleet.js +140 -0
- package/dist/src/worker.d.ts +26 -0
- package/dist/src/worker.d.ts.map +1 -0
- package/dist/src/worker.js +135 -0
- package/package.json +175 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Updater for AgentFactory CLI.
|
|
3
|
+
*
|
|
4
|
+
* When enabled, checks for new versions and can automatically update
|
|
5
|
+
* the CLI package. For long-running processes (fleet, governor), it
|
|
6
|
+
* waits until there are no active workers before restarting.
|
|
7
|
+
*
|
|
8
|
+
* Configuration (in order of precedence):
|
|
9
|
+
* 1. CLI flag: --auto-update / --no-auto-update
|
|
10
|
+
* 2. .env.local: AF_AUTO_UPDATE=true
|
|
11
|
+
* 3. .agentfactory/config.yaml: autoUpdate: true
|
|
12
|
+
*/
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// ANSI colors
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const c = {
|
|
18
|
+
reset: '\x1b[0m',
|
|
19
|
+
bold: '\x1b[1m',
|
|
20
|
+
dim: '\x1b[2m',
|
|
21
|
+
green: '\x1b[32m',
|
|
22
|
+
yellow: '\x1b[33m',
|
|
23
|
+
cyan: '\x1b[36m',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Resolve whether auto-update is enabled from all config sources.
|
|
27
|
+
*/
|
|
28
|
+
export function isAutoUpdateEnabled(cliFlag) {
|
|
29
|
+
// CLI flag takes highest precedence
|
|
30
|
+
if (cliFlag !== undefined)
|
|
31
|
+
return cliFlag;
|
|
32
|
+
// Environment variable
|
|
33
|
+
const envVal = process.env.AF_AUTO_UPDATE;
|
|
34
|
+
if (envVal === '1' || envVal === 'true')
|
|
35
|
+
return true;
|
|
36
|
+
if (envVal === '0' || envVal === 'false')
|
|
37
|
+
return false;
|
|
38
|
+
// Default: disabled
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Update execution
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
/**
|
|
45
|
+
* Attempt to install the latest version of the CLI package.
|
|
46
|
+
* Returns true if the update succeeded.
|
|
47
|
+
*/
|
|
48
|
+
function installUpdate(targetVersion) {
|
|
49
|
+
const pkg = `@renseiai/agentfactory-cli@${targetVersion}`;
|
|
50
|
+
console.log(`${c.cyan}Updating to ${pkg}...${c.reset}`);
|
|
51
|
+
try {
|
|
52
|
+
// Detect package manager used for global install
|
|
53
|
+
execSync(`npm ls -g @renseiai/agentfactory-cli --depth=0 2>/dev/null`, { stdio: 'ignore' });
|
|
54
|
+
execSync(`npm install -g ${pkg}`, { stdio: 'inherit' });
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// npm global didn't work — try pnpm
|
|
59
|
+
try {
|
|
60
|
+
execSync(`pnpm add -g ${pkg}`, { stdio: 'inherit' });
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
console.log(`${c.yellow}Auto-update failed. Update manually: npm i -g ${pkg}${c.reset}`);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Public API
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
/**
|
|
73
|
+
* Perform auto-update if enabled and an update is available.
|
|
74
|
+
*
|
|
75
|
+
* For long-running processes (fleet, governor), this should be called
|
|
76
|
+
* periodically. It will:
|
|
77
|
+
* 1. Check if auto-update is enabled
|
|
78
|
+
* 2. Verify an update is available
|
|
79
|
+
* 3. Wait for active workers to finish (if hasActiveWorkers provided)
|
|
80
|
+
* 4. Install the update
|
|
81
|
+
* 5. Exit the process so the service manager can restart with the new version
|
|
82
|
+
*
|
|
83
|
+
* Returns true if an update was applied (process will exit shortly after).
|
|
84
|
+
*/
|
|
85
|
+
export async function maybeAutoUpdate(updateCheck, config) {
|
|
86
|
+
if (!isAutoUpdateEnabled(config.cliFlag))
|
|
87
|
+
return false;
|
|
88
|
+
if (!updateCheck?.updateAvailable)
|
|
89
|
+
return false;
|
|
90
|
+
// If there are active workers, skip this cycle
|
|
91
|
+
if (config.hasActiveWorkers) {
|
|
92
|
+
const active = await config.hasActiveWorkers();
|
|
93
|
+
if (active) {
|
|
94
|
+
console.log(`${c.dim}Update v${updateCheck.latestVersion} available but workers are active — deferring${c.reset}`);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
console.log(`\n${c.green}${c.bold}Auto-updating:${c.reset} v${updateCheck.currentVersion} → v${updateCheck.latestVersion}`);
|
|
99
|
+
const success = installUpdate(updateCheck.latestVersion);
|
|
100
|
+
if (!success)
|
|
101
|
+
return false;
|
|
102
|
+
// Call pre-restart hook
|
|
103
|
+
if (config.onBeforeRestart) {
|
|
104
|
+
await config.onBeforeRestart();
|
|
105
|
+
}
|
|
106
|
+
console.log(`${c.green}Update complete. Restarting...${c.reset}\n`);
|
|
107
|
+
// Exit with code 0 — service managers (systemd, pm2, etc.) will restart
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleanup Runner -- Programmatic API for the worktree cleanup CLI.
|
|
3
|
+
*
|
|
4
|
+
* Exports `runCleanup()` so worktree cleanup can be invoked from code
|
|
5
|
+
* without going through process.argv / process.env / process.exit.
|
|
6
|
+
*/
|
|
7
|
+
export interface CleanupRunnerConfig {
|
|
8
|
+
/** Show what would be cleaned up without removing (default: false) */
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
/** Force removal even if worktree appears active (default: false) */
|
|
11
|
+
force?: boolean;
|
|
12
|
+
/** Custom worktrees directory (default: {gitRoot}/.worktrees) */
|
|
13
|
+
worktreePath?: string;
|
|
14
|
+
/** Git root for default worktree path (default: auto-detect) */
|
|
15
|
+
gitRoot?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface CleanupResult {
|
|
18
|
+
scanned: number;
|
|
19
|
+
orphaned: number;
|
|
20
|
+
cleaned: number;
|
|
21
|
+
skipped: number;
|
|
22
|
+
errors: Array<{
|
|
23
|
+
path: string;
|
|
24
|
+
error: string;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
export declare function getGitRoot(): string;
|
|
28
|
+
export declare function runCleanup(config?: CleanupRunnerConfig): CleanupResult;
|
|
29
|
+
//# sourceMappingURL=cleanup-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/cleanup-runner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,MAAM,WAAW,mBAAmB;IAClC,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC/C;AAuBD,wBAAgB,UAAU,IAAI,MAAM,CASnC;AAgSD,wBAAgB,UAAU,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,aAAa,CAUtE"}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleanup Runner -- Programmatic API for the worktree cleanup CLI.
|
|
3
|
+
*
|
|
4
|
+
* Exports `runCleanup()` so worktree cleanup can be invoked from code
|
|
5
|
+
* without going through process.argv / process.env / process.exit.
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { existsSync, readdirSync, statSync } from 'fs';
|
|
9
|
+
import { resolve, basename } from 'path';
|
|
10
|
+
/** Delay between worktree removals (ms) to let IDEs process filesystem events. */
|
|
11
|
+
const IDE_SETTLE_DELAY_MS = 1500;
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
export function getGitRoot() {
|
|
16
|
+
try {
|
|
17
|
+
return execSync('git rev-parse --show-toplevel', {
|
|
18
|
+
encoding: 'utf-8',
|
|
19
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
20
|
+
}).trim();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return process.cwd();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get list of git worktrees from 'git worktree list'
|
|
28
|
+
*/
|
|
29
|
+
function getGitWorktrees() {
|
|
30
|
+
const worktrees = new Map();
|
|
31
|
+
try {
|
|
32
|
+
const output = execSync('git worktree list --porcelain', {
|
|
33
|
+
encoding: 'utf-8',
|
|
34
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
35
|
+
});
|
|
36
|
+
let currentPath = '';
|
|
37
|
+
for (const line of output.split('\n')) {
|
|
38
|
+
if (line.startsWith('worktree ')) {
|
|
39
|
+
currentPath = line.substring(9);
|
|
40
|
+
}
|
|
41
|
+
else if (line.startsWith('branch ')) {
|
|
42
|
+
const branch = line.substring(7).replace('refs/heads/', '');
|
|
43
|
+
worktrees.set(currentPath, branch);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error('Failed to list git worktrees:', error);
|
|
49
|
+
}
|
|
50
|
+
return worktrees;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if a git branch exists
|
|
54
|
+
*/
|
|
55
|
+
function branchExists(branchName) {
|
|
56
|
+
try {
|
|
57
|
+
execSync(`git show-ref --verify --quiet refs/heads/${branchName}`, {
|
|
58
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
59
|
+
});
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if any processes (IDEs, language servers, etc.) have files open in a
|
|
68
|
+
* directory. Uses `lsof` on macOS/Linux. Returns a deduplicated list of
|
|
69
|
+
* process command names so the caller can warn the user.
|
|
70
|
+
*/
|
|
71
|
+
function getProcessesUsingPath(dirPath) {
|
|
72
|
+
try {
|
|
73
|
+
// lsof +D is recursive but can be slow on large trees.
|
|
74
|
+
// We use a short timeout and suppress errors — if it fails, we treat the
|
|
75
|
+
// path as free (better to over-delete than hang forever).
|
|
76
|
+
const output = execSync(`lsof +D "${dirPath}" 2>/dev/null || true`, {
|
|
77
|
+
encoding: 'utf-8',
|
|
78
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
79
|
+
timeout: 5000,
|
|
80
|
+
});
|
|
81
|
+
if (!output.trim())
|
|
82
|
+
return [];
|
|
83
|
+
const commands = new Set();
|
|
84
|
+
for (const line of output.split('\n').slice(1)) {
|
|
85
|
+
const cmd = line.split(/\s+/)[0];
|
|
86
|
+
if (cmd)
|
|
87
|
+
commands.add(cmd);
|
|
88
|
+
}
|
|
89
|
+
return [...commands];
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Sleep synchronously for the given number of milliseconds.
|
|
97
|
+
*/
|
|
98
|
+
function sleepSync(ms) {
|
|
99
|
+
execSync(`sleep ${(ms / 1000).toFixed(1)}`, { stdio: 'pipe' });
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Scan the worktrees directory and identify orphaned worktrees
|
|
103
|
+
*/
|
|
104
|
+
function scanWorktrees(options) {
|
|
105
|
+
const worktreesDir = resolve(options.worktreePath);
|
|
106
|
+
const result = [];
|
|
107
|
+
if (!existsSync(worktreesDir)) {
|
|
108
|
+
console.log(`Worktrees directory not found: ${worktreesDir}`);
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
const gitWorktrees = getGitWorktrees();
|
|
112
|
+
const entries = readdirSync(worktreesDir);
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
const entryPath = resolve(worktreesDir, entry);
|
|
115
|
+
try {
|
|
116
|
+
if (!statSync(entryPath).isDirectory()) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const info = {
|
|
124
|
+
path: entryPath,
|
|
125
|
+
branch: entry,
|
|
126
|
+
isOrphaned: false,
|
|
127
|
+
};
|
|
128
|
+
const isKnownWorktree = gitWorktrees.has(entryPath);
|
|
129
|
+
const branchName = isKnownWorktree ? gitWorktrees.get(entryPath) : entry;
|
|
130
|
+
if (options.force) {
|
|
131
|
+
info.isOrphaned = true;
|
|
132
|
+
info.reason = 'force cleanup requested';
|
|
133
|
+
}
|
|
134
|
+
else if (!isKnownWorktree) {
|
|
135
|
+
info.isOrphaned = true;
|
|
136
|
+
info.reason = 'not registered with git worktree';
|
|
137
|
+
}
|
|
138
|
+
else if (!branchExists(branchName)) {
|
|
139
|
+
info.isOrphaned = true;
|
|
140
|
+
info.reason = `branch '${branchName}' no longer exists`;
|
|
141
|
+
}
|
|
142
|
+
result.push(info);
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Remove a single worktree with safety checks.
|
|
148
|
+
*
|
|
149
|
+
* SAFETY: Refuses to remove paths where .git is a directory (main working tree)
|
|
150
|
+
* to prevent catastrophic data loss.
|
|
151
|
+
*
|
|
152
|
+
* IDE SAFETY: Detects processes (VS Code, Cursor, etc.) with open file handles
|
|
153
|
+
* in the worktree. When detected and `force` is false, the removal is skipped
|
|
154
|
+
* to prevent IDE crashes caused by sudden workspace root deletion.
|
|
155
|
+
*/
|
|
156
|
+
function removeWorktree(worktreePath, force) {
|
|
157
|
+
// Safety check: never remove the main working tree
|
|
158
|
+
try {
|
|
159
|
+
const gitPath = resolve(worktreePath, '.git');
|
|
160
|
+
if (existsSync(gitPath) && statSync(gitPath).isDirectory()) {
|
|
161
|
+
return {
|
|
162
|
+
success: false,
|
|
163
|
+
error: `SAFETY: ${worktreePath} is the main working tree (.git is a directory). Refusing to remove.`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// If we can't check, err on the side of caution
|
|
169
|
+
return { success: false, error: `SAFETY: Could not verify ${worktreePath} is not the main working tree.` };
|
|
170
|
+
}
|
|
171
|
+
// IDE safety: check for processes with open file handles in this worktree
|
|
172
|
+
const processes = getProcessesUsingPath(worktreePath);
|
|
173
|
+
if (processes.length > 0 && !force) {
|
|
174
|
+
return {
|
|
175
|
+
success: false,
|
|
176
|
+
skipped: true,
|
|
177
|
+
error: `IDE/process still open: ${processes.join(', ')}. Use --force to remove anyway.`,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (processes.length > 0) {
|
|
181
|
+
console.log(`\n warning: ${processes.join(', ')} has files open — forcing removal`);
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
execSync(`git worktree remove "${worktreePath}" --force`, {
|
|
185
|
+
encoding: 'utf-8',
|
|
186
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
187
|
+
});
|
|
188
|
+
return { success: true };
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
try {
|
|
192
|
+
execSync(`rm -rf "${worktreePath}"`, {
|
|
193
|
+
encoding: 'utf-8',
|
|
194
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
195
|
+
});
|
|
196
|
+
return { success: true };
|
|
197
|
+
}
|
|
198
|
+
catch (rmError) {
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
error: rmError instanceof Error ? rmError.message : String(rmError),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Core cleanup logic
|
|
208
|
+
*/
|
|
209
|
+
function cleanup(options) {
|
|
210
|
+
const result = {
|
|
211
|
+
scanned: 0,
|
|
212
|
+
orphaned: 0,
|
|
213
|
+
cleaned: 0,
|
|
214
|
+
skipped: 0,
|
|
215
|
+
errors: [],
|
|
216
|
+
};
|
|
217
|
+
console.log('Scanning worktrees...\n');
|
|
218
|
+
const worktrees = scanWorktrees(options);
|
|
219
|
+
result.scanned = worktrees.length;
|
|
220
|
+
if (worktrees.length === 0) {
|
|
221
|
+
console.log('No worktrees found.\n');
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
console.log(`Found ${worktrees.length} worktree(s) in ${options.worktreePath}/\n`);
|
|
225
|
+
for (const wt of worktrees) {
|
|
226
|
+
const status = wt.isOrphaned ? ' orphaned' : ' active';
|
|
227
|
+
const reason = wt.reason ? ` (${wt.reason})` : '';
|
|
228
|
+
console.log(` ${status}: ${basename(wt.path)}${reason}`);
|
|
229
|
+
}
|
|
230
|
+
console.log('');
|
|
231
|
+
const orphaned = worktrees.filter((wt) => wt.isOrphaned);
|
|
232
|
+
result.orphaned = orphaned.length;
|
|
233
|
+
if (orphaned.length === 0) {
|
|
234
|
+
console.log('No orphaned worktrees to clean up.\n');
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
if (options.dryRun) {
|
|
238
|
+
console.log(`[DRY RUN] Would clean up ${orphaned.length} orphaned worktree(s):\n`);
|
|
239
|
+
for (const wt of orphaned) {
|
|
240
|
+
const procs = getProcessesUsingPath(wt.path);
|
|
241
|
+
const procNote = procs.length > 0 ? ` [open: ${procs.join(', ')}]` : '';
|
|
242
|
+
console.log(` Would remove: ${wt.path}${procNote}`);
|
|
243
|
+
}
|
|
244
|
+
console.log('');
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
console.log(`Cleaning up ${orphaned.length} orphaned worktree(s)...\n`);
|
|
248
|
+
let removedCount = 0;
|
|
249
|
+
for (const wt of orphaned) {
|
|
250
|
+
// Settle between removals so IDE file watchers can process previous
|
|
251
|
+
// deletion events without being overwhelmed.
|
|
252
|
+
if (removedCount > 0) {
|
|
253
|
+
sleepSync(IDE_SETTLE_DELAY_MS);
|
|
254
|
+
}
|
|
255
|
+
process.stdout.write(` Removing ${basename(wt.path)}... `);
|
|
256
|
+
const removal = removeWorktree(wt.path, options.force);
|
|
257
|
+
if (removal.success) {
|
|
258
|
+
console.log('done');
|
|
259
|
+
result.cleaned++;
|
|
260
|
+
removedCount++;
|
|
261
|
+
}
|
|
262
|
+
else if (removal.skipped) {
|
|
263
|
+
console.log(`SKIPPED: ${removal.error}`);
|
|
264
|
+
result.skipped++;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
console.log(`FAILED: ${removal.error}`);
|
|
268
|
+
result.errors.push({ path: wt.path, error: removal.error || 'Unknown error' });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
console.log('');
|
|
272
|
+
try {
|
|
273
|
+
execSync('git worktree prune', {
|
|
274
|
+
encoding: 'utf-8',
|
|
275
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
276
|
+
});
|
|
277
|
+
console.log('Pruned git worktree metadata.\n');
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// Ignore prune errors
|
|
281
|
+
}
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// Runner
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
export function runCleanup(config) {
|
|
288
|
+
const gitRoot = config?.gitRoot ?? getGitRoot();
|
|
289
|
+
const options = {
|
|
290
|
+
dryRun: config?.dryRun ?? false,
|
|
291
|
+
force: config?.force ?? false,
|
|
292
|
+
worktreePath: config?.worktreePath ?? resolve(gitRoot, '.worktrees'),
|
|
293
|
+
};
|
|
294
|
+
return cleanup(options);
|
|
295
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real Governor Dependencies
|
|
3
|
+
*
|
|
4
|
+
* Maps each GovernorDependencies callback to its real implementation
|
|
5
|
+
* using the Linear SDK (via LinearAgentClient) and Redis storage
|
|
6
|
+
* (from @renseiai/agentfactory-server).
|
|
7
|
+
*/
|
|
8
|
+
import type { LinearAgentClient, WorkflowContext } from '@renseiai/agentfactory-linear';
|
|
9
|
+
import type { GovernorDependencies } from '@renseiai/agentfactory';
|
|
10
|
+
export interface RealDependenciesConfig {
|
|
11
|
+
linearClient: LinearAgentClient;
|
|
12
|
+
resolveOAuthClient?: () => Promise<LinearAgentClient | undefined>;
|
|
13
|
+
organizationId?: string;
|
|
14
|
+
generatePrompt?: (identifier: string, workType: string, mentionContext?: string, workflowContext?: WorkflowContext) => string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create real GovernorDependencies backed by the Linear SDK and Redis.
|
|
18
|
+
*
|
|
19
|
+
* Each callback wraps its implementation in a try/catch so that a single
|
|
20
|
+
* failing dependency does not crash the entire governor scan loop.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createRealDependencies(config: RealDependenciesConfig): GovernorDependencies;
|
|
23
|
+
//# sourceMappingURL=governor-dependencies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"governor-dependencies.d.ts","sourceRoot":"","sources":["../../../src/lib/governor-dependencies.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AACvF,OAAO,KAAK,EACV,oBAAoB,EAGrB,MAAM,wBAAwB,CAAA;AAiC/B,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,iBAAiB,CAAA;IAC/B,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAAA;IACjE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,eAAe,KAAK,MAAM,CAAA;CAC9H;AA6BD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,oBAAoB,CA6TtB"}
|