@renseiai/agentfactory-cli 0.8.11 → 0.8.13

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.
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentFactory Code Intelligence CLI
4
+ *
5
+ * Exposes code-intelligence tools as CLI commands for use by Task sub-agents
6
+ * and other non-MCP contexts.
7
+ *
8
+ * Usage:
9
+ * af-code <command> [options]
10
+ *
11
+ * Commands:
12
+ * search-symbols <query> Search for code symbols by name
13
+ * get-repo-map Get PageRank-ranked repository file map
14
+ * search-code <query> BM25/hybrid code search
15
+ * check-duplicate Check content for duplicates
16
+ * help Show this help message
17
+ *
18
+ * Environment:
19
+ * VOYAGE_AI_API_KEY Optional — enables semantic vector embeddings
20
+ * COHERE_API_KEY Optional — enables cross-encoder reranking
21
+ */
22
+ export {};
23
+ //# sourceMappingURL=code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code.d.ts","sourceRoot":"","sources":["../../src/code.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;GAmBG"}
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentFactory Code Intelligence CLI
4
+ *
5
+ * Exposes code-intelligence tools as CLI commands for use by Task sub-agents
6
+ * and other non-MCP contexts.
7
+ *
8
+ * Usage:
9
+ * af-code <command> [options]
10
+ *
11
+ * Commands:
12
+ * search-symbols <query> Search for code symbols by name
13
+ * get-repo-map Get PageRank-ranked repository file map
14
+ * search-code <query> BM25/hybrid code search
15
+ * check-duplicate Check content for duplicates
16
+ * help Show this help message
17
+ *
18
+ * Environment:
19
+ * VOYAGE_AI_API_KEY Optional — enables semantic vector embeddings
20
+ * COHERE_API_KEY Optional — enables cross-encoder reranking
21
+ */
22
+ import path from 'path';
23
+ import { config } from 'dotenv';
24
+ // Load environment variables from .env.local
25
+ config({ path: path.resolve(process.cwd(), '.env.local'), quiet: true });
26
+ import { runCodeIntelligence, parseCodeArgs } from './lib/code-intelligence-runner.js';
27
+ function printHelp() {
28
+ console.log(`
29
+ AgentFactory Code Intelligence CLI — codebase search and analysis
30
+
31
+ Usage:
32
+ af-code <command> [options]
33
+
34
+ Commands:
35
+ search-symbols <query> Search for functions, classes, types by name
36
+ get-repo-map Get PageRank-ranked repository file map
37
+ search-code <query> BM25/hybrid keyword search with code-aware tokenization
38
+ check-duplicate Check content for exact/near duplicates
39
+ help Show this help message
40
+
41
+ Options (search-symbols):
42
+ --max-results <N> Maximum results (default: 20)
43
+ --kinds <kinds> Comma-separated symbol kinds: function,class,interface,type,etc.
44
+ --file-pattern <pattern> Filter by file pattern (e.g. "*.ts", "src/**")
45
+
46
+ Options (get-repo-map):
47
+ --max-files <N> Maximum files to include (default: 50)
48
+ --file-patterns <patterns> Comma-separated file pattern filters
49
+
50
+ Options (search-code):
51
+ --max-results <N> Maximum results (default: 20)
52
+ --language <lang> Filter by language (e.g. typescript, python)
53
+
54
+ Options (check-duplicate):
55
+ --content <string> Content to check (inline)
56
+ --content-file <path> Path to file containing content to check
57
+
58
+ Index:
59
+ First invocation builds the index from source files (~5-10s).
60
+ Subsequent calls reuse the persisted index from .agentfactory/code-index/.
61
+
62
+ Examples:
63
+ af-code search-symbols "SearchEngine"
64
+ af-code search-symbols "handleRequest" --kinds "function,method" --file-pattern "*.ts"
65
+ af-code get-repo-map --max-files 20
66
+ af-code search-code "incremental indexer" --language typescript
67
+ af-code check-duplicate --content "function hello() { return 'world' }"
68
+ af-code check-duplicate --content-file /tmp/snippet.ts
69
+ `);
70
+ }
71
+ async function main() {
72
+ const { command, args, positionalArgs } = parseCodeArgs(process.argv.slice(2));
73
+ if (!command || command === 'help' || args['help'] || args['h']) {
74
+ printHelp();
75
+ return;
76
+ }
77
+ const result = await runCodeIntelligence({
78
+ command,
79
+ args,
80
+ positionalArgs,
81
+ cwd: process.cwd(),
82
+ });
83
+ console.log(JSON.stringify(result.output, null, 2));
84
+ }
85
+ main().catch((error) => {
86
+ console.error('Error:', error instanceof Error ? error.message : error);
87
+ process.exit(1);
88
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,QAAA,MAAM,OAAO,QAAkB,CAAA;AAE/B,iBAAS,SAAS,IAAI,IAAI,CA0BzB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,QAAA,MAAM,OAAO,QAAkB,CAAA;AAE/B,iBAAS,SAAS,IAAI,IAAI,CA2BzB"}
package/dist/src/index.js CHANGED
@@ -22,6 +22,7 @@ Commands:
22
22
  agent Manage running agent sessions (stop, chat, status, reconnect)
23
23
  cleanup Clean up orphaned git worktrees
24
24
  queue-admin Manage Redis work queue and sessions
25
+ merge-queue Manage the Refinery merge queue
25
26
  analyze-logs Analyze agent session logs for errors
26
27
  linear Linear issue tracker operations
27
28
  sync-routes Generate missing route and page files from manifest
@@ -56,6 +57,9 @@ switch (command) {
56
57
  case 'queue-admin':
57
58
  import('./queue-admin');
58
59
  break;
60
+ case 'merge-queue':
61
+ import('./merge-queue');
62
+ break;
59
63
  case 'analyze-logs':
60
64
  import('./analyze-logs');
61
65
  break;
@@ -0,0 +1,16 @@
1
+ export interface CodeIntelligenceRunnerConfig {
2
+ command: string;
3
+ args: Record<string, string | string[] | boolean>;
4
+ positionalArgs: string[];
5
+ cwd: string;
6
+ }
7
+ export interface CodeIntelligenceRunnerResult {
8
+ output: unknown;
9
+ }
10
+ export declare function parseCodeArgs(argv: string[]): {
11
+ command: string | undefined;
12
+ args: Record<string, string | string[] | boolean>;
13
+ positionalArgs: string[];
14
+ };
15
+ export declare function runCodeIntelligence(config: CodeIntelligenceRunnerConfig): Promise<CodeIntelligenceRunnerResult>;
16
+ //# sourceMappingURL=code-intelligence-runner.d.ts.map
@@ -0,0 +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;AAgLD,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC,4BAA4B,CAAC,CAevC"}
@@ -0,0 +1,189 @@
1
+ import { readFile, readdir, stat } from 'node:fs/promises';
2
+ import { join, extname, relative } from 'node:path';
3
+ // ── Supported extensions (mirrors SymbolExtractor's EXTENSION_MAP) ──────────
4
+ const SUPPORTED_EXTENSIONS = new Set([
5
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
6
+ '.py', '.go', '.rs',
7
+ ]);
8
+ const IGNORE_DIRS = new Set([
9
+ 'node_modules', 'dist', '.git', '.next', '.turbo',
10
+ 'build', 'coverage', '__pycache__', '.agentfactory',
11
+ '.worktrees', 'vendor', 'target',
12
+ ]);
13
+ // ── Arg parsing ─────────────────────────────────────────────────────────────
14
+ export function parseCodeArgs(argv) {
15
+ const args = {};
16
+ const positionalArgs = [];
17
+ let command;
18
+ for (let i = 0; i < argv.length; i++) {
19
+ const arg = argv[i];
20
+ if (arg.startsWith('--')) {
21
+ const key = arg.slice(2);
22
+ const next = argv[i + 1];
23
+ if (next && !next.startsWith('--')) {
24
+ args[key] = next;
25
+ i++;
26
+ }
27
+ else {
28
+ args[key] = true;
29
+ }
30
+ }
31
+ else if (!command) {
32
+ command = arg;
33
+ }
34
+ else {
35
+ positionalArgs.push(arg);
36
+ }
37
+ }
38
+ return { command, args, positionalArgs };
39
+ }
40
+ // ── File discovery ──────────────────────────────────────────────────────────
41
+ async function discoverSourceFiles(cwd) {
42
+ const files = new Map();
43
+ async function walk(dir) {
44
+ let entries;
45
+ try {
46
+ entries = await readdir(dir, { withFileTypes: true });
47
+ }
48
+ catch {
49
+ return;
50
+ }
51
+ for (const entry of entries) {
52
+ if (entry.name.startsWith('.') && IGNORE_DIRS.has(entry.name))
53
+ continue;
54
+ if (IGNORE_DIRS.has(entry.name))
55
+ continue;
56
+ const fullPath = join(dir, entry.name);
57
+ if (entry.isDirectory()) {
58
+ await walk(fullPath);
59
+ }
60
+ else if (entry.isFile() && SUPPORTED_EXTENSIONS.has(extname(entry.name))) {
61
+ // Skip files larger than 512KB (likely generated)
62
+ try {
63
+ const s = await stat(fullPath);
64
+ if (s.size > 512 * 1024)
65
+ continue;
66
+ }
67
+ catch {
68
+ continue;
69
+ }
70
+ try {
71
+ const content = await readFile(fullPath, 'utf-8');
72
+ const relPath = relative(cwd, fullPath);
73
+ files.set(relPath, content);
74
+ }
75
+ catch {
76
+ // Skip unreadable files
77
+ }
78
+ }
79
+ }
80
+ }
81
+ await walk(cwd);
82
+ return files;
83
+ }
84
+ // ── Index initialization ────────────────────────────────────────────────────
85
+ async function initializeIndex(cwd) {
86
+ const { SymbolExtractor, IncrementalIndexer, SearchEngine, HybridSearchEngine, RepoMapGenerator, DedupPipeline, InMemoryStore, } = await import('@renseiai/agentfactory-code-intelligence');
87
+ const extractor = new SymbolExtractor();
88
+ const indexer = new IncrementalIndexer(extractor, { indexDir: '.agentfactory/code-index' });
89
+ const searchEngine = new SearchEngine();
90
+ const hybridEngine = new HybridSearchEngine(searchEngine, null, null);
91
+ const repoMapGen = new RepoMapGenerator();
92
+ const dedupStore = new InMemoryStore();
93
+ const dedupPipeline = new DedupPipeline(dedupStore);
94
+ // Try loading persisted index
95
+ const loaded = await indexer.load(cwd);
96
+ if (loaded) {
97
+ const symbols = indexer.getAllSymbols();
98
+ searchEngine.buildIndex(symbols);
99
+ }
100
+ else {
101
+ // No persisted index — discover files and build fresh
102
+ const files = await discoverSourceFiles(cwd);
103
+ await indexer.index(files);
104
+ await indexer.save(cwd);
105
+ const symbols = indexer.getAllSymbols();
106
+ searchEngine.buildIndex(symbols);
107
+ }
108
+ return { indexer, searchEngine, hybridEngine, repoMapGen, dedupPipeline, extractor };
109
+ }
110
+ // ── Command handlers ────────────────────────────────────────────────────────
111
+ async function searchSymbols(config, engines) {
112
+ const query = config.positionalArgs[0];
113
+ if (!query)
114
+ throw new Error('Usage: af-code search-symbols <query> [--max-results N] [--kinds "function,class"] [--file-pattern "*.ts"]');
115
+ const maxResults = config.args['max-results'] ? Number(config.args['max-results']) : 20;
116
+ const kinds = config.args['kinds']
117
+ ? (typeof config.args['kinds'] === 'string' ? config.args['kinds'].split(',').map(s => s.trim()) : config.args['kinds'])
118
+ : undefined;
119
+ const filePattern = typeof config.args['file-pattern'] === 'string' ? config.args['file-pattern'] : undefined;
120
+ const results = engines.searchEngine.search({
121
+ query,
122
+ maxResults,
123
+ symbolKinds: kinds,
124
+ filePattern,
125
+ });
126
+ return results;
127
+ }
128
+ async function getRepoMap(config, engines) {
129
+ const maxFiles = config.args['max-files'] ? Number(config.args['max-files']) : 50;
130
+ const rawPatterns = config.args['file-patterns'];
131
+ const filePatterns = typeof rawPatterns === 'string'
132
+ ? rawPatterns.split(',').map(s => s.trim())
133
+ : Array.isArray(rawPatterns) ? rawPatterns : undefined;
134
+ // Rebuild ASTs from the file index for PageRank computation
135
+ const fileIndex = engines.indexer.getFileIndex();
136
+ const asts = [...fileIndex.values()].map(fi => ({
137
+ filePath: fi.filePath,
138
+ language: fi.symbols[0]?.language ?? 'unknown',
139
+ symbols: fi.symbols,
140
+ imports: [],
141
+ exports: fi.symbols.filter(s => s.exported).map(s => s.name),
142
+ }));
143
+ const entries = engines.repoMapGen.generate(asts, { maxFiles, filePatterns });
144
+ const formatted = engines.repoMapGen.format(entries);
145
+ return { entries, formatted };
146
+ }
147
+ async function searchCode(config, engines) {
148
+ const query = config.positionalArgs[0];
149
+ if (!query)
150
+ throw new Error('Usage: af-code search-code <query> [--max-results N] [--language ts]');
151
+ const maxResults = config.args['max-results'] ? Number(config.args['max-results']) : 20;
152
+ const language = typeof config.args['language'] === 'string' ? config.args['language'] : undefined;
153
+ const results = await engines.hybridEngine.search({
154
+ query,
155
+ maxResults,
156
+ language,
157
+ });
158
+ return results;
159
+ }
160
+ async function checkDuplicate(config, engines) {
161
+ let content;
162
+ if (typeof config.args['content-file'] === 'string') {
163
+ content = await readFile(config.args['content-file'], 'utf-8');
164
+ }
165
+ else if (typeof config.args['content'] === 'string') {
166
+ content = config.args['content'];
167
+ }
168
+ else {
169
+ throw new Error('Usage: af-code check-duplicate --content "<code>" or --content-file /path/to/file');
170
+ }
171
+ const result = await engines.dedupPipeline.check(content);
172
+ return result;
173
+ }
174
+ // ── Main runner ─────────────────────────────────────────────────────────────
175
+ export async function runCodeIntelligence(config) {
176
+ const engines = await initializeIndex(config.cwd);
177
+ switch (config.command) {
178
+ case 'search-symbols':
179
+ return { output: await searchSymbols(config, engines) };
180
+ case 'get-repo-map':
181
+ return { output: await getRepoMap(config, engines) };
182
+ case 'search-code':
183
+ return { output: await searchCode(config, engines) };
184
+ case 'check-duplicate':
185
+ return { output: await checkDuplicate(config, engines) };
186
+ default:
187
+ throw new Error(`Unknown command: ${config.command}. Available: search-symbols, get-repo-map, search-code, check-duplicate`);
188
+ }
189
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Merge Queue Runner -- Programmatic API for the merge-queue CLI.
3
+ *
4
+ * Extracts ALL command handlers from the merge-queue bin script so they can be
5
+ * invoked programmatically (e.g. from a Next.js route handler or test) without
6
+ * process.exit / dotenv / argv coupling.
7
+ */
8
+ export type MergeQueueCommand = 'status' | 'list' | 'retry' | 'skip' | 'pause' | 'resume' | 'priority';
9
+ export interface MergeQueueRunnerConfig {
10
+ /** Command to execute */
11
+ command: MergeQueueCommand;
12
+ /** Remaining CLI arguments (flags, positional args) */
13
+ args: string[];
14
+ }
15
+ export declare const C: {
16
+ readonly reset: "\u001B[0m";
17
+ readonly bold: "\u001B[1m";
18
+ readonly dim: "\u001B[2m";
19
+ readonly red: "\u001B[31m";
20
+ readonly green: "\u001B[32m";
21
+ readonly yellow: "\u001B[33m";
22
+ readonly blue: "\u001B[34m";
23
+ readonly cyan: "\u001B[36m";
24
+ };
25
+ export declare function parseArgs(args: string[]): {
26
+ repoId: string;
27
+ prNumber?: number;
28
+ priority?: number;
29
+ };
30
+ export declare function formatAge(ms: number): string;
31
+ /**
32
+ * Run a merge-queue command programmatically.
33
+ *
34
+ * Throws if REDIS_URL is not set or if required arguments are missing.
35
+ */
36
+ export declare function runMergeQueueCommand(config: MergeQueueRunnerConfig): Promise<void>;
37
+ //# sourceMappingURL=merge-queue-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-queue-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/merge-queue-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,MAAM,GACN,OAAO,GACP,MAAM,GACN,OAAO,GACP,QAAQ,GACR,UAAU,CAAA;AAEd,MAAM,WAAW,sBAAsB;IACrC,yBAAyB;IACzB,OAAO,EAAE,iBAAiB,CAAA;IAC1B,uDAAuD;IACvD,IAAI,EAAE,MAAM,EAAE,CAAA;CACf;AAMD,eAAO,MAAM,CAAC;;;;;;;;;CASJ,CAAA;AAYV,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBlG;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAS5C;AAiHD;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDxF"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Merge Queue Runner -- Programmatic API for the merge-queue CLI.
3
+ *
4
+ * Extracts ALL command handlers from the merge-queue bin script so they can be
5
+ * invoked programmatically (e.g. from a Next.js route handler or test) without
6
+ * process.exit / dotenv / argv coupling.
7
+ */
8
+ import { MergeQueueStorage, isRedisConfigured, disconnectRedis, redisSet, redisDel, } from '@renseiai/agentfactory-server';
9
+ // ---------------------------------------------------------------------------
10
+ // ANSI colors
11
+ // ---------------------------------------------------------------------------
12
+ export const C = {
13
+ reset: '\x1b[0m',
14
+ bold: '\x1b[1m',
15
+ dim: '\x1b[2m',
16
+ red: '\x1b[31m',
17
+ green: '\x1b[32m',
18
+ yellow: '\x1b[33m',
19
+ blue: '\x1b[34m',
20
+ cyan: '\x1b[36m',
21
+ };
22
+ // ---------------------------------------------------------------------------
23
+ // Helpers
24
+ // ---------------------------------------------------------------------------
25
+ function ensureRedis() {
26
+ if (!isRedisConfigured()) {
27
+ throw new Error('REDIS_URL environment variable is not set');
28
+ }
29
+ }
30
+ export function parseArgs(args) {
31
+ let repoId = 'default';
32
+ let prNumber;
33
+ let priority;
34
+ for (let i = 0; i < args.length; i++) {
35
+ if (args[i] === '--repo' && args[i + 1]) {
36
+ repoId = args[i + 1];
37
+ i++;
38
+ }
39
+ else if (prNumber === undefined && /^\d+$/.test(args[i])) {
40
+ prNumber = parseInt(args[i], 10);
41
+ }
42
+ else if (prNumber !== undefined && priority === undefined && /^\d+$/.test(args[i])) {
43
+ priority = parseInt(args[i], 10);
44
+ }
45
+ }
46
+ return { repoId, prNumber, priority };
47
+ }
48
+ export function formatAge(ms) {
49
+ const seconds = Math.floor(ms / 1000);
50
+ if (seconds < 60)
51
+ return `${seconds}s`;
52
+ const minutes = Math.floor(seconds / 60);
53
+ if (minutes < 60)
54
+ return `${minutes}m`;
55
+ const hours = Math.floor(minutes / 60);
56
+ if (hours < 24)
57
+ return `${hours}h ${minutes % 60}m`;
58
+ const days = Math.floor(hours / 24);
59
+ return `${days}d ${hours % 24}h`;
60
+ }
61
+ // ---------------------------------------------------------------------------
62
+ // Command handlers
63
+ // ---------------------------------------------------------------------------
64
+ async function handleStatus(storage, repoId) {
65
+ const status = await storage.getStatus(repoId);
66
+ console.log(`\n${C.bold}Merge Queue Status${C.reset} (repo: ${C.cyan}${repoId}${C.reset})`);
67
+ console.log('\u2500'.repeat(50));
68
+ console.log(` Queue depth: ${C.bold}${status.depth}${C.reset}`);
69
+ console.log(` Processing: ${status.processing
70
+ ? `${C.green}PR #${status.processing.prNumber}${C.reset} (${status.processing.sourceBranch})`
71
+ : `${C.dim}none${C.reset}`}`);
72
+ console.log(` Failed: ${status.failedCount > 0 ? `${C.red}${status.failedCount}${C.reset}` : `${C.dim}0${C.reset}`}`);
73
+ console.log(` Blocked: ${status.blockedCount > 0 ? `${C.yellow}${status.blockedCount}${C.reset}` : `${C.dim}0${C.reset}`}`);
74
+ console.log();
75
+ }
76
+ async function handleList(storage, repoId) {
77
+ const entries = await storage.list(repoId);
78
+ if (entries.length === 0) {
79
+ console.log(`\n${C.dim}No PRs in merge queue for repo: ${repoId}${C.reset}\n`);
80
+ return;
81
+ }
82
+ console.log(`\n${C.bold}Queued PRs${C.reset} (repo: ${C.cyan}${repoId}${C.reset})`);
83
+ console.log('\u2500'.repeat(70));
84
+ console.log(` ${C.dim}# PR Branch Priority Age${C.reset}`);
85
+ console.log('\u2500'.repeat(70));
86
+ entries.forEach((entry, index) => {
87
+ const age = formatAge(Date.now() - entry.enqueuedAt);
88
+ const branch = entry.sourceBranch.length > 30
89
+ ? entry.sourceBranch.slice(0, 27) + '...'
90
+ : entry.sourceBranch.padEnd(30);
91
+ console.log(` ${String(index + 1).padStart(2)} ${C.bold}#${String(entry.prNumber).padEnd(4)}${C.reset} ${branch} ${entry.priority} ${age}`);
92
+ });
93
+ // Also show failed
94
+ const failed = await storage.listFailed(repoId);
95
+ if (failed.length > 0) {
96
+ console.log(`\n${C.red}${C.bold}Failed PRs${C.reset}`);
97
+ console.log('\u2500'.repeat(70));
98
+ failed.forEach((entry) => {
99
+ console.log(` ${C.red}#${entry.prNumber}${C.reset} ${entry.sourceBranch} \u2014 ${entry.failureReason}`);
100
+ });
101
+ }
102
+ // Show blocked
103
+ const blocked = await storage.listBlocked(repoId);
104
+ if (blocked.length > 0) {
105
+ console.log(`\n${C.yellow}${C.bold}Blocked PRs${C.reset}`);
106
+ console.log('\u2500'.repeat(70));
107
+ blocked.forEach((entry) => {
108
+ console.log(` ${C.yellow}#${entry.prNumber}${C.reset} ${entry.sourceBranch} \u2014 ${entry.blockReason}`);
109
+ });
110
+ }
111
+ console.log();
112
+ }
113
+ async function handleRetry(storage, repoId, prNumber) {
114
+ await storage.retry(repoId, prNumber);
115
+ console.log(`${C.green}PR #${prNumber} moved back to queue${C.reset}`);
116
+ }
117
+ async function handleSkip(storage, repoId, prNumber) {
118
+ await storage.skip(repoId, prNumber);
119
+ console.log(`${C.yellow}PR #${prNumber} removed from queue${C.reset}`);
120
+ }
121
+ async function handlePause(repoId) {
122
+ await redisSet(`merge:paused:${repoId}`, 'true');
123
+ console.log(`${C.yellow}Merge queue paused for repo: ${repoId}${C.reset}`);
124
+ }
125
+ async function handleResume(repoId) {
126
+ await redisDel(`merge:paused:${repoId}`);
127
+ console.log(`${C.green}Merge queue resumed for repo: ${repoId}${C.reset}`);
128
+ }
129
+ async function handlePriority(storage, repoId, prNumber, priority) {
130
+ await storage.reorder(repoId, prNumber, priority);
131
+ console.log(`${C.green}PR #${prNumber} priority updated to ${priority}${C.reset}`);
132
+ }
133
+ // ---------------------------------------------------------------------------
134
+ // Public entry point
135
+ // ---------------------------------------------------------------------------
136
+ /**
137
+ * Run a merge-queue command programmatically.
138
+ *
139
+ * Throws if REDIS_URL is not set or if required arguments are missing.
140
+ */
141
+ export async function runMergeQueueCommand(config) {
142
+ ensureRedis();
143
+ const storage = new MergeQueueStorage();
144
+ const { repoId, prNumber, priority } = parseArgs(config.args);
145
+ try {
146
+ switch (config.command) {
147
+ case 'status':
148
+ await handleStatus(storage, repoId);
149
+ break;
150
+ case 'list':
151
+ await handleList(storage, repoId);
152
+ break;
153
+ case 'retry':
154
+ if (!prNumber) {
155
+ console.error(`${C.red}Error: PR number required. Usage: af merge-queue retry <prNumber>${C.reset}`);
156
+ process.exit(1);
157
+ }
158
+ await handleRetry(storage, repoId, prNumber);
159
+ break;
160
+ case 'skip':
161
+ if (!prNumber) {
162
+ console.error(`${C.red}Error: PR number required. Usage: af merge-queue skip <prNumber>${C.reset}`);
163
+ process.exit(1);
164
+ }
165
+ await handleSkip(storage, repoId, prNumber);
166
+ break;
167
+ case 'pause':
168
+ await handlePause(repoId);
169
+ break;
170
+ case 'resume':
171
+ await handleResume(repoId);
172
+ break;
173
+ case 'priority':
174
+ if (!prNumber || priority === undefined) {
175
+ console.error(`${C.red}Error: PR number and priority required. Usage: af merge-queue priority <prNumber> <priority>${C.reset}`);
176
+ process.exit(1);
177
+ }
178
+ await handlePriority(storage, repoId, prNumber, priority);
179
+ break;
180
+ }
181
+ }
182
+ finally {
183
+ await disconnectRedis();
184
+ }
185
+ }
@@ -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,EACvB,MAAM,wBAAwB,CAAA;AAY/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,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"}
@@ -9,7 +9,14 @@ import path from 'path';
9
9
  import { execSync } from 'child_process';
10
10
  import { createOrchestrator, } from '@renseiai/agentfactory';
11
11
  import { LinearIssueTrackerClient, createLinearStatusMappings, linearPlugin, } from '@renseiai/plugin-linear';
12
- import { codeIntelligencePlugin } from '@renseiai/agentfactory-code-intelligence';
12
+ let codeIntelligencePlugin;
13
+ try {
14
+ ;
15
+ ({ codeIntelligencePlugin } = await import('@renseiai/agentfactory-code-intelligence'));
16
+ }
17
+ catch {
18
+ // code-intelligence is optional — agents run without af_code_* tools
19
+ }
13
20
  // ---------------------------------------------------------------------------
14
21
  // Helpers
15
22
  // ---------------------------------------------------------------------------
@@ -88,7 +95,7 @@ export async function runOrchestrator(config) {
88
95
  linearApiKey: config.linearApiKey,
89
96
  issueTrackerClient,
90
97
  statusMappings,
91
- toolPlugins: [linearPlugin, codeIntelligencePlugin],
98
+ toolPlugins: [linearPlugin, codeIntelligencePlugin].filter(Boolean),
92
99
  };
93
100
  if (config.templateDir) {
94
101
  orchestratorConfig.templateDir = config.templateDir;
@@ -1 +1 @@
1
- {"version":3,"file":"worker-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/worker-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAyBH,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;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"}
@@ -10,7 +10,14 @@ import { execSync } from 'child_process';
10
10
  import os from 'os';
11
11
  import { createOrchestrator, createLogger, } from '@renseiai/agentfactory';
12
12
  import { LinearIssueTrackerClient, createLinearStatusMappings, linearPlugin, } from '@renseiai/plugin-linear';
13
- import { codeIntelligencePlugin } from '@renseiai/agentfactory-code-intelligence';
13
+ let codeIntelligencePlugin;
14
+ try {
15
+ ;
16
+ ({ codeIntelligencePlugin } = await import('@renseiai/agentfactory-code-intelligence'));
17
+ }
18
+ catch {
19
+ // code-intelligence is optional — agents run without af_code_* tools
20
+ }
14
21
  // ---------------------------------------------------------------------------
15
22
  // Helpers (stateless)
16
23
  // ---------------------------------------------------------------------------
@@ -424,7 +431,7 @@ export async function runWorker(config, signal) {
424
431
  worktreePath: path.resolve(gitRoot, '..', path.basename(gitRoot) + '.wt'),
425
432
  issueTrackerClient,
426
433
  statusMappings,
427
- toolPlugins: [linearPlugin, codeIntelligencePlugin],
434
+ toolPlugins: [linearPlugin, codeIntelligencePlugin].filter(Boolean),
428
435
  apiActivityConfig: {
429
436
  baseUrl: workerConfig.apiUrl,
430
437
  apiKey: workerConfig.apiKey,
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentFactory Merge Queue CLI
4
+ *
5
+ * Thin wrapper around the merge-queue runner. Handles dotenv, arg parsing,
6
+ * and process.exit so the runner stays process-agnostic.
7
+ *
8
+ * Usage:
9
+ * af merge-queue <command> [options]
10
+ *
11
+ * Commands:
12
+ * status [--repo <repoId>] Show queue overview
13
+ * list [--repo <repoId>] List all queued PRs
14
+ * retry <prNumber> [--repo <repoId>] Move failed/blocked PR back to queue
15
+ * skip <prNumber> [--repo <repoId>] Remove PR from queue
16
+ * pause [--repo <repoId>] Pause queue processing
17
+ * resume [--repo <repoId>] Resume queue processing
18
+ * priority <prNumber> <priority> [--repo <repoId>] Change PR priority
19
+ *
20
+ * Environment (loaded from .env.local in CWD):
21
+ * REDIS_URL Required for Redis connection
22
+ */
23
+ export {};
24
+ //# sourceMappingURL=merge-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-queue.d.ts","sourceRoot":"","sources":["../../src/merge-queue.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;GAoBG"}
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentFactory Merge Queue CLI
4
+ *
5
+ * Thin wrapper around the merge-queue runner. Handles dotenv, arg parsing,
6
+ * and process.exit so the runner stays process-agnostic.
7
+ *
8
+ * Usage:
9
+ * af merge-queue <command> [options]
10
+ *
11
+ * Commands:
12
+ * status [--repo <repoId>] Show queue overview
13
+ * list [--repo <repoId>] List all queued PRs
14
+ * retry <prNumber> [--repo <repoId>] Move failed/blocked PR back to queue
15
+ * skip <prNumber> [--repo <repoId>] Remove PR from queue
16
+ * pause [--repo <repoId>] Pause queue processing
17
+ * resume [--repo <repoId>] Resume queue processing
18
+ * priority <prNumber> <priority> [--repo <repoId>] Change PR priority
19
+ *
20
+ * Environment (loaded from .env.local in CWD):
21
+ * REDIS_URL Required for Redis connection
22
+ */
23
+ import path from 'path';
24
+ import { config } from 'dotenv';
25
+ // Load environment variables from .env.local in CWD
26
+ config({ path: path.resolve(process.cwd(), '.env.local') });
27
+ import { runMergeQueueCommand, C } from './lib/merge-queue-runner.js';
28
+ // ---------------------------------------------------------------------------
29
+ // Usage
30
+ // ---------------------------------------------------------------------------
31
+ function printUsage() {
32
+ console.log(`
33
+ ${C.cyan}AgentFactory Merge Queue${C.reset} - Manage the Refinery merge queue
34
+
35
+ ${C.yellow}Usage:${C.reset}
36
+ af merge-queue <command> [options]
37
+
38
+ ${C.yellow}Commands:${C.reset}
39
+ status [--repo <repoId>] Show queue overview
40
+ list [--repo <repoId>] List all queued PRs
41
+ retry <prNumber> [--repo <repoId>] Move failed/blocked PR back to queue
42
+ skip <prNumber> [--repo <repoId>] Remove PR from queue
43
+ pause [--repo <repoId>] Pause queue processing
44
+ resume [--repo <repoId>] Resume queue processing
45
+ priority <prNumber> <priority> [--repo <repoId>] Change PR priority
46
+
47
+ ${C.yellow}Examples:${C.reset}
48
+ af merge-queue status
49
+ af merge-queue list --repo my-org/my-repo
50
+ af merge-queue retry 42
51
+ af merge-queue skip 42 --repo my-org/my-repo
52
+ af merge-queue pause
53
+ af merge-queue resume
54
+ af merge-queue priority 42 1
55
+ `);
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Valid commands
59
+ // ---------------------------------------------------------------------------
60
+ const VALID_COMMANDS = new Set([
61
+ 'status',
62
+ 'list',
63
+ 'retry',
64
+ 'skip',
65
+ 'pause',
66
+ 'resume',
67
+ 'priority',
68
+ ]);
69
+ // ---------------------------------------------------------------------------
70
+ // Main
71
+ // ---------------------------------------------------------------------------
72
+ async function main() {
73
+ const [command, ...args] = process.argv.slice(3); // argv[2] is 'merge-queue'
74
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
75
+ printUsage();
76
+ return;
77
+ }
78
+ if (!VALID_COMMANDS.has(command)) {
79
+ console.error(`Unknown command: ${command}`);
80
+ printUsage();
81
+ process.exit(1);
82
+ }
83
+ await runMergeQueueCommand({
84
+ command: command,
85
+ args,
86
+ });
87
+ }
88
+ main().catch((error) => {
89
+ console.error('Error:', error instanceof Error ? error.message : error);
90
+ process.exit(1);
91
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@renseiai/agentfactory-cli",
3
- "version": "0.8.11",
3
+ "version": "0.8.13",
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)",
@@ -40,7 +40,9 @@
40
40
  "af-sync-routes": "dist/src/sync-routes.js",
41
41
  "af-setup": "dist/src/setup.js",
42
42
  "af-status": "dist/src/status.js",
43
- "af-migrate-worktrees": "dist/src/migrate-worktrees.js"
43
+ "af-migrate-worktrees": "dist/src/migrate-worktrees.js",
44
+ "af-merge-queue": "dist/src/merge-queue.js",
45
+ "af-code": "dist/src/code.js"
44
46
  },
45
47
  "main": "./dist/src/index.js",
46
48
  "module": "./dist/src/index.js",
@@ -105,6 +107,16 @@
105
107
  "types": "./dist/src/lib/status-runner.d.ts",
106
108
  "import": "./dist/src/lib/status-runner.js",
107
109
  "default": "./dist/src/lib/status-runner.js"
110
+ },
111
+ "./merge-queue": {
112
+ "types": "./dist/src/lib/merge-queue-runner.d.ts",
113
+ "import": "./dist/src/lib/merge-queue-runner.js",
114
+ "default": "./dist/src/lib/merge-queue-runner.js"
115
+ },
116
+ "./code-intelligence": {
117
+ "types": "./dist/src/lib/code-intelligence-runner.d.ts",
118
+ "import": "./dist/src/lib/code-intelligence-runner.js",
119
+ "default": "./dist/src/lib/code-intelligence-runner.js"
108
120
  }
109
121
  },
110
122
  "files": [
@@ -114,10 +126,12 @@
114
126
  ],
115
127
  "dependencies": {
116
128
  "dotenv": "^17.2.3",
117
- "@renseiai/agentfactory-server": "0.8.11",
118
- "@renseiai/agentfactory-code-intelligence": "0.8.11",
119
- "@renseiai/plugin-linear": "0.8.11",
120
- "@renseiai/agentfactory": "0.8.11"
129
+ "@renseiai/agentfactory": "0.8.13",
130
+ "@renseiai/agentfactory-server": "0.8.13",
131
+ "@renseiai/plugin-linear": "0.8.13"
132
+ },
133
+ "optionalDependencies": {
134
+ "@renseiai/agentfactory-code-intelligence": "0.8.13"
121
135
  },
122
136
  "devDependencies": {
123
137
  "@types/node": "^22.5.4",