moflo 4.10.24 → 4.10.25

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 (95) hide show
  1. package/.claude/guidance/shipped/moflo-yaml-reference.md +5 -5
  2. package/.claude/skills/meditate/SKILL.md +2 -2
  3. package/bin/cli-hooks/statusline.js +12 -8
  4. package/bin/cli.js +1 -1
  5. package/bin/hooks.mjs +1 -1
  6. package/bin/lib/meditate.mjs +1 -0
  7. package/bin/lib/pii-scrub.mjs +2 -5
  8. package/dist/src/cli/commands/daemon.js +4 -3
  9. package/dist/src/cli/commands/doctor-checks-deep.js +2 -2
  10. package/dist/src/cli/commands/doctor-checks-swarm.js +122 -13
  11. package/dist/src/cli/commands/hooks.js +7 -23
  12. package/dist/src/cli/commands/init.js +1 -1
  13. package/dist/src/cli/commands/mcp.js +2 -1
  14. package/dist/src/cli/commands/session.js +1 -1
  15. package/dist/src/cli/commands/start.js +1 -1
  16. package/dist/src/cli/commands/status.js +1 -1
  17. package/dist/src/cli/commands/task.js +1 -1
  18. package/dist/src/cli/commands/update.js +12 -12
  19. package/dist/src/cli/guidance/analyzer.js +3 -3
  20. package/dist/src/cli/guidance/gates.js +1 -1
  21. package/dist/src/cli/guidance/hooks.js +1 -1
  22. package/dist/src/cli/guidance/meta-governance.js +1 -1
  23. package/dist/src/cli/hooks/index.js +1 -1
  24. package/dist/src/cli/hooks/reasoningbank/guidance-provider.js +1 -1
  25. package/dist/src/cli/hooks/workers/index.js +1 -1
  26. package/dist/src/cli/hooks/workers/session-hook.js +0 -40
  27. package/dist/src/cli/index.js +2 -2
  28. package/dist/src/cli/init/executor.js +36 -20
  29. package/dist/src/cli/init/mcp-generator.js +10 -8
  30. package/dist/src/cli/init/settings-generator.js +10 -7
  31. package/dist/src/cli/init/types.js +2 -2
  32. package/dist/src/cli/mcp-server.js +2 -1
  33. package/dist/src/cli/memory/bridge-loader.js +42 -0
  34. package/dist/src/cli/memory/embedding-model.js +157 -0
  35. package/dist/src/cli/memory/entries-read.js +380 -0
  36. package/dist/src/cli/memory/entries-shared.js +73 -0
  37. package/dist/src/cli/memory/entries-write.js +384 -0
  38. package/dist/src/cli/memory/hnsw-singleton.js +242 -0
  39. package/dist/src/cli/memory/init.js +367 -0
  40. package/dist/src/cli/memory/learnings-overview.js +156 -0
  41. package/dist/src/cli/memory/memory-initializer.js +37 -2257
  42. package/dist/src/cli/memory/quantization.js +221 -0
  43. package/dist/src/cli/memory/schema.js +382 -0
  44. package/dist/src/cli/memory/verify.js +178 -0
  45. package/dist/src/cli/movector/index.js +1 -1
  46. package/dist/src/cli/plugins/store/discovery.js +9 -9
  47. package/dist/src/cli/{transfer/ipfs/client.js → plugins/store/ipfs-client.js} +4 -1
  48. package/dist/src/cli/plugins/tests/demo-plugin-store.js +1 -1
  49. package/dist/src/cli/plugins/tests/standalone-test.js +1 -1
  50. package/dist/src/cli/runtime/headless.js +5 -4
  51. package/dist/src/cli/scripts/publish-registry.js +6 -6
  52. package/dist/src/cli/services/daemon-dashboard.js +108 -7
  53. package/dist/src/cli/services/daemon-readiness.js +1 -1
  54. package/dist/src/cli/services/daemon-service.js +1 -1
  55. package/dist/src/cli/services/env-compat.js +29 -0
  56. package/dist/src/cli/services/hook-block-hash.js +5 -6
  57. package/dist/src/cli/services/registry-api.js +1 -1
  58. package/dist/src/cli/shared/core/config/loader.js +19 -11
  59. package/dist/src/cli/shared/events/example-usage.js +2 -2
  60. package/dist/src/cli/shared/events/index.js +1 -1
  61. package/dist/src/cli/shared/index.js +1 -1
  62. package/dist/src/cli/shared/mcp/index.js +1 -1
  63. package/dist/src/cli/shared/mcp/server.js +3 -3
  64. package/dist/src/cli/shared/plugin-interface.js +1 -1
  65. package/dist/src/cli/shared/plugins/index.js +1 -1
  66. package/dist/src/cli/shared/plugins/official/index.js +1 -1
  67. package/dist/src/cli/shared/security/index.js +1 -1
  68. package/dist/src/cli/shared/services/v3-progress.service.js +40 -29
  69. package/dist/src/cli/shared/types.js +1 -1
  70. package/dist/src/cli/swarm/coordination/swarm-hub.js +1 -1
  71. package/dist/src/cli/update/index.js +1 -1
  72. package/dist/src/cli/update/rate-limiter.js +3 -2
  73. package/dist/src/cli/version.js +1 -1
  74. package/package.json +2 -2
  75. package/dist/src/cli/commands/transfer-store.js +0 -428
  76. package/dist/src/cli/transfer/anonymization/index.js +0 -281
  77. package/dist/src/cli/transfer/deploy-seraphine.js +0 -205
  78. package/dist/src/cli/transfer/export.js +0 -113
  79. package/dist/src/cli/transfer/index.js +0 -31
  80. package/dist/src/cli/transfer/ipfs/upload.js +0 -411
  81. package/dist/src/cli/transfer/models/seraphine.js +0 -373
  82. package/dist/src/cli/transfer/serialization/cfp.js +0 -184
  83. package/dist/src/cli/transfer/storage/gcs.js +0 -242
  84. package/dist/src/cli/transfer/storage/index.js +0 -6
  85. package/dist/src/cli/transfer/store/discovery.js +0 -382
  86. package/dist/src/cli/transfer/store/download.js +0 -334
  87. package/dist/src/cli/transfer/store/index.js +0 -153
  88. package/dist/src/cli/transfer/store/publish.js +0 -294
  89. package/dist/src/cli/transfer/store/registry.js +0 -285
  90. package/dist/src/cli/transfer/store/search.js +0 -232
  91. package/dist/src/cli/transfer/store/tests/standalone-test.js +0 -190
  92. package/dist/src/cli/transfer/store/types.js +0 -6
  93. package/dist/src/cli/transfer/test-seraphine.js +0 -105
  94. package/dist/src/cli/transfer/tests/test-store.js +0 -214
  95. package/dist/src/cli/transfer/types.js +0 -6
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Memory initialization verification.
3
+ *
4
+ * Extracted from `memory-initializer.ts` (#1203 decomposition). Runs a
5
+ * self-test suite (schema, write, read, embedding, pattern, vector-index)
6
+ * against a database to confirm a healthy install — used by the healer /
7
+ * `flo memory verify` paths.
8
+ *
9
+ * @module memory/verify
10
+ */
11
+ import { openDaemonDatabase } from './daemon-backend.js';
12
+ import { generateEmbedding } from './embedding-model.js';
13
+ /**
14
+ * Verify memory initialization works correctly
15
+ * Tests: write, read, search, patterns
16
+ */
17
+ export async function verifyMemoryInit(dbPath, options) {
18
+ const { verbose = false } = options || {};
19
+ const tests = [];
20
+ try {
21
+ const db = openDaemonDatabase(dbPath);
22
+ // Test 1: Schema verification
23
+ const schemaStart = Date.now();
24
+ const tables = db.exec("SELECT name FROM sqlite_master WHERE type='table'");
25
+ const tableNames = tables[0]?.values?.map(v => v[0]) || [];
26
+ const expectedTables = ['memory_entries', 'patterns', 'metadata', 'vector_indexes'];
27
+ const missingTables = expectedTables.filter(t => !tableNames.includes(t));
28
+ tests.push({
29
+ name: 'Schema verification',
30
+ passed: missingTables.length === 0,
31
+ details: missingTables.length > 0 ? `Missing: ${missingTables.join(', ')}` : `${tableNames.length} tables found`,
32
+ duration: Date.now() - schemaStart
33
+ });
34
+ // Test 2: Write entry
35
+ const writeStart = Date.now();
36
+ const testId = `test_${Date.now()}`;
37
+ const testKey = 'verification_test';
38
+ const testValue = 'This is a verification test entry for memory initialization';
39
+ try {
40
+ db.run(`
41
+ INSERT INTO memory_entries (id, key, namespace, content, type, created_at, updated_at)
42
+ VALUES (?, ?, 'test', ?, 'semantic', ?, ?)
43
+ `, [testId, testKey, testValue, Date.now(), Date.now()]);
44
+ tests.push({
45
+ name: 'Write entry',
46
+ passed: true,
47
+ details: 'Entry written successfully',
48
+ duration: Date.now() - writeStart
49
+ });
50
+ }
51
+ catch (e) {
52
+ tests.push({
53
+ name: 'Write entry',
54
+ passed: false,
55
+ details: e instanceof Error ? e.message : 'Write failed',
56
+ duration: Date.now() - writeStart
57
+ });
58
+ }
59
+ // Test 3: Read entry
60
+ const readStart = Date.now();
61
+ try {
62
+ const result = db.exec(`SELECT content FROM memory_entries WHERE id = ?`, [testId]);
63
+ const content = result[0]?.values[0]?.[0];
64
+ tests.push({
65
+ name: 'Read entry',
66
+ passed: content === testValue,
67
+ details: content === testValue ? 'Content matches' : 'Content mismatch',
68
+ duration: Date.now() - readStart
69
+ });
70
+ }
71
+ catch (e) {
72
+ tests.push({
73
+ name: 'Read entry',
74
+ passed: false,
75
+ details: e instanceof Error ? e.message : 'Read failed',
76
+ duration: Date.now() - readStart
77
+ });
78
+ }
79
+ // Test 4: Write with embedding
80
+ const embeddingStart = Date.now();
81
+ try {
82
+ const { embedding, dimensions, model } = await generateEmbedding(testValue);
83
+ const embeddingJson = JSON.stringify(embedding);
84
+ db.run(`
85
+ UPDATE memory_entries
86
+ SET embedding = ?, embedding_dimensions = ?, embedding_model = ?
87
+ WHERE id = ?
88
+ `, [embeddingJson, dimensions, model, testId]);
89
+ tests.push({
90
+ name: 'Generate embedding',
91
+ passed: true,
92
+ details: `${dimensions}-dim vector (${model})`,
93
+ duration: Date.now() - embeddingStart
94
+ });
95
+ }
96
+ catch (e) {
97
+ tests.push({
98
+ name: 'Generate embedding',
99
+ passed: false,
100
+ details: e instanceof Error ? e.message : 'Embedding failed',
101
+ duration: Date.now() - embeddingStart
102
+ });
103
+ }
104
+ // Test 5: Pattern storage
105
+ const patternStart = Date.now();
106
+ try {
107
+ const patternId = `pattern_${Date.now()}`;
108
+ db.run(`
109
+ INSERT INTO patterns (id, name, pattern_type, condition, action, confidence, created_at, updated_at)
110
+ VALUES (?, 'test-pattern', 'task-routing', 'test condition', 'test action', 0.5, ?, ?)
111
+ `, [patternId, Date.now(), Date.now()]);
112
+ tests.push({
113
+ name: 'Pattern storage',
114
+ passed: true,
115
+ details: 'Pattern stored with confidence scoring',
116
+ duration: Date.now() - patternStart
117
+ });
118
+ // Cleanup test pattern
119
+ db.run(`DELETE FROM patterns WHERE id = ?`, [patternId]);
120
+ }
121
+ catch (e) {
122
+ tests.push({
123
+ name: 'Pattern storage',
124
+ passed: false,
125
+ details: e instanceof Error ? e.message : 'Pattern storage failed',
126
+ duration: Date.now() - patternStart
127
+ });
128
+ }
129
+ // Test 6: Vector index configuration
130
+ const indexStart = Date.now();
131
+ try {
132
+ const indexResult = db.exec(`SELECT name, dimensions, hnsw_m, hnsw_ef_construction FROM vector_indexes`);
133
+ const indexes = indexResult[0]?.values || [];
134
+ tests.push({
135
+ name: 'Vector index config',
136
+ passed: indexes.length > 0,
137
+ details: `${indexes.length} indexes configured (HNSW M=16, ef=200)`,
138
+ duration: Date.now() - indexStart
139
+ });
140
+ }
141
+ catch (e) {
142
+ tests.push({
143
+ name: 'Vector index config',
144
+ passed: false,
145
+ details: e instanceof Error ? e.message : 'Index check failed',
146
+ duration: Date.now() - indexStart
147
+ });
148
+ }
149
+ // Cleanup test entry — WAL persists this DELETE incrementally; no
150
+ // export+rewrite needed (the sql.js whole-file dump that lived here
151
+ // was the multi-writer clobber vector epic #1078 killed).
152
+ db.run(`DELETE FROM memory_entries WHERE id = ?`, [testId]);
153
+ db.close();
154
+ const passed = tests.filter(t => t.passed).length;
155
+ const failed = tests.filter(t => !t.passed).length;
156
+ return {
157
+ success: failed === 0,
158
+ tests,
159
+ summary: {
160
+ passed,
161
+ failed,
162
+ total: tests.length
163
+ }
164
+ };
165
+ }
166
+ catch (error) {
167
+ return {
168
+ success: false,
169
+ tests: [{
170
+ name: 'Database access',
171
+ passed: false,
172
+ details: error instanceof Error ? error.message : 'Unknown error'
173
+ }],
174
+ summary: { passed: 0, failed: 1, total: 1 }
175
+ };
176
+ }
177
+ }
178
+ //# sourceMappingURL=verify.js.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * MoVector Integration Module for Claude Flow CLI
2
+ * MoVector Integration Module for MoFlo CLI
3
3
  *
4
4
  * Provides pure TS vector and neural utilities for:
5
5
  * - Q-Learning based task routing
@@ -4,7 +4,7 @@
4
4
  * Parallel implementation to pattern store for plugins
5
5
  */
6
6
  import * as crypto from 'crypto';
7
- import { resolveIPNS, fetchFromIPFS } from '../../transfer/ipfs/client.js';
7
+ import { resolveIPNS, fetchFromIPFS } from './ipfs-client.js';
8
8
  /**
9
9
  * Fetch real npm download stats for a package
10
10
  */
@@ -50,8 +50,8 @@ export const MODEL_REGISTRY_CID = 'QmNr1yYMKi7YBaL8JSztQyuB5ZUaTdRMLxJC1pBpGbjsT
50
50
  export const DEFAULT_PLUGIN_STORE_CONFIG = {
51
51
  registries: [
52
52
  {
53
- name: 'claude-flow-official',
54
- description: 'Official Claude Flow plugin registry',
53
+ name: 'moflo-official',
54
+ description: 'Official MoFlo plugin registry',
55
55
  // Use direct CID for reliable resolution (IPNS can be slow)
56
56
  ipnsName: LIVE_REGISTRY_CID,
57
57
  gateway: 'https://gateway.pinata.cloud',
@@ -69,7 +69,7 @@ export const DEFAULT_PLUGIN_STORE_CONFIG = {
69
69
  official: false,
70
70
  },
71
71
  ],
72
- defaultRegistry: 'claude-flow-official',
72
+ defaultRegistry: 'moflo-official',
73
73
  gateway: 'https://gateway.pinata.cloud',
74
74
  timeout: 30000,
75
75
  cacheDir: '.moflo/plugins/cache',
@@ -186,8 +186,8 @@ export class PluginDiscoveryService {
186
186
  ],
187
187
  authors: [
188
188
  {
189
- id: 'claude-flow-team',
190
- displayName: 'Claude Flow Team',
189
+ id: 'moflo-team',
190
+ displayName: 'MoFlo Team',
191
191
  verified: true,
192
192
  plugins: plugins.length,
193
193
  totalDownloads: plugins.reduce((sum, p) => sum + p.downloads, 0),
@@ -224,8 +224,8 @@ export class PluginDiscoveryService {
224
224
  getDemoPlugins() {
225
225
  const baseTime = new Date().toISOString();
226
226
  const officialAuthor = {
227
- id: 'claude-flow-team',
228
- displayName: 'Claude Flow Team',
227
+ id: 'moflo-team',
228
+ displayName: 'MoFlo Team',
229
229
  verified: true,
230
230
  plugins: 5,
231
231
  totalDownloads: 50000,
@@ -273,7 +273,7 @@ export class PluginDiscoveryService {
273
273
  id: 'community-analytics',
274
274
  name: 'community-analytics',
275
275
  displayName: 'Analytics Dashboard',
276
- description: 'Analytics and metrics visualization for Claude Flow operations',
276
+ description: 'Analytics and metrics visualization for MoFlo operations',
277
277
  version: '1.2.0',
278
278
  cid: 'bafybeianalyticsplugin',
279
279
  size: 210000,
@@ -7,6 +7,9 @@
7
7
  * - Cloudflare IPFS
8
8
  * - Protocol Labs ipfs.io
9
9
  * - dweb.link (LibP2P)
10
+ *
11
+ * Relocated from the (deleted) transfer/ pattern-marketplace tree in #1210; the
12
+ * plugin store is the sole consumer of these primitives.
10
13
  */
11
14
  import * as crypto from 'crypto';
12
15
  import { errorDetail } from '../../shared/utils/error-detail.js';
@@ -305,4 +308,4 @@ export function formatBytes(bytes) {
305
308
  const i = Math.floor(Math.log(bytes) / Math.log(k));
306
309
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
307
310
  }
308
- //# sourceMappingURL=client.js.map
311
+ //# sourceMappingURL=ipfs-client.js.map
@@ -7,7 +7,7 @@ import { createPluginDiscoveryService, searchPlugins, getOfficialPlugins, } from
7
7
  async function demo() {
8
8
  console.log('');
9
9
  console.log('╔══════════════════════════════════════════════════════════════╗');
10
- console.log('║ CLAUDE FLOW V3 - IPFS PLUGIN STORE DEMO ║');
10
+ console.log('║ MoFlo V3 - IPFS PLUGIN STORE DEMO ║');
11
11
  console.log('║ Decentralized Plugin Marketplace ║');
12
12
  console.log('╚══════════════════════════════════════════════════════════════╝');
13
13
  console.log('');
@@ -12,7 +12,7 @@ import { createPluginDiscoveryService, searchPlugins, getFeaturedPlugins, getOff
12
12
  async function main() {
13
13
  console.log('');
14
14
  console.log('╔══════════════════════════════════════════════════════════════╗');
15
- console.log('║ CLAUDE FLOW V3 - STANDALONE PLUGIN STORE TEST ║');
15
+ console.log('║ MoFlo V3 - STANDALONE PLUGIN STORE TEST ║');
16
16
  console.log('║ IPFS-Based Decentralized Plugin Marketplace ║');
17
17
  console.log('╚══════════════════════════════════════════════════════════════╝');
18
18
  console.log('');
@@ -9,7 +9,7 @@
9
9
  * npx moflo headless --benchmark
10
10
  *
11
11
  * Environment:
12
- * CLAUDE_FLOW_HEADLESS=true
12
+ * MOFLO_HEADLESS=true
13
13
  * CLAUDE_CODE_HEADLESS=true
14
14
  *
15
15
  * @module v3/cli/runtime/headless
@@ -20,6 +20,7 @@ import { attachSignalHandlers } from '../shared/resilience/signal-handlers.js';
20
20
  import { errorDetail } from '../shared/utils/error-detail.js';
21
21
  import { initializeIntelligence, benchmarkAdaptation, getIntelligenceStats } from '../memory/intelligence.js';
22
22
  import { getHNSWStatus, batchCosineSim, flashAttentionSearch } from '../memory/memory-initializer.js';
23
+ import { readMofloEnv } from '../services/env-compat.js';
23
24
  // ============================================================================
24
25
  // Main Runtime
25
26
  // ============================================================================
@@ -85,7 +86,7 @@ Options:
85
86
  -h, --help Show help
86
87
 
87
88
  Environment:
88
- CLAUDE_FLOW_HEADLESS=true Enable headless mode
89
+ MOFLO_HEADLESS=true Enable headless mode
89
90
  CLAUDE_CODE_HEADLESS=true Enable Claude Code headless
90
91
 
91
92
  Examples:
@@ -237,7 +238,7 @@ async function showStatus() {
237
238
  console.log(` Initialized: ${hnsw.initialized}`);
238
239
  console.log(` Entries: ${hnsw.entryCount}`);
239
240
  console.log('\nEnvironment:');
240
- console.log(` CLAUDE_FLOW_HEADLESS: ${process.env.CLAUDE_FLOW_HEADLESS || 'not set'}`);
241
+ console.log(` MOFLO_HEADLESS: ${readMofloEnv('HEADLESS') || 'not set'}`);
241
242
  console.log(` CLAUDE_CODE_HEADLESS: ${process.env.CLAUDE_CODE_HEADLESS || 'not set'}`);
242
243
  console.log(` NODE_ENV: ${process.env.NODE_ENV || 'development'}`);
243
244
  }
@@ -246,7 +247,7 @@ async function showStatus() {
246
247
  */
247
248
  async function main() {
248
249
  // Set headless environment
249
- process.env.CLAUDE_FLOW_HEADLESS = 'true';
250
+ process.env.MOFLO_HEADLESS = 'true';
250
251
  const config = parseArgs();
251
252
  try {
252
253
  switch (config.mode) {
@@ -123,13 +123,13 @@ async function generateRegistry() {
123
123
  id: pkg,
124
124
  name: pkg,
125
125
  displayName: pkg.replace('@moflo/plugin-', '').replace('@moflo/', ''),
126
- description: `Official Claude Flow plugin: ${pkg}`,
126
+ description: `Official MoFlo plugin: ${pkg}`,
127
127
  version: stats?.version || '0.0.0',
128
128
  size: 100000,
129
129
  checksum: `sha256:${crypto.randomBytes(32).toString('hex')}`,
130
130
  author: {
131
- id: 'claude-flow-team',
132
- displayName: 'Claude Flow Team',
131
+ id: 'moflo-team',
132
+ displayName: 'MoFlo Team',
133
133
  verified: true,
134
134
  },
135
135
  license: 'MIT',
@@ -156,7 +156,7 @@ async function generateRegistry() {
156
156
  ipnsName: '', // Will be set after publishing
157
157
  plugins,
158
158
  categories: [
159
- { id: 'official', name: 'Official', description: 'Official Claude Flow plugins', pluginCount: plugins.length },
159
+ { id: 'official', name: 'Official', description: 'Official MoFlo plugins', pluginCount: plugins.length },
160
160
  ],
161
161
  totalPlugins: plugins.length,
162
162
  totalDownloads,
@@ -215,7 +215,7 @@ async function main() {
215
215
  // Pin to IPFS
216
216
  console.log('\n📌 Pinning to IPFS via Pinata...');
217
217
  try {
218
- const result = await pinToIPFS(registry, 'claude-flow-plugin-registry', jwt);
218
+ const result = await pinToIPFS(registry, 'moflo-plugin-registry', jwt);
219
219
  console.log('\n✅ Published successfully!');
220
220
  console.log(` CID: ${result.IpfsHash}`);
221
221
  console.log(` Size: ${(result.PinSize / 1024).toFixed(2)} KB`);
@@ -232,7 +232,7 @@ async function main() {
232
232
  console.log('\n📝 Next steps:');
233
233
  console.log(' 1. Update DEFAULT_PLUGIN_STORE_CONFIG in discovery.ts with the new CID');
234
234
  console.log(' 2. If using IPNS, update the IPNS pointer via Pinata dashboard');
235
- console.log(' 3. Test with: npx claude-flow@latest plugins list');
235
+ console.log(' 3. Test with: npx moflo@latest plugins list');
236
236
  }
237
237
  catch (error) {
238
238
  console.error('\n❌ Publish failed:', error);
@@ -289,6 +289,23 @@ async function handleMemoryStats() {
289
289
  available: total > 0 || Object.keys(namespaces).length > 0,
290
290
  };
291
291
  }
292
+ /**
293
+ * Build the `/api/learnings` response (#1203).
294
+ *
295
+ * Surfaces the `learnings` namespace for the Luminarium "Learnings" panel:
296
+ * recent lessons (key + first line + capped body), a per-day growth series,
297
+ * and a provenance tally from the write-time `source:<origin>` tag. The
298
+ * `total` is an authoritative COUNT (not the capped recent-list length), so
299
+ * the panel never under-reports — the #1149 "memory_stats lies" guard.
300
+ *
301
+ * Errors propagate to the request handler's 500 path (matching
302
+ * `handleMemoryStats`) rather than degrading to a fake-empty panel.
303
+ */
304
+ async function handleLearnings() {
305
+ const { getLearningsOverview } = await import('../memory/learnings-overview.js');
306
+ const overview = await getLearningsOverview();
307
+ return { ok: true, available: overview.total > 0, ...overview };
308
+ }
292
309
  /**
293
310
  * Build the `/api/claude-stats` response (#1044).
294
311
  *
@@ -488,6 +505,9 @@ async function handleRequest(req, res, daemon, opts) {
488
505
  else if (url === '/api/memory/stats') {
489
506
  sendJson(res, 200, await handleMemoryStats());
490
507
  }
508
+ else if (url === '/api/learnings') {
509
+ sendJson(res, 200, await handleLearnings());
510
+ }
491
511
  else if (url === '/api/claude-stats') {
492
512
  sendJson(res, 200, await handleClaudeStats());
493
513
  }
@@ -784,6 +804,20 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
784
804
  .stat-card { background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 12px; }
785
805
  .stat-card .label { color: #8b949e; font-size: 0.75rem; }
786
806
  .stat-card .value { font-size: 1.25rem; font-weight: 700; color: #58a6ff; }
807
+ /* Learnings panel (#1203) */
808
+ .lrn-bars { display: flex; align-items: flex-end; gap: 3px; height: 70px; padding: 8px 0; margin-bottom: 16px; }
809
+ .lrn-bar { flex: 1; background: #58a6ff; border-radius: 2px 2px 0 0; min-height: 2px; opacity: 0.85; }
810
+ .lrn-bar:hover { opacity: 1; }
811
+ .lrn-prov { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
812
+ .lrn-item { background: #161b22; border: 1px solid #30363d; border-radius: 6px; margin-bottom: 8px; }
813
+ .lrn-head { display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer; }
814
+ .lrn-head:hover { background: #1c2128; }
815
+ .lrn-key { color: #58a6ff; font-weight: 600; font-size: 0.85rem; }
816
+ .lrn-first { color: #c9d1d9; font-size: 0.82rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
817
+ .lrn-body { padding: 0 12px 12px; color: #c9d1d9; font-size: 0.83rem; white-space: pre-wrap; line-height: 1.55; border-top: 1px solid #21262d; margin-top: 2px; padding-top: 10px; }
818
+ .lrn-item.collapsed .lrn-body { display: none; }
819
+ .lrn-item.collapsed .lrn-chevron { transform: rotate(-90deg); }
820
+ .lrn-chevron { transition: transform 0.15s; display: inline-block; color: #484f58; font-size: 0.75rem; }
787
821
  .poll-indicator { position: fixed; top: 8px; right: 12px; font-size: 0.7rem; color: #484f58; }
788
822
  .api-links { margin-top: 12px; padding: 12px 16px; background: #161b22; border: 1px solid #30363d; border-radius: 6px; }
789
823
  .api-links a { color: #58a6ff; text-decoration: none; font-size: 0.85rem; margin-right: 16px; }
@@ -819,12 +853,13 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
819
853
  <div id="panel-schedules" class="panel" style="display:none"><div id="schedules-active"></div><div id="schedules-events"></div></div>
820
854
  <div id="panel-executions" class="panel" style="display:none"></div>
821
855
  <div id="panel-memory" class="panel" style="display:none"></div>
856
+ <div id="panel-learnings" class="panel" style="display:none"><div class="loading-block" role="status" aria-label="Loading learnings"><div class="spinner"></div><div class="msg">Reading learnings…</div></div></div>
822
857
  <div id="panel-claude-stats" class="panel" style="display:none"><div class="loading-block" role="status" aria-label="Loading Claude Code transcripts"><div class="spinner"></div><div class="msg">Reading Claude Code transcripts…</div><div class="hint">First load can take 10–15 seconds — moflo walks every session file in this project's transcript directory. Subsequent loads in this tab are much faster.</div></div></div>
823
858
  <div id="poll-indicator" class="poll-indicator"></div>
824
859
  <script>
825
860
  // Tab navigation — plain DOM, no framework
826
- const tabIds = ['workers', 'schedules', 'executions', 'memory', 'claude-stats'];
827
- const tabLabels = ['Workers', 'Schedules', 'Flo Runs', 'Memory', 'Claude Stats'];
861
+ const tabIds = ['workers', 'schedules', 'executions', 'memory', 'learnings', 'claude-stats'];
862
+ const tabLabels = ['Workers', 'Schedules', 'Flo Runs', 'Memory', 'Learnings', 'Claude Stats'];
828
863
  let activeTab = 'workers';
829
864
 
830
865
  function switchTab(id) {
@@ -836,10 +871,10 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
836
871
  document.querySelectorAll('.nav-tab').forEach(el => {
837
872
  el.classList.toggle('active', el.dataset.tab === id);
838
873
  });
839
- // Tabs whose data is fetched lazily (currently only Claude Stats)
840
- // need an immediate poll on entry — otherwise the user waits up to
841
- // the 5s polling interval for first paint.
842
- if (id === 'claude-stats' && prev !== id && typeof poll === 'function') {
874
+ // Tabs whose data is fetched lazily (Claude Stats + Learnings) need an
875
+ // immediate poll on entry — otherwise the user waits up to the 5s
876
+ // polling interval for first paint.
877
+ if ((id === 'claude-stats' || id === 'learnings') && prev !== id && typeof poll === 'function') {
843
878
  poll();
844
879
  }
845
880
  }
@@ -1137,6 +1172,67 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
1137
1172
  '<table><thead><tr><th>Namespace</th><th>Entries</th></tr></thead><tbody>' + rows + '</tbody></table>';
1138
1173
  }
1139
1174
 
1175
+ // Friendly labels + badge colors for the write-time source:<origin> tag (#1203).
1176
+ const PROV_LABELS = { 'auto-meditate': 'Auto-meditate', 'meditate-manual': '/meditate', 'manual': 'Manual', 'user': 'User (locked)', 'unknown': 'Legacy / unknown' };
1177
+ const PROV_COLORS = { 'auto-meditate': 'green', 'meditate-manual': 'yellow', 'manual': 'gray', 'user': 'gray', 'unknown': 'gray' };
1178
+ const provLabel = (s) => PROV_LABELS[s] || s;
1179
+ const provColor = (s) => PROV_COLORS[s] || 'gray';
1180
+
1181
+ function renderLearnings(l) {
1182
+ const el = document.getElementById('panel-learnings');
1183
+ // Null on first paint AND on fetch error (.catch(() => null)) — show the
1184
+ // spinner block on both so the tab never looks frozen.
1185
+ if (!l) {
1186
+ el.innerHTML = '<div class="loading-block" role="status" aria-label="Loading learnings"><div class="spinner"></div><div class="msg">Reading learnings…</div></div>';
1187
+ return;
1188
+ }
1189
+ if (!l.available) {
1190
+ el.innerHTML = '<h2>Learnings</h2><div class="empty">No learnings captured yet — run <code>/meditate</code>, or let auto-meditate distill a session.</div>';
1191
+ return;
1192
+ }
1193
+
1194
+ const cards = '<div class="grid">' +
1195
+ '<div class="stat-card"><div class="label">Total Learnings</div><div class="value">' + l.total + '</div></div>' +
1196
+ '<div class="stat-card"><div class="label">Added (7d)</div><div class="value">' + l.addedLast7d + '</div></div>' +
1197
+ '<div class="stat-card"><div class="label">Added (30d)</div><div class="value">' + l.addedLast30d + '</div></div>' +
1198
+ '</div>';
1199
+
1200
+ const provEntries = Object.entries(l.provenance || {}).sort((a, b) => b[1] - a[1]);
1201
+ // esc() the source label — for unknown sources provLabel returns the raw
1202
+ // DB tag value, so it must be escaped before going into badge() (which
1203
+ // does not escape its text). Guards stored-XSS from a crafted source tag.
1204
+ const provHtml = provEntries.length
1205
+ ? '<div class="lrn-prov">' + provEntries.map(e => badge(esc(provLabel(e[0])) + ': ' + e[1], provColor(e[0]))).join('') + '</div>'
1206
+ : '<div class="empty">No provenance recorded</div>';
1207
+
1208
+ let growthHtml = '';
1209
+ if (l.growth && l.growth.length) {
1210
+ const maxG = Math.max(1, ...l.growth.map(g => g.count));
1211
+ const bars = l.growth.map(g =>
1212
+ '<div class="lrn-bar" style="height:' + Math.max(2, Math.round(g.count / maxG * 60)) + 'px" title="' + esc(g.date) + ': ' + g.count + ' new"></div>'
1213
+ ).join('');
1214
+ growthHtml = '<h2>Growth (new learnings per day)</h2><div class="lrn-bars">' + bars + '</div>';
1215
+ }
1216
+
1217
+ const recentHtml = (l.recent || []).map(r => {
1218
+ const srcBadge = r.source ? ' ' + badge(esc(provLabel(r.source)), provColor(r.source)) : '';
1219
+ const trunc = r.truncated ? '<span class="dim"> … (truncated)</span>' : '';
1220
+ return '<div class="lrn-item collapsed">' +
1221
+ '<div class="lrn-head" onclick="this.parentElement.classList.toggle(\\'collapsed\\')">' +
1222
+ '<span class="lrn-chevron">&#9660;</span>' +
1223
+ '<span class="lrn-key">' + esc(r.key) + '</span>' + srcBadge +
1224
+ '<span class="lrn-first">' + esc(r.firstLine) + '</span>' +
1225
+ '<span class="dim" style="margin-left:auto">' + fmtTimeAgo(r.updatedAt) + '</span>' +
1226
+ '</div>' +
1227
+ '<div class="lrn-body">' + esc(r.body) + trunc + '</div>' +
1228
+ '</div>';
1229
+ }).join('');
1230
+ const recentSection = '<h2>Recent Learnings (' + (l.recent ? l.recent.length : 0) + ' of ' + l.total + ')</h2>' +
1231
+ (recentHtml || '<div class="empty">None</div>');
1232
+
1233
+ el.innerHTML = cards + '<h2>Provenance</h2>' + provHtml + growthHtml + recentSection;
1234
+ }
1235
+
1140
1236
  // Format: 1234567 → "1.23M", 5432 → "5.43K"
1141
1237
  const fmtCount = (n) => {
1142
1238
  if (n == null) return '-';
@@ -1250,6 +1346,7 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
1250
1346
  // four lightweight endpoints. Switching to the tab triggers an
1251
1347
  // immediate poll so the user doesn't wait up to 5s for first paint.
1252
1348
  const wantClaudeStats = activeTab === 'claude-stats';
1349
+ const wantLearnings = activeTab === 'learnings';
1253
1350
  const fetches = [
1254
1351
  fetch('/api/status').then(r => r.json()),
1255
1352
  fetch('/api/schedules').then(r => r.json()),
@@ -1258,14 +1355,18 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
1258
1355
  wantClaudeStats
1259
1356
  ? fetch('/api/claude-stats').then(r => r.json()).catch(() => null)
1260
1357
  : Promise.resolve(null),
1358
+ wantLearnings
1359
+ ? fetch('/api/learnings').then(r => r.json()).catch(() => null)
1360
+ : Promise.resolve(null),
1261
1361
  ];
1262
- const [s, sc, w, m, cs] = await Promise.all(fetches);
1362
+ const [s, sc, w, m, cs, lr] = await Promise.all(fetches);
1263
1363
  renderStatus(s);
1264
1364
  renderWorkers(s);
1265
1365
  renderSchedules(sc);
1266
1366
  renderExecutions(w);
1267
1367
  renderMemory(m);
1268
1368
  if (wantClaudeStats) renderClaudeStats(cs);
1369
+ if (wantLearnings) renderLearnings(lr);
1269
1370
  document.getElementById('poll-indicator').textContent = 'Last poll: ' + new Date().toLocaleTimeString();
1270
1371
  } catch (e) {
1271
1372
  console.error('Poll failed:', e);
@@ -85,7 +85,7 @@ async function defaultStartDaemon(projectRoot) {
85
85
  const spawnArgs = [cliPath, 'daemon', 'start', '--foreground', '--quiet'];
86
86
  const daemonEnv = {
87
87
  ...process.env,
88
- CLAUDE_FLOW_DAEMON: '1',
88
+ MOFLO_DAEMON: '1',
89
89
  ...(process.platform === 'darwin' ? { NOHUP: '1' } : {}),
90
90
  };
91
91
  // On Windows, join command + args into a single shell string to avoid
@@ -176,7 +176,7 @@ function generateSystemdUnit(projectRoot, nodePath, cliPath) {
176
176
  `WorkingDirectory=${projectRoot}`,
177
177
  'Restart=on-failure',
178
178
  'RestartSec=10',
179
- `Environment=CLAUDE_FLOW_DAEMON=1`,
179
+ `Environment=MOFLO_DAEMON=1`,
180
180
  '',
181
181
  '[Install]',
182
182
  'WantedBy=default.target',
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Backward-compatible environment-variable reader for the claude-flow → moflo
3
+ * rebrand (issue #1209).
4
+ *
5
+ * Reads the canonical `MOFLO_<suffix>` variable first, falling back to the
6
+ * pre-rebrand `CLAUDE_FLOW_<suffix>` name. Every writer across the codebase
7
+ * emits ONLY `MOFLO_*`; this fallback exists so consumers whose shell env or
8
+ * already-persisted files (e.g. an installed systemd unit written by an older
9
+ * moflo) still set the old names keep working without a manual migration.
10
+ *
11
+ * Deprecation window: keep the `CLAUDE_FLOW_*` fallback for at least one
12
+ * release cycle after the rebrand ships, then drop both this fallback and the
13
+ * writer-side exemption in `published-package-drift-guard.test.ts`.
14
+ *
15
+ * Zero imports on purpose — safe to import from any layer (including
16
+ * `shared/core/config`) without risking a cycle.
17
+ */
18
+ const MOFLO_PREFIX = 'MOFLO_';
19
+ const LEGACY_PREFIX = 'CLAUDE_FLOW_';
20
+ /**
21
+ * Read an env var by its `MOFLO_<suffix>` name, falling back to the legacy
22
+ * `CLAUDE_FLOW_<suffix>` name. Returns `undefined` when neither is set.
23
+ *
24
+ * @param suffix the shared variable suffix, e.g. `'MAX_AGENTS'`, `'DAEMON'`.
25
+ */
26
+ export function readMofloEnv(suffix) {
27
+ return process.env[MOFLO_PREFIX + suffix] ?? process.env[LEGACY_PREFIX + suffix];
28
+ }
29
+ //# sourceMappingURL=env-compat.js.map
@@ -274,12 +274,11 @@ export function computeHookBlockDrift(consumerHooks, referenceHooks) {
274
274
  * `claudeFlow.hooks.locked: true` (legacy alias) in their settings.json —
275
275
  * a sentinel that suppresses drift surfacing entirely.
276
276
  *
277
- * The `claudeFlow.*` settings tree is a pre-rebrand legacy name that survives
278
- * in writers + readers across the codebase. Renaming the whole tree is a
279
- * separate effort; this one reader accepts `moflo.hooks.locked` ahead of the
280
- * legacy key so the #1180 escape hatch is documented under the canonical
281
- * brand from day one and consumers never have to migrate the key after we
282
- * tell them to set it.
277
+ * The `claudeFlow.*` settings tree was migrated to `moflo.*` in #1209: every
278
+ * writer now emits `moflo.*` and the upgrade merge folds an existing
279
+ * `claudeFlow.*` tree into `moflo.*`. This reader keeps the `claudeFlow.hooks.locked`
280
+ * fallback for one deprecation cycle so a consumer who set the escape hatch but
281
+ * hasn't re-run init yet stays covered.
283
282
  */
284
283
  export function isHookBlockLocked(settings) {
285
284
  const root = settings;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Registry API Client
3
- * Secure integration with Claude Flow Cloud Functions
3
+ * Secure integration with MoFlo Cloud Functions
4
4
  *
5
5
  * Security:
6
6
  * - HTTPS only