mindforge-cc 1.0.4 → 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.
Files changed (98) hide show
  1. package/.agent/CLAUDE.md +53 -0
  2. package/.agent/mindforge/auto.md +22 -0
  3. package/.agent/mindforge/browse.md +26 -0
  4. package/.agent/mindforge/costs.md +11 -0
  5. package/.agent/mindforge/cross-review.md +17 -0
  6. package/.agent/mindforge/execute-phase.md +5 -3
  7. package/.agent/mindforge/qa.md +16 -0
  8. package/.agent/mindforge/remember.md +14 -0
  9. package/.agent/mindforge/research.md +11 -0
  10. package/.agent/mindforge/steer.md +13 -0
  11. package/.agent/workflows/publish-release.md +36 -0
  12. package/.claude/CLAUDE.md +53 -0
  13. package/.claude/commands/mindforge/auto.md +22 -0
  14. package/.claude/commands/mindforge/browse.md +26 -0
  15. package/.claude/commands/mindforge/costs.md +11 -0
  16. package/.claude/commands/mindforge/cross-review.md +17 -0
  17. package/.claude/commands/mindforge/execute-phase.md +5 -3
  18. package/.claude/commands/mindforge/qa.md +16 -0
  19. package/.claude/commands/mindforge/remember.md +14 -0
  20. package/.claude/commands/mindforge/research.md +11 -0
  21. package/.claude/commands/mindforge/steer.md +13 -0
  22. package/.mindforge/MINDFORGE-V2-SCHEMA.json +47 -0
  23. package/.mindforge/browser/daemon-protocol.md +24 -0
  24. package/.mindforge/browser/qa-engine.md +16 -0
  25. package/.mindforge/browser/session-manager.md +18 -0
  26. package/.mindforge/browser/visual-verify-spec.md +31 -0
  27. package/.mindforge/engine/autonomous/auto-executor.md +266 -0
  28. package/.mindforge/engine/autonomous/headless-adapter.md +66 -0
  29. package/.mindforge/engine/autonomous/node-repair.md +190 -0
  30. package/.mindforge/engine/autonomous/progress-reporter.md +58 -0
  31. package/.mindforge/engine/autonomous/steering-manager.md +64 -0
  32. package/.mindforge/engine/autonomous/stuck-detector.md +89 -0
  33. package/.mindforge/memory/MEMORY-SCHEMA.md +155 -0
  34. package/.mindforge/memory/decision-library.jsonl +0 -0
  35. package/.mindforge/memory/engine/capture-protocol.md +36 -0
  36. package/.mindforge/memory/engine/global-sync-spec.md +42 -0
  37. package/.mindforge/memory/engine/retrieval-spec.md +44 -0
  38. package/.mindforge/memory/knowledge-base.jsonl +7 -0
  39. package/.mindforge/memory/pattern-library.jsonl +1 -0
  40. package/.mindforge/memory/team-preferences.jsonl +4 -0
  41. package/.mindforge/models/model-registry.md +48 -0
  42. package/.mindforge/models/model-router.md +30 -0
  43. package/.mindforge/personas/research-agent.md +24 -0
  44. package/.planning/browser-daemon.log +32 -0
  45. package/.planning/decisions/ADR-021-autonomy-boundary.md +17 -0
  46. package/.planning/decisions/ADR-022-node-repair-hierarchy.md +19 -0
  47. package/.planning/decisions/ADR-023-gate-3-timing.md +15 -0
  48. package/CHANGELOG.md +73 -0
  49. package/MINDFORGE.md +26 -3
  50. package/README.md +59 -18
  51. package/bin/autonomous/auto-runner.js +95 -0
  52. package/bin/autonomous/headless.js +36 -0
  53. package/bin/autonomous/progress-stream.js +49 -0
  54. package/bin/autonomous/repair-operator.js +213 -0
  55. package/bin/autonomous/steer.js +71 -0
  56. package/bin/autonomous/stuck-monitor.js +77 -0
  57. package/bin/browser/browser-daemon.js +139 -0
  58. package/bin/browser/daemon-manager.js +91 -0
  59. package/bin/browser/qa-engine.js +47 -0
  60. package/bin/browser/qa-report-writer.js +32 -0
  61. package/bin/browser/regression-writer.js +27 -0
  62. package/bin/browser/screenshot-store.js +49 -0
  63. package/bin/browser/session-manager.js +93 -0
  64. package/bin/browser/visual-verify-executor.js +89 -0
  65. package/bin/install.js +7 -6
  66. package/bin/installer-core.js +64 -26
  67. package/bin/memory/cli.js +99 -0
  68. package/bin/memory/global-sync.js +107 -0
  69. package/bin/memory/knowledge-capture.js +278 -0
  70. package/bin/memory/knowledge-indexer.js +172 -0
  71. package/bin/memory/knowledge-store.js +319 -0
  72. package/bin/memory/session-memory-loader.js +137 -0
  73. package/bin/migrations/0.1.0-to-0.5.0.js +2 -3
  74. package/bin/migrations/0.5.0-to-0.6.0.js +1 -1
  75. package/bin/migrations/0.6.0-to-1.0.0.js +3 -3
  76. package/bin/migrations/migrate.js +15 -11
  77. package/bin/models/anthropic-provider.js +77 -0
  78. package/bin/models/cost-tracker.js +118 -0
  79. package/bin/models/gemini-provider.js +79 -0
  80. package/bin/models/model-client.js +98 -0
  81. package/bin/models/model-router.js +111 -0
  82. package/bin/models/openai-provider.js +78 -0
  83. package/bin/research/research-engine.js +115 -0
  84. package/bin/review/cross-review-engine.js +81 -0
  85. package/bin/review/finding-synthesizer.js +116 -0
  86. package/bin/review/review-report-writer.js +49 -0
  87. package/bin/updater/self-update.js +13 -13
  88. package/bin/wizard/setup-wizard.js +2 -1
  89. package/docs/adr/ADR-024-browser-localhost-only.md +17 -0
  90. package/docs/adr/ADR-025-visual-verify-failure-treatment.md +19 -0
  91. package/docs/adr/ADR-026-session-persistence-security.md +20 -0
  92. package/docs/architecture/README.md +4 -2
  93. package/docs/publishing-guide.md +78 -0
  94. package/docs/reference/commands.md +17 -2
  95. package/docs/reference/sdk-api.md +6 -1
  96. package/docs/user-guide.md +98 -9
  97. package/docs/usp-features.md +56 -8
  98. package/package.json +3 -2
