@supaku/agentfactory-cli 0.7.10 → 0.7.12

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/README.md CHANGED
@@ -24,6 +24,15 @@ af-orchestrator --single PROJ-123
24
24
  # Dry run — preview what would be processed
25
25
  af-orchestrator --project MyProject --dry-run
26
26
 
27
+ # Start the Workflow Governor (event-driven + poll sweep)
28
+ af-governor --project MyProject --project OtherProject
29
+
30
+ # Governor: single scan and exit (for cron jobs)
31
+ af-governor --project MyProject --once
32
+
33
+ # Governor: poll-only mode (no event bus)
34
+ af-governor --project MyProject --mode poll-only
35
+
27
36
  # Start a remote worker
28
37
  af-worker --api-url https://your-app.vercel.app --api-key your-key
29
38
 
@@ -41,6 +50,30 @@ af-queue-admin drain
41
50
  af-analyze-logs --follow
42
51
  ```
43
52
 
53
+ ### Governor
54
+
55
+ The Workflow Governor scans projects and decides what work to dispatch based on issue status, active sessions, cooldowns, and human overrides.
56
+
57
+ **Modes:**
58
+ - `event-driven` (default) — Listens to a GovernorEventBus for real-time webhook events, with a periodic poll sweep as safety net
59
+ - `poll-only` — Periodic scan loop only
60
+
61
+ **Options:**
62
+ ```
63
+ --project <name> Project to scan (repeatable)
64
+ --scan-interval <ms> Poll interval (default: 60000 for poll-only, 300000 for event-driven)
65
+ --max-dispatches <n> Max concurrent dispatches per scan (default: 3)
66
+ --mode <mode> poll-only or event-driven (default: event-driven)
67
+ --once Single scan pass and exit
68
+ --no-auto-research Disable Icebox → research
69
+ --no-auto-backlog-creation Disable Icebox → backlog-creation
70
+ --no-auto-development Disable Backlog → development
71
+ --no-auto-qa Disable Finished → QA
72
+ --no-auto-acceptance Disable Delivered → acceptance
73
+ ```
74
+
75
+ When `LINEAR_API_KEY` and `REDIS_URL` are set, the governor uses real dependencies (Linear SDK + Redis). Without them, it falls back to stub dependencies for testing.
76
+
44
77
  ## Programmatic Usage
45
78
 
46
79
  All CLI tools are available as importable functions via subpath exports:
@@ -72,10 +105,10 @@ Each function accepts a config object and returns a Promise — use them to buil
72
105
 
73
106
  | Variable | Used By | Description |
74
107
  |----------|---------|-------------|
75
- | `LINEAR_API_KEY` | orchestrator | Linear API key |
108
+ | `LINEAR_API_KEY` | orchestrator, governor | Linear API key |
109
+ | `REDIS_URL` | governor, queue-admin | Redis connection URL |
76
110
  | `WORKER_API_URL` | worker, fleet | Webhook server URL |
77
111
  | `WORKER_API_KEY` | worker, fleet | API key for authentication |
78
- | `REDIS_URL` | queue-admin | Redis connection URL |
79
112
 
80
113
  ## Related Packages
81
114
 
@@ -26,6 +26,10 @@ import { config } from 'dotenv';
26
26
  // Load environment variables from .env.local
27
27
  config({ path: path.resolve(process.cwd(), '.env.local') });
28
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';
29
33
  // ---------------------------------------------------------------------------
30
34
  // Stub dependencies
31
35
  // ---------------------------------------------------------------------------
@@ -73,8 +77,32 @@ async function main() {
73
77
  console.log(`Projects: ${args.projects.join(', ')}`);
74
78
  console.log(`Scan interval: ${args.scanIntervalMs}ms`);
75
79
  console.log(`Max dispatches per scan: ${args.maxConcurrentDispatches}`);
80
+ console.log(`Execution mode: ${args.mode}`);
76
81
  console.log(`Mode: ${args.once ? 'single scan' : 'continuous'}`);
77
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
+ }
78
106
  const runnerConfig = {
79
107
  projects: args.projects,
80
108
  scanIntervalMs: args.scanIntervalMs,
@@ -85,7 +113,8 @@ async function main() {
85
113
  enableAutoQA: args.enableAutoQA,
86
114
  enableAutoAcceptance: args.enableAutoAcceptance,
87
115
  once: args.once,
88
- dependencies: createStubDependencies(),
116
+ mode: args.mode,
117
+ dependencies,
89
118
  callbacks: {
90
119
  onScanComplete: (results) => {
91
120
  for (const result of results) {
@@ -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;AAgC7B,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,iBAAiB,CAAA;CAChC;AAmFD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,sBAAsB,GAC7B,oBAAoB,CAsPtB"}
@@ -0,0 +1,327 @@
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, storeSessionState, } 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 and project for the queue entry
248
+ let issueIdentifier = issueId;
249
+ let projectName;
250
+ try {
251
+ const issue = await config.linearClient.getIssue(issueId);
252
+ issueIdentifier = issue.identifier;
253
+ const project = await issue.project;
254
+ projectName = project?.name;
255
+ }
256
+ catch {
257
+ log.warn('Could not fetch issue details, using issueId', { issueId });
258
+ }
259
+ // Create a Linear Agent Session on the issue so the UI shows activity
260
+ let sessionId;
261
+ try {
262
+ const sessionResult = await config.linearClient.createAgentSessionOnIssue({
263
+ issueId,
264
+ });
265
+ sessionId = sessionResult.sessionId;
266
+ }
267
+ catch (err) {
268
+ log.warn('Could not create agent session, will queue without sessionId', {
269
+ issueId,
270
+ error: err instanceof Error ? err.message : String(err),
271
+ });
272
+ }
273
+ const finalSessionId = sessionId ?? `governor-${issueId}-${Date.now()}`;
274
+ const now = Date.now();
275
+ // Register a pending session FIRST so hasActiveSession() returns true
276
+ // immediately, preventing re-dispatch on subsequent poll sweeps.
277
+ await storeSessionState(finalSessionId, {
278
+ issueId,
279
+ issueIdentifier,
280
+ claudeSessionId: null,
281
+ worktreePath: '',
282
+ status: 'pending',
283
+ workerId: null,
284
+ queuedAt: now,
285
+ priority: 3,
286
+ workType: workType,
287
+ projectName,
288
+ });
289
+ // Queue the work item for a worker to pick up
290
+ const queuedWork = {
291
+ sessionId: finalSessionId,
292
+ issueId,
293
+ issueIdentifier,
294
+ priority: 3,
295
+ queuedAt: now,
296
+ workType: workType,
297
+ projectName,
298
+ };
299
+ const queued = await queueWork(queuedWork);
300
+ if (!queued) {
301
+ log.warn('Failed to queue work (Redis may not be configured)', {
302
+ issueId,
303
+ action,
304
+ });
305
+ }
306
+ else {
307
+ log.info('Work queued successfully', {
308
+ issueId,
309
+ issueIdentifier,
310
+ action,
311
+ workType,
312
+ projectName,
313
+ sessionId: finalSessionId,
314
+ });
315
+ }
316
+ }
317
+ catch (err) {
318
+ log.error('dispatchWork failed', {
319
+ issueId,
320
+ action,
321
+ error: err instanceof Error ? err.message : String(err),
322
+ });
323
+ throw err; // Re-throw so the governor can record the error
324
+ }
325
+ },
326
+ };
327
+ }
@@ -5,7 +5,7 @@
5
5
  * (e.g. Next.js route handlers, tests, or custom scripts) without going
6
6
  * through process.argv / process.env / process.exit.
7
7
  */
8
- import { WorkflowGovernor, type GovernorDependencies } from '@supaku/agentfactory';
8
+ import { WorkflowGovernor, EventDrivenGovernor, type GovernorDependencies, type GovernorEventBus, type EventDeduplicator } from '@supaku/agentfactory';
9
9
  import type { ScanResult } from '@supaku/agentfactory';
10
10
  export interface GovernorRunnerConfig {
11
11
  /** Projects to scan */
@@ -14,9 +14,9 @@ export interface GovernorRunnerConfig {
14
14
  scanIntervalMs?: number;
15
15
  /** Maximum concurrent dispatches per scan (default: 3) */
16
16
  maxConcurrentDispatches?: number;
17
- /** Enable auto-research from Icebox (default: true) */
17
+ /** Enable auto-research from Icebox (default: false) */
18
18
  enableAutoResearch?: boolean;
19
- /** Enable auto-backlog-creation from Icebox (default: true) */
19
+ /** Enable auto-backlog-creation from Icebox (default: false) */
20
20
  enableAutoBacklogCreation?: boolean;
21
21
  /** Enable auto-development from Backlog (default: true) */
22
22
  enableAutoDevelopment?: boolean;
@@ -30,22 +30,34 @@ export interface GovernorRunnerConfig {
30
30
  dependencies: GovernorDependencies;
31
31
  /** Callbacks for governor lifecycle events */
32
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;
33
39
  }
34
40
  export interface GovernorRunnerCallbacks {
35
41
  onScanComplete?: (results: ScanResult[]) => void;
36
42
  onError?: (error: Error) => void;
37
43
  }
38
44
  export interface GovernorRunnerResult {
39
- governor: WorkflowGovernor;
40
- /** Only populated in --once mode */
45
+ governor: WorkflowGovernor | EventDrivenGovernor;
46
+ /** Only populated in --once mode (poll-only) */
41
47
  scanResults?: ScanResult[];
42
48
  }
43
49
  /**
44
50
  * Start the Workflow Governor with the given configuration.
45
51
  *
46
- * In `once` mode, runs a single scan pass and returns the results.
47
- * Otherwise, starts the scan loop and returns the governor instance
48
- * (caller is responsible for calling `governor.stop()` on shutdown).
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).
49
61
  */
50
62
  export declare function runGovernor(config: GovernorRunnerConfig): Promise<GovernorRunnerResult>;
51
63
  export interface GovernorCLIArgs {
@@ -58,6 +70,7 @@ export interface GovernorCLIArgs {
58
70
  enableAutoQA: boolean;
59
71
  enableAutoAcceptance: boolean;
60
72
  once: boolean;
73
+ mode: 'poll-only' | 'event-driven';
61
74
  }
62
75
  /**
63
76
  * Parse CLI arguments for the governor command.
@@ -69,8 +82,10 @@ export interface GovernorCLIArgs {
69
82
  * --project <name> Project to scan (can be repeated)
70
83
  * --scan-interval <ms> Scan interval in milliseconds (default: 60000)
71
84
  * --max-dispatches <n> Maximum concurrent dispatches per scan (default: 3)
72
- * --no-auto-research Disable auto-research from Icebox
73
- * --no-auto-backlog-creation Disable auto-backlog-creation from Icebox
85
+ * --auto-research Enable auto-research from Icebox (default: off)
86
+ * --auto-backlog-creation Enable auto-backlog-creation from Icebox (default: off)
87
+ * --no-auto-research Disable auto-research (explicit override)
88
+ * --no-auto-backlog-creation Disable auto-backlog-creation (explicit override)
74
89
  * --no-auto-development Disable auto-development from Backlog
75
90
  * --no-auto-qa Disable auto-QA from Finished
76
91
  * --no-auto-acceptance Disable auto-acceptance from Delivered
@@ -1 +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,KAAK,oBAAoB,EAC1B,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;CACpC;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,CAAA;IAC1B,oCAAoC;IACpC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;CAC3B;AAMD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,oBAAoB,CAAC,CAwB/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;CACd;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,eAAe,CAmDzF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAmCxC"}
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,wDAAwD;IACxD,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,gEAAgE;IAChE,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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,eAAe,CA6DzF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CA6CxC"}
@@ -5,16 +5,22 @@
5
5
  * (e.g. Next.js route handlers, tests, or custom scripts) without going
6
6
  * through process.argv / process.env / process.exit.
7
7
  */
8
- import { WorkflowGovernor, } from '@supaku/agentfactory';
8
+ import { WorkflowGovernor, EventDrivenGovernor, InMemoryEventBus, InMemoryEventDeduplicator, } from '@supaku/agentfactory';
9
9
  // ---------------------------------------------------------------------------
10
10
  // Runner
11
11
  // ---------------------------------------------------------------------------
12
12
  /**
13
13
  * Start the Workflow Governor with the given configuration.
14
14
  *
15
- * In `once` mode, runs a single scan pass and returns the results.
16
- * Otherwise, starts the scan loop and returns the governor instance
17
- * (caller is responsible for calling `governor.stop()` on shutdown).
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).
18
24
  */
19
25
  export async function runGovernor(config) {
20
26
  const governorConfig = {
@@ -27,6 +33,30 @@ export async function runGovernor(config) {
27
33
  enableAutoQA: config.enableAutoQA,
28
34
  enableAutoAcceptance: config.enableAutoAcceptance,
29
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 ?? false,
48
+ enableAutoBacklogCreation: config.enableAutoBacklogCreation ?? false,
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) --
30
60
  const governor = new WorkflowGovernor(governorConfig, config.dependencies);
31
61
  // -- Single scan mode (--once) --
32
62
  if (config.once) {
@@ -48,8 +78,10 @@ export async function runGovernor(config) {
48
78
  * --project <name> Project to scan (can be repeated)
49
79
  * --scan-interval <ms> Scan interval in milliseconds (default: 60000)
50
80
  * --max-dispatches <n> Maximum concurrent dispatches per scan (default: 3)
51
- * --no-auto-research Disable auto-research from Icebox
52
- * --no-auto-backlog-creation Disable auto-backlog-creation from Icebox
81
+ * --auto-research Enable auto-research from Icebox (default: off)
82
+ * --auto-backlog-creation Enable auto-backlog-creation from Icebox (default: off)
83
+ * --no-auto-research Disable auto-research (explicit override)
84
+ * --no-auto-backlog-creation Disable auto-backlog-creation (explicit override)
53
85
  * --no-auto-development Disable auto-development from Backlog
54
86
  * --no-auto-qa Disable auto-QA from Finished
55
87
  * --no-auto-acceptance Disable auto-acceptance from Delivered
@@ -61,12 +93,13 @@ export function parseGovernorArgs(argv = process.argv.slice(2)) {
61
93
  projects: [],
62
94
  scanIntervalMs: 60_000,
63
95
  maxConcurrentDispatches: 3,
64
- enableAutoResearch: true,
65
- enableAutoBacklogCreation: true,
96
+ enableAutoResearch: false,
97
+ enableAutoBacklogCreation: false,
66
98
  enableAutoDevelopment: true,
67
99
  enableAutoQA: true,
68
100
  enableAutoAcceptance: true,
69
101
  once: false,
102
+ mode: 'poll-only',
70
103
  };
71
104
  for (let i = 0; i < argv.length; i++) {
72
105
  const arg = argv[i];
@@ -80,9 +113,15 @@ export function parseGovernorArgs(argv = process.argv.slice(2)) {
80
113
  case '--max-dispatches':
81
114
  result.maxConcurrentDispatches = parseInt(argv[++i], 10);
82
115
  break;
116
+ case '--auto-research':
117
+ result.enableAutoResearch = true;
118
+ break;
83
119
  case '--no-auto-research':
84
120
  result.enableAutoResearch = false;
85
121
  break;
122
+ case '--auto-backlog-creation':
123
+ result.enableAutoBacklogCreation = true;
124
+ break;
86
125
  case '--no-auto-backlog-creation':
87
126
  result.enableAutoBacklogCreation = false;
88
127
  break;
@@ -98,6 +137,9 @@ export function parseGovernorArgs(argv = process.argv.slice(2)) {
98
137
  case '--once':
99
138
  result.once = true;
100
139
  break;
140
+ case '--mode':
141
+ result.mode = argv[++i];
142
+ break;
101
143
  case '--help':
102
144
  case '-h':
103
145
  printGovernorHelp();
@@ -120,16 +162,23 @@ Options:
120
162
  --project <name> Project to scan (can be repeated for multiple projects)
121
163
  --scan-interval <ms> Scan interval in milliseconds (default: 60000)
122
164
  --max-dispatches <n> Maximum concurrent dispatches per scan (default: 3)
123
- --no-auto-research Disable auto-research from Icebox
124
- --no-auto-backlog-creation Disable auto-backlog-creation from Icebox
165
+ --mode <mode> Execution mode: poll-only (default) or event-driven
166
+ --auto-research Enable auto-research from Icebox (default: off)
167
+ --auto-backlog-creation Enable auto-backlog-creation from Icebox (default: off)
125
168
  --no-auto-development Disable auto-development from Backlog
126
169
  --no-auto-qa Disable auto-QA from Finished
127
170
  --no-auto-acceptance Disable auto-acceptance from Delivered
128
171
  --once Run a single scan pass and exit
129
172
  --help, -h Show this help message
130
173
 
174
+ Modes:
175
+ poll-only Periodic scan loop using WorkflowGovernor (default)
176
+ event-driven Hybrid event-driven + poll sweep using EventDrivenGovernor.
177
+ Reacts to events in real time with a periodic safety-net poll.
178
+
131
179
  Environment:
132
180
  LINEAR_API_KEY Required API key for Linear authentication
181
+ REDIS_URL Redis connection URL (required for real dependencies)
133
182
 
134
183
  Examples:
135
184
  # Start the governor for a project
@@ -143,5 +192,8 @@ Examples:
143
192
 
144
193
  # Disable auto-QA (only scan for development work)
145
194
  agentfactory governor --project MyProject --no-auto-qa --no-auto-acceptance
195
+
196
+ # Use event-driven mode
197
+ agentfactory governor --project MyProject --mode event-driven
146
198
  `);
