@supaku/agentfactory-cli 0.7.9 → 0.7.11
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/dist/src/governor.d.ts +25 -0
- package/dist/src/governor.d.ts.map +1 -0
- package/dist/src/governor.js +166 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/lib/governor-dependencies.d.ts +20 -0
- package/dist/src/lib/governor-dependencies.d.ts.map +1 -0
- package/dist/src/lib/governor-dependencies.js +306 -0
- package/dist/src/lib/governor-runner.d.ts +98 -0
- package/dist/src/lib/governor-runner.d.ts.map +1 -0
- package/dist/src/lib/governor-runner.js +191 -0
- package/dist/src/lib/linear-runner.d.ts.map +1 -1
- package/dist/src/lib/linear-runner.js +6 -4
- package/dist/src/lib/orchestrator-runner.d.ts +4 -0
- package/dist/src/lib/orchestrator-runner.d.ts.map +1 -1
- package/dist/src/lib/orchestrator-runner.js +9 -2
- package/dist/src/orchestrator.js +13 -0
- package/package.json +6 -5
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AgentFactory Governor CLI
|
|
4
|
+
*
|
|
5
|
+
* Thin wrapper around the governor runner.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* af-governor [options]
|
|
9
|
+
*
|
|
10
|
+
* Options:
|
|
11
|
+
* --project <name> Project to scan (can be repeated)
|
|
12
|
+
* --scan-interval <ms> Scan interval in milliseconds (default: 60000)
|
|
13
|
+
* --max-dispatches <n> Maximum concurrent dispatches per scan (default: 3)
|
|
14
|
+
* --no-auto-research Disable auto-research from Icebox
|
|
15
|
+
* --no-auto-backlog-creation Disable auto-backlog-creation from Icebox
|
|
16
|
+
* --no-auto-development Disable auto-development from Backlog
|
|
17
|
+
* --no-auto-qa Disable auto-QA from Finished
|
|
18
|
+
* --no-auto-acceptance Disable auto-acceptance from Delivered
|
|
19
|
+
* --once Run a single scan pass and exit
|
|
20
|
+
*
|
|
21
|
+
* Environment:
|
|
22
|
+
* LINEAR_API_KEY Required API key for Linear authentication
|
|
23
|
+
*/
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=governor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"governor.d.ts","sourceRoot":"","sources":["../../src/governor.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AgentFactory Governor CLI
|
|
4
|
+
*
|
|
5
|
+
* Thin wrapper around the governor runner.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* af-governor [options]
|
|
9
|
+
*
|
|
10
|
+
* Options:
|
|
11
|
+
* --project <name> Project to scan (can be repeated)
|
|
12
|
+
* --scan-interval <ms> Scan interval in milliseconds (default: 60000)
|
|
13
|
+
* --max-dispatches <n> Maximum concurrent dispatches per scan (default: 3)
|
|
14
|
+
* --no-auto-research Disable auto-research from Icebox
|
|
15
|
+
* --no-auto-backlog-creation Disable auto-backlog-creation from Icebox
|
|
16
|
+
* --no-auto-development Disable auto-development from Backlog
|
|
17
|
+
* --no-auto-qa Disable auto-QA from Finished
|
|
18
|
+
* --no-auto-acceptance Disable auto-acceptance from Delivered
|
|
19
|
+
* --once Run a single scan pass and exit
|
|
20
|
+
*
|
|
21
|
+
* Environment:
|
|
22
|
+
* LINEAR_API_KEY Required API key for Linear authentication
|
|
23
|
+
*/
|
|
24
|
+
import path from 'path';
|
|
25
|
+
import { config } from 'dotenv';
|
|
26
|
+
// Load environment variables from .env.local
|
|
27
|
+
config({ path: path.resolve(process.cwd(), '.env.local') });
|
|
28
|
+
import { parseGovernorArgs, runGovernor, } from './lib/governor-runner.js';
|
|
29
|
+
import { createRealDependencies } from './lib/governor-dependencies.js';
|
|
30
|
+
import { createLinearAgentClient } from '@supaku/agentfactory-linear';
|
|
31
|
+
import { initTouchpointStorage } from '@supaku/agentfactory';
|
|
32
|
+
import { RedisOverrideStorage } from '@supaku/agentfactory-server';
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Stub dependencies
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* Create placeholder dependencies for the Governor.
|
|
38
|
+
*
|
|
39
|
+
* In a production deployment, these would be backed by the Linear SDK
|
|
40
|
+
* and Redis (via packages/server). For now we provide stubs that log
|
|
41
|
+
* calls and return safe defaults. The WorkSchedulingFrontend (SUP-709
|
|
42
|
+
* Wave 3) will provide the real implementations.
|
|
43
|
+
*/
|
|
44
|
+
function createStubDependencies() {
|
|
45
|
+
const log = {
|
|
46
|
+
warn: (msg, data) => console.warn(`[governor-stub] ${msg}`, data ? JSON.stringify(data) : ''),
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
listIssues: async (_project) => {
|
|
50
|
+
log.warn('listIssues stub called — no issues returned', { project: _project });
|
|
51
|
+
return [];
|
|
52
|
+
},
|
|
53
|
+
hasActiveSession: async (_issueId) => false,
|
|
54
|
+
isWithinCooldown: async (_issueId) => false,
|
|
55
|
+
isParentIssue: async (_issueId) => false,
|
|
56
|
+
isHeld: async (_issueId) => false,
|
|
57
|
+
getOverridePriority: async (_issueId) => null,
|
|
58
|
+
getWorkflowStrategy: async (_issueId) => undefined,
|
|
59
|
+
isResearchCompleted: async (_issueId) => false,
|
|
60
|
+
isBacklogCreationCompleted: async (_issueId) => false,
|
|
61
|
+
dispatchWork: async (_issueId, _action) => {
|
|
62
|
+
log.warn('dispatchWork stub called', { issueId: _issueId, action: _action });
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Main
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
async function main() {
|
|
70
|
+
const args = parseGovernorArgs();
|
|
71
|
+
if (args.projects.length === 0) {
|
|
72
|
+
console.error('Error: at least one --project is required');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
console.log('AgentFactory Governor');
|
|
76
|
+
console.log('=====================');
|
|
77
|
+
console.log(`Projects: ${args.projects.join(', ')}`);
|
|
78
|
+
console.log(`Scan interval: ${args.scanIntervalMs}ms`);
|
|
79
|
+
console.log(`Max dispatches per scan: ${args.maxConcurrentDispatches}`);
|
|
80
|
+
console.log(`Execution mode: ${args.mode}`);
|
|
81
|
+
console.log(`Mode: ${args.once ? 'single scan' : 'continuous'}`);
|
|
82
|
+
console.log('');
|
|
83
|
+
// -----------------------------------------------------------------------
|
|
84
|
+
// Choose real or stub dependencies based on environment
|
|
85
|
+
// -----------------------------------------------------------------------
|
|
86
|
+
let dependencies;
|
|
87
|
+
const linearApiKey = process.env.LINEAR_API_KEY;
|
|
88
|
+
const redisUrl = process.env.REDIS_URL;
|
|
89
|
+
if (linearApiKey) {
|
|
90
|
+
console.log('LINEAR_API_KEY detected — using real dependencies');
|
|
91
|
+
const linearClient = createLinearAgentClient({ apiKey: linearApiKey });
|
|
92
|
+
// Initialize touchpoint storage (for isHeld / getOverridePriority) when Redis is available
|
|
93
|
+
if (redisUrl) {
|
|
94
|
+
console.log('REDIS_URL detected — initializing Redis-backed touchpoint storage');
|
|
95
|
+
initTouchpointStorage(new RedisOverrideStorage());
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.warn('Warning: REDIS_URL not set — touchpoint overrides (HOLD, PRIORITY) will not persist');
|
|
99
|
+
}
|
|
100
|
+
dependencies = createRealDependencies({ linearClient });
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.warn('Warning: LINEAR_API_KEY not set — using stub dependencies (no real work will be dispatched)');
|
|
104
|
+
dependencies = createStubDependencies();
|
|
105
|
+
}
|
|
106
|
+
const runnerConfig = {
|
|
107
|
+
projects: args.projects,
|
|
108
|
+
scanIntervalMs: args.scanIntervalMs,
|
|
109
|
+
maxConcurrentDispatches: args.maxConcurrentDispatches,
|
|
110
|
+
enableAutoResearch: args.enableAutoResearch,
|
|
111
|
+
enableAutoBacklogCreation: args.enableAutoBacklogCreation,
|
|
112
|
+
enableAutoDevelopment: args.enableAutoDevelopment,
|
|
113
|
+
enableAutoQA: args.enableAutoQA,
|
|
114
|
+
enableAutoAcceptance: args.enableAutoAcceptance,
|
|
115
|
+
once: args.once,
|
|
116
|
+
mode: args.mode,
|
|
117
|
+
dependencies,
|
|
118
|
+
callbacks: {
|
|
119
|
+
onScanComplete: (results) => {
|
|
120
|
+
for (const result of results) {
|
|
121
|
+
console.log(`[${result.project}] Scanned ${result.scannedIssues} issues, dispatched ${result.actionsDispatched}`);
|
|
122
|
+
if (result.errors.length > 0) {
|
|
123
|
+
for (const err of result.errors) {
|
|
124
|
+
console.error(` Error: ${err.issueId} — ${err.error}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
onError: (error) => {
|
|
130
|
+
console.error('Governor error:', error.message);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
const { governor, scanResults } = await runGovernor(runnerConfig);
|
|
136
|
+
if (args.once && scanResults) {
|
|
137
|
+
// Print summary and exit
|
|
138
|
+
let totalDispatched = 0;
|
|
139
|
+
let totalErrors = 0;
|
|
140
|
+
for (const result of scanResults) {
|
|
141
|
+
totalDispatched += result.actionsDispatched;
|
|
142
|
+
totalErrors += result.errors.length;
|
|
143
|
+
}
|
|
144
|
+
console.log('');
|
|
145
|
+
console.log(`Scan complete: ${totalDispatched} actions dispatched, ${totalErrors} errors`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// Continuous mode — handle graceful shutdown
|
|
149
|
+
console.log('Governor running. Press Ctrl+C to stop.');
|
|
150
|
+
const shutdown = () => {
|
|
151
|
+
console.log('\nShutting down governor...');
|
|
152
|
+
governor.stop();
|
|
153
|
+
process.exit(0);
|
|
154
|
+
};
|
|
155
|
+
process.on('SIGINT', shutdown);
|
|
156
|
+
process.on('SIGTERM', shutdown);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.error('Governor failed:', error instanceof Error ? error.message : error);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
main().catch((error) => {
|
|
164
|
+
console.error('Fatal error:', error);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
});
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,CAsBzB"}
|
package/dist/src/index.js
CHANGED
|
@@ -16,6 +16,7 @@ Usage:
|
|
|
16
16
|
|
|
17
17
|
Commands:
|
|
18
18
|
orchestrator Spawn concurrent agents on backlog issues
|
|
19
|
+
governor Automated workflow scan loop with configurable triggers
|
|
19
20
|
worker Start a remote worker that polls for queued work
|
|
20
21
|
worker-fleet Spawn and manage multiple worker processes
|
|
21
22
|
cleanup Clean up orphaned git worktrees
|
|
@@ -33,6 +34,9 @@ switch (command) {
|
|
|
33
34
|
case 'orchestrator':
|
|
34
35
|
import('./orchestrator');
|
|
35
36
|
break;
|
|
37
|
+
case 'governor':
|
|
38
|
+
import('./governor');
|
|
39
|
+
break;
|
|
36
40
|
case 'worker':
|
|
37
41
|
import('./worker');
|
|
38
42
|
break;
|
|
@@ -0,0 +1,20 @@
|
|
|
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 @supaku/agentfactory-server).
|
|
7
|
+
*/
|
|
8
|
+
import type { LinearAgentClient } from '@supaku/agentfactory-linear';
|
|
9
|
+
import type { GovernorDependencies } from '@supaku/agentfactory';
|
|
10
|
+
export interface RealDependenciesConfig {
|
|
11
|
+
linearClient: LinearAgentClient;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create real GovernorDependencies backed by the Linear SDK and Redis.
|
|
15
|
+
*
|
|
16
|
+
* Each callback wraps its implementation in a try/catch so that a single
|
|
17
|
+
* failing dependency does not crash the entire governor scan loop.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createRealDependencies(config: RealDependenciesConfig): GovernorDependencies;
|
|
20
|
+
//# 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,MAAM,6BAA6B,CAAA;AACpE,OAAO,KAAK,EACV,oBAAoB,EAGrB,MAAM,sBAAsB,CAAA;AA+B7B,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,iBAAiB,CAAA;CAChC;AAmFD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,oBAAoB,CA+NtB"}
|
|
@@ -0,0 +1,306 @@
|
|
|
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 @supaku/agentfactory-server).
|
|
7
|
+
*/
|
|
8
|
+
import { isHeld as checkIsHeld, getOverridePriority as checkOverridePriority, } from '@supaku/agentfactory';
|
|
9
|
+
import { getSessionStateByIssue, didJustFailQA, getWorkflowState, RedisProcessingStateStorage, queueWork, } from '@supaku/agentfactory-server';
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Logging
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
const log = {
|
|
14
|
+
info: (msg, data) => console.log(`[governor-deps] ${msg}`, data ? JSON.stringify(data) : ''),
|
|
15
|
+
warn: (msg, data) => console.warn(`[governor-deps] ${msg}`, data ? JSON.stringify(data) : ''),
|
|
16
|
+
error: (msg, data) => console.error(`[governor-deps] ${msg}`, data ? JSON.stringify(data) : ''),
|
|
17
|
+
};
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Action-to-WorkType mapping
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
function actionToWorkType(action) {
|
|
22
|
+
switch (action) {
|
|
23
|
+
case 'trigger-research':
|
|
24
|
+
return 'research';
|
|
25
|
+
case 'trigger-backlog-creation':
|
|
26
|
+
return 'backlog-creation';
|
|
27
|
+
case 'trigger-development':
|
|
28
|
+
return 'development';
|
|
29
|
+
case 'trigger-qa':
|
|
30
|
+
return 'qa';
|
|
31
|
+
case 'trigger-acceptance':
|
|
32
|
+
return 'acceptance';
|
|
33
|
+
case 'trigger-refinement':
|
|
34
|
+
return 'refinement';
|
|
35
|
+
case 'decompose':
|
|
36
|
+
return 'coordination';
|
|
37
|
+
case 'escalate-human':
|
|
38
|
+
return 'escalation';
|
|
39
|
+
default:
|
|
40
|
+
return 'development';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Factory
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Terminal statuses (issues in these states are excluded from scans)
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
const TERMINAL_STATUSES = ['Accepted', 'Canceled', 'Duplicate'];
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Helpers
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
/**
|
|
54
|
+
* Convert a Linear SDK Issue to a GovernorIssue.
|
|
55
|
+
* Resolves lazy-loaded relations (state, labels, parent, project).
|
|
56
|
+
*
|
|
57
|
+
* Uses `unknown` + explicit casts to avoid importing `Issue` from
|
|
58
|
+
* `@linear/sdk`, keeping the CLI package dependency graph clean.
|
|
59
|
+
*/
|
|
60
|
+
async function sdkIssueToGovernorIssue(issue) {
|
|
61
|
+
// The Linear SDK Issue type uses LinearFetch (thenable) for lazy-loaded relations.
|
|
62
|
+
// We cast to `any` to access these properties without importing the SDK types.
|
|
63
|
+
const i = issue;
|
|
64
|
+
const state = await i.state;
|
|
65
|
+
const labels = await i.labels();
|
|
66
|
+
const parent = await i.parent;
|
|
67
|
+
const project = await i.project;
|
|
68
|
+
return {
|
|
69
|
+
id: i.id,
|
|
70
|
+
identifier: i.identifier,
|
|
71
|
+
title: i.title,
|
|
72
|
+
description: i.description ?? undefined,
|
|
73
|
+
status: state?.name ?? 'Backlog',
|
|
74
|
+
labels: labels.nodes.map((l) => l.name),
|
|
75
|
+
createdAt: i.createdAt.getTime(),
|
|
76
|
+
parentId: parent?.id,
|
|
77
|
+
project: project?.name,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create real GovernorDependencies backed by the Linear SDK and Redis.
|
|
82
|
+
*
|
|
83
|
+
* Each callback wraps its implementation in a try/catch so that a single
|
|
84
|
+
* failing dependency does not crash the entire governor scan loop.
|
|
85
|
+
*/
|
|
86
|
+
export function createRealDependencies(config) {
|
|
87
|
+
const processingState = new RedisProcessingStateStorage();
|
|
88
|
+
return {
|
|
89
|
+
// -----------------------------------------------------------------------
|
|
90
|
+
// 1. listIssues -- scan Linear project for non-terminal issues
|
|
91
|
+
// -----------------------------------------------------------------------
|
|
92
|
+
listIssues: async (project) => {
|
|
93
|
+
try {
|
|
94
|
+
const linearClient = config.linearClient.linearClient;
|
|
95
|
+
const issueConnection = await linearClient.issues({
|
|
96
|
+
filter: {
|
|
97
|
+
project: { name: { eq: project } },
|
|
98
|
+
state: { name: { nin: [...TERMINAL_STATUSES] } },
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
const results = [];
|
|
102
|
+
for (const issue of issueConnection.nodes) {
|
|
103
|
+
results.push(await sdkIssueToGovernorIssue(issue));
|
|
104
|
+
}
|
|
105
|
+
return results;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
log.error('listIssues failed', {
|
|
109
|
+
project,
|
|
110
|
+
error: err instanceof Error ? err.message : String(err),
|
|
111
|
+
});
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
// -----------------------------------------------------------------------
|
|
116
|
+
// 2. hasActiveSession -- check Redis session storage
|
|
117
|
+
// -----------------------------------------------------------------------
|
|
118
|
+
hasActiveSession: async (issueId) => {
|
|
119
|
+
try {
|
|
120
|
+
const session = await getSessionStateByIssue(issueId);
|
|
121
|
+
if (!session)
|
|
122
|
+
return false;
|
|
123
|
+
const activeStatuses = ['running', 'claimed', 'pending'];
|
|
124
|
+
return activeStatuses.includes(session.status);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
log.error('hasActiveSession failed', {
|
|
128
|
+
issueId,
|
|
129
|
+
error: err instanceof Error ? err.message : String(err),
|
|
130
|
+
});
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
// -----------------------------------------------------------------------
|
|
135
|
+
// 3. isWithinCooldown -- check if QA just failed for this issue
|
|
136
|
+
// -----------------------------------------------------------------------
|
|
137
|
+
isWithinCooldown: async (issueId) => {
|
|
138
|
+
try {
|
|
139
|
+
return await didJustFailQA(issueId);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
log.error('isWithinCooldown failed', {
|
|
143
|
+
issueId,
|
|
144
|
+
error: err instanceof Error ? err.message : String(err),
|
|
145
|
+
});
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
// -----------------------------------------------------------------------
|
|
150
|
+
// 4. isParentIssue -- check via Linear API
|
|
151
|
+
// -----------------------------------------------------------------------
|
|
152
|
+
isParentIssue: async (issueId) => {
|
|
153
|
+
try {
|
|
154
|
+
return await config.linearClient.isParentIssue(issueId);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
log.error('isParentIssue failed', {
|
|
158
|
+
issueId,
|
|
159
|
+
error: err instanceof Error ? err.message : String(err),
|
|
160
|
+
});
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
// -----------------------------------------------------------------------
|
|
165
|
+
// 5. isHeld -- check touchpoint override storage
|
|
166
|
+
// -----------------------------------------------------------------------
|
|
167
|
+
isHeld: async (issueId) => {
|
|
168
|
+
try {
|
|
169
|
+
return await checkIsHeld(issueId);
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
log.error('isHeld failed', {
|
|
173
|
+
issueId,
|
|
174
|
+
error: err instanceof Error ? err.message : String(err),
|
|
175
|
+
});
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
// -----------------------------------------------------------------------
|
|
180
|
+
// 6. getOverridePriority -- check touchpoint override storage
|
|
181
|
+
// -----------------------------------------------------------------------
|
|
182
|
+
getOverridePriority: async (issueId) => {
|
|
183
|
+
try {
|
|
184
|
+
return await checkOverridePriority(issueId);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
log.error('getOverridePriority failed', {
|
|
188
|
+
issueId,
|
|
189
|
+
error: err instanceof Error ? err.message : String(err),
|
|
190
|
+
});
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
// -----------------------------------------------------------------------
|
|
195
|
+
// 7. getWorkflowStrategy -- check Redis workflow state
|
|
196
|
+
// -----------------------------------------------------------------------
|
|
197
|
+
getWorkflowStrategy: async (issueId) => {
|
|
198
|
+
try {
|
|
199
|
+
const workflowState = await getWorkflowState(issueId);
|
|
200
|
+
return workflowState?.strategy;
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
log.error('getWorkflowStrategy failed', {
|
|
204
|
+
issueId,
|
|
205
|
+
error: err instanceof Error ? err.message : String(err),
|
|
206
|
+
});
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
// -----------------------------------------------------------------------
|
|
211
|
+
// 8. isResearchCompleted -- check Redis processing state
|
|
212
|
+
// -----------------------------------------------------------------------
|
|
213
|
+
isResearchCompleted: async (issueId) => {
|
|
214
|
+
try {
|
|
215
|
+
return await processingState.isPhaseCompleted(issueId, 'research');
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
log.error('isResearchCompleted failed', {
|
|
219
|
+
issueId,
|
|
220
|
+
error: err instanceof Error ? err.message : String(err),
|
|
221
|
+
});
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
// -----------------------------------------------------------------------
|
|
226
|
+
// 9. isBacklogCreationCompleted -- check Redis processing state
|
|
227
|
+
// -----------------------------------------------------------------------
|
|
228
|
+
isBacklogCreationCompleted: async (issueId) => {
|
|
229
|
+
try {
|
|
230
|
+
return await processingState.isPhaseCompleted(issueId, 'backlog-creation');
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
log.error('isBacklogCreationCompleted failed', {
|
|
234
|
+
issueId,
|
|
235
|
+
error: err instanceof Error ? err.message : String(err),
|
|
236
|
+
});
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
// -----------------------------------------------------------------------
|
|
241
|
+
// 10. dispatchWork -- create Linear session and queue work
|
|
242
|
+
// -----------------------------------------------------------------------
|
|
243
|
+
dispatchWork: async (issueId, action) => {
|
|
244
|
+
try {
|
|
245
|
+
const workType = actionToWorkType(action);
|
|
246
|
+
log.info('Dispatching work', { issueId, action, workType });
|
|
247
|
+
// Fetch the issue to get its identifier for the queue entry
|
|
248
|
+
let issueIdentifier = issueId;
|
|
249
|
+
try {
|
|
250
|
+
const issue = await config.linearClient.getIssue(issueId);
|
|
251
|
+
issueIdentifier = issue.identifier;
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
log.warn('Could not fetch issue identifier, using issueId', { issueId });
|
|
255
|
+
}
|
|
256
|
+
// Create a Linear Agent Session on the issue so the UI shows activity
|
|
257
|
+
let sessionId;
|
|
258
|
+
try {
|
|
259
|
+
const sessionResult = await config.linearClient.createAgentSessionOnIssue({
|
|
260
|
+
issueId,
|
|
261
|
+
});
|
|
262
|
+
sessionId = sessionResult.sessionId;
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
log.warn('Could not create agent session, will queue without sessionId', {
|
|
266
|
+
issueId,
|
|
267
|
+
error: err instanceof Error ? err.message : String(err),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// Queue the work item for a worker to pick up
|
|
271
|
+
const queuedWork = {
|
|
272
|
+
sessionId: sessionId ?? `governor-${issueId}-${Date.now()}`,
|
|
273
|
+
issueId,
|
|
274
|
+
issueIdentifier,
|
|
275
|
+
priority: 3, // Default priority; PRIORITY overrides are handled by the governor sort
|
|
276
|
+
queuedAt: Date.now(),
|
|
277
|
+
workType: workType,
|
|
278
|
+
};
|
|
279
|
+
const queued = await queueWork(queuedWork);
|
|
280
|
+
if (!queued) {
|
|
281
|
+
log.warn('Failed to queue work (Redis may not be configured)', {
|
|
282
|
+
issueId,
|
|
283
|
+
action,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
log.info('Work queued successfully', {
|
|
288
|
+
issueId,
|
|
289
|
+
issueIdentifier,
|
|
290
|
+
action,
|
|
291
|
+
workType,
|
|
292
|
+
sessionId: queuedWork.sessionId,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
log.error('dispatchWork failed', {
|
|
298
|
+
issueId,
|
|
299
|
+
action,
|
|
300
|
+
error: err instanceof Error ? err.message : String(err),
|
|
301
|
+
});
|
|
302
|
+
throw err; // Re-throw so the governor can record the error
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governor Runner -- Programmatic API for the governor CLI.
|
|
3
|
+
*
|
|
4
|
+
* Exports `runGovernor()` so the governor can be invoked from code
|
|
5
|
+
* (e.g. Next.js route handlers, tests, or custom scripts) without going
|
|
6
|
+
* through process.argv / process.env / process.exit.
|
|
7
|
+
*/
|
|
8
|
+
import { WorkflowGovernor, EventDrivenGovernor, type GovernorDependencies, type GovernorEventBus, type EventDeduplicator } from '@supaku/agentfactory';
|
|
9
|
+
import type { ScanResult } from '@supaku/agentfactory';
|
|
10
|
+
export interface GovernorRunnerConfig {
|
|
11
|
+
/** Projects to scan */
|
|
12
|
+
projects: string[];
|
|
13
|
+
/** Scan interval in milliseconds (default: 60000) */
|
|
14
|
+
scanIntervalMs?: number;
|
|
15
|
+
/** Maximum concurrent dispatches per scan (default: 3) */
|
|
16
|
+
maxConcurrentDispatches?: number;
|
|
17
|
+
/** Enable auto-research from Icebox (default: true) */
|
|
18
|
+
enableAutoResearch?: boolean;
|
|
19
|
+
/** Enable auto-backlog-creation from Icebox (default: true) */
|
|
20
|
+
enableAutoBacklogCreation?: boolean;
|
|
21
|
+
/** Enable auto-development from Backlog (default: true) */
|
|
22
|
+
enableAutoDevelopment?: boolean;
|
|
23
|
+
/** Enable auto-QA from Finished (default: true) */
|
|
24
|
+
enableAutoQA?: boolean;
|
|
25
|
+
/** Enable auto-acceptance from Delivered (default: true) */
|
|
26
|
+
enableAutoAcceptance?: boolean;
|
|
27
|
+
/** Run a single scan pass and exit (for testing / cron) */
|
|
28
|
+
once?: boolean;
|
|
29
|
+
/** Dependency injection for the governor (required) */
|
|
30
|
+
dependencies: GovernorDependencies;
|
|
31
|
+
/** Callbacks for governor lifecycle events */
|
|
32
|
+
callbacks?: GovernorRunnerCallbacks;
|
|
33
|
+
/** Governor execution mode (default: 'poll-only') */
|
|
34
|
+
mode?: 'poll-only' | 'event-driven';
|
|
35
|
+
/** Event bus for event-driven mode (created automatically if not provided) */
|
|
36
|
+
eventBus?: GovernorEventBus;
|
|
37
|
+
/** Event deduplicator for event-driven mode (created automatically if not provided) */
|
|
38
|
+
deduplicator?: EventDeduplicator;
|
|
39
|
+
}
|
|
40
|
+
export interface GovernorRunnerCallbacks {
|
|
41
|
+
onScanComplete?: (results: ScanResult[]) => void;
|
|
42
|
+
onError?: (error: Error) => void;
|
|
43
|
+
}
|
|
44
|
+
export interface GovernorRunnerResult {
|
|
45
|
+
governor: WorkflowGovernor | EventDrivenGovernor;
|
|
46
|
+
/** Only populated in --once mode (poll-only) */
|
|
47
|
+
scanResults?: ScanResult[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Start the Workflow Governor with the given configuration.
|
|
51
|
+
*
|
|
52
|
+
* In `poll-only` mode (default):
|
|
53
|
+
* - `once` mode: runs a single scan pass and returns the results.
|
|
54
|
+
* - Otherwise: starts the scan loop and returns the governor instance
|
|
55
|
+
* (caller is responsible for calling `governor.stop()` on shutdown).
|
|
56
|
+
*
|
|
57
|
+
* In `event-driven` mode:
|
|
58
|
+
* - Creates an EventDrivenGovernor with an event bus and optional deduplicator.
|
|
59
|
+
* - Starts the event loop and periodic poll sweep.
|
|
60
|
+
* - `once` mode is not supported in event-driven mode (falls back to poll-only).
|
|
61
|
+
*/
|
|
62
|
+
export declare function runGovernor(config: GovernorRunnerConfig): Promise<GovernorRunnerResult>;
|
|
63
|
+
export interface GovernorCLIArgs {
|
|
64
|
+
projects: string[];
|
|
65
|
+
scanIntervalMs: number;
|
|
66
|
+
maxConcurrentDispatches: number;
|
|
67
|
+
enableAutoResearch: boolean;
|
|
68
|
+
enableAutoBacklogCreation: boolean;
|
|
69
|
+
enableAutoDevelopment: boolean;
|
|
70
|
+
enableAutoQA: boolean;
|
|
71
|
+
enableAutoAcceptance: boolean;
|
|
72
|
+
once: boolean;
|
|
73
|
+
mode: 'poll-only' | 'event-driven';
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Parse CLI arguments for the governor command.
|
|
77
|
+
*
|
|
78
|
+
* Usage:
|
|
79
|
+
* agentfactory governor --project <name> [--project <name>] [options]
|
|
80
|
+
*
|
|
81
|
+
* Options:
|
|
82
|
+
* --project <name> Project to scan (can be repeated)
|
|
83
|
+
* --scan-interval <ms> Scan interval in milliseconds (default: 60000)
|
|
84
|
+
* --max-dispatches <n> Maximum concurrent dispatches per scan (default: 3)
|
|
85
|
+
* --no-auto-research Disable auto-research from Icebox
|
|
86
|
+
* --no-auto-backlog-creation Disable auto-backlog-creation from Icebox
|
|
87
|
+
* --no-auto-development Disable auto-development from Backlog
|
|
88
|
+
* --no-auto-qa Disable auto-QA from Finished
|
|
89
|
+
* --no-auto-acceptance Disable auto-acceptance from Delivered
|
|
90
|
+
* --once Run a single scan pass and exit
|
|
91
|
+
* --help, -h Show help
|
|
92
|
+
*/
|
|
93
|
+
export declare function parseGovernorArgs(argv?: string[]): GovernorCLIArgs;
|
|
94
|
+
/**
|
|
95
|
+
* Print help text for the governor command.
|
|
96
|
+
*/
|
|
97
|
+
export declare function printGovernorHelp(): void;
|
|
98
|
+
//# sourceMappingURL=governor-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"governor-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/governor-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EAGnB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,KAAK,EAIV,UAAU,EACX,MAAM,sBAAsB,CAAA;AAM7B,MAAM,WAAW,oBAAoB;IACnC,uBAAuB;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,0DAA0D;IAC1D,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,uDAAuD;IACvD,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,+DAA+D;IAC/D,yBAAyB,CAAC,EAAE,OAAO,CAAA;IACnC,2DAA2D;IAC3D,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,mDAAmD;IACnD,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,4DAA4D;IAC5D,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,2DAA2D;IAC3D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,uDAAuD;IACvD,YAAY,EAAE,oBAAoB,CAAA;IAClC,8CAA8C;IAC9C,SAAS,CAAC,EAAE,uBAAuB,CAAA;IACnC,qDAAqD;IACrD,IAAI,CAAC,EAAE,WAAW,GAAG,cAAc,CAAA;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,gBAAgB,CAAA;IAC3B,uFAAuF;IACvF,YAAY,CAAC,EAAE,iBAAiB,CAAA;CACjC;AAED,MAAM,WAAW,uBAAuB;IACtC,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,CAAA;IAChD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,gBAAgB,GAAG,mBAAmB,CAAA;IAChD,gDAAgD;IAChD,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;CAC3B;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,oBAAoB,CAAC,CAuD/B;AAMD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,kBAAkB,EAAE,OAAO,CAAA;IAC3B,yBAAyB,EAAE,OAAO,CAAA;IAClC,qBAAqB,EAAE,OAAO,CAAA;IAC9B,YAAY,EAAE,OAAO,CAAA;IACrB,oBAAoB,EAAE,OAAO,CAAA;IAC7B,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE,WAAW,GAAG,cAAc,CAAA;CACnC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,eAAe,CAuDzF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CA6CxC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governor Runner -- Programmatic API for the governor CLI.
|
|
3
|
+
*
|
|
4
|
+
* Exports `runGovernor()` so the governor can be invoked from code
|
|
5
|
+
* (e.g. Next.js route handlers, tests, or custom scripts) without going
|
|
6
|
+
* through process.argv / process.env / process.exit.
|
|
7
|
+
*/
|
|
8
|
+
import { WorkflowGovernor, EventDrivenGovernor, InMemoryEventBus, InMemoryEventDeduplicator, } from '@supaku/agentfactory';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Runner
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/**
|
|
13
|
+
* Start the Workflow Governor with the given configuration.
|
|
14
|
+
*
|
|
15
|
+
* In `poll-only` mode (default):
|
|
16
|
+
* - `once` mode: runs a single scan pass and returns the results.
|
|
17
|
+
* - Otherwise: starts the scan loop and returns the governor instance
|
|
18
|
+
* (caller is responsible for calling `governor.stop()` on shutdown).
|
|
19
|
+
*
|
|
20
|
+
* In `event-driven` mode:
|
|
21
|
+
* - Creates an EventDrivenGovernor with an event bus and optional deduplicator.
|
|
22
|
+
* - Starts the event loop and periodic poll sweep.
|
|
23
|
+
* - `once` mode is not supported in event-driven mode (falls back to poll-only).
|
|
24
|
+
*/
|
|
25
|
+
export async function runGovernor(config) {
|
|
26
|
+
const governorConfig = {
|
|
27
|
+
projects: config.projects,
|
|
28
|
+
scanIntervalMs: config.scanIntervalMs,
|
|
29
|
+
maxConcurrentDispatches: config.maxConcurrentDispatches,
|
|
30
|
+
enableAutoResearch: config.enableAutoResearch,
|
|
31
|
+
enableAutoBacklogCreation: config.enableAutoBacklogCreation,
|
|
32
|
+
enableAutoDevelopment: config.enableAutoDevelopment,
|
|
33
|
+
enableAutoQA: config.enableAutoQA,
|
|
34
|
+
enableAutoAcceptance: config.enableAutoAcceptance,
|
|
35
|
+
};
|
|
36
|
+
const mode = config.mode ?? 'poll-only';
|
|
37
|
+
// -- Event-driven mode --
|
|
38
|
+
if (mode === 'event-driven' && !config.once) {
|
|
39
|
+
const eventBus = config.eventBus ?? new InMemoryEventBus();
|
|
40
|
+
const deduplicator = config.deduplicator ?? new InMemoryEventDeduplicator();
|
|
41
|
+
const governor = new EventDrivenGovernor({
|
|
42
|
+
...governorConfig,
|
|
43
|
+
// Spread required GovernorConfig defaults so TypeScript is happy
|
|
44
|
+
projects: config.projects,
|
|
45
|
+
scanIntervalMs: config.scanIntervalMs ?? 60_000,
|
|
46
|
+
maxConcurrentDispatches: config.maxConcurrentDispatches ?? 3,
|
|
47
|
+
enableAutoResearch: config.enableAutoResearch ?? true,
|
|
48
|
+
enableAutoBacklogCreation: config.enableAutoBacklogCreation ?? true,
|
|
49
|
+
enableAutoDevelopment: config.enableAutoDevelopment ?? true,
|
|
50
|
+
enableAutoQA: config.enableAutoQA ?? true,
|
|
51
|
+
enableAutoAcceptance: config.enableAutoAcceptance ?? true,
|
|
52
|
+
humanResponseTimeoutMs: 4 * 60 * 60 * 1000,
|
|
53
|
+
eventBus,
|
|
54
|
+
deduplicator,
|
|
55
|
+
}, config.dependencies);
|
|
56
|
+
await governor.start();
|
|
57
|
+
return { governor };
|
|
58
|
+
}
|
|
59
|
+
// -- Poll-only mode (default) --
|
|
60
|
+
const governor = new WorkflowGovernor(governorConfig, config.dependencies);
|
|
61
|
+
// -- Single scan mode (--once) --
|
|
62
|
+
if (config.once) {
|
|
63
|
+
const results = await governor.scanOnce();
|
|
64
|
+
config.callbacks?.onScanComplete?.(results);
|
|
65
|
+
return { governor, scanResults: results };
|
|
66
|
+
}
|
|
67
|
+
// -- Continuous scan loop --
|
|
68
|
+
governor.start();
|
|
69
|
+
return { governor };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Parse CLI arguments for the governor command.
|
|
73
|
+
*
|
|
74
|
+
* Usage:
|
|
75
|
+
* agentfactory governor --project <name> [--project <name>] [options]
|
|
76
|
+
*
|
|
77
|
+
* Options:
|
|
78
|
+
* --project <name> Project to scan (can be repeated)
|
|
79
|
+
* --scan-interval <ms> Scan interval in milliseconds (default: 60000)
|
|
80
|
+
* --max-dispatches <n> Maximum concurrent dispatches per scan (default: 3)
|
|
81
|
+
* --no-auto-research Disable auto-research from Icebox
|
|
82
|
+
* --no-auto-backlog-creation Disable auto-backlog-creation from Icebox
|
|
83
|
+
* --no-auto-development Disable auto-development from Backlog
|
|
84
|
+
* --no-auto-qa Disable auto-QA from Finished
|
|
85
|
+
* --no-auto-acceptance Disable auto-acceptance from Delivered
|
|
86
|
+
* --once Run a single scan pass and exit
|
|
87
|
+
* --help, -h Show help
|
|
88
|
+
*/
|
|
89
|
+
export function parseGovernorArgs(argv = process.argv.slice(2)) {
|
|
90
|
+
const result = {
|
|
91
|
+
projects: [],
|
|
92
|
+
scanIntervalMs: 60_000,
|
|
93
|
+
maxConcurrentDispatches: 3,
|
|
94
|
+
enableAutoResearch: true,
|
|
95
|
+
enableAutoBacklogCreation: true,
|
|
96
|
+
enableAutoDevelopment: true,
|
|
97
|
+
enableAutoQA: true,
|
|
98
|
+
enableAutoAcceptance: true,
|
|
99
|
+
once: false,
|
|
100
|
+
mode: 'poll-only',
|
|
101
|
+
};
|
|
102
|
+
for (let i = 0; i < argv.length; i++) {
|
|
103
|
+
const arg = argv[i];
|
|
104
|
+
switch (arg) {
|
|
105
|
+
case '--project':
|
|
106
|
+
result.projects.push(argv[++i]);
|
|
107
|
+
break;
|
|
108
|
+
case '--scan-interval':
|
|
109
|
+
result.scanIntervalMs = parseInt(argv[++i], 10);
|
|
110
|
+
break;
|
|
111
|
+
case '--max-dispatches':
|
|
112
|
+
result.maxConcurrentDispatches = parseInt(argv[++i], 10);
|
|
113
|
+
break;
|
|
114
|
+
case '--no-auto-research':
|
|
115
|
+
result.enableAutoResearch = false;
|
|
116
|
+
break;
|
|
117
|
+
case '--no-auto-backlog-creation':
|
|
118
|
+
result.enableAutoBacklogCreation = false;
|
|
119
|
+
break;
|
|
120
|
+
case '--no-auto-development':
|
|
121
|
+
result.enableAutoDevelopment = false;
|
|
122
|
+
break;
|
|
123
|
+
case '--no-auto-qa':
|
|
124
|
+
result.enableAutoQA = false;
|
|
125
|
+
break;
|
|
126
|
+
case '--no-auto-acceptance':
|
|
127
|
+
result.enableAutoAcceptance = false;
|
|
128
|
+
break;
|
|
129
|
+
case '--once':
|
|
130
|
+
result.once = true;
|
|
131
|
+
break;
|
|
132
|
+
case '--mode':
|
|
133
|
+
result.mode = argv[++i];
|
|
134
|
+
break;
|
|
135
|
+
case '--help':
|
|
136
|
+
case '-h':
|
|
137
|
+
printGovernorHelp();
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Print help text for the governor command.
|
|
145
|
+
*/
|
|
146
|
+
export function printGovernorHelp() {
|
|
147
|
+
console.log(`
|
|
148
|
+
AgentFactory Governor — Automated workflow scan loop
|
|
149
|
+
|
|
150
|
+
Usage:
|
|
151
|
+
agentfactory governor [options]
|
|
152
|
+
|
|
153
|
+
Options:
|
|
154
|
+
--project <name> Project to scan (can be repeated for multiple projects)
|
|
155
|
+
--scan-interval <ms> Scan interval in milliseconds (default: 60000)
|
|
156
|
+
--max-dispatches <n> Maximum concurrent dispatches per scan (default: 3)
|
|
157
|
+
--mode <mode> Execution mode: poll-only (default) or event-driven
|
|
158
|
+
--no-auto-research Disable auto-research from Icebox
|
|
159
|
+
--no-auto-backlog-creation Disable auto-backlog-creation from Icebox
|
|
160
|
+
--no-auto-development Disable auto-development from Backlog
|
|
161
|
+
--no-auto-qa Disable auto-QA from Finished
|
|
162
|
+
--no-auto-acceptance Disable auto-acceptance from Delivered
|
|
163
|
+
--once Run a single scan pass and exit
|
|
164
|
+
--help, -h Show this help message
|
|
165
|
+
|
|
166
|
+
Modes:
|
|
167
|
+
poll-only Periodic scan loop using WorkflowGovernor (default)
|
|
168
|
+
event-driven Hybrid event-driven + poll sweep using EventDrivenGovernor.
|
|
169
|
+
Reacts to events in real time with a periodic safety-net poll.
|
|
170
|
+
|
|
171
|
+
Environment:
|
|
172
|
+
LINEAR_API_KEY Required API key for Linear authentication
|
|
173
|
+
REDIS_URL Redis connection URL (required for real dependencies)
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
# Start the governor for a project
|
|
177
|
+
agentfactory governor --project MyProject
|
|
178
|
+
|
|
179
|
+
# Scan multiple projects with custom interval
|
|
180
|
+
agentfactory governor --project ProjectA --project ProjectB --scan-interval 30000
|
|
181
|
+
|
|
182
|
+
# Run a single scan and exit (useful for cron jobs)
|
|
183
|
+
agentfactory governor --project MyProject --once
|
|
184
|
+
|
|
185
|
+
# Disable auto-QA (only scan for development work)
|
|
186
|
+
agentfactory governor --project MyProject --no-auto-qa --no-auto-acceptance
|
|
187
|
+
|
|
188
|
+
# Use event-driven mode
|
|
189
|
+
agentfactory governor --project MyProject --mode event-driven
|
|
190
|
+
`);
|
|
191
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linear-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/linear-runner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,MAAM,WAAW,kBAAkB;IACjC,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,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAA;CAChB;AAOD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAC/C,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,CA2CA;AAymBD,wBAAsB,SAAS,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,
|
|
1
|
+
{"version":3,"file":"linear-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/linear-runner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,MAAM,WAAW,kBAAkB;IACjC,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,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAA;CAChB;AAOD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG;IAC/C,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,CA2CA;AAymBD,wBAAsB,SAAS,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA6MvF"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* All 15 command implementations extracted from packages/core/src/linear-cli.ts.
|
|
5
5
|
* This module does NOT call process.exit, read process.argv, or load dotenv.
|
|
6
6
|
*/
|
|
7
|
-
import { createLinearAgentClient } from '@supaku/agentfactory-linear';
|
|
7
|
+
import { createLinearAgentClient, getDefaultTeamName } from '@supaku/agentfactory-linear';
|
|
8
8
|
import { checkPRDeploymentStatus, formatDeploymentStatus, } from '@supaku/agentfactory';
|
|
9
9
|
// ── Arg parsing ────────────────────────────────────────────────────
|
|
10
10
|
/** Fields that should be split on commas to create arrays */
|
|
@@ -548,12 +548,14 @@ export async function runLinear(config) {
|
|
|
548
548
|
break;
|
|
549
549
|
}
|
|
550
550
|
case 'create-issue': {
|
|
551
|
-
|
|
552
|
-
|
|
551
|
+
const teamArg = args.team ?? (getDefaultTeamName() || undefined);
|
|
552
|
+
if (!args.title || !teamArg) {
|
|
553
|
+
throw new Error('Usage: af-linear create-issue --title "Title" --team "Team" [--description "..."] [--project "..."] [--labels "Label1,Label2"] [--state "Backlog"] [--parentId "..."]\n' +
|
|
554
|
+
'Tip: Set LINEAR_TEAM_NAME env var to provide a default team.');
|
|
553
555
|
}
|
|
554
556
|
output = await createIssue(client(), {
|
|
555
557
|
title: args.title,
|
|
556
|
-
team:
|
|
558
|
+
team: teamArg,
|
|
557
559
|
description: args.description,
|
|
558
560
|
project: args.project,
|
|
559
561
|
labels: args.labels,
|
|
@@ -23,6 +23,10 @@ export interface OrchestratorRunnerConfig {
|
|
|
23
23
|
gitRoot?: string;
|
|
24
24
|
/** Callbacks for agent lifecycle events */
|
|
25
25
|
callbacks?: OrchestratorCallbacks;
|
|
26
|
+
/** Custom workflow template directory path */
|
|
27
|
+
templateDir?: string;
|
|
28
|
+
/** Git repository URL for worktree cloning */
|
|
29
|
+
repository?: string;
|
|
26
30
|
}
|
|
27
31
|
export interface OrchestratorCallbacks {
|
|
28
32
|
onIssueSelected?: (issue: OrchestratorIssue) => void;
|
|
@@ -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,EACjB,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAA;AAM7B,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;
|
|
1
|
+
{"version":3,"file":"orchestrator-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/orchestrator-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAA;AAM7B,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;CACpB;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,CA6FnC"}
|
|
@@ -77,12 +77,19 @@ export async function runOrchestrator(config) {
|
|
|
77
77
|
const dryRun = config.dryRun ?? false;
|
|
78
78
|
const gitRoot = config.gitRoot ?? getGitRoot();
|
|
79
79
|
const cb = config.callbacks ?? defaultCallbacks();
|
|
80
|
-
const
|
|
80
|
+
const orchestratorConfig = {
|
|
81
81
|
project: config.project,
|
|
82
82
|
maxConcurrent,
|
|
83
83
|
worktreePath: path.resolve(gitRoot, '.worktrees'),
|
|
84
84
|
linearApiKey: config.linearApiKey,
|
|
85
|
-
}
|
|
85
|
+
};
|
|
86
|
+
if (config.templateDir) {
|
|
87
|
+
orchestratorConfig.templateDir = config.templateDir;
|
|
88
|
+
}
|
|
89
|
+
if (config.repository) {
|
|
90
|
+
orchestratorConfig.repository = config.repository;
|
|
91
|
+
}
|
|
92
|
+
const orchestrator = createOrchestrator(orchestratorConfig, {
|
|
86
93
|
onIssueSelected: cb.onIssueSelected,
|
|
87
94
|
onAgentStart: cb.onAgentStart,
|
|
88
95
|
onAgentComplete: cb.onAgentComplete,
|
package/dist/src/orchestrator.js
CHANGED
|
@@ -30,6 +30,8 @@ function parseArgs() {
|
|
|
30
30
|
single: undefined,
|
|
31
31
|
wait: true,
|
|
32
32
|
dryRun: false,
|
|
33
|
+
templates: undefined,
|
|
34
|
+
repo: undefined,
|
|
33
35
|
};
|
|
34
36
|
for (let i = 0; i < args.length; i++) {
|
|
35
37
|
const arg = args[i];
|
|
@@ -49,6 +51,12 @@ function parseArgs() {
|
|
|
49
51
|
case '--dry-run':
|
|
50
52
|
result.dryRun = true;
|
|
51
53
|
break;
|
|
54
|
+
case '--templates':
|
|
55
|
+
result.templates = args[++i];
|
|
56
|
+
break;
|
|
57
|
+
case '--repo':
|
|
58
|
+
result.repo = args[++i];
|
|
59
|
+
break;
|
|
52
60
|
case '--help':
|
|
53
61
|
case '-h':
|
|
54
62
|
printHelp();
|
|
@@ -70,6 +78,8 @@ Options:
|
|
|
70
78
|
--single <id> Process a single issue by ID
|
|
71
79
|
--no-wait Don't wait for agents to complete
|
|
72
80
|
--dry-run Show what would be done without executing
|
|
81
|
+
--templates <path> Custom workflow template directory
|
|
82
|
+
--repo <url> Git repository URL for worktree cloning
|
|
73
83
|
--help, -h Show this help message
|
|
74
84
|
|
|
75
85
|
Environment:
|
|
@@ -96,6 +106,7 @@ async function main() {
|
|
|
96
106
|
console.log('========================');
|
|
97
107
|
console.log(`Project: ${args.project ?? 'All'}`);
|
|
98
108
|
console.log(`Max concurrent: ${args.max}`);
|
|
109
|
+
console.log(`Repo: ${args.repo ?? 'Any'}`);
|
|
99
110
|
console.log(`Dry run: ${args.dryRun}`);
|
|
100
111
|
console.log('');
|
|
101
112
|
if (args.single) {
|
|
@@ -113,6 +124,8 @@ async function main() {
|
|
|
113
124
|
single: args.single,
|
|
114
125
|
wait: args.wait,
|
|
115
126
|
dryRun: args.dryRun,
|
|
127
|
+
templateDir: args.templates,
|
|
128
|
+
repository: args.repo,
|
|
116
129
|
});
|
|
117
130
|
if (!args.single && !args.dryRun) {
|
|
118
131
|
console.log('');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supaku/agentfactory-cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI tools for AgentFactory — local orchestrator, remote worker, queue admin",
|
|
6
6
|
"author": "Supaku (https://supaku.com)",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"af-cleanup": "./dist/src/cleanup.js",
|
|
35
35
|
"af-queue-admin": "./dist/src/queue-admin.js",
|
|
36
36
|
"af-analyze-logs": "./dist/src/analyze-logs.js",
|
|
37
|
-
"af-linear": "./dist/src/linear.js"
|
|
37
|
+
"af-linear": "./dist/src/linear.js",
|
|
38
|
+
"af-governor": "./dist/src/governor.js"
|
|
38
39
|
},
|
|
39
40
|
"main": "./dist/src/index.js",
|
|
40
41
|
"module": "./dist/src/index.js",
|
|
@@ -88,9 +89,9 @@
|
|
|
88
89
|
],
|
|
89
90
|
"dependencies": {
|
|
90
91
|
"dotenv": "^17.2.3",
|
|
91
|
-
"@supaku/agentfactory": "0.7.
|
|
92
|
-
"@supaku/agentfactory
|
|
93
|
-
"@supaku/agentfactory-server": "0.7.
|
|
92
|
+
"@supaku/agentfactory-linear": "0.7.11",
|
|
93
|
+
"@supaku/agentfactory": "0.7.11",
|
|
94
|
+
"@supaku/agentfactory-server": "0.7.11"
|
|
94
95
|
},
|
|
95
96
|
"devDependencies": {
|
|
96
97
|
"@types/node": "^22.5.4",
|