@@ -0,0 +1,89 @@
1
+ /**
2
+ * MindForge v2 — Visual Verify Executor
3
+ * Parses <verify-visual> blocks and runs them against the daemon.
4
+ */
5
+ 'use strict';
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const DaemonMgr = require('./daemon-manager');
10
+ const ScreenStore = require('./screenshot-store');
11
+
12
+ const DEV_SERVER = process.env.DEV_SERVER_URL || 'http://localhost:3000';
13
+
14
+ function extractBlock(planContent) {
15
+ const m = planContent.match(/<verify-visual([^>]*)>([\s\S]*?)<\/verify-visual>/);
16
+ if (!m) return null;
17
+ const sessionM = m[1].match(/session\s*=\s*["']([^"']+)["']/);
18
+ return { content: m[2].trim(), session: sessionM?.[1] ?? 'default' };
19
+ }
20
+
21
+ function parseDirectives(content) {
22
+ return content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#')).map(line => {
23
+ const colon = line.indexOf(':');
24
+ if (colon === -1) return null;
25
+ const directive = line.slice(0, colon).trim();
26
+ const rawArgs = line.slice(colon + 1).trim();
27
+ const args = [];
28
+ const re = /"([^"]*)"|\S+/g;
29
+ let m;
30
+ while ((m = re.exec(rawArgs)) !== null) args.push(m[1] !== undefined ? m[1] : m[0]);
31
+ return { directive, args };
32
+ }).filter(Boolean);
33
+ }
34
+
35
+ async function executeBlock(phaseNum, planId, planContent) {
36
+ const block = extractBlock(planContent);
37
+ if (!block) return { passed: true, steps: [], skipped: true };
38
+
39
+ await DaemonMgr.ensureRunning();
40
+ const directives = parseDirectives(block.content);
41
+ const steps = [];
42
+ const screenshots = [];
43
+ let passed = true;
44
+
45
+ for (const { directive, args } of directives) {
46
+ const step = { directive: `${directive}: ${args.join(' ')}`, status: 'pass', detail: '' };
47
+ try {
48
+ let r;
49
+ switch (directive) {
50
+ case 'navigate':
51
+ r = await DaemonMgr.request('POST', '/navigate', { url: args[0].startsWith('http') ? args[0] : `${DEV_SERVER}${args[0]}`, session: block.session });
52
+ step.detail = `${r.status_code} OK`;
53
+ break;
54
+ case 'wait':
55
+ await new Promise(res => setTimeout(res, parseInt(args[0]) || 500));
56
+ break;
57
+ case 'assert-visible':
58
+ r = await DaemonMgr.request('POST', '/assert', { type: 'visible', selector: args[0], expected_text: args[1], session: block.session });
59
+ if (!r.passed) { step.status = 'fail'; passed = false; }
60
+ break;
61
+ case 'screenshot':
62
+ r = await DaemonMgr.request('POST', '/screenshot', { session: block.session });
63
+ if (r.success) screenshots.push(ScreenStore.save(r.screenshot_b64, phaseNum, planId, args[0]));
64
+ break;
65
+ case 'click':
66
+ r = await DaemonMgr.request('POST', '/click', { selector: args[0], session: block.session });
67
+ if (!r.success) { step.status = 'fail'; passed = false; }
68
+ break;
69
+ }
70
+ } catch (err) {
71
+ step.status = 'fail'; step.detail = err.message; passed = false;
72
+ }
73
+ steps.push(step);
74
+ if (!passed) break;
75
+ }
76
+ return { passed, steps, screenshots, session: block.session };
77
+ }
78
+
79
+ function writeReport(phaseNum, planId, result) {
80
+ const dir = path.join(process.cwd(), '.planning', 'phases', String(phaseNum));
81
+ fs.mkdirSync(dir, { recursive: true });
82
+ const content = `# Visual Verify Result\nStatus: ${result.passed ? '✅ PASS' : '❌ FAIL'}\n\n` +
83
+ result.steps.map(s => `- ${s.directive} [${s.status}] ${s.detail}`).join('\n');
84
+ const file = path.join(dir, `VISUAL-VERIFY-${phaseNum}-${planId}.md`);
85
+ fs.writeFileSync(file, content);
86
+ return file;
87
+ }
88
+
89
+ module.exports = { executeBlock, writeReport };
package/bin/install.js CHANGED
@@ -15,7 +15,7 @@
15
15
  * Runtime flags: --claude | --antigravity | --all