147
199
  }
@@ -25,6 +25,8 @@ export interface OrchestratorRunnerConfig {
25
25
  callbacks?: OrchestratorCallbacks;
26
26
  /** Custom workflow template directory path */
27
27
  templateDir?: string;
28
+ /** Git repository URL for worktree cloning */
29
+ repository?: string;
28
30
  }
29
31
  export interface OrchestratorCallbacks {
30
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;IACjC,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;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,CA0FnC"}
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"}
@@ -86,6 +86,9 @@ export async function runOrchestrator(config) {
86
86
  if (config.templateDir) {
87
87
  orchestratorConfig.templateDir = config.templateDir;
88
88
  }
89
+ if (config.repository) {
90
+ orchestratorConfig.repository = config.repository;
91
+ }
89
92
  const orchestrator = createOrchestrator(orchestratorConfig, {
90
93
  onIssueSelected: cb.onIssueSelected,
91
94
  onAgentStart: cb.onAgentStart,
@@ -31,6 +31,7 @@ function parseArgs() {
31
31
  wait: true,
32
32
  dryRun: false,
33
33
  templates: undefined,
34
+ repo: undefined,
34
35
  };
35
36
  for (let i = 0; i < args.length; i++) {
36
37
  const arg = args[i];
@@ -53,6 +54,9 @@ function parseArgs() {
53
54
  case '--templates':
54
55
  result.templates = args[++i];
55
56
  break;
57
+ case '--repo':
58
+ result.repo = args[++i];
59
+ break;
56
60
  case '--help':
57
61
  case '-h':
58
62
  printHelp();
@@ -75,6 +79,7 @@ Options:
75
79
  --no-wait Don't wait for agents to complete
76
80
  --dry-run Show what would be done without executing
77
81
  --templates <path> Custom workflow template directory
82
+ --repo <url> Git repository URL for worktree cloning
78
83
  --help, -h Show this help message
79
84
 
80
85
  Environment:
@@ -101,6 +106,7 @@ async function main() {
101
106
  console.log('========================');
102
107
  console.log(`Project: ${args.project ?? 'All'}`);
103
108
  console.log(`Max concurrent: ${args.max}`);
109
+ console.log(`Repo: ${args.repo ?? 'Any'}`);
104
110
  console.log(`Dry run: ${args.dryRun}`);
105
111
  console.log('');
106
112
  if (args.single) {
@@ -119,6 +125,7 @@ async function main() {
119
125
  wait: args.wait,
120
126
  dryRun: args.dryRun,
121
127
  templateDir: args.templates,
128
+ repository: args.repo,
122
129
  });
123
130
  if (!args.single && !args.dryRun) {
124
131
  console.log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supaku/agentfactory-cli",
3
- "version": "0.7.10",
3
+ "version": "0.7.12",
4
4
  "type": "module",
5
5
  "description": "CLI tools for AgentFactory — local orchestrator, remote worker, queue admin",
6
6
  "author": "Supaku (https://supaku.com)",
@@ -89,9 +89,9 @@
89
89
  ],
90
90
  "dependencies": {
91
91
  "dotenv": "^17.2.3",
92
- "@supaku/agentfactory": "0.7.10",
93
- "@supaku/agentfactory-linear": "0.7.10",
94
- "@supaku/agentfactory-server": "0.7.10"
92
+ "@supaku/agentfactory-server": "0.7.12",
93
+ "@supaku/agentfactory": "0.7.12",
94
+ "@supaku/agentfactory-linear": "0.7.12"
95
95
  },
96
96
  "devDependencies": {
97
97
  "@types/node": "^22.5.4",