mindforge-cc 1.0.5 → 2.0.0-alpha.4
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/.agent/CLAUDE.md +53 -0
- package/.agent/mindforge/auto.md +22 -0
- package/.agent/mindforge/browse.md +26 -0
- package/.agent/mindforge/costs.md +11 -0
- package/.agent/mindforge/cross-review.md +17 -0
- package/.agent/mindforge/execute-phase.md +5 -3
- package/.agent/mindforge/qa.md +16 -0
- package/.agent/mindforge/remember.md +14 -0
- package/.agent/mindforge/research.md +11 -0
- package/.agent/mindforge/steer.md +13 -0
- package/.agent/workflows/publish-release.md +36 -0
- package/.claude/CLAUDE.md +53 -0
- package/.claude/commands/mindforge/auto.md +22 -0
- package/.claude/commands/mindforge/browse.md +26 -0
- package/.claude/commands/mindforge/costs.md +11 -0
- package/.claude/commands/mindforge/cross-review.md +17 -0
- package/.claude/commands/mindforge/execute-phase.md +5 -3
- package/.claude/commands/mindforge/qa.md +16 -0
- package/.claude/commands/mindforge/remember.md +14 -0
- package/.claude/commands/mindforge/research.md +11 -0
- package/.claude/commands/mindforge/steer.md +13 -0
- package/.mindforge/MINDFORGE-V2-SCHEMA.json +47 -0
- package/.mindforge/browser/daemon-protocol.md +24 -0
- package/.mindforge/browser/qa-engine.md +16 -0
- package/.mindforge/browser/session-manager.md +18 -0
- package/.mindforge/browser/visual-verify-spec.md +31 -0
- package/.mindforge/engine/autonomous/auto-executor.md +266 -0
- package/.mindforge/engine/autonomous/headless-adapter.md +66 -0
- package/.mindforge/engine/autonomous/node-repair.md +190 -0
- package/.mindforge/engine/autonomous/progress-reporter.md +58 -0
- package/.mindforge/engine/autonomous/steering-manager.md +64 -0
- package/.mindforge/engine/autonomous/stuck-detector.md +89 -0
- package/.mindforge/memory/MEMORY-SCHEMA.md +155 -0
- package/.mindforge/memory/decision-library.jsonl +0 -0
- package/.mindforge/memory/engine/capture-protocol.md +36 -0
- package/.mindforge/memory/engine/global-sync-spec.md +42 -0
- package/.mindforge/memory/engine/retrieval-spec.md +44 -0
- package/.mindforge/memory/knowledge-base.jsonl +7 -0
- package/.mindforge/memory/pattern-library.jsonl +1 -0
- package/.mindforge/memory/team-preferences.jsonl +4 -0
- package/.mindforge/models/model-registry.md +48 -0
- package/.mindforge/models/model-router.md +30 -0
- package/.mindforge/personas/research-agent.md +24 -0
- package/.planning/browser-daemon.log +32 -0
- package/.planning/decisions/ADR-021-autonomy-boundary.md +17 -0
- package/.planning/decisions/ADR-022-node-repair-hierarchy.md +19 -0
- package/.planning/decisions/ADR-023-gate-3-timing.md +15 -0
- package/CHANGELOG.md +68 -0
- package/MINDFORGE.md +26 -3
- package/README.md +54 -18
- package/bin/autonomous/auto-runner.js +95 -0
- package/bin/autonomous/headless.js +36 -0
- package/bin/autonomous/progress-stream.js +49 -0
- package/bin/autonomous/repair-operator.js +213 -0
- package/bin/autonomous/steer.js +71 -0
- package/bin/autonomous/stuck-monitor.js +77 -0
- package/bin/browser/browser-daemon.js +139 -0
- package/bin/browser/daemon-manager.js +91 -0
- package/bin/browser/qa-engine.js +47 -0
- package/bin/browser/qa-report-writer.js +32 -0
- package/bin/browser/regression-writer.js +27 -0
- package/bin/browser/screenshot-store.js +49 -0
- package/bin/browser/session-manager.js +93 -0
- package/bin/browser/visual-verify-executor.js +89 -0
- package/bin/install.js +4 -4
- package/bin/installer-core.js +24 -24
- package/bin/memory/cli.js +99 -0
- package/bin/memory/global-sync.js +107 -0
- package/bin/memory/knowledge-capture.js +278 -0
- package/bin/memory/knowledge-indexer.js +172 -0
- package/bin/memory/knowledge-store.js +319 -0
- package/bin/memory/session-memory-loader.js +137 -0
- package/bin/migrations/0.1.0-to-0.5.0.js +2 -3
- package/bin/migrations/0.5.0-to-0.6.0.js +1 -1
- package/bin/migrations/0.6.0-to-1.0.0.js +3 -3
- package/bin/migrations/migrate.js +15 -11
- package/bin/models/anthropic-provider.js +77 -0
- package/bin/models/cost-tracker.js +118 -0
- package/bin/models/gemini-provider.js +79 -0
- package/bin/models/model-client.js +98 -0
- package/bin/models/model-router.js +111 -0
- package/bin/models/openai-provider.js +78 -0
- package/bin/research/research-engine.js +115 -0
- package/bin/review/cross-review-engine.js +81 -0
- package/bin/review/finding-synthesizer.js +116 -0
- package/bin/review/review-report-writer.js +49 -0
- package/bin/updater/self-update.js +13 -13
- package/docs/adr/ADR-024-browser-localhost-only.md +17 -0
- package/docs/adr/ADR-025-visual-verify-failure-treatment.md +19 -0
- package/docs/adr/ADR-026-session-persistence-security.md +20 -0
- package/docs/architecture/README.md +4 -2
- package/docs/publishing-guide.md +78 -0
- package/docs/reference/commands.md +17 -2
- package/docs/reference/sdk-api.md +6 -1
- package/docs/user-guide.md +93 -9
- package/docs/usp-features.md +56 -8
- package/package.json +3 -2
package/MINDFORGE.md
CHANGED
|
@@ -22,9 +22,9 @@ If a configured model is unavailable, fallback to `inherit` and warn.
|
|
|
22
22
|
|
|
23
23
|
## Project identity
|
|
24
24
|
NAME=MindForge
|
|
25
|
-
VERSION=
|
|
26
|
-
DESCRIPTION=Enterprise agentic framework with
|
|
27
|
-
MINDFORGE_VERSION_REQUIRED=
|
|
25
|
+
VERSION=2.0.0
|
|
26
|
+
DESCRIPTION=Enterprise agentic framework with Multi-Model Intelligence Layer
|
|
27
|
+
MINDFORGE_VERSION_REQUIRED=2.0.0
|
|
28
28
|
|
|
29
29
|
## Model preferences
|
|
30
30
|
PLANNER_MODEL=claude-opus-4-5
|
|
@@ -33,6 +33,15 @@ REVIEWER_MODEL=claude-sonnet-4-5
|
|
|
33
33
|
VERIFIER_MODEL=claude-sonnet-4-5
|
|
34
34
|
SECURITY_MODEL=claude-opus-4-5
|
|
35
35
|
DEBUG_MODEL=claude-opus-4-5
|
|
36
|
+
RESEARCH_MODEL=gemini-1.5-pro
|
|
37
|
+
QA_MODEL=claude-4-5-sonnet
|
|
38
|
+
QUICK_MODEL=claude-4-5-haiku
|
|
39
|
+
|
|
40
|
+
## Cost Management
|
|
41
|
+
MODEL_COST_WARN_USD=1.00
|
|
42
|
+
MODEL_COST_HARD_LIMIT_USD=10.00
|
|
43
|
+
MODEL_PREFER_CHEAP_BELOW_DIFFICULTY=2.0
|
|
44
|
+
REQUIRE_CROSS_REVIEW=false
|
|
36
45
|
|
|
37
46
|
## Execution behavior
|
|
38
47
|
TIER1_AUTO_APPROVE=true
|
|
@@ -59,6 +68,20 @@ DISCUSS_PHASE_REQUIRED_ABOVE_DIFFICULTY=3.5
|
|
|
59
68
|
ANTIPATTERN_SENSITIVITY=standard
|
|
60
69
|
BLOCK_ON_HIGH_ANTIPATTERNS=false
|
|
61
70
|
|
|
71
|
+
## Autonomous mode settings
|
|
72
|
+
AUTO_MODE_DEFAULT_TIMEOUT_MINUTES=120
|
|
73
|
+
AUTO_PUSH_ON_WAVE_COMPLETE=false
|
|
74
|
+
AUTO_NODE_REPAIR_BUDGET=2
|
|
75
|
+
AUTO_PLAN_AMBIGUITY_THRESHOLD=3.5
|
|
76
|
+
SLACK_WEBHOOK_URL=
|
|
77
|
+
|
|
78
|
+
## Browser Runtime
|
|
79
|
+
BROWSER_PORT=7338
|
|
80
|
+
BROWSER_HEADLESS=true
|
|
81
|
+
DEV_SERVER_URL=http://localhost:3000
|
|
82
|
+
AUTO_RUN_QA_AFTER_UI_WAVES=true
|
|
83
|
+
BROWSER_IDLE_TIMEOUT_MINUTES=30
|
|
84
|
+
|
|
62
85
|
## Project-specific agent instructions
|
|
63
86
|
ADDITIONAL_AGENT_INSTRUCTIONS="""
|
|
64
87
|
- Check packages/shared before creating utilities.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# MindForge — Enterprise Agentic Framework (
|
|
1
|
+
# MindForge — Enterprise Agentic Framework (v2.0.0-alpha.4)
|
|
2
2
|
|
|
3
3
|
MindForge turns Claude Code and Antigravity into production-grade engineering
|
|
4
4
|
partners with governance, observability, and a disciplined workflow engine.
|
|
@@ -18,6 +18,10 @@ Decisions get forgotten. MindForge fixes that with:
|
|
|
18
18
|
- **Role personas** — specialised agent modes for each task type
|
|
19
19
|
- **Skills** — just-in-time domain knowledge loaded on demand
|
|
20
20
|
- **Wave execution** — parallelism with dependency safety
|
|
21
|
+
- **Autonomous Engine** — walk-away execution with steerability (v2)
|
|
22
|
+
- **Browser Runtime** — headful/headless visual QA and sessions (v2)
|
|
23
|
+
- **Multi-Model Intelligence** — dynamic routing, adversarial reviews, and deep research (v2)
|
|
24
|
+
- **Persistent Knowledge Graph** — long-term memory across all engineering sessions (v2)
|
|
21
25
|
- **Quality gates** — compliance and security are non-bypassable
|
|
22
26
|
- **Audit trail** — append-only history of every action
|
|
23
27
|
|
|
@@ -61,18 +65,19 @@ npx mindforge-cc@latest --all --global
|
|
|
61
65
|
|
|
62
66
|
## Verify
|
|
63
67
|
Open Claude Code or Antigravity in your project directory and run:
|
|
64
|
-
```
|
|
68
|
+
```bash
|
|
65
69
|
/mindforge:health
|
|
66
70
|
```
|
|
71
|
+
|
|
67
72
|
If issues are found, run:
|
|
68
|
-
```
|
|
73
|
+
```bash
|
|
69
74
|
/mindforge:health --repair
|
|
70
75
|
```
|
|
71
76
|
|
|
72
77
|
---
|
|
73
78
|
|
|
74
79
|
## Quick start (new project)
|
|
75
|
-
```
|
|
80
|
+
```bash
|
|
76
81
|
/mindforge:init-project
|
|
77
82
|
/mindforge:plan-phase 1
|
|
78
83
|
/mindforge:execute-phase 1
|
|
@@ -81,7 +86,7 @@ If issues are found, run:
|
|
|
81
86
|
```
|
|
82
87
|
|
|
83
88
|
## Quick start (existing codebase)
|
|
84
|
-
```
|
|
89
|
+
```bash
|
|
85
90
|
/mindforge:map-codebase
|
|
86
91
|
/mindforge:plan-phase 1
|
|
87
92
|
```
|
|
@@ -89,7 +94,7 @@ If issues are found, run:
|
|
|
89
94
|
---
|
|
90
95
|
|
|
91
96
|
## Core workflow
|
|
92
|
-
```
|
|
97
|
+
```bash
|
|
93
98
|
/ mindforge:init-project
|
|
94
99
|
→ Requirements interview
|
|
95
100
|
→ Creates PROJECT.md, REQUIREMENTS.md, STATE.md
|
|
@@ -113,12 +118,38 @@ If issues are found, run:
|
|
|
113
118
|
→ Changelog generation
|
|
114
119
|
→ Final quality gates
|
|
115
120
|
→ PR creation
|
|
121
|
+
|
|
122
|
+
/ mindforge:auto --phase 1
|
|
123
|
+
→ Walk-away autonomous execution (v2)
|
|
124
|
+
→ Intelligent stuck detection and node repair
|
|
125
|
+
→ External steering via steering-queue
|
|
126
|
+
|
|
127
|
+
/ mindforge:qa
|
|
128
|
+
→ Systematic visual verification of UI changes (v2)
|
|
129
|
+
→ Automated regression test generation
|
|
130
|
+
→ Persistent browser sessions and daemon
|
|
131
|
+
|
|
132
|
+
/ mindforge:cross-review
|
|
133
|
+
→ Adversarial multi-model code review and synthesis (v2)
|
|
134
|
+
→ Consensus detection and severity normalization
|
|
135
|
+
|
|
136
|
+
/ mindforge:research
|
|
137
|
+
→ Deep research using Gemini 1.5 Pro 1M context (v2)
|
|
138
|
+
→ Codebase-wide context packaging and SSRF protection
|
|
139
|
+
|
|
140
|
+
/ mindforge:costs
|
|
141
|
+
→ Real-time token usage and cost profiling (v2)
|
|
142
|
+
→ Daily budget tracking across all providers
|
|
143
|
+
|
|
144
|
+
/ mindforge:remember
|
|
145
|
+
→ Manual knowledge management and search (v2)
|
|
146
|
+
→ Persistent knowledge graph retrieval and promotion
|
|
116
147
|
```
|
|
117
148
|
|
|
118
149
|
---
|
|
119
150
|
|
|
120
151
|
## Updates and migrations
|
|
121
|
-
```
|
|
152
|
+
```bash
|
|
122
153
|
/mindforge:update
|
|
123
154
|
/mindforge:update --apply
|
|
124
155
|
/mindforge:migrate --from v0.6.0 --to v1.0.0
|
|
@@ -169,17 +200,22 @@ See `.mindforge/production/token-optimiser.md`.
|
|
|
169
200
|
|
|
170
201
|
---
|
|
171
202
|
|
|
172
|
-
## What ships in
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
203
|
+
## What ships in v2.0.0-alpha.4
|
|
204
|
+
- **Persistent Knowledge Graph**: `/mindforge:remember` and long-term memory engine.
|
|
205
|
+
- **Multi-Model Intelligence Layer**: `/mindforge:cross-review`, `/mindforge:research`, and `/mindforge:costs`.
|
|
206
|
+
- **Visual QA Engine**: `/mindforge:qa` and automated regression tests.
|
|
207
|
+
- **Persistent Browser Runtime**: `/mindforge:browse` and Playwright integration.
|
|
208
|
+
- **Autonomous Execution Engine**: `/mindforge:auto` and `/mindforge:steer`.
|
|
209
|
+
- 48+ commands across 10 workflow categories.
|
|
210
|
+
- 12 core skill packs with a three-tier registry.
|
|
211
|
+
- 8 specialised agent personas.
|
|
212
|
+
- Wave-based execution with dependency graph and compaction.
|
|
213
|
+
- Enterprise integrations: Jira, Confluence, Slack, GitHub, GitLab.
|
|
214
|
+
- Three-tier governance with 6 non-bypassable compliance gates.
|
|
215
|
+
- Intelligence layer: health engine, difficulty scoring, anti-pattern detection.
|
|
216
|
+
- Public skills registry and plugin system.
|
|
217
|
+
- @mindforge/sdk with event stream and command builders.
|
|
218
|
+
- 20 test suites, production checklist, and 32 ADRs.
|
|
183
219
|
|
|
184
220
|
---
|
|
185
221
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MindForge — Auto-Runner Engine
|
|
3
|
+
* The main entry point for /mindforge:auto.
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const repairOperator = require('./repair-operator');
|
|
10
|
+
const stuckMonitor = require('./stuck-monitor');
|
|
11
|
+
const steeringManager = require('./steer');
|
|
12
|
+
const progressStream = require('./progress-stream');
|
|
13
|
+
const headlessAdapter = require('./headless');
|
|
14
|
+
|
|
15
|
+
class AutoRunner {
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.phase = options.phase;
|
|
18
|
+
this.isHeadless = options.headless || false;
|
|
19
|
+
this.auditPath = path.join(process.cwd(), '.planning/AUDIT.jsonl');
|
|
20
|
+
this.statePath = path.join(process.cwd(), '.planning/auto-state.json');
|
|
21
|
+
this.monitor = new stuckMonitor(this.auditPath);
|
|
22
|
+
this.isPaused = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async run() {
|
|
26
|
+
console.log(`🚀 Starting MindForge Autonomous Engine [Phase ${this.phase}]`);
|
|
27
|
+
|
|
28
|
+
if (this.isHeadless) {
|
|
29
|
+
headlessAdapter.setupHeadlessMode(this);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 1. Pre-flight checks
|
|
33
|
+
this.runPreFlight();
|
|
34
|
+
|
|
35
|
+
// 2. Main Wave Loop
|
|
36
|
+
while (await this.hasNextWave()) {
|
|
37
|
+
if (this.isPaused) break;
|
|
38
|
+
await this.executeWave();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.complete();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
runPreFlight() {
|
|
45
|
+
console.log('🔍 Running pre-flight checks...');
|
|
46
|
+
// Real logic would check git status, health, etc.
|
|
47
|
+
this.writeAudit({ event: 'auto_mode_started', phase: this.phase, timestamp: new Date().toISOString() });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async hasNextWave() {
|
|
51
|
+
// Logic to check HANDOFF.json for incomplete waves
|
|
52
|
+
return false; // Placeholder for now
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async executeWave() {
|
|
56
|
+
// Parallel task execution logic...
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async pause() {
|
|
60
|
+
this.isPaused = true;
|
|
61
|
+
this.updateState({ status: 'paused' });
|
|
62
|
+
this.writeAudit({ event: 'auto_mode_paused', timestamp: new Date().toISOString() });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
complete() {
|
|
66
|
+
console.log('✅ Phase complete!');
|
|
67
|
+
const report = progressStream.generateReport(this.auditPath, this.phase);
|
|
68
|
+
fs.writeFileSync(path.join(process.cwd(), `.planning/phases/${this.phase}/AUTONOMOUS-REPORT.md`), report);
|
|
69
|
+
this.writeAudit({ event: 'auto_mode_completed', timestamp: new Date().toISOString() });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
writeAudit(event) {
|
|
73
|
+
if (!event.timestamp) event.timestamp = new Date().toISOString();
|
|
74
|
+
fs.appendFileSync(this.auditPath, JSON.stringify(event) + '\n');
|
|
75
|
+
const result = this.monitor.analyze(event);
|
|
76
|
+
if (result) this.handleStuck(result);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
handleStuck(result) {
|
|
80
|
+
console.error(`🛑 STUCK PATTERN DETECTED: ${result.pattern} - ${result.message}`);
|
|
81
|
+
this.writeAudit({ event: 'auto_mode_escalated', reason: result.message });
|
|
82
|
+
process.exit(10);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
updateState(update) {
|
|
86
|
+
let state = {};
|
|
87
|
+
if (fs.existsSync(this.statePath)) {
|
|
88
|
+
state = JSON.parse(fs.readFileSync(this.statePath, 'utf8'));
|
|
89
|
+
}
|
|
90
|
+
Object.assign(state, update);
|
|
91
|
+
fs.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = AutoRunner;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MindForge — Headless Adapter
|
|
3
|
+
* Handles signal management and non-interactive output.
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
function setupHeadlessMode(executor) {
|
|
8
|
+
// Disable fancy TTY reporting
|
|
9
|
+
process.env.NO_COLOR = '1';
|
|
10
|
+
process.env.INTERACTIVE = '0';
|
|
11
|
+
|
|
12
|
+
// Hardened signal handling to prevent race conditions during write
|
|
13
|
+
let isShuttingDown = false;
|
|
14
|
+
|
|
15
|
+
async function handleSignal(signal) {
|
|
16
|
+
if (isShuttingDown) return;
|
|
17
|
+
isShuttingDown = true;
|
|
18
|
+
|
|
19
|
+
console.error(`\n⚠️ Received ${signal}. Snapshotting state for resumption...`);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// pause() ensures all state is flushed to disk and current step is stabilized
|
|
23
|
+
await executor.pause();
|
|
24
|
+
console.error('✅ State saved. You can resume with /mindforge:auto --resume');
|
|
25
|
+
process.exit(0);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error('❌ Failed to save state during shutdown:', err.message);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
process.on('SIGTERM', () => handleSignal('SIGTERM'));
|
|
33
|
+
process.on('SIGINT', () => handleSignal('SIGINT'));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { setupHeadlessMode };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MindForge — Progress Streamer
|
|
3
|
+
* Formats AUDIT events for terminal display and report generation.
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate a summary report from an audit file.
|
|
11
|
+
*/
|
|
12
|
+
function generateReport(auditPath, phaseNum) {
|
|
13
|
+
if (!fs.existsSync(auditPath)) return '# No audit log found';
|
|
14
|
+
|
|
15
|
+
const lines = fs.readFileSync(auditPath, 'utf8').split('\n').filter(Boolean);
|
|
16
|
+
const events = lines.map(JSON.parse);
|
|
17
|
+
|
|
18
|
+
let report = `# Autonomous Execution Report — Phase ${phaseNum}\n\n`;
|
|
19
|
+
|
|
20
|
+
const start = events.find(e => e.event === 'auto_mode_started');
|
|
21
|
+
const end = events.find(e => e.event === 'auto_mode_completed' || e.event === 'auto_mode_escalated');
|
|
22
|
+
|
|
23
|
+
report += '## Summary\n';
|
|
24
|
+
report += `- **Status**: ${end ? end.event.replace('auto_mode_', '').toUpperCase() : 'IN PROGRESS'}\n`;
|
|
25
|
+
report += `- **Started**: ${start ? start.timestamp : 'Unknown'}\n`;
|
|
26
|
+
report += `- **Duration**: ${calculateDuration(start, end)}\n`;
|
|
27
|
+
report += `- **Tasks Completed**: ${events.filter(e => e.event === 'task_completed').length}\n\n`;
|
|
28
|
+
|
|
29
|
+
report += '## Audit Log\n';
|
|
30
|
+
events.forEach(e => {
|
|
31
|
+
const time = e.timestamp.split('T')[1].split('.')[0];
|
|
32
|
+
if (e.event === 'task_completed') report += `- [${time}] ✅ Task ${e.plan} completed\n`;
|
|
33
|
+
if (e.event === 'node_repair') report += `- [${time}] 🔧 Repair: ${e.repair_type} on ${e.plan} (${e.repair_outcome})\n`;
|
|
34
|
+
if (e.event === 'stuck_pattern_detected') report += `- [${time}] ⚠️ Stuck Pattern: ${e.pattern} on ${e.file}\n`;
|
|
35
|
+
if (e.event === 'steering_applied') report += `- [${time}] 🚢 Steering: "${e.instruction}"\n`;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return report;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function calculateDuration(start, end) {
|
|
42
|
+
if (!start || !end) return 'Unknown';
|
|
43
|
+
const diff = new Date(end.timestamp) - new Date(start.timestamp);
|
|
44
|
+
const mins = Math.floor(diff / 60000);
|
|
45
|
+
const secs = Math.floor((diff % 60000) / 1000);
|
|
46
|
+
return `${mins}m ${secs}s`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { generateReport };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MindForge — Node Repair Operator
|
|
3
|
+
* Implements RETRY → DECOMPOSE → PRUNE → ESCALATE decision logic.
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Determine the repair strategy for a failed task.
|
|
12
|
+
* @param {object} context - Task context
|
|
13
|
+
* @returns {'RETRY'|'DECOMPOSE'|'PRUNE'|'ESCALATE'}
|
|
14
|
+
*/
|
|
15
|
+
function determineRepairStrategy(context) {
|
|
16
|
+
const {
|
|
17
|
+
planId,
|
|
18
|
+
phase,
|
|
19
|
+
attemptNumber, // 1 = first failure, 2 = retry also failed
|
|
20
|
+
errorOutput,
|
|
21
|
+
isTier3Change,
|
|
22
|
+
isOnCriticalPath, // other plans have this plan as a dependency
|
|
23
|
+
planFilePath,
|
|
24
|
+
repairBudget = 2,
|
|
25
|
+
} = context;
|
|
26
|
+
|
|
27
|
+
// Tier 3 changes ALWAYS escalate — never auto-approve auth/payment/PII
|
|
28
|
+
if (isTier3Change) {
|
|
29
|
+
return 'ESCALATE';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// First failure — always try RETRY first
|
|
33
|
+
if (attemptNumber === 1) {
|
|
34
|
+
return 'RETRY';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Retry also failed — try DECOMPOSE if the plan is decomposable
|
|
38
|
+
if (attemptNumber === 2 && isPlanDecomposable(planFilePath)) {
|
|
39
|
+
return 'DECOMPOSE';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Cannot DECOMPOSE (or decompose also failed) — try PRUNE if not critical path
|
|
43
|
+
if (!isOnCriticalPath) {
|
|
44
|
+
return 'PRUNE';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// On critical path and all repair strategies exhausted — must ESCALATE
|
|
48
|
+
return 'ESCALATE';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a plan can be split into independent sub-plans.
|
|
53
|
+
*/
|
|
54
|
+
function isPlanDecomposable(planFilePath) {
|
|
55
|
+
if (!fs.existsSync(planFilePath)) return false;
|
|
56
|
+
const content = fs.readFileSync(planFilePath, 'utf8');
|
|
57
|
+
|
|
58
|
+
// Count distinct file domains
|
|
59
|
+
const filesMatch = content.match(/<files>([\s\S]*?)<\/files>/);
|
|
60
|
+
if (!filesMatch) return false;
|
|
61
|
+
const files = filesMatch[1].trim().split('\n').filter(Boolean);
|
|
62
|
+
|
|
63
|
+
// Decomposable if:
|
|
64
|
+
// 1. More than 2 files (enough to split)
|
|
65
|
+
// 2. Files span different directories (different concerns)
|
|
66
|
+
if (files.length <= 1) return false;
|
|
67
|
+
const dirs = new Set(files.map(f => f.trim().split('/').slice(0, 2).join('/')));
|
|
68
|
+
return dirs.size > 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Generate RETRY context injection — adds error details for the retry subagent.
|
|
73
|
+
*/
|
|
74
|
+
function buildRetryContext(errorOutput, attemptNumber) {
|
|
75
|
+
const normalized = errorOutput
|
|
76
|
+
.split('\n')
|
|
77
|
+
.filter(l => /error|FAIL|error/i.test(l))
|
|
78
|
+
.slice(0, 5)
|
|
79
|
+
.join('\n');
|
|
80
|
+
|
|
81
|
+
return `
|
|
82
|
+
[RETRY CONTEXT — attempt ${attemptNumber} of 2]
|
|
83
|
+
Previous attempt failed with:
|
|
84
|
+
${normalized}
|
|
85
|
+
|
|
86
|
+
Instructions:
|
|
87
|
+
- Do NOT repeat the same approach that caused this error
|
|
88
|
+
- Fix the specific error above before implementing new functionality
|
|
89
|
+
- If the error is an import/module error: verify the package is installed
|
|
90
|
+
- If the error is a type error: check the exact type definitions
|
|
91
|
+
- If the error is a test failure: read the test assertion carefully
|
|
92
|
+
`.trim();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Build decomposed sub-plans from a failed plan.
|
|
97
|
+
* Returns two PLAN file contents as strings.
|
|
98
|
+
*/
|
|
99
|
+
function buildDecomposedPlans(planContent, originalPlanId, phase) {
|
|
100
|
+
const xmlMatch = planContent.match(/<task[^>]*>([\s\S]*?)<\/task>/);
|
|
101
|
+
if (!xmlMatch) return null;
|
|
102
|
+
|
|
103
|
+
const name = (planContent.match(/<n>(.*?)<\/n>/) || [])[1] || 'Task';
|
|
104
|
+
const action = (planContent.match(/<action>([\s\S]*?)<\/action>/) || [])[1] || '';
|
|
105
|
+
const filesMatch = planContent.match(/<files>([\s\S]*?)<\/files>/);
|
|
106
|
+
const files = filesMatch ? filesMatch[1].trim().split('\n').filter(Boolean) : [];
|
|
107
|
+
const persona = (planContent.match(/<persona>(.*?)<\/persona>/) || [])[1] || 'developer';
|
|
108
|
+
|
|
109
|
+
const mid = Math.ceil(files.length / 2);
|
|
110
|
+
const filesA = files.slice(0, mid);
|
|
111
|
+
const filesB = files.slice(mid);
|
|
112
|
+
const idA = `${originalPlanId}a`;
|
|
113
|
+
const idB = `${originalPlanId}b`;
|
|
114
|
+
|
|
115
|
+
const planA = `<task type="auto">
|
|
116
|
+
<n>${name} — Part A (Foundation)</n>
|
|
117
|
+
<persona>${persona}</persona>
|
|
118
|
+
<phase>${phase}</phase>
|
|
119
|
+
<plan>${idA}</plan>
|
|
120
|
+
<decomposed_from>${originalPlanId}</decomposed_from>
|
|
121
|
+
<dependencies>${getPlanDependencies(planContent)}</dependencies>
|
|
122
|
+
<files>
|
|
123
|
+
${filesA.map(f => ` ${f.trim()}`).join('\n')}
|
|
124
|
+
</files>
|
|
125
|
+
<action>
|
|
126
|
+
${splitAction(action, 'first_half')}
|
|
127
|
+
</action>
|
|
128
|
+
<verify>Run the tests for the files modified in this sub-task only</verify>
|
|
129
|
+
<done>Part A files exist, compile cleanly, and their specific tests pass</done>
|
|
130
|
+
</task>`;
|
|
131
|
+
|
|
132
|
+
const planB = `<task type="auto">
|
|
133
|
+
<n>${name} — Part B (Integration)</n>
|
|
134
|
+
<persona>${persona}</persona>
|
|
135
|
+
<phase>${phase}</phase>
|
|
136
|
+
<plan>${idB}</plan>
|
|
137
|
+
<decomposed_from>${originalPlanId}</decomposed_from>
|
|
138
|
+
<dependencies>${idA}</dependencies>
|
|
139
|
+
<files>
|
|
140
|
+
${filesB.map(f => ` ${f.trim()}`).join('\n')}
|
|
141
|
+
</files>
|
|
142
|
+
<action>
|
|
143
|
+
${splitAction(action, 'second_half')}
|
|
144
|
+
</action>
|
|
145
|
+
<verify>Run the full test suite for the entire ${name} feature</verify>
|
|
146
|
+
<done>All ${name} functionality working end-to-end</done>
|
|
147
|
+
</task>`;
|
|
148
|
+
|
|
149
|
+
return { planA, planB, idA, idB };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Fix the dependency chain after decomposing a plan.
|
|
154
|
+
* Any plan that depended on the original plan now depends on the second sub-plan.
|
|
155
|
+
*/
|
|
156
|
+
function fixDependencyChain(phaseDir, originalPlanId, newDependentPlanId, phase) {
|
|
157
|
+
const planFiles = fs.readdirSync(phaseDir)
|
|
158
|
+
.filter(f => f.match(new RegExp(`^PLAN-${phase}-\\d`)) && f.endsWith('.md'));
|
|
159
|
+
|
|
160
|
+
let fixed = 0;
|
|
161
|
+
for (const planFile of planFiles) {
|
|
162
|
+
const filePath = path.join(phaseDir, planFile);
|
|
163
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
164
|
+
|
|
165
|
+
// Check if this plan depends on the original decomposed plan
|
|
166
|
+
const depPattern = new RegExp(`<dependencies>([^<]*\\b${originalPlanId}\\b[^<]*)</dependencies>`);
|
|
167
|
+
if (depPattern.test(content)) {
|
|
168
|
+
const updated = content.replace(depPattern, (match, deps) => {
|
|
169
|
+
const newDeps = deps.replace(new RegExp(`\\b${originalPlanId}\\b`, 'g'), newDependentPlanId);
|
|
170
|
+
return `<dependencies>${newDeps}</dependencies>`;
|
|
171
|
+
});
|
|
172
|
+
fs.writeFileSync(filePath, updated);
|
|
173
|
+
fixed++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return fixed;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getPlanDependencies(planContent) {
|
|
180
|
+
return (planContent.match(/<dependencies>(.*?)<\/dependencies>/) || [])[1] || 'none';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function splitAction(action, half) {
|
|
184
|
+
// Try period-delimited split first
|
|
185
|
+
const periodSplit = action.split(/\.\s+/);
|
|
186
|
+
if (periodSplit.length > 1) {
|
|
187
|
+
const mid = Math.ceil(periodSplit.length / 2);
|
|
188
|
+
return half === 'first_half'
|
|
189
|
+
? periodSplit.slice(0, mid).join('. ').trim()
|
|
190
|
+
: periodSplit.slice(mid).join('. ').trim();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Try newline-bullet split
|
|
194
|
+
const bulletSplit = action.split(/\n-\s+/);
|
|
195
|
+
if (bulletSplit.length > 1) {
|
|
196
|
+
const mid = Math.ceil(bulletSplit.length / 2);
|
|
197
|
+
return half === 'first_half'
|
|
198
|
+
? bulletSplit.slice(0, mid).join('\n- ').trim()
|
|
199
|
+
: bulletSplit.slice(mid).join('\n- ').trim();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Fallback: prefix the whole action with a half indicator
|
|
203
|
+
const prefix = half === 'first_half' ? 'First half of: ' : 'Second half of: ';
|
|
204
|
+
return prefix + action.trim().slice(0, Math.ceil(action.length / 2));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
determineRepairStrategy,
|
|
209
|
+
isPlanDecomposable,
|
|
210
|
+
buildRetryContext,
|
|
211
|
+
buildDecomposedPlans,
|
|
212
|
+
fixDependencyChain,
|
|
213
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MindForge — Steering Manager
|
|
3
|
+
* Manages mid-execution guidance via steering-queue.jsonl.
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const QUEUE_PATH = path.join(process.cwd(), '.planning/steering-queue.jsonl');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Add guidance to the queue.
|
|
14
|
+
*/
|
|
15
|
+
function pushGuidance(instruction, scope = 'global', taskId = null) {
|
|
16
|
+
const item = {
|
|
17
|
+
id: `steer-${Date.now()}`,
|
|
18
|
+
timestamp: new Date().toISOString(),
|
|
19
|
+
instruction,
|
|
20
|
+
scope,
|
|
21
|
+
taskId
|
|
22
|
+
};
|
|
23
|
+
fs.appendFileSync(QUEUE_PATH, JSON.stringify(item) + '\n');
|
|
24
|
+
return item.id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get and remove all pending guidance for the current context.
|
|
29
|
+
*/
|
|
30
|
+
function popGuidance(taskId = null) {
|
|
31
|
+
if (!fs.existsSync(QUEUE_PATH)) return [];
|
|
32
|
+
|
|
33
|
+
const lines = fs.readFileSync(QUEUE_PATH, 'utf8').split('\n').filter(Boolean);
|
|
34
|
+
const relevant = [];
|
|
35
|
+
const remaining = [];
|
|
36
|
+
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
const item = JSON.parse(line);
|
|
39
|
+
if (item.scope === 'global' || item.taskId === taskId) {
|
|
40
|
+
relevant.push(item);
|
|
41
|
+
} else {
|
|
42
|
+
remaining.push(item);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Rewrite remaining
|
|
47
|
+
fs.writeFileSync(QUEUE_PATH, remaining.map(JSON.stringify).join('\n') + (remaining.length ? '\n' : ''));
|
|
48
|
+
return relevant;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Inject steering guidance into a PLAN file content.
|
|
53
|
+
*/
|
|
54
|
+
function injectSteering(planContent, guidanceItems) {
|
|
55
|
+
if (!guidanceItems || guidanceItems.length === 0) return planContent;
|
|
56
|
+
|
|
57
|
+
const guidanceText = guidanceItems
|
|
58
|
+
.map(g => `[STEERING GUIDANCE — DO NOT IGNORE] ${g.instruction}`)
|
|
59
|
+
.join('\n');
|
|
60
|
+
|
|
61
|
+
// Hardened injection: inject at beginning of <action> to ensure visibility
|
|
62
|
+
return planContent.replace(/<action>([\s\S]*?)<\/action>/, (match, action) => {
|
|
63
|
+
return `<action>\n${guidanceText}\n\nOriginal plan instructions:\n${action}\n</action>`;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
pushGuidance,
|
|
69
|
+
popGuidance,
|
|
70
|
+
injectSteering
|
|
71
|
+
};
|