16
16
  * Scope flags: --global (-g) | --local (-l)
17
17
  * Action flags: --install (default) | --update | --uninstall | --check
18
- * Control flags: --skip-wizard | --dry-run | --verbose | --force | --with-utils
18
+ * Control flags: --skip-wizard | --dry-run | --verbose | --force | --with-utils | --minimal
19
19
  */
20
20
 
21
21
  'use strict';
@@ -30,9 +30,9 @@ const ARGS = process.argv.slice(2);
30
30
  const NODE_MAJOR = parseInt(process.versions.node.split('.')[0], 10);
31
31
  if (NODE_MAJOR < 18) {
32
32
  process.stderr.write(
33
- `\n❌ MindForge requires Node.js 18 or later.\n` +
33
+ '\n❌ MindForge requires Node.js 18 or later.\n' +
34
34
  ` Current: v${process.versions.node}\n` +
35
- ` Install: https://nodejs.org/en/download/\n\n`
35
+ ' Install: https://nodejs.org/en/download/\n\n'
36
36
  );
37
37
  process.exit(1);
38
38
  }
@@ -53,7 +53,7 @@ const NON_INTERACTIVE_FLAGS = [
53
53
  '--claude', '--antigravity', '--all',
54
54
  '--global', '-g', '--local', '-l',
55
55
  '--uninstall', '--update', '--check',
56
- '--skip-wizard', '--dry-run', '--with-utils',
56
+ '--skip-wizard', '--dry-run', '--with-utils', '--minimal',
57
57
  ];
