mindforge-sdk 10.7.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 +102 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.js +232 -0
- package/dist/commands.d.ts +39 -0
- package/dist/commands.js +58 -0
- package/dist/events.d.ts +27 -0
- package/dist/events.js +186 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +16 -0
- package/dist/memory.d.ts +183 -0
- package/dist/memory.js +628 -0
- package/dist/types.d.ts +125 -0
- package/dist/types.js +5 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @mindforge/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for embedding MindForge in tools, dashboards, and CI pipelines.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mindforge/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { MindForgeClient } from '@mindforge/sdk';
|
|
15
|
+
|
|
16
|
+
const client = new MindForgeClient({
|
|
17
|
+
projectRoot: '/path/to/project',
|
|
18
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Health check
|
|
22
|
+
const health = await client.health();
|
|
23
|
+
console.log(health.overallStatus); // 'healthy' | 'warning' | 'error'
|
|
24
|
+
|
|
25
|
+
// Read audit log
|
|
26
|
+
const findings = client.readAuditLog({ event: 'security_finding' });
|
|
27
|
+
console.log(findings);
|
|
28
|
+
|
|
29
|
+
// Read metrics
|
|
30
|
+
const metrics = client.readSessionMetrics(5);
|
|
31
|
+
console.log(metrics);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Real-time event streaming
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { MindForgeEventStream } from '@mindforge/sdk';
|
|
38
|
+
|
|
39
|
+
const stream = new MindForgeEventStream();
|
|
40
|
+
await stream.start(7337);
|
|
41
|
+
stream.watchAuditLog('/path/to/project');
|
|
42
|
+
|
|
43
|
+
// Subscribe from browser or tool:
|
|
44
|
+
const es = new EventSource('http://localhost:7337/events');
|
|
45
|
+
es.addEventListener('audit_entry', (e) => {
|
|
46
|
+
const entry = JSON.parse(e.data);
|
|
47
|
+
if (entry.event === 'task_completed') {
|
|
48
|
+
console.log('Task done:', entry.task_name);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Config validation
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const { valid, errors } = client.validateConfig();
|
|
57
|
+
if (!valid) console.error(errors);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Security notes
|
|
61
|
+
|
|
62
|
+
- `HANDOFF.json` may contain sensitive project state. Do not expose it to untrusted clients
|
|
63
|
+
or log its contents in external systems.
|
|
64
|
+
- The SDK operates on local files and provides no network authentication. Do not expose SDK
|
|
65
|
+
endpoints to the public internet.
|
|
66
|
+
|
|
67
|
+
## New in v9.0.0
|
|
68
|
+
|
|
69
|
+
### Additional exports
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import {
|
|
73
|
+
MindForgeClient,
|
|
74
|
+
MindForgeEventStream,
|
|
75
|
+
VERSION, // '9.0.0'
|
|
76
|
+
} from '@mindforge/sdk';
|
|
77
|
+
|
|
78
|
+
import type {
|
|
79
|
+
WaveExecutionResult, // Result type returned by wave execution operations
|
|
80
|
+
MigrationResult, // Result type returned by schema migration operations
|
|
81
|
+
} from '@mindforge/sdk';
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### New `MindForgeClient` methods
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const client = new MindForgeClient({ projectRoot: '/path/to/project' });
|
|
88
|
+
|
|
89
|
+
// Read the current auto-state from auto-state.json
|
|
90
|
+
const state = client.readAutoState();
|
|
91
|
+
console.log(state.status); // 'idle' | 'running' | 'awaiting_regeneration'
|
|
92
|
+
|
|
93
|
+
// Check whether the local MindForge database has been initialized
|
|
94
|
+
const ready = client.isDatabaseInitialized();
|
|
95
|
+
if (!ready) {
|
|
96
|
+
console.warn('Run mindforge:init-project first');
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## TypeScript support
|
|
101
|
+
|
|
102
|
+
Full type definitions included. No `@types/` package needed.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MindForge SDK — Main Client
|
|
3
|
+
*/
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
import type { MindForgeConfig, HealthReport, AuditLogEntry } from './types';
|
|
6
|
+
export declare class MindForgeClient extends EventEmitter {
|
|
7
|
+
private config;
|
|
8
|
+
private projectRoot;
|
|
9
|
+
constructor(config?: MindForgeConfig);
|
|
10
|
+
isInitialised(): boolean;
|
|
11
|
+
readState(): Record<string, unknown> | null;
|
|
12
|
+
readHandoff(): Record<string, unknown> | null;
|
|
13
|
+
health(): Promise<HealthReport>;
|
|
14
|
+
readAuditLog(filter?: {
|
|
15
|
+
event?: string;
|
|
16
|
+
phase?: number;
|
|
17
|
+
since?: Date;
|
|
18
|
+
}): AuditLogEntry[];
|
|
19
|
+
readSessionMetrics(limit?: number): unknown[];
|
|
20
|
+
validateConfig(): {
|
|
21
|
+
valid: boolean;
|
|
22
|
+
errors: string[];
|
|
23
|
+
warnings: string[];
|
|
24
|
+
};
|
|
25
|
+
readAutoState(): Record<string, unknown> | null;
|
|
26
|
+
getDbPath(): string;
|
|
27
|
+
isDatabaseInitialized(): boolean;
|
|
28
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MindForge SDK — Main Client
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.MindForgeClient = void 0;
|
|
40
|
+
const events_1 = require("events");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
class MindForgeClient extends events_1.EventEmitter {
|
|
44
|
+
constructor(config = {}) {
|
|
45
|
+
super();
|
|
46
|
+
this.projectRoot = config.projectRoot ?? process.cwd();
|
|
47
|
+
this.config = {
|
|
48
|
+
projectRoot: this.projectRoot,
|
|
49
|
+
apiKey: config.apiKey ?? process.env.ANTHROPIC_API_KEY ?? '',
|
|
50
|
+
ciMode: config.ciMode ?? (process.env.CI === 'true'),
|
|
51
|
+
outputFormat: config.outputFormat ?? 'json',
|
|
52
|
+
taskTimeoutMs: config.taskTimeoutMs ?? 600000,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// ── Project state ──────────────────────────────────────────────────────────
|
|
56
|
+
isInitialised() {
|
|
57
|
+
return fs.existsSync(path.join(this.projectRoot, '.planning', 'PROJECT.md'));
|
|
58
|
+
}
|
|
59
|
+
readState() {
|
|
60
|
+
const statePath = path.join(this.projectRoot, '.planning', 'STATE.md');
|
|
61
|
+
if (!fs.existsSync(statePath))
|
|
62
|
+
return null;
|
|
63
|
+
return { raw: fs.readFileSync(statePath, 'utf8') };
|
|
64
|
+
}
|
|
65
|
+
readHandoff() {
|
|
66
|
+
const handoffPath = path.join(this.projectRoot, '.planning', 'HANDOFF.json');
|
|
67
|
+
if (!fs.existsSync(handoffPath))
|
|
68
|
+
return null;
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(fs.readFileSync(handoffPath, 'utf8'));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ── Health check ───────────────────────────────────────────────────────────
|
|
77
|
+
async health() {
|
|
78
|
+
const errors = [];
|
|
79
|
+
const warnings = [];
|
|
80
|
+
const info = [];
|
|
81
|
+
const requiredFiles = [
|
|
82
|
+
'.planning/STATE.md',
|
|
83
|
+
'.planning/HANDOFF.json',
|
|
84
|
+
'.planning/AUDIT.jsonl',
|
|
85
|
+
'.mindforge/org/CONVENTIONS.md',
|
|
86
|
+
];
|
|
87
|
+
for (const file of requiredFiles) {
|
|
88
|
+
const fullPath = path.join(this.projectRoot, file);
|
|
89
|
+
if (!fs.existsSync(fullPath)) {
|
|
90
|
+
warnings.push({ category: 'installation', message: `Missing: ${file}`, autoRepairable: false });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Check HANDOFF.json validity
|
|
94
|
+
const handoff = this.readHandoff();
|
|
95
|
+
if (handoff && !handoff.schema_version) {
|
|
96
|
+
errors.push({ category: 'state', message: 'HANDOFF.json missing schema_version field', autoRepairable: false });
|
|
97
|
+
}
|
|
98
|
+
// Check AUDIT.jsonl
|
|
99
|
+
const auditPath = path.join(this.projectRoot, '.planning', 'AUDIT.jsonl');
|
|
100
|
+
if (fs.existsSync(auditPath)) {
|
|
101
|
+
const lineCount = fs.readFileSync(auditPath, 'utf8').split('\n').filter(Boolean).length;
|
|
102
|
+
if (lineCount > 9000) {
|
|
103
|
+
warnings.push({ category: 'audit', message: `AUDIT.jsonl has ${lineCount} lines — archive soon`, autoRepairable: true });
|
|
104
|
+
}
|
|
105
|
+
info.push({ category: 'audit', message: `AUDIT.jsonl: ${lineCount} entries`, autoRepairable: false });
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
overallStatus: errors.length > 0 ? 'error' : warnings.length > 0 ? 'warning' : 'healthy',
|
|
109
|
+
errors,
|
|
110
|
+
warnings,
|
|
111
|
+
informational: info,
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// ── Audit log reading ──────────────────────────────────────────────────────
|
|
116
|
+
readAuditLog(filter) {
|
|
117
|
+
const auditPath = path.join(this.projectRoot, '.planning', 'AUDIT.jsonl');
|
|
118
|
+
if (!fs.existsSync(auditPath))
|
|
119
|
+
return [];
|
|
120
|
+
return fs.readFileSync(auditPath, 'utf8')
|
|
121
|
+
.split('\n')
|
|
122
|
+
.filter(Boolean)
|
|
123
|
+
.map((line) => { try {
|
|
124
|
+
return JSON.parse(line);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
} })
|
|
129
|
+
.filter((entry) => !!entry)
|
|
130
|
+
.filter(entry => {
|
|
131
|
+
if (filter?.event && entry.event !== filter.event)
|
|
132
|
+
return false;
|
|
133
|
+
if (filter?.phase !== undefined && entry.phase !== filter.phase)
|
|
134
|
+
return false;
|
|
135
|
+
if (filter?.since && new Date(entry.timestamp) < filter.since)
|
|
136
|
+
return false;
|
|
137
|
+
return true;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// ── Metrics reading ────────────────────────────────────────────────────────
|
|
141
|
+
readSessionMetrics(limit = 10) {
|
|
142
|
+
const metricsPath = path.join(this.projectRoot, '.mindforge', 'metrics', 'session-quality.jsonl');
|
|
143
|
+
if (!fs.existsSync(metricsPath))
|
|
144
|
+
return [];
|
|
145
|
+
return fs.readFileSync(metricsPath, 'utf8')
|
|
146
|
+
.split('\n')
|
|
147
|
+
.filter(Boolean)
|
|
148
|
+
.slice(-limit)
|
|
149
|
+
.map((line) => { try {
|
|
150
|
+
return JSON.parse(line);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
} })
|
|
155
|
+
.filter(Boolean);
|
|
156
|
+
}
|
|
157
|
+
// ── Config validation ──────────────────────────────────────────────────────
|
|
158
|
+
validateConfig() {
|
|
159
|
+
const errors = [];
|
|
160
|
+
const warnings = [];
|
|
161
|
+
const configPath = path.join(this.projectRoot, 'MINDFORGE.md');
|
|
162
|
+
if (!fs.existsSync(configPath)) {
|
|
163
|
+
return { valid: false, errors: ['MINDFORGE.md not found at project root'], warnings: [] };
|
|
164
|
+
}
|
|
165
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
166
|
+
// Required fields that must be present in a valid MINDFORGE.md
|
|
167
|
+
const requiredFields = [
|
|
168
|
+
{ key: 'VERSION', pattern: /\[VERSION\]\s*=\s*.+/ },
|
|
169
|
+
{ key: 'REACTIVE_MODE', pattern: /\[REACTIVE_MODE\]\s*=\s*.+/ },
|
|
170
|
+
{ key: 'PLANNER', pattern: /\[PLANNER\]\s*=\s*.+/ },
|
|
171
|
+
{ key: 'EXECUTOR', pattern: /\[EXECUTOR\]\s*=\s*.+/ },
|
|
172
|
+
{ key: 'MIN_SOUL_SCORE', pattern: /\[MIN_SOUL_SCORE\]\s*=\s*.+/ },
|
|
173
|
+
];
|
|
174
|
+
for (const field of requiredFields) {
|
|
175
|
+
if (!field.pattern.test(content)) {
|
|
176
|
+
errors.push(`Missing required field: [${field.key}]`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Recommended fields — warn if missing
|
|
180
|
+
const recommendedFields = [
|
|
181
|
+
{ key: 'COST_WARN_USD', pattern: /\[COST_WARN_USD\]\s*=\s*.+/ },
|
|
182
|
+
{ key: 'COST_HARD_LIMIT_USD', pattern: /\[COST_HARD_LIMIT_USD\]\s*=\s*.+/ },
|
|
183
|
+
{ key: 'BLOCK_ON_SECURITY', pattern: /\[BLOCK_ON_SECURITY\]\s*=\s*.+/ },
|
|
184
|
+
];
|
|
185
|
+
for (const field of recommendedFields) {
|
|
186
|
+
if (!field.pattern.test(content)) {
|
|
187
|
+
warnings.push(`Recommended field missing: [${field.key}]`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Schema-based validation if schema file exists
|
|
191
|
+
const schemaPath = path.join(this.projectRoot, '.mindforge', 'MINDFORGE-SCHEMA.json');
|
|
192
|
+
if (fs.existsSync(schemaPath)) {
|
|
193
|
+
try {
|
|
194
|
+
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
|
195
|
+
const nonOverridableKeys = Object.entries(schema.properties ?? {})
|
|
196
|
+
.filter(([, def]) => def.nonOverridable === true)
|
|
197
|
+
.map(([key]) => key);
|
|
198
|
+
for (const key of nonOverridableKeys) {
|
|
199
|
+
// Non-overridable fields must not be set to false
|
|
200
|
+
const disabledPattern = new RegExp(`\\[${key}\\]\\s*=\\s*false`, 'i');
|
|
201
|
+
if (disabledPattern.test(content)) {
|
|
202
|
+
errors.push(`Non-overridable field [${key}] cannot be disabled`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
warnings.push('MINDFORGE-SCHEMA.json exists but could not be parsed');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
211
|
+
}
|
|
212
|
+
// ── v9 Pillar XXIV: Wave execution status ─────────────────────────────────
|
|
213
|
+
readAutoState() {
|
|
214
|
+
const statePath = path.join(this.projectRoot, '.planning', 'auto-state.json');
|
|
215
|
+
if (!fs.existsSync(statePath))
|
|
216
|
+
return null;
|
|
217
|
+
try {
|
|
218
|
+
return JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// ── v9 Pillar XXVI: Knowledge query ───────────────────────────────────────
|
|
225
|
+
getDbPath() {
|
|
226
|
+
return path.join(this.projectRoot, '.mindforge', 'celestial.db');
|
|
227
|
+
}
|
|
228
|
+
isDatabaseInitialized() {
|
|
229
|
+
return fs.existsSync(this.getDbPath());
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
exports.MindForgeClient = MindForgeClient;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MindForge SDK — Command Builders
|
|
3
|
+
* Builds the command strings that can be sent to Claude Code / Antigravity
|
|
4
|
+
* via their programmatic APIs.
|
|
5
|
+
*/
|
|
6
|
+
export interface CommandOptions {
|
|
7
|
+
flags?: string[];
|
|
8
|
+
args?: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare const commands: {
|
|
11
|
+
/**
|
|
12
|
+
* Build a /mindforge:health command string
|
|
13
|
+
*/
|
|
14
|
+
health(opts?: CommandOptions): string;
|
|
15
|
+
/**
|
|
16
|
+
* Build a /mindforge:plan-phase command string
|
|
17
|
+
*/
|
|
18
|
+
planPhase(phase: number, opts?: CommandOptions): string;
|
|
19
|
+
/**
|
|
20
|
+
* Build a /mindforge:execute-phase command string
|
|
21
|
+
*/
|
|
22
|
+
executePhase(phase: number, opts?: CommandOptions): string;
|
|
23
|
+
/**
|
|
24
|
+
* Build a /mindforge:security-scan command string
|
|
25
|
+
*/
|
|
26
|
+
securityScan(path?: string, opts?: CommandOptions): string;
|
|
27
|
+
/**
|
|
28
|
+
* Build a /mindforge:audit command string with filters
|
|
29
|
+
*/
|
|
30
|
+
audit(filter?: {
|
|
31
|
+
phase?: number;
|
|
32
|
+
event?: string;
|
|
33
|
+
since?: string;
|
|
34
|
+
}): string;
|
|
35
|
+
/**
|
|
36
|
+
* Build a /mindforge:pr-review command string
|
|
37
|
+
*/
|
|
38
|
+
prReview(opts?: CommandOptions): string;
|
|
39
|
+
};
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MindForge SDK — Command Builders
|
|
4
|
+
* Builds the command strings that can be sent to Claude Code / Antigravity
|
|
5
|
+
* via their programmatic APIs.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.commands = void 0;
|
|
9
|
+
exports.commands = {
|
|
10
|
+
/**
|
|
11
|
+
* Build a /mindforge:health command string
|
|
12
|
+
*/
|
|
13
|
+
health(opts = {}) {
|
|
14
|
+
const flags = opts.flags?.join(' ') ?? '';
|
|
15
|
+
return `/mindforge:health ${flags}`.trim();
|
|
16
|
+
},
|
|
17
|
+
/**
|
|
18
|
+
* Build a /mindforge:plan-phase command string
|
|
19
|
+
*/
|
|
20
|
+
planPhase(phase, opts = {}) {
|
|
21
|
+
const flags = opts.flags?.join(' ') ?? '';
|
|
22
|
+
return `/mindforge:plan-phase ${phase} ${flags}`.trim();
|
|
23
|
+
},
|
|
24
|
+
/**
|
|
25
|
+
* Build a /mindforge:execute-phase command string
|
|
26
|
+
*/
|
|
27
|
+
executePhase(phase, opts = {}) {
|
|
28
|
+
const flags = opts.flags?.join(' ') ?? '';
|
|
29
|
+
return `/mindforge:execute-phase ${phase} ${flags}`.trim();
|
|
30
|
+
},
|
|
31
|
+
/**
|
|
32
|
+
* Build a /mindforge:security-scan command string
|
|
33
|
+
*/
|
|
34
|
+
securityScan(path, opts = {}) {
|
|
35
|
+
const flags = opts.flags?.join(' ') ?? '';
|
|
36
|
+
return `/mindforge:security-scan ${path ?? ''} ${flags}`.trim();
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* Build a /mindforge:audit command string with filters
|
|
40
|
+
*/
|
|
41
|
+
audit(filter = {}) {
|
|
42
|
+
const parts = ['/mindforge:audit'];
|
|
43
|
+
if (filter.phase)
|
|
44
|
+
parts.push(`--phase ${filter.phase}`);
|
|
45
|
+
if (filter.event)
|
|
46
|
+
parts.push(`--event ${filter.event}`);
|
|
47
|
+
if (filter.since)
|
|
48
|
+
parts.push(`--since ${filter.since}`);
|
|
49
|
+
return parts.join(' ');
|
|
50
|
+
},
|
|
51
|
+
/**
|
|
52
|
+
* Build a /mindforge:pr-review command string
|
|
53
|
+
*/
|
|
54
|
+
prReview(opts = {}) {
|
|
55
|
+
const flags = opts.flags?.join(' ') ?? '';
|
|
56
|
+
return `/mindforge:pr-review ${flags}`.trim();
|
|
57
|
+
},
|
|
58
|
+
};
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MindForge SDK — Server-Sent Events (SSE) stream for real-time progress
|
|
3
|
+
* Enables external tools to subscribe to MindForge execution progress.
|
|
4
|
+
*/
|
|
5
|
+
export declare class MindForgeEventStream {
|
|
6
|
+
private clients;
|
|
7
|
+
private server;
|
|
8
|
+
private auditWatcher;
|
|
9
|
+
private lastAuditLine;
|
|
10
|
+
/**
|
|
11
|
+
* Start the SSE server on the given port
|
|
12
|
+
*/
|
|
13
|
+
start(port?: number): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Watch AUDIT.jsonl for new entries and broadcast as SSE events
|
|
16
|
+
*/
|
|
17
|
+
watchAuditLog(projectRoot: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Broadcast an event to all connected clients
|
|
20
|
+
*/
|
|
21
|
+
broadcast(eventType: string, data: unknown): void;
|
|
22
|
+
private sendEvent;
|
|
23
|
+
/**
|
|
24
|
+
* Stop the event stream server
|
|
25
|
+
*/
|
|
26
|
+
stop(): void;
|
|
27
|
+
}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MindForge SDK — Server-Sent Events (SSE) stream for real-time progress
|
|
4
|
+
* Enables external tools to subscribe to MindForge execution progress.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.MindForgeEventStream = void 0;
|
|
41
|
+
const http = __importStar(require("http"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
class MindForgeEventStream {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.clients = [];
|
|
47
|
+
this.server = null;
|
|
48
|
+
this.auditWatcher = null;
|
|
49
|
+
this.lastAuditLine = 0;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Start the SSE server on the given port
|
|
53
|
+
*/
|
|
54
|
+
start(port = 7337) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
this.server = http.createServer((req, res) => {
|
|
57
|
+
if (req.url !== '/events') {
|
|
58
|
+
res.writeHead(404);
|
|
59
|
+
res.end();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// SECURITY: Only allow connections from localhost
|
|
63
|
+
const clientIp = req.socket.remoteAddress;
|
|
64
|
+
const isLocalhost = clientIp === '127.0.0.1' ||
|
|
65
|
+
clientIp === '::1' ||
|
|
66
|
+
clientIp === '::ffff:127.0.0.1';
|
|
67
|
+
if (!isLocalhost) {
|
|
68
|
+
res.writeHead(403);
|
|
69
|
+
res.end('Forbidden: MindForge event stream is localhost-only');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// CORS: Only allow localhost origins (exact match, not wildcard)
|
|
73
|
+
const origin = req.headers.origin;
|
|
74
|
+
const allowedOriginPattern = /^https?:\/\/localhost(:\d+)?$/;
|
|
75
|
+
const corsOrigin = origin && allowedOriginPattern.test(origin)
|
|
76
|
+
? origin
|
|
77
|
+
: 'http://localhost';
|
|
78
|
+
res.writeHead(200, {
|
|
79
|
+
'Content-Type': 'text/event-stream',
|
|
80
|
+
'Cache-Control': 'no-cache',
|
|
81
|
+
'Connection': 'keep-alive',
|
|
82
|
+
'Access-Control-Allow-Origin': corsOrigin,
|
|
83
|
+
'X-Content-Type-Options': 'nosniff',
|
|
84
|
+
});
|
|
85
|
+
const clientId = Date.now().toString();
|
|
86
|
+
this.clients.push({ id: clientId, response: res });
|
|
87
|
+
// Send initial connection event
|
|
88
|
+
this.sendEvent(res, 'connected', { clientId, timestamp: new Date().toISOString() });
|
|
89
|
+
// Clean up on disconnect
|
|
90
|
+
req.on('close', () => {
|
|
91
|
+
this.clients = this.clients.filter(c => c.id !== clientId);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
// SECURITY: Bind to localhost ONLY — not all interfaces
|
|
95
|
+
this.server.listen(port, '127.0.0.1', () => {
|
|
96
|
+
console.log(`MindForge event stream: http://localhost:${port}/events (localhost only)`);
|
|
97
|
+
resolve();
|
|
98
|
+
});
|
|
99
|
+
this.server.on('error', (err) => {
|
|
100
|
+
if (err.code === 'EADDRINUSE') {
|
|
101
|
+
reject(new Error(`Port ${port} is already in use. Use: new MindForgeEventStream().start(${port + 1})`));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
reject(err);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Watch AUDIT.jsonl for new entries and broadcast as SSE events
|
|
111
|
+
*/
|
|
112
|
+
watchAuditLog(projectRoot) {
|
|
113
|
+
const auditPath = path.join(projectRoot, '.planning', 'AUDIT.jsonl');
|
|
114
|
+
if (!fs.existsSync(auditPath)) {
|
|
115
|
+
// Create the file if it doesn't exist yet
|
|
116
|
+
fs.writeFileSync(auditPath, '');
|
|
117
|
+
}
|
|
118
|
+
// Set initial line count
|
|
119
|
+
const content = fs.readFileSync(auditPath, 'utf8');
|
|
120
|
+
this.lastAuditLine = content.split('\n').filter(Boolean).length;
|
|
121
|
+
try {
|
|
122
|
+
this.auditWatcher = fs.watch(auditPath, () => {
|
|
123
|
+
const lines = fs.readFileSync(auditPath, 'utf8')
|
|
124
|
+
.split('\n')
|
|
125
|
+
.filter(Boolean);
|
|
126
|
+
// Broadcast new lines
|
|
127
|
+
for (let i = this.lastAuditLine; i < lines.length; i++) {
|
|
128
|
+
try {
|
|
129
|
+
const entry = JSON.parse(lines[i]);
|
|
130
|
+
this.broadcast('audit_entry', entry);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Ignore parse errors for incomplete lines
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this.lastAuditLine = lines.length;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
if (err.code === 'ENOSPC') {
|
|
141
|
+
// Linux inotify limit reached — fall back to polling
|
|
142
|
+
console.warn('MindForge: inotify limit reached, falling back to 2s polling');
|
|
143
|
+
setInterval(() => {
|
|
144
|
+
const lines = fs.readFileSync(auditPath, 'utf8').split('\n').filter(Boolean);
|
|
145
|
+
for (let i = this.lastAuditLine; i < lines.length; i++) {
|
|
146
|
+
try {
|
|
147
|
+
this.broadcast('audit_entry', JSON.parse(lines[i]));
|
|
148
|
+
}
|
|
149
|
+
catch { /* ignore */ }
|
|
150
|
+
}
|
|
151
|
+
this.lastAuditLine = lines.length;
|
|
152
|
+
}, 2000);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Broadcast an event to all connected clients
|
|
161
|
+
*/
|
|
162
|
+
broadcast(eventType, data) {
|
|
163
|
+
this.clients.forEach(client => {
|
|
164
|
+
this.sendEvent(client.response, eventType, data);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
sendEvent(res, type, data) {
|
|
168
|
+
try {
|
|
169
|
+
res.write(`event: ${type}\n`);
|
|
170
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Client disconnected
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Stop the event stream server
|
|
178
|
+
*/
|
|
179
|
+
stop() {
|
|
180
|
+
this.auditWatcher?.close();
|
|
181
|
+
this.server?.close();
|
|
182
|
+
this.clients.forEach(c => c.response.end());
|
|
183
|
+
this.clients = [];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
exports.MindForgeEventStream = MindForgeEventStream;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MindForge SDK — Public API
|
|
3
|
+
* @module @mindforge/sdk
|
|
4
|
+
*/
|
|
5
|
+
export { MindForgeClient } from './client';
|
|
6
|
+
export { MindForgeEventStream } from './events';
|
|
7
|
+
export { commands } from './commands';
|
|
8
|
+
export { MindForgeMemory } from './memory';
|
|
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";
|