@renseiai/agentfactory-cli 0.8.18 → 0.8.20

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.
@@ -1 +1 @@
1
- {"version":3,"file":"code-intelligence-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/code-intelligence-runner.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAA;IACjD,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,OAAO,CAAA;CAChB;AAID,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAC7C,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAA;IACjD,cAAc,EAAE,MAAM,EAAE,CAAA;CACzB,CAwBA;AA0aD,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC,4BAA4B,CAAC,CAuBvC"}
1
+ {"version":3,"file":"code-intelligence-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/code-intelligence-runner.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAA;IACjD,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,OAAO,CAAA;CAChB;AAID,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAC7C,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAA;IACjD,cAAc,EAAE,MAAM,EAAE,CAAA;CACzB,CAwBA;AA+gBD,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC,4BAA4B,CAAC,CAuBvC"}
@@ -256,6 +256,80 @@ function hasRelatedCases(lines, switchLine, _typeName) {
256
256
  function escapeRegex(str) {
257
257
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
258
258
  }
259
+ /**
260
+ * Determine whether a line contains a real import statement (not one inside
261
+ * a comment, string literal, template literal, or test assertion).
262
+ *
263
+ * The caller must maintain the parse state across lines.
264
+ */
265
+ function isRealImportLine(line, state) {
266
+ const trimmed = line.trim();
267
+ // Track block comment boundaries
268
+ if (state.inBlockComment) {
269
+ if (trimmed.includes('*/')) {
270
+ return { real: false, state: { ...state, inBlockComment: false } };
271
+ }
272
+ return { real: false, state };
273
+ }
274
+ if (trimmed.startsWith('/*')) {
275
+ const closesOnSameLine = trimmed.includes('*/');
276
+ return { real: false, state: { ...state, inBlockComment: !closesOnSameLine } };
277
+ }
278
+ // Track template literal boundaries (backtick strings spanning multiple lines)
279
+ // Count unescaped backticks to toggle state
280
+ if (state.inTemplateLiteral) {
281
+ const backtickCount = countUnescapedBackticks(line);
282
+ if (backtickCount % 2 === 1) {
283
+ // Odd number of backticks — template literal ends on this line
284
+ return { real: false, state: { ...state, inTemplateLiteral: false } };
285
+ }
286
+ return { real: false, state };
287
+ }
288
+ // Single-line comments and JSDoc continuation lines
289
+ if (trimmed.startsWith('//') || trimmed.startsWith('*'))
290
+ return { real: false, state };
291
+ // Check if this line opens a template literal that doesn't close on the same line
292
+ const backticks = countUnescapedBackticks(line);
293
+ if (backticks % 2 === 1) {
294
+ // Odd backticks — a template literal opens and doesn't close on this line.
295
+ // The import on this line (if any) could be before or after the backtick.
296
+ // If the import keyword appears after a backtick, it's template content.
297
+ const importIdx = line.search(/\b(import|export)\s/);
298
+ const firstBacktick = line.indexOf('`');
299
+ if (importIdx >= 0 && firstBacktick >= 0 && importIdx > firstBacktick) {
300
+ // Import appears inside the template literal
301
+ return { real: false, state: { ...state, inTemplateLiteral: true } };
302
+ }
303
+ // Import appears before the backtick — real import, but template opens
304
+ if (importIdx >= 0 && (firstBacktick < 0 || importIdx < firstBacktick)) {
305
+ return { real: true, state: { ...state, inTemplateLiteral: true } };
306
+ }
307
+ return { real: false, state: { ...state, inTemplateLiteral: true } };
308
+ }
309
+ // Real import/export/require statements start at the beginning of the line
310
+ if (/^\s*(import|export)\s/.test(line))
311
+ return { real: true, state };
312
+ // Dynamic require: `const x = require('pkg')`
313
+ if (/\brequire\s*\(/.test(line)) {
314
+ const reqIdx = line.indexOf('require');
315
+ const beforeReq = line.slice(0, reqIdx);
316
+ if (beforeReq.includes('`') || beforeReq.includes("'require") || beforeReq.includes('"require')) {
317
+ return { real: false, state };
318
+ }
319
+ return { real: true, state };
320
+ }
321
+ return { real: false, state };
322
+ }
323
+ /** Count unescaped backticks in a line */
324
+ function countUnescapedBackticks(line) {
325
+ let count = 0;
326
+ for (let i = 0; i < line.length; i++) {
327
+ if (line[i] === '`' && (i === 0 || line[i - 1] !== '\\')) {
328
+ count++;
329
+ }
330
+ }
331
+ return count;
332
+ }
259
333
  async function validateCrossDeps(config) {
260
334
  const targetPath = config.positionalArgs[0]; // Optional: specific file or directory
261
335
  const files = await discoverSourceFiles(config.cwd);
@@ -283,8 +357,14 @@ async function validateCrossDeps(config) {
283
357
  if (!owningPkg)
284
358
  continue;
285
359
  const lines = content.split('\n');
360
+ let parseState = { inBlockComment: false, inTemplateLiteral: false };
286
361
  for (let i = 0; i < lines.length; i++) {
287
362
  const line = lines[i];
363
+ // Skip comments, string literals, and template content
364
+ const classification = isRealImportLine(line, parseState);
365
+ parseState = classification.state;
366
+ if (!classification.real)
367
+ continue;
288
368
  // Match import/require of workspace packages
289
369
  const importMatch = line.match(/(?:from\s+['"]|require\s*\(\s*['"]|import\s+['"])(@[^'"\/]+\/[^'"\/]+|[^.'"\/@][^'"\/]*)/);
290
370
  if (!importMatch)
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Merge Worker Sidecar
3
+ *
4
+ * Starts the merge worker as a background loop alongside the fleet.
5
+ * Only one merge worker runs per repo (enforced by Redis lock).
6
+ *
7
+ * The sidecar:
8
+ * 1. Reads .agentfactory/config.yaml for merge queue settings
9
+ * 2. Creates a dedicated worktree for merge operations
10
+ * 3. Starts the MergeWorker poll loop
11
+ * 4. Stops gracefully on AbortSignal
12
+ *
13
+ * If Redis is not configured or merge queue is not enabled, this is a no-op.
14
+ */
15
+ export interface MergeWorkerSidecarConfig {
16
+ /** Git repository root (default: auto-detect from cwd) */
17
+ gitRoot?: string;
18
+ /** Override repoId (default: derived from git remote) */
19
+ repoId?: string;
20
+ }
21
+ export interface MergeWorkerSidecarHandle {
22
+ /** Stop the merge worker gracefully */
23
+ stop(): void;
24
+ /** Promise that resolves when the worker exits */
25
+ done: Promise<void>;
26
+ }
27
+ /**
28
+ * Start the merge worker sidecar if merge queue is enabled.
29
+ *
30
+ * Returns a handle to stop the worker, or null if merge queue is not
31
+ * configured or Redis is unavailable. Safe to call unconditionally —
32
+ * it checks all preconditions before starting.
33
+ */
34
+ export declare function startMergeWorkerSidecar(config?: MergeWorkerSidecarConfig, signal?: AbortSignal): MergeWorkerSidecarHandle | null;
35
+ //# sourceMappingURL=merge-worker-sidecar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-worker-sidecar.d.ts","sourceRoot":"","sources":["../../../src/lib/merge-worker-sidecar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAyBH,MAAM,WAAW,wBAAwB;IACvC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,uCAAuC;IACvC,IAAI,IAAI,IAAI,CAAA;IACZ,kDAAkD;IAClD,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CACpB;AAkED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,GAAE,wBAA6B,EACrC,MAAM,CAAC,EAAE,WAAW,GACnB,wBAAwB,GAAG,IAAI,CA0FjC"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Merge Worker Sidecar
3
+ *
4
+ * Starts the merge worker as a background loop alongside the fleet.
5
+ * Only one merge worker runs per repo (enforced by Redis lock).
6
+ *
7
+ * The sidecar:
8
+ * 1. Reads .agentfactory/config.yaml for merge queue settings
9
+ * 2. Creates a dedicated worktree for merge operations
10
+ * 3. Starts the MergeWorker poll loop
11
+ * 4. Stops gracefully on AbortSignal
12
+ *
13
+ * If Redis is not configured or merge queue is not enabled, this is a no-op.
14
+ */
15
+ import { execSync } from 'child_process';
16
+ import path from 'path';
17
+ import fs from 'fs';
18
+ import { loadRepositoryConfig, MergeWorker, } from '@renseiai/agentfactory';
19
+ import { MergeQueueStorage, isRedisConfigured, redisSetNX, redisDel, redisGet, redisSet, redisExpire, } from '@renseiai/agentfactory-server';
20
+ // ---------------------------------------------------------------------------
21
+ // Helpers
22
+ // ---------------------------------------------------------------------------
23
+ function getGitRoot() {
24
+ return execSync('git rev-parse --show-toplevel', {
25
+ encoding: 'utf-8',
26
+ stdio: ['pipe', 'pipe', 'pipe'],
27
+ }).trim();
28
+ }
29
+ function getRepoId(gitRoot) {
30
+ try {
31
+ const remote = execSync('git remote get-url origin', {
32
+ encoding: 'utf-8',
33
+ cwd: gitRoot,
34
+ stdio: ['pipe', 'pipe', 'pipe'],
35
+ }).trim();
36
+ // Extract owner/repo from git URL
37
+ const match = remote.match(/[:/]([^/]+\/[^/.]+?)(?:\.git)?$/);
38
+ return match ? match[1] : 'default';
39
+ }
40
+ catch {
41
+ return 'default';
42
+ }
43
+ }
44
+ function ensureMergeWorktree(gitRoot) {
45
+ const worktreeBase = path.resolve(gitRoot, '..', path.basename(gitRoot) + '.wt');
46
+ const worktreePath = path.join(worktreeBase, '__merge-worker__');
47
+ if (fs.existsSync(worktreePath)) {
48
+ // Verify it's a valid worktree
49
+ try {
50
+ execSync('git rev-parse --git-dir', { cwd: worktreePath, stdio: ['pipe', 'pipe', 'pipe'] });
51
+ // Pull latest main
52
+ execSync('git fetch origin main && git reset --hard origin/main', {
53
+ cwd: worktreePath,
54
+ stdio: ['pipe', 'pipe', 'pipe'],
55
+ });
56
+ return worktreePath;
57
+ }
58
+ catch {
59
+ // Invalid worktree — recreate
60
+ try {
61
+ execSync(`git worktree remove --force "${worktreePath}"`, { cwd: gitRoot, stdio: ['pipe', 'pipe', 'pipe'] });
62
+ }
63
+ catch { /* best effort */ }
64
+ }
65
+ }
66
+ // Create the worktree directory parent
67
+ fs.mkdirSync(worktreeBase, { recursive: true });
68
+ // Create a detached worktree on main
69
+ execSync(`git worktree add --detach "${worktreePath}" origin/main`, {
70
+ cwd: gitRoot,
71
+ stdio: ['pipe', 'pipe', 'pipe'],
72
+ });
73
+ return worktreePath;
74
+ }
75
+ // ---------------------------------------------------------------------------
76
+ // Sidecar
77
+ // ---------------------------------------------------------------------------
78
+ /**
79
+ * Start the merge worker sidecar if merge queue is enabled.
80
+ *
81
+ * Returns a handle to stop the worker, or null if merge queue is not
82
+ * configured or Redis is unavailable. Safe to call unconditionally —
83
+ * it checks all preconditions before starting.
84
+ */
85
+ export function startMergeWorkerSidecar(config = {}, signal) {
86
+ // Check Redis
87
+ if (!isRedisConfigured()) {
88
+ return null;
89
+ }
90
+ // Check repo config
91
+ const gitRoot = config.gitRoot ?? getGitRoot();
92
+ const repoConfig = loadRepositoryConfig(gitRoot);
93
+ if (!repoConfig?.mergeQueue?.enabled) {
94
+ return null;
95
+ }
96
+ const provider = repoConfig.mergeQueue.provider ?? 'local';
97
+ if (provider !== 'local') {
98
+ // GitHub-native or other external providers handle merging themselves
99
+ return null;
100
+ }
101
+ const repoId = config.repoId ?? getRepoId(gitRoot);
102
+ // Create dedicated worktree for merge operations
103
+ let worktreePath;
104
+ try {
105
+ worktreePath = ensureMergeWorktree(gitRoot);
106
+ }
107
+ catch (error) {
108
+ console.warn(`[merge-worker] Failed to create merge worktree: ${error instanceof Error ? error.message : String(error)}`);
109
+ return null;
110
+ }
111
+ // Build merge worker config from repo config
112
+ const mqConfig = repoConfig.mergeQueue;
113
+ const workerConfig = {
114
+ repoId,
115
+ repoPath: worktreePath,
116
+ strategy: mqConfig.strategy ?? 'rebase',
117
+ testCommand: mqConfig.testCommand ?? 'pnpm test',
118
+ testTimeout: mqConfig.testTimeout ?? 300_000,
119
+ lockFileRegenerate: mqConfig.lockFileRegenerate ?? true,
120
+ mergiraf: mqConfig.mergiraf ?? true,
121
+ pollInterval: mqConfig.pollInterval ?? 10_000,
122
+ maxRetries: mqConfig.maxRetries ?? 2,
123
+ escalation: {
124
+ onConflict: mqConfig.escalation?.onConflict ?? 'reassign',
125
+ onTestFailure: mqConfig.escalation?.onTestFailure ?? 'notify',
126
+ },
127
+ deleteBranchOnMerge: mqConfig.deleteBranchOnMerge ?? true,
128
+ packageManager: (repoConfig.packageManager ?? 'pnpm'),
129
+ remote: 'origin',
130
+ targetBranch: 'main',
131
+ };
132
+ // Wire Redis deps for the merge worker
133
+ const storage = new MergeQueueStorage();
134
+ const deps = {
135
+ storage: {
136
+ dequeue: (id) => storage.dequeue(id),
137
+ markCompleted: (id, pr) => storage.markCompleted(id, pr),
138
+ markFailed: (id, pr, reason) => storage.markFailed(id, pr, reason),
139
+ markBlocked: (id, pr, reason) => storage.markBlocked(id, pr, reason),
140
+ },
141
+ redis: {
142
+ setNX: async (key, value, ttl) => {
143
+ const result = await redisSetNX(key, value, ttl);
144
+ return result;
145
+ },
146
+ del: (key) => redisDel(key).then(() => undefined),
147
+ get: (key) => redisGet(key),
148
+ set: (key, value) => redisSet(key, value).then(() => undefined),
149
+ expire: (key, seconds) => redisExpire(key, seconds).then(() => undefined),
150
+ },
151
+ };
152
+ const worker = new MergeWorker(workerConfig, deps);
153
+ console.log(`[merge-worker] Starting merge worker sidecar (repo: ${repoId}, worktree: ${worktreePath})`);
154
+ const done = worker.start(signal).catch((error) => {
155
+ // Lock acquisition failure is expected if another worker is already running
156
+ if (error instanceof Error && error.message.includes('already running')) {
157
+ console.log(`[merge-worker] Another merge worker is already running for ${repoId} — skipping`);
158
+ }
159
+ else {
160
+ console.error(`[merge-worker] Merge worker error: ${error instanceof Error ? error.message : String(error)}`);
161
+ }
162
+ });
163
+ return {
164
+ stop: () => worker.stop(),
165
+ done,
166
+ };
167
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/orchestrator-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,iBAAiB,EAEvB,MAAM,wBAAwB,CAAA;AAkB/B,MAAM,WAAW,wBAAwB;IACvC,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,iEAAiE;IACjE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,qBAAqB,CAAA;IACjC,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACpD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IAC5C,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IAC/C,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAC1D,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;CAClD;AAED,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAA;IAChD,SAAS,EAAE,YAAY,EAAE,CAAA;CAC1B;AAMD,wBAAgB,UAAU,IAAI,MAAM,CASnC;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAYjD;AA0CD,wBAAsB,eAAe,CACnC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CAmGnC"}
1
+ {"version":3,"file":"orchestrator-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/orchestrator-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAGL,KAAK,YAAY,EAEjB,KAAK,iBAAiB,EAEvB,MAAM,wBAAwB,CAAA;AAmB/B,MAAM,WAAW,wBAAwB;IACvC,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,iEAAiE;IACjE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,qBAAqB,CAAA;IACjC,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACpD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IAC5C,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IAC/C,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAC1D,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;CAClD;AAED,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAA;IAChD,SAAS,EAAE,YAAY,EAAE,CAAA;CAC1B;AAMD,wBAAgB,UAAU,IAAI,MAAM,CASnC;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAYjD;AA0CD,wBAAsB,eAAe,CACnC,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,wBAAwB,CAAC,CA4GnC"}
@@ -7,8 +7,9 @@
7
7
  */
8
8
  import path from 'path';
9
9
  import { execSync } from 'child_process';
10
- import { createOrchestrator, } from '@renseiai/agentfactory';
10
+ import { createOrchestrator, loadRepositoryConfig, } from '@renseiai/agentfactory';
11
11
  import { LinearIssueTrackerClient, createLinearStatusMappings, linearPlugin, } from '@renseiai/plugin-linear';
12
+ import { MergeQueueStorage, createLocalMergeQueueStorage } from '@renseiai/agentfactory-server';
12
13
  let codeIntelligencePlugin;
13
14
  try {
14
15
  ;
@@ -88,6 +89,13 @@ export async function runOrchestrator(config) {
88
89
  const cb = config.callbacks ?? defaultCallbacks();
89
90
  const issueTrackerClient = new LinearIssueTrackerClient({ apiKey: config.linearApiKey });
90
91
  const statusMappings = createLinearStatusMappings();
92
+ // Create local merge queue storage if configured
93
+ const repoConfig = loadRepositoryConfig(gitRoot);
94
+ const needsLocalStorage = repoConfig?.mergeQueue?.enabled &&
95
+ (!repoConfig.mergeQueue.provider || repoConfig.mergeQueue.provider === 'local');
96
+ const mergeQueueStorage = needsLocalStorage
97
+ ? createLocalMergeQueueStorage(new MergeQueueStorage())
98
+ : undefined;
91
99
  const orchestratorConfig = {
92
100
  project: config.project,
93
101
  maxConcurrent,
@@ -95,6 +103,7 @@ export async function runOrchestrator(config) {
95
103
  linearApiKey: config.linearApiKey,
96
104
  issueTrackerClient,
97
105
  statusMappings,
106
+ mergeQueueStorage,
98
107
  toolPlugins: [linearPlugin, codeIntelligencePlugin].filter(Boolean),
99
108
  };
100
109
  if (config.templateDir) {
@@ -1 +1 @@
1
- {"version":3,"file":"worker-fleet-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/worker-fleet-runner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAcH,MAAM,WAAW,iBAAiB;IAChC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AA6UD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,iBAAiB,EACzB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
1
+ {"version":3,"file":"worker-fleet-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/worker-fleet-runner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH,MAAM,WAAW,iBAAiB;IAChC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AA4VD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,iBAAiB,EACzB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
@@ -11,6 +11,7 @@ import path from 'path';
11
11
  import { fileURLToPath } from 'url';
12
12
  import { getVersion, checkForUpdate, printUpdateNotification } from './version.js';
13
13
  import { maybeAutoUpdate, isAutoUpdateEnabled } from './auto-updater.js';
14
+ import { startMergeWorkerSidecar } from './merge-worker-sidecar.js';
14
15
  // ---------------------------------------------------------------------------
15
16
  // ANSI colors
16
17
  // ---------------------------------------------------------------------------
@@ -60,6 +61,7 @@ function getDefaultWorkerScript() {
60
61
  // ---------------------------------------------------------------------------
61
62
  class WorkerFleet {
62
63
  workers = new Map();
64
+ mergeWorkerHandle = null;
63
65
  fleetConfig;
64
66
  shuttingDown = false;
65
67
  workerScript;
@@ -114,6 +116,11 @@ ${colors.cyan}================================================================${
114
116
  if (signal?.aborted)
115
117
  return;
116
118
  fleetLog(null, colors.green, 'INF', `All ${workers} workers started`);
119
+ // Start merge worker sidecar (one per fleet, Redis lock prevents duplicates)
120
+ this.mergeWorkerHandle = startMergeWorkerSidecar({}, signal);
121
+ if (this.mergeWorkerHandle) {
122
+ fleetLog(null, colors.cyan, 'INF', 'Merge worker sidecar started');
123
+ }
117
124
  // Periodic auto-update check (every 4 hours)
118
125
  if (isAutoUpdateEnabled(this.autoUpdateFlag)) {
119
126
  this.updateInterval = setInterval(async () => {
@@ -225,6 +232,13 @@ ${colors.cyan}================================================================${
225
232
  worker.process.on('exit', () => resolve());
226
233
  })));
227
234
  clearTimeout(forceKillTimeout);
235
+ // Stop merge worker sidecar
236
+ if (this.mergeWorkerHandle) {
237
+ fleetLog(null, colors.cyan, 'INF', 'Stopping merge worker sidecar...');
238
+ this.mergeWorkerHandle.stop();
239
+ await this.mergeWorkerHandle.done;
240
+ fleetLog(null, colors.cyan, 'INF', 'Merge worker sidecar stopped');
241
+ }
228
242
  console.log(`${colors.green}All workers stopped${colors.reset}`);
229
243
  // Resolve the running promise so start() returns
230
244
  this.resolveRunning?.();
@@ -1 +1 @@
1
- {"version":3,"file":"worker-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/worker-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgCH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mDAAmD;IACnD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AA4ED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAi/Bf"}
1
+ {"version":3,"file":"worker-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/worker-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkCH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mDAAmD;IACnD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AA4ED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CA0/Bf"}
@@ -8,8 +8,9 @@
8
8
  import path from 'path';
9
9
  import { execSync } from 'child_process';
10
10
  import os from 'os';
11
- import { createOrchestrator, createLogger, } from '@renseiai/agentfactory';
11
+ import { createOrchestrator, createLogger, loadRepositoryConfig, } from '@renseiai/agentfactory';
12
12
  import { LinearIssueTrackerClient, createLinearStatusMappings, linearPlugin, } from '@renseiai/plugin-linear';
13
+ import { MergeQueueStorage, createLocalMergeQueueStorage } from '@renseiai/agentfactory-server';
13
14
  let codeIntelligencePlugin;
14
15
  try {
15
16
  ;
@@ -426,11 +427,19 @@ export async function runWorker(config, signal) {
426
427
  // Create orchestrator with API activity proxy
427
428
  const issueTrackerClient = new LinearIssueTrackerClient({ apiKey: linearApiKey });
428
429
  const statusMappings = createLinearStatusMappings();
430
+ // Create local merge queue storage if configured
431
+ const repoConfig = loadRepositoryConfig(gitRoot);
432
+ const needsLocalStorage = repoConfig?.mergeQueue?.enabled &&
433
+ (!repoConfig.mergeQueue.provider || repoConfig.mergeQueue.provider === 'local');
434
+ const mergeQueueStorage = needsLocalStorage
435
+ ? createLocalMergeQueueStorage(new MergeQueueStorage())
436
+ : undefined;
429
437
  const orchestrator = createOrchestrator({
430
438
  maxConcurrent: 1,
431
439
  worktreePath: path.resolve(gitRoot, '..', path.basename(gitRoot) + '.wt'),
432
440
  issueTrackerClient,
433
441
  statusMappings,
442
+ mergeQueueStorage,
434
443
  toolPlugins: [linearPlugin, codeIntelligencePlugin].filter(Boolean),
435
444
  apiActivityConfig: {
436
445
  baseUrl: workerConfig.apiUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@renseiai/agentfactory-cli",
3
- "version": "0.8.18",
3
+ "version": "0.8.20",
4
4
  "type": "module",
5
5
  "description": "CLI tools for AgentFactory — local orchestrator, remote worker, queue admin",
6
6
  "author": "Rensei AI (https://rensei.ai)",
@@ -126,12 +126,12 @@
126
126
  ],
127
127
  "dependencies": {
128
128
  "dotenv": "^17.2.3",
129
- "@renseiai/plugin-linear": "0.8.18",
130
- "@renseiai/agentfactory-server": "0.8.18",
131
- "@renseiai/agentfactory": "0.8.18"
129
+ "@renseiai/agentfactory": "0.8.20",
130
+ "@renseiai/agentfactory-server": "0.8.20",
131
+ "@renseiai/plugin-linear": "0.8.20"
132
132
  },
133
133
  "optionalDependencies": {
134
- "@renseiai/agentfactory-code-intelligence": "0.8.18"
134
+ "@renseiai/agentfactory-code-intelligence": "0.8.20"
135
135
  },
136
136
  "devDependencies": {
137
137
  "@types/node": "^22.5.4",