mindforge-sdk 10.7.0 → 11.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,7 +64,7 @@ if (!valid) console.error(errors);
64
64
  - The SDK operates on local files and provides no network authentication. Do not expose SDK
65
65
  endpoints to the public internet.
66
66
 
67
- ## New in v9.0.0
67
+ ## New in v11.6.0
68
68
 
69
69
  ### Additional exports
70
70
 
@@ -72,13 +72,71 @@ if (!valid) console.error(errors);
72
72
  import {
73
73
  MindForgeClient,
74
74
  MindForgeEventStream,
75
- VERSION, // '9.0.0'
76
- } from '@mindforge/sdk';
75
+ WebSocketEventStream,
76
+ VERSION, // '11.6.0'
77
+ } from 'mindforge-sdk';
77
78
 
78
79
  import type {
79
- WaveExecutionResult, // Result type returned by wave execution operations
80
- MigrationResult, // Result type returned by schema migration operations
81
- } from '@mindforge/sdk';
80
+ WaveExecutionResult,
81
+ MigrationResult,
82
+ StreamChunk,
83
+ StreamingExecutionResult,
84
+ BatchExecutionRequest,
85
+ BatchExecutionResult,
86
+ } from 'mindforge-sdk';
87
+ ```
88
+
89
+ ### Streaming execution
90
+
91
+ ```typescript
92
+ import { MindForgeClient, WebSocketEventStream } from 'mindforge-sdk';
93
+
94
+ const client = new MindForgeClient({ projectRoot: '.' });
95
+ const { stream } = await client.streamExecution(1);
96
+
97
+ for await (const chunk of stream) {
98
+ if (chunk.type === 'content') process.stdout.write(chunk.content!);
99
+ if (chunk.type === 'done') break;
100
+ }
101
+ ```
102
+
103
+ ### Batch execution
104
+
105
+ Runs commands concurrently (semaphore-bounded by `maxConcurrency`, default 3). Each
106
+ task's `command` is the executable and `options.args` is a **string array** — it is NOT a
107
+ shell string. Commands run with `shell: false`, so arguments are passed directly to the
108
+ process and are safe from shell injection.
109
+
110
+ ```typescript
111
+ const batch = await client.batchExecute({
112
+ tasks: [
113
+ { id: 'task-a', command: 'node', options: { args: ['--version'] } },
114
+ { id: 'task-b', command: 'git', options: { args: ['rev-parse', 'HEAD'] } },
115
+ ],
116
+ maxConcurrency: 4,
117
+ });
118
+
119
+ for (const entry of batch.results) {
120
+ if (entry.status === 'fulfilled') {
121
+ // entry.result is { stdout, stderr, exitCode }
122
+ const { stdout, stderr, exitCode } = entry.result as {
123
+ stdout: string; stderr: string; exitCode: number;
124
+ };
125
+ console.log(`${entry.taskId} exited ${exitCode}: ${stdout.trim()}`);
126
+ } else {
127
+ // entry.status === 'rejected'
128
+ console.error(`${entry.taskId} failed: ${entry.error}`);
129
+ }
130
+ }
131
+
132
+ console.log(`batch finished in ${batch.totalDurationMs}ms`);
133
+ ```
134
+
135
+ ### Runtime config validation
136
+
137
+ ```typescript
138
+ const { valid, errors } = client.validateRuntimeConfig();
139
+ if (!valid) console.error(errors);
82
140
  ```
83
141
 
84
142
  ### New `MindForgeClient` methods
package/dist/client.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * MindForge SDK — Main Client
3
3
  */
4
4
  import { EventEmitter } from 'events';
5
- import type { MindForgeConfig, HealthReport, AuditLogEntry } from './types';
5
+ import type { MindForgeConfig, HealthReport, AuditLogEntry, StreamingExecutionResult, BatchExecutionRequest, BatchExecutionResult } from './types';
6
6
  export declare class MindForgeClient extends EventEmitter {
7
7
  private config;
8
8
  private projectRoot;
@@ -25,4 +25,13 @@ export declare class MindForgeClient extends EventEmitter {
25
25
  readAutoState(): Record<string, unknown> | null;
26
26
  getDbPath(): string;
27
27
  isDatabaseInitialized(): boolean;
28
+ streamExecution(phase: number, options?: {
29
+ taskFilter?: string;
30
+ }): Promise<StreamingExecutionResult>;
31
+ batchExecute(request: BatchExecutionRequest): Promise<BatchExecutionResult>;
32
+ validateRuntimeConfig(): {
33
+ valid: boolean;
34
+ errors: string[];
35
+ };
36
+ private executeCommand;
28
37
  }
package/dist/client.js CHANGED
@@ -38,8 +38,10 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.MindForgeClient = void 0;
40
40
  const events_1 = require("events");
41
+ const child_process_1 = require("child_process");
41
42
  const fs = __importStar(require("fs"));
42
43
  const path = __importStar(require("path"));
44
+ const events_2 = require("./events");
43
45
  class MindForgeClient extends events_1.EventEmitter {
44
46
  constructor(config = {}) {
45
47
  super();
@@ -228,5 +230,121 @@ class MindForgeClient extends events_1.EventEmitter {
228
230
  isDatabaseInitialized() {
229
231
  return fs.existsSync(this.getDbPath());
230
232
  }
233
+ // ── v11 Phase 5B: Streaming execution ─────────────────────────────────────
234
+ async streamExecution(phase, options) {
235
+ const eventSource = new events_2.WebSocketEventStream();
236
+ await eventSource.connect();
237
+ const chunks = [];
238
+ let resolveNext = null;
239
+ eventSource.on('stream_chunk', (raw) => {
240
+ const data = raw; // socket payload is untyped JSON; narrow at the boundary
241
+ if (resolveNext) {
242
+ resolveNext({ value: data, done: data.type === 'done' });
243
+ resolveNext = null;
244
+ }
245
+ else {
246
+ chunks.push(data);
247
+ }
248
+ });
249
+ const stream = {
250
+ [Symbol.asyncIterator]() {
251
+ return {
252
+ next() {
253
+ if (chunks.length > 0) {
254
+ const chunk = chunks.shift();
255
+ if (chunk.type === 'done')
256
+ eventSource.disconnect();
257
+ return Promise.resolve({ value: chunk, done: chunk.type === 'done' });
258
+ }
259
+ return new Promise(resolve => { resolveNext = resolve; });
260
+ },
261
+ return() {
262
+ eventSource.disconnect();
263
+ return Promise.resolve({ value: undefined, done: true });
264
+ }
265
+ };
266
+ }
267
+ };
268
+ return { phaseId: phase, taskId: options?.taskFilter || '*', stream };
269
+ }
270
+ // ── v11 Phase 5B: Batch execution with semaphore-based concurrency ────────
271
+ async batchExecute(request) {
272
+ const startTime = Date.now();
273
+ const maxConcurrency = request.maxConcurrency || 3;
274
+ const results = [];
275
+ let running = 0;
276
+ const queue = [...request.tasks];
277
+ await new Promise((resolve) => {
278
+ const processNext = () => {
279
+ if (queue.length === 0 && running === 0) {
280
+ resolve();
281
+ return;
282
+ }
283
+ while (running < maxConcurrency && queue.length > 0) {
284
+ const task = queue.shift();
285
+ running++;
286
+ this.executeCommand(task.command, task.options)
287
+ .then(result => {
288
+ results.push({ taskId: task.id, status: 'fulfilled', result });
289
+ })
290
+ .catch(error => {
291
+ results.push({ taskId: task.id, status: 'rejected', error: error.message });
292
+ })
293
+ .finally(() => {
294
+ running--;
295
+ processNext();
296
+ });
297
+ }
298
+ };
299
+ processNext();
300
+ });
301
+ return { results, totalDurationMs: Date.now() - startTime };
302
+ }
303
+ // ── v11 Phase 5B: Runtime config validation ───────────────────────────────
304
+ validateRuntimeConfig() {
305
+ const errors = [];
306
+ if (!this.config.projectRoot)
307
+ errors.push('projectRoot is required');
308
+ if (this.config.taskTimeoutMs && this.config.taskTimeoutMs < 0)
309
+ errors.push('taskTimeoutMs must be positive');
310
+ return { valid: errors.length === 0, errors };
311
+ }
312
+ executeCommand(command, options) {
313
+ const args = Array.isArray(options?.args) ? options.args : [];
314
+ const cwd = options?.cwd ?? this.projectRoot;
315
+ const timeoutMs = this.config.taskTimeoutMs;
316
+ return new Promise((resolve, reject) => {
317
+ const child = (0, child_process_1.spawn)(command, args, { cwd, shell: false });
318
+ let stdout = '';
319
+ let stderr = '';
320
+ let settled = false;
321
+ const timer = setTimeout(() => {
322
+ if (settled)
323
+ return;
324
+ child.kill('SIGTERM');
325
+ setTimeout(() => { if (!child.killed)
326
+ child.kill('SIGKILL'); }, 2000).unref();
327
+ settled = true;
328
+ reject(new Error(`Command timed out after ${timeoutMs}ms: ${command}`));
329
+ }, timeoutMs);
330
+ timer.unref();
331
+ child.stdout?.on('data', (d) => { stdout += d.toString(); });
332
+ child.stderr?.on('data', (d) => { stderr += d.toString(); });
333
+ child.on('error', (err) => {
334
+ if (settled)
335
+ return;
336
+ clearTimeout(timer);
337
+ settled = true;
338
+ reject(err);
339
+ });
340
+ child.on('close', (code) => {
341
+ if (settled)
342
+ return;
343
+ clearTimeout(timer);
344
+ settled = true;
345
+ resolve({ stdout, stderr, exitCode: code ?? -1 });
346
+ });
347
+ });
348
+ }
231
349
  }
232
350
  exports.MindForgeClient = MindForgeClient;
@@ -37,3 +37,4 @@ export declare const commands: {
37
37
  */
38
38
  prReview(opts?: CommandOptions): string;
39
39
  };
40
+ export declare function batch(commands: string[]): string;
package/dist/commands.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.commands = void 0;
9
+ exports.batch = batch;
9
10
  exports.commands = {
10
11
  /**
11
12
  * Build a /mindforge:health command string
@@ -56,3 +57,6 @@ exports.commands = {
56
57
  return `/mindforge:pr-review ${flags}`.trim();
57
58
  },
58
59
  };
60
+ function batch(commands) {
61
+ return commands.map(cmd => cmd.trim()).filter(Boolean).join(' && ');
62
+ }
package/dist/events.d.ts CHANGED
@@ -25,3 +25,17 @@ export declare class MindForgeEventStream {
25
25
  */
26
26
  stop(): void;
27
27
  }
28
+ type EventHandler = (data: unknown) => void;
29
+ export declare class WebSocketEventStream {
30
+ private url;
31
+ private ws;
32
+ private reconnectAttempts;
33
+ private maxReconnectAttempts;
34
+ private listeners;
35
+ constructor(url?: string);
36
+ connect(): Promise<void>;
37
+ on(eventType: string, handler: EventHandler): void;
38
+ off(eventType: string, handler: EventHandler): void;
39
+ disconnect(): void;
40
+ }
41
+ export {};
package/dist/events.js CHANGED
@@ -37,7 +37,7 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  };
38
38
  })();
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.MindForgeEventStream = void 0;
40
+ exports.WebSocketEventStream = exports.MindForgeEventStream = void 0;
41
41
  const http = __importStar(require("http"));
42
42
  const fs = __importStar(require("fs"));
43
43
  const path = __importStar(require("path"));
@@ -184,3 +184,51 @@ class MindForgeEventStream {
184
184
  }
185
185
  }
186
186
  exports.MindForgeEventStream = MindForgeEventStream;
187
+ class WebSocketEventStream {
188
+ constructor(url = 'ws://127.0.0.1:7337/ws') {
189
+ this.url = url;
190
+ this.ws = null;
191
+ this.reconnectAttempts = 0;
192
+ this.maxReconnectAttempts = 5;
193
+ this.listeners = new Map();
194
+ }
195
+ async connect() {
196
+ return new Promise((resolve, reject) => {
197
+ this.ws = new WebSocket(this.url);
198
+ this.ws.onopen = () => {
199
+ this.reconnectAttempts = 0;
200
+ resolve();
201
+ };
202
+ this.ws.onerror = (err) => reject(err);
203
+ this.ws.onmessage = (event) => {
204
+ try {
205
+ const parsed = JSON.parse(String(event.data));
206
+ const handlers = this.listeners.get(parsed.type) || new Set();
207
+ handlers.forEach(handler => handler(parsed.data));
208
+ }
209
+ catch { /* malformed message */ }
210
+ };
211
+ this.ws.onclose = () => {
212
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
213
+ this.reconnectAttempts++;
214
+ setTimeout(() => this.connect(), 1000 * this.reconnectAttempts);
215
+ }
216
+ };
217
+ });
218
+ }
219
+ on(eventType, handler) {
220
+ if (!this.listeners.has(eventType)) {
221
+ this.listeners.set(eventType, new Set());
222
+ }
223
+ this.listeners.get(eventType).add(handler);
224
+ }
225
+ off(eventType, handler) {
226
+ this.listeners.get(eventType)?.delete(handler);
227
+ }
228
+ disconnect() {
229
+ this.maxReconnectAttempts = 0;
230
+ this.ws?.close();
231
+ this.ws = null;
232
+ }
233
+ }
234
+ exports.WebSocketEventStream = WebSocketEventStream;
package/dist/index.d.ts CHANGED
@@ -3,9 +3,9 @@
3
3
  * @module @mindforge/sdk
4
4
  */
5
5
  export { MindForgeClient } from './client';
6
- export { MindForgeEventStream } from './events';
7
- export { commands } from './commands';
6
+ export { MindForgeEventStream, WebSocketEventStream } from './events';
7
+ export { commands, batch } from './commands';
8
8
  export { MindForgeMemory } from './memory';
9
9
  export type { CommandOptions } from './commands';
10
- export type { MindForgeConfig, PhaseResult, TaskResult, SecurityFinding, GateResult, HealthReport, HealthIssue, MindForgeEvent, AuditLogEntry, WaveExecutionResult, MigrationResult, } from './types';
11
- export declare const VERSION = "10.7.0";
10
+ export type { MindForgeConfig, PhaseResult, TaskResult, SecurityFinding, GateResult, HealthReport, HealthIssue, MindForgeEvent, AuditLogEntry, WaveExecutionResult, MigrationResult, StreamChunk, StreamingExecutionResult, BatchExecutionRequest, BatchExecutionResult, } from './types';
11
+ export declare const VERSION = "11.6.0";
package/dist/index.js CHANGED
@@ -4,13 +4,15 @@
4
4
  * @module @mindforge/sdk
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.VERSION = exports.MindForgeMemory = exports.commands = exports.MindForgeEventStream = exports.MindForgeClient = void 0;
7
+ exports.VERSION = exports.MindForgeMemory = exports.batch = exports.commands = exports.WebSocketEventStream = exports.MindForgeEventStream = exports.MindForgeClient = void 0;
8
8
  var client_1 = require("./client");
9
9
  Object.defineProperty(exports, "MindForgeClient", { enumerable: true, get: function () { return client_1.MindForgeClient; } });
10
10
  var events_1 = require("./events");
11
11
  Object.defineProperty(exports, "MindForgeEventStream", { enumerable: true, get: function () { return events_1.MindForgeEventStream; } });
12
+ Object.defineProperty(exports, "WebSocketEventStream", { enumerable: true, get: function () { return events_1.WebSocketEventStream; } });
12
13
  var commands_1 = require("./commands");
13
14
  Object.defineProperty(exports, "commands", { enumerable: true, get: function () { return commands_1.commands; } });
15
+ Object.defineProperty(exports, "batch", { enumerable: true, get: function () { return commands_1.batch; } });
14
16
  var memory_1 = require("./memory");
15
17
  Object.defineProperty(exports, "MindForgeMemory", { enumerable: true, get: function () { return memory_1.MindForgeMemory; } });
16
- exports.VERSION = '10.7.0';
18
+ exports.VERSION = '11.6.0';
package/dist/types.d.ts CHANGED
@@ -123,3 +123,36 @@ export interface MigrationResult {
123
123
  migrationsApplied?: string[];
124
124
  backupDir?: string;
125
125
  }
126
+ /** v11 Phase 5B: Streaming execution chunk from WebSocket event stream */
127
+ export interface StreamChunk {
128
+ type: 'content' | 'tool_use' | 'thinking' | 'done';
129
+ content?: string;
130
+ toolName?: string;
131
+ toolInput?: Record<string, unknown>;
132
+ finishReason?: 'end_turn' | 'max_tokens' | 'stop_sequence';
133
+ }
134
+ /** v11 Phase 5B: Result handle for a streaming execution */
135
+ export interface StreamingExecutionResult {
136
+ phaseId: number;
137
+ taskId: string;
138
+ stream: AsyncIterable<StreamChunk>;
139
+ }
140
+ /** v11 Phase 5B: Request payload for batch execution */
141
+ export interface BatchExecutionRequest {
142
+ tasks: Array<{
143
+ id: string;
144
+ command: string;
145
+ options?: Record<string, unknown>;
146
+ }>;
147
+ maxConcurrency?: number;
148
+ }
149
+ /** v11 Phase 5B: Aggregated result from batch execution */
150
+ export interface BatchExecutionResult {
151
+ results: Array<{
152
+ taskId: string;
153
+ status: 'fulfilled' | 'rejected';
154
+ result?: unknown;
155
+ error?: string;
156
+ }>;
157
+ totalDurationMs: number;
158
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindforge-sdk",
3
- "version": "10.7.0",
3
+ "version": "11.6.0",
4
4
  "description": "MindForge SDK \u2014 Programmatic API for embedding MindForge in tools",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,9 +20,10 @@
20
20
  },
21
21
  "scripts": {
22
22
  "build": "tsc",
23
- "test": "node tests/sdk.test.js",
23
+ "pretest": "npm run build",
24
+ "test": "node tests/sdk.test.js && node tests/execute-command.test.js",
24
25
  "lint": "eslint .",
25
- "prepublishOnly": "npm run build && npm test"
26
+ "prepublishOnly": "npm test"
26
27
  },
27
28
  "keywords": [
28
29
  "mindforge",