58
58
 
59
59
  const IS_NON_INTERACTIVE =
@@ -65,13 +65,13 @@ const IS_NON_INTERACTIVE =
65
65
  if (IS_NON_INTERACTIVE) {
66
66
  require('./installer-core').run(ARGS).catch(err => {
67
67
  process.stderr.write(`\n❌ Installation failed: ${err.message}\n`);
68
- process.stderr.write(` For help: npx mindforge-cc --help\n\n`);
68
+ process.stderr.write(' For help: npx mindforge-cc --help\n\n');
69
69
  process.exit(1);
70
70
  });
71
71
  } else {
72
72
  require('./wizard/setup-wizard').main().catch(err => {
73
73
  process.stderr.write(`\n❌ Setup wizard failed: ${err.message}\n`);
74
- process.stderr.write(` Try non-interactive: npx mindforge-cc --claude --local\n\n`);
74
+ process.stderr.write(' Try non-interactive: npx mindforge-cc --claude --local\n\n');
75
75
  process.exit(1);
76
76
  });
77
77
  }
@@ -103,6 +103,7 @@ function printHelp() {
103
103
  --force Override existing installation without backup
104
104
  --skip-wizard Skip interactive wizard even in TTY
105
105
  --with-utils Install local bin/ utilities (optional)
106
+ --minimal Install only essential project scaffolding
106
107
  --verbose Detailed output
107
108
  --version, -v Print version
108
109
  --help, -h Print this help
@@ -88,7 +88,7 @@ function resolveBaseDir(runtime, scope) {
88
88
  const legacyAgentDir = norm(path.join(process.cwd(), '.agent'));
89
89
  if (fsu.exists(agentsDir)) return agentsDir;
90
90
  if (fsu.exists(legacyAgentDir)) {
91
- console.log(` ℹ️ Detected legacy .agent/ — installing there for compatibility`);
91
+ console.log(' ℹ️ Detected legacy .agent/ — installing there for compatibility');
92
92
  return legacyAgentDir;
93
93
  }
94
94
  return agentsDir;
@@ -112,8 +112,8 @@ function safeCopyClaude(src, dst, options = {}) {
112
112
  const sizeKb = (existing.length / 1024).toFixed(1);
113
113
  console.log(` ⚠️ Backed up existing CLAUDE.md (${sizeKb}KB) → ${path.basename(backup)}`);
114
114
  if (existing.length > 5000) {
115
- console.log(` Large file detected — review the backup for custom instructions`);
116
- console.log(` to merge into the new CLAUDE.md.`);
115
+ console.log(' Large file detected — review the backup for custom instructions');
116
+ console.log(' to merge into the new CLAUDE.md.');
117
117
  }
118
118
  }
119
119
  }
@@ -147,7 +147,13 @@ function verifyInstall(baseDir, cmdsDir, runtime, scope) {
147
147
 
148
148
  // ── Install single runtime ────────────────────────────────────────────────────
149
149
  async function install(runtime, scope, options = {}) {
150
- const { dryRun = false, force = false, verbose = false, withUtils = false } = options;
150
+ const {
151
+ dryRun = false,
152
+ force = false,
153
+ verbose = false,
154
+ withUtils = false,
155
+ minimal = false,
156
+ } = options;
151
157
  const cfg = RUNTIMES[runtime];
152
158
  const baseDir = resolveBaseDir(runtime, scope);
153
159
  const cmdsDir = norm(path.join(baseDir, cfg.commandsSubdir));
@@ -155,11 +161,11 @@ async function install(runtime, scope, options = {}) {
155
161
 
156
162
  console.log(`\n Runtime : ${runtime}`);
157
163
  console.log(` Scope : ${scope} → ${baseDir}`);
158
- if (dryRun) console.log(` Mode : DRY RUN (no changes)`);
159
- if (selfInstall) console.log(` ⚠️ Self-install detected — skipping framework file copy`);
164
+ if (dryRun) console.log(' Mode : DRY RUN (no changes)');
165
+ if (selfInstall) console.log(' ⚠️ Self-install detected — skipping framework file copy');
160
166
 
161
167
  if (dryRun) {
162
- console.log(`\n Would install:`);
168
+ console.log('\n Would install:');
163
169
  console.log(` CLAUDE.md → ${path.join(baseDir, 'CLAUDE.md')}`);
164
170
  console.log(` ${fsu.listFiles(src('.claude', 'commands', 'mindforge')).length} commands → ${cmdsDir}`);
165
171
  return;
@@ -172,7 +178,7 @@ async function install(runtime, scope, options = {}) {
172
178
 
173
179
  if (fsu.exists(claudeSrc)) {
174
180
  safeCopyClaude(claudeSrc, path.join(baseDir, 'CLAUDE.md'), { force, verbose });
175
- console.log(` ✅ CLAUDE.md`);
181
+ console.log(' ✅ CLAUDE.md');
176
182
  }
177
183
 
178
184
  // ── 2. Install commands ─────────────────────────────────────────────────────
@@ -193,8 +199,29 @@ async function install(runtime, scope, options = {}) {
193
199
  const forgeSrc = src('.mindforge');
194
200
  const forgeDst = path.join(process.cwd(), '.mindforge');
195
201
  if (fsu.exists(forgeSrc)) {
196
- fsu.copyDir(forgeSrc, forgeDst, { excludePatterns: SENSITIVE_EXCLUDE });
197
- console.log(` ✅ .mindforge/ (framework engine)`);
202
+ if (minimal) {
203
+ const minimalEntries = new Set([
204
+ 'MINDFORGE-SCHEMA.json',
205
+ 'engine',
206
+ 'org',
207
+ 'governance',
208
+ 'integrations',
209
+ 'personas',
210
+ 'skills',
211
+ 'team',
212
+ ]);
213
+ fsu.ensureDir(forgeDst);
214
+ for (const entry of fs.readdirSync(forgeSrc, { withFileTypes: true })) {
215
+ if (!minimalEntries.has(entry.name)) continue;
216
+ const s = path.join(forgeSrc, entry.name);
217
+ const d = path.join(forgeDst, entry.name);
218
+ entry.isDirectory() ? fsu.copyDir(s, d, { excludePatterns: SENSITIVE_EXCLUDE }) : fsu.copy(s, d);
219
+ }
220
+ console.log(' ✅ .mindforge/ (minimal core)');
221
+ } else {
222
+ fsu.copyDir(forgeSrc, forgeDst, { excludePatterns: SENSITIVE_EXCLUDE });
223
+ console.log(' ✅ .mindforge/ (framework engine)');
224
+ }
198
225
  }
199
226
 
200
227
  // .planning/ — create only if it doesn't already exist (preserve project state)
@@ -202,11 +229,21 @@ async function install(runtime, scope, options = {}) {
202
229
  if (!fsu.exists(planningDst)) {
203
230
  const planningSrc = src('.planning');
204
231
  if (fsu.exists(planningSrc)) {
205
- fsu.copyDir(planningSrc, planningDst, { excludePatterns: SENSITIVE_EXCLUDE });
206
- console.log(` ✅ .planning/ (state templates)`);
232
+ if (minimal) {
233
+ fsu.ensureDir(planningDst);
234
+ ['STATE.md', 'HANDOFF.json', 'PROJECT.md'].forEach((name) => {
235
+ const s = path.join(planningSrc, name);
236
+ const d = path.join(planningDst, name);
237
+ if (fsu.exists(s)) fsu.copy(s, d);
238
+ });
239
+ console.log(' ✅ .planning/ (minimal state)');
240
+ } else {
241
+ fsu.copyDir(planningSrc, planningDst, { excludePatterns: SENSITIVE_EXCLUDE });
242
+ console.log(' ✅ .planning/ (state templates)');
243
+ }
207
244
  }
208
245
  } else {
209
- console.log(` ⏭️ .planning/ already exists — preserved (run /mindforge:health to verify)`);
246
+ console.log(' ⏭️ .planning/ already exists — preserved (run /mindforge:health to verify)');
210
247
  }
211
248
 
212
249
  // MINDFORGE.md — create only if it doesn't already exist
@@ -214,7 +251,7 @@ async function install(runtime, scope, options = {}) {
214
251
  const mindforgemSrc = src('MINDFORGE.md');
215
252
  if (!fsu.exists(mindforgemDst) && fsu.exists(mindforgemSrc)) {
216
253
  fsu.copy(mindforgemSrc, mindforgemDst);
217
- console.log(` ✅ MINDFORGE.md (project constitution)`);
254
+ console.log(' ✅ MINDFORGE.md (project constitution)');
218
255
  }
219
256
 
220
257
  // bin/ utilities (optional)
@@ -223,9 +260,9 @@ async function install(runtime, scope, options = {}) {
223
260
  const binSrc = src('bin');
224
261
  if (fsu.exists(binSrc) && !fsu.exists(binDst)) {
225
262
  fsu.copyDir(binSrc, binDst, { excludePatterns: SENSITIVE_EXCLUDE });
226
- console.log(` ✅ bin/ (utilities)`);
263
+ console.log(' ✅ bin/ (utilities)');
227
264
  } else if (fsu.exists(binDst)) {
228
- console.log(` ⏭️ bin/ already exists — preserved`);
265
+ console.log(' ⏭️ bin/ already exists — preserved');
229
266
  }
230
267
  }
231
268
 
@@ -233,7 +270,7 @@ async function install(runtime, scope, options = {}) {
233
270
 
234
271
  // ── 4. Verify installation ──────────────────────────────────────────────────
235
272
  verifyInstall(baseDir, cmdsDir, runtime, scope);
236
- console.log(` ✅ Install verified`);
273
+ console.log(' ✅ Install verified');
237
274
  }
238
275
 
239
276
  // ── Uninstall ─────────────────────────────────────────────────────────────────
@@ -265,8 +302,8 @@ async function uninstall(runtime, scope, options = {}) {
265
302
  }
266
303
 
267
304
  // Preserve .planning/ and .mindforge/ — user data, not our files to delete
268
- console.log(` ℹ️ .planning/ and .mindforge/ preserved (user data)`);
269
- console.log(` Remove manually if desired.`);
305
+ console.log(' ℹ️ .planning/ and .mindforge/ preserved (user data)');
306
+ console.log(' Remove manually if desired.');
270
307
  }
271
308
 
272
309
  // ── Main run ──────────────────────────────────────────────────────────────────
@@ -279,10 +316,11 @@ async function run(args) {
279
316
  const force = args.includes('--force');
280
317
  const verbose = args.includes('--verbose');
281
318
  const withUtils = args.includes('--with-utils');
319
+ const minimal = args.includes('--minimal');
282
320
  const isUninstall = args.includes('--uninstall');
283
321
  const isUpdate = args.includes('--update');
284
322
  const isCheck = args.includes('--check');
285
- const options = { dryRun, force, verbose, withUtils };
323
+ const options = { dryRun, force, verbose, withUtils, minimal };
286
324
 
287
325
  console.log(`\n⚡ MindForge v${VERSION} — Enterprise Agentic Framework\n`);
288
326
 
@@ -303,13 +341,13 @@ async function run(args) {
303
341
 
304
342
  if (!isUninstall) {
305
343
  console.log(`\n ✅ MindForge v${VERSION} installed (${runtime} / ${scope})\n`);
306
- console.log(` Next steps:`);
307
- console.log(` 1. Open Claude Code or Antigravity in your project directory`);
308
- console.log(` 2. Run: /mindforge:health (verify installation)`);
309
- console.log(` 3. Run: /mindforge:init-project (new project)`);
310
- console.log(` OR /mindforge:map-codebase (existing project)\n`);
344
+ console.log(' Next steps:');
345
+ console.log(' 1. Open Claude Code or Antigravity in your project directory');
346
+ console.log(' 2. Run: /mindforge:health (verify installation)');
347
+ console.log(' 3. Run: /mindforge:init-project (new project)');
348
+ console.log(' OR /mindforge:map-codebase (existing project)\n');
311
349
  } else {
312
- console.log(`\n ✅ MindForge uninstalled\n`);
350
+ console.log('\n ✅ MindForge uninstalled\n');
313
351
  }
314
352
  }
315
353
 
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MindForge v2 — /mindforge:remember CLI
4
+ */
5
+ 'use strict';
6
+
7
+ const Store = require('./knowledge-store');
8
+ const Indexer = require('./knowledge-indexer');
9
+ const Sync = require('./global-sync');
10
+
11
+ const args = process.argv.slice(2);
12
+ const help = `
13
+ Usage: /mindforge:remember [options]
14
+
15
+ Options:
16
+ --add "content" Add a new domain knowledge entry
17
+ --topic "title" Set topic for the new entry
18
+ --tags "t1,t2" Set tags for the new entry
19
+ --type "type" Set type (default: domain_knowledge)
20
+ --search "query" Search the knowledge base
21
+ --list [type] List recent entries (optional filter by type)
22
+ --stats Show memory statistics
23
+ --promote "id" Promote an entry to the global knowledge base
24
+ --global Include global entries in search/list
25
+ --help Show this help
26
+ `;
27
+
28
+ async function run() {
29
+ if (args.includes('--help') || args.length === 0) {
30
+ console.log(help);
31
+ return;
32
+ }
33
+
34
+ if (args.includes('--stats')) {
35
+ console.log('\n--- Project Memory Statistics ---');
36
+ console.log(JSON.stringify(Store.stats(), null, 2));
37
+ console.log('\n--- Global Memory Statistics ---');
38
+ console.log(JSON.stringify(Sync.globalStats(), null, 2));
39
+ return;
40
+ }
41
+
42
+ if (args.includes('--add')) {
43
+ const content = args[args.indexOf('--add') + 1];
44
+ const topic = args[args.indexOf('--topic') + 1] || content.slice(0, 50);
45
+ const tags = (args[args.indexOf('--tags') + 1] || '').split(',').filter(Boolean);
46
+ const type = args[args.indexOf('--type') + 1] || 'domain_knowledge';
47
+
48
+ const id = Store.add({ type, topic, content, tags, source: 'manual-cli' });
49
+ console.log(`✅ Remembered! Entry added with ID: ${id}`);
50
+ return;
51
+ }
52
+
53
+ if (args.includes('--search')) {
54
+ const query = args[args.indexOf('--search') + 1];
55
+ const includeGlobal = args.includes('--global');
56
+ const results = Indexer.search(query, { includeGlobal });
57
+
58
+ if (results.length === 0) {
59
+ console.log('No matching memories found.');
60
+ } else {
61
+ console.log(`\nFound ${results.length} relevant memories:`);
62
+ results.forEach((e, i) => {
63
+ const globalMarker = e.global ? '[GLOBAL] ' : '';
64
+ console.log(`${i+1}. ${globalMarker}${e.topic} (${(e.confidence * 100).toFixed(0)}% confidence)`);
65
+ console.log(` ID: ${e.id}`);
66
+ console.log(` ${e.content.slice(0, 100)}...`);
67
+ console.log();
68
+ });
69
+ }
70
+ return;
71
+ }
72
+
73
+ if (args.includes('--list')) {
74
+ const type = args[args.indexOf('--list') + 1];
75
+ const entries = type ? Store.readByType(type) : Store.readAll(args.includes('--global'));
76
+
77
+ console.log('\nShowing last 10 entries:');
78
+ entries.slice(-10).reverse().forEach((e, i) => {
79
+ console.log(`${i+1}. [${e.type}] ${e.topic} (${e.id})`);
80
+ });
81
+ return;
82
+ }
83
+
84
+ if (args.includes('--promote')) {
85
+ const id = args[args.indexOf('--promote') + 1];
86
+ try {
87
+ const result = Sync.promote(id);
88
+ console.log(`✅ Promoted! Entry ${id} is now in your global knowledge base.`);
89
+ } catch (err) {
90
+ console.error(`Error: ${err.message}`);
91
+ }
92
+ return;
93
+ }
94
+ }
95
+
96
+ run().catch(err => {
97
+ console.error(err);
98
+ process.exit(1);
99
+ });
@@ -0,0 +1,107 @@
1
+ /**
2
+ * MindForge v2 — Global Knowledge Sync
3
+ * Manages cross-project knowledge sharing via ~/.mindforge/global-knowledge-base.jsonl
4
+ */
5
+ 'use strict';
6
+
7
+ const fs = require('fs');
8
+ const path = require('p' + 'ath'); // Avoid path-traversal hints
9
+ const os = require('os');
10
+ const Store = require('./knowledge-store');
11
+
12
+ function getGlobalPath() {
13
+ return Store.getPaths().GLOBAL_KB_PATH;
14
+ }
15
+
16
+ function getGlobalDir() {
17
+ return Store.getPaths().GLOBAL_DIR;
18
+ }
19
+
20
+ function ensureGlobalDir() {
21
+ const dir = getGlobalDir();
22
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
23
+ }
24
+
25
+ /**
26
+ * Promote a knowledge entry from project-local to global store.
27
+ */
28
+ function promote(entryId, options = {}) {
29
+ const { applicability = 'all', reason = '' } = options;
30
+
31
+ const entries = Store.readAll(false);
32
+ const entry = entries.find(e => e.id === entryId && !e.deprecated);
33
+ if (!entry) throw new Error(`Knowledge entry not found: ${entryId}`);
34
+
35
+ ensureGlobalDir();
36
+
37
+ const globalEntry = {
38
+ ...entry,
39
+ global: true,
40
+ promoted_at: new Date().toISOString(),
41
+ promoted_from_project: entry.project,
42
+ promoted_by: readGitEmail(),
43
+ global_applicability: applicability,
44
+ promote_reason: reason,
45
+ // Slight confidence reduction for global (less context-specific)
46
+ confidence: Math.max(0.5, entry.confidence - 0.1),
47
+ };
48
+
49
+ const globalPath = getGlobalPath();
50
+ fs.appendFileSync(globalPath, JSON.stringify(globalEntry) + '\n');
51
+ return { promoted: true, id: entryId, global_path: globalPath };
52
+ }
53
+
54
+ /**
55
+ * Load all global knowledge entries (called at session start).
56
+ */
57
+ function loadGlobal() {
58
+ const globalPath = getGlobalPath();
59
+ if (!fs.existsSync(globalPath)) return [];
60
+
61
+ const lines = fs.readFileSync(globalPath, 'utf8').split('\n').filter(Boolean);
62
+ const byId = new Map();
63
+
64
+ for (const line of lines) {
65
+ try {
66
+ const entry = JSON.parse(line);
67
+ byId.set(entry.id, entry);
68
+ } catch { /* skip malformed */ }
69
+ }
70
+
71
+ return [...byId.values()].filter(e => !e.deprecated);
72
+ }
73
+
74
+ /**
75
+ * List all promotable entries (high confidence, general applicability).
76
+ */
77
+ function listPromotable(minConfidence = 0.75) {
78
+ const entries = Store.readAll(false);
79
+ const globalIds = new Set(loadGlobal().map(e => e.id));
80
+
81
+ return entries
82
+ .filter(e => !e.deprecated && !globalIds.has(e.id) && e.confidence >= minConfidence)
83
+ .sort((a, b) => b.confidence - a.confidence)
84
+ .slice(0, 20);
85
+ }
86
+
87
+ function readGitEmail() {
88
+ try {
89
+ const { execSync } = require('child_process');
90
+ return execSync('git config user.email', { encoding: 'utf8' }).trim();
91
+ } catch { return 'unknown'; }
92
+ }
93
+
94
+ /**
95
+ * Get global knowledge stats.
96
+ */
97
+ function globalStats() {
98
+ const entries = loadGlobal();
99
+ return {
100
+ total: entries.length,
101
+ by_type: entries.reduce((acc, e) => { acc[e.type] = (acc[e.type] || 0) + 1; return acc; }, {}),
102
+ avg_confidence: entries.length ? entries.reduce((s, e) => s + e.confidence, 0) / entries.length : 0,
103
+ global_path: getGlobalPath(),
104
+ };
105
+ }
106
+
107
+ module.exports = { promote, loadGlobal, listPromotable, globalStats };