moflo 4.8.41 → 4.8.43

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.
@@ -1,127 +1,183 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Sequential indexer chain for session-start.
4
- *
5
- * Runs all DB-writing indexers one at a time to avoid sql.js last-write-wins
6
- * concurrency issues, then triggers HNSW rebuild once everything is committed.
7
- *
8
- * Spawned as a single detached background process by hooks.mjs session-start.
9
- */
10
-
11
- import { existsSync, appendFileSync } from 'fs';
12
- import { resolve, dirname } from 'path';
13
- import { fileURLToPath } from 'url';
14
- import { execFileSync } from 'child_process';
15
-
16
- const __dirname = dirname(fileURLToPath(import.meta.url));
17
- const projectRoot = resolve(__dirname, '../..');
18
- const LOG_PATH = resolve(projectRoot, '.claude/hooks.log');
19
-
20
- function log(msg) {
21
- const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
22
- const line = `[${ts}] [index-all] ${msg}\n`;
23
- try { appendFileSync(LOG_PATH, line); } catch { /* ignore */ }
24
- }
25
-
26
- function resolveBin(binName, localScript) {
27
- const mofloScript = resolve(projectRoot, 'node_modules/moflo/bin', localScript);
28
- if (existsSync(mofloScript)) return mofloScript;
29
- const npmBin = resolve(projectRoot, 'node_modules/.bin', binName);
30
- if (existsSync(npmBin)) return npmBin;
31
- const localPath = resolve(projectRoot, '.claude/scripts', localScript);
32
- if (existsSync(localPath)) return localPath;
33
- return null;
34
- }
35
-
36
- function getLocalCliPath() {
37
- const paths = [
38
- resolve(projectRoot, 'node_modules/moflo/src/@claude-flow/cli/bin/cli.js'),
39
- resolve(projectRoot, 'node_modules/moflo/bin/cli.js'),
40
- resolve(projectRoot, 'node_modules/.bin/flo'),
41
- resolve(projectRoot, 'src/@claude-flow/cli/bin/cli.js'),
42
- ];
43
- for (const p of paths) {
44
- if (existsSync(p)) return p;
45
- }
46
- return null;
47
- }
48
-
49
- function runStep(label, cmd, args, timeoutMs = 120_000) {
50
- const start = Date.now();
51
- log(`START ${label}`);
52
- try {
53
- execFileSync(cmd, args, {
54
- cwd: projectRoot,
55
- timeout: timeoutMs,
56
- stdio: 'ignore',
57
- windowsHide: true,
58
- });
59
- const elapsed = ((Date.now() - start) / 1000).toFixed(1);
60
- log(`DONE ${label} (${elapsed}s)`);
61
- return true;
62
- } catch (err) {
63
- const elapsed = ((Date.now() - start) / 1000).toFixed(1);
64
- log(`FAIL ${label} (${elapsed}s): ${err.message?.split('\n')[0] || 'unknown'}`);
65
- return false;
66
- }
67
- }
68
-
69
- async function main() {
70
- const startTime = Date.now();
71
- log('Sequential indexing chain started');
72
-
73
- // 1. Guidance indexer
74
- const guidanceScript = resolveBin('flo-index', 'index-guidance.mjs');
75
- if (guidanceScript) {
76
- runStep('guidance-index', 'node', [guidanceScript]);
77
- } else {
78
- log('SKIP guidance-index (script not found)');
79
- }
80
-
81
- // 2. Code map generator (the big one — ~22s)
82
- const codeMapScript = resolveBin('flo-codemap', 'generate-code-map.mjs');
83
- if (codeMapScript) {
84
- runStep('code-map', 'node', [codeMapScript], 180_000);
85
- } else {
86
- log('SKIP code-map (script not found)');
87
- }
88
-
89
- // 3. Test indexer
90
- const testScript = resolveBin('flo-testmap', 'index-tests.mjs');
91
- if (testScript) {
92
- runStep('test-index', 'node', [testScript]);
93
- } else {
94
- log('SKIP test-index (script not found)');
95
- }
96
-
97
- // 4. Patterns indexer
98
- const patternsScript = resolveBin('flo-patterns', 'index-patterns.mjs');
99
- if (patternsScript) {
100
- runStep('patterns-index', 'node', [patternsScript]);
101
- } else {
102
- log('SKIP patterns-index (script not found)');
103
- }
104
-
105
- // 5. Pretrain (extracts patterns from repository)
106
- const localCli = getLocalCliPath();
107
- if (localCli) {
108
- runStep('pretrain', 'node', [localCli, 'hooks', 'pretrain']);
109
- } else {
110
- log('SKIP pretrain (CLI not found)');
111
- }
112
-
113
- // 6. HNSW rebuild — MUST run last, after all writes are committed (#81)
114
- if (localCli) {
115
- runStep('hnsw-rebuild', 'node', [localCli, 'memory', 'rebuild', '--force']);
116
- } else {
117
- log('SKIP hnsw-rebuild (CLI not found)');
118
- }
119
-
120
- const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1);
121
- log(`Sequential indexing chain complete (${totalElapsed}s)`);
122
- }
123
-
124
- main().catch(err => {
125
- log(`FATAL: ${err.message}`);
126
- process.exit(1);
127
- });
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Sequential indexer chain for session-start.
4
+ *
5
+ * Runs all DB-writing indexers one at a time to avoid sql.js last-write-wins
6
+ * concurrency issues (#78), then triggers HNSW rebuild once everything is
7
+ * committed (#81).
8
+ *
9
+ * Spawned as a single detached background process by hooks.mjs session-start.
10
+ */
11
+
12
+ import { existsSync, appendFileSync, readFileSync } from 'fs';
13
+ import { resolve, dirname } from 'path';
14
+ import { fileURLToPath } from 'url';
15
+ import { execFileSync } from 'child_process';
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+
19
+ // Detect project root by walking up from cwd to find package.json.
20
+ // IMPORTANT: Do NOT use resolve(__dirname, '..') — this script lives in bin/
21
+ // during development but gets synced to .claude/scripts/ in consumer projects,
22
+ // so __dirname-relative paths break. findProjectRoot() works in both locations.
23
+ function findProjectRoot() {
24
+ let dir = process.cwd();
25
+ const root = resolve(dir, '/');
26
+ while (dir !== root) {
27
+ if (existsSync(resolve(dir, 'package.json'))) return dir;
28
+ dir = dirname(dir);
29
+ }
30
+ return process.cwd();
31
+ }
32
+
33
+ const projectRoot = findProjectRoot();
34
+ const LOG_PATH = resolve(projectRoot, '.swarm/hooks.log');
35
+
36
+ function log(msg) {
37
+ const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
38
+ const line = `[${ts}] [index-all] ${msg}\n`;
39
+ try { appendFileSync(LOG_PATH, line); } catch { /* ignore */ }
40
+ }
41
+
42
+ function resolveBin(binName, localScript) {
43
+ const mofloScript = resolve(projectRoot, 'node_modules/moflo/bin', localScript);
44
+ if (existsSync(mofloScript)) return mofloScript;
45
+ const npmBin = resolve(projectRoot, 'node_modules/.bin', binName);
46
+ if (existsSync(npmBin)) return npmBin;
47
+ const localPath = resolve(projectRoot, '.claude/scripts', localScript);
48
+ if (existsSync(localPath)) return localPath;
49
+ // Also check bin/ directory (for development use)
50
+ const binPath = resolve(projectRoot, 'bin', localScript);
51
+ if (existsSync(binPath)) return binPath;
52
+ return null;
53
+ }
54
+
55
+ function getLocalCliPath() {
56
+ const paths = [
57
+ resolve(projectRoot, 'node_modules/moflo/src/@claude-flow/cli/bin/cli.js'),
58
+ resolve(projectRoot, 'node_modules/moflo/bin/cli.js'),
59
+ resolve(projectRoot, 'node_modules/.bin/flo'),
60
+ // Development: local CLI
61
+ resolve(projectRoot, 'src/@claude-flow/cli/bin/cli.js'),
62
+ ];
63
+ for (const p of paths) {
64
+ if (existsSync(p)) return p;
65
+ }
66
+ return null;
67
+ }
68
+
69
+ /** Read moflo.yaml once and cache auto_index flags. */
70
+ let _autoIndexFlags = null;
71
+ function isIndexEnabled(key) {
72
+ if (_autoIndexFlags === null) {
73
+ _autoIndexFlags = {};
74
+ const yamlPath = resolve(projectRoot, 'moflo.yaml');
75
+ if (existsSync(yamlPath)) {
76
+ try {
77
+ const content = readFileSync(yamlPath, 'utf-8');
78
+ for (const k of ['guidance', 'code_map', 'tests', 'patterns']) {
79
+ const re = new RegExp(`auto_index:\\s*\\n(?:.*\\n)*?\\s+${k}:\\s*(true|false)`);
80
+ const match = content.match(re);
81
+ _autoIndexFlags[k] = match ? match[1] !== 'false' : true;
82
+ }
83
+ } catch { /* ignore, all default to true */ }
84
+ }
85
+ }
86
+ return _autoIndexFlags[key] !== false;
87
+ }
88
+
89
+ function runStep(label, cmd, args, timeoutMs = 120_000) {
90
+ const start = Date.now();
91
+ log(`START ${label}`);
92
+ try {
93
+ execFileSync(cmd, args, {
94
+ cwd: projectRoot,
95
+ timeout: timeoutMs,
96
+ stdio: 'ignore',
97
+ windowsHide: true,
98
+ });
99
+ const elapsed = ((Date.now() - start) / 1000).toFixed(1);
100
+ log(`DONE ${label} (${elapsed}s)`);
101
+ return true;
102
+ } catch (err) {
103
+ const elapsed = ((Date.now() - start) / 1000).toFixed(1);
104
+ log(`FAIL ${label} (${elapsed}s): ${err.message?.split('\n')[0] || 'unknown'}`);
105
+ return false;
106
+ }
107
+ }
108
+
109
+ async function main() {
110
+ const startTime = Date.now();
111
+ log('Sequential indexing chain started');
112
+
113
+ // 1. Guidance indexer
114
+ if (isIndexEnabled('guidance')) {
115
+ const guidanceScript = resolveBin('flo-index', 'index-guidance.mjs');
116
+ if (guidanceScript) {
117
+ runStep('guidance-index', 'node', [guidanceScript]);
118
+ } else {
119
+ log('SKIP guidance-index (script not found)');
120
+ }
121
+ } else {
122
+ log('SKIP guidance-index (disabled in moflo.yaml)');
123
+ }
124
+
125
+ // 2. Code map generator (the big one — ~22s)
126
+ if (isIndexEnabled('code_map')) {
127
+ const codeMapScript = resolveBin('flo-codemap', 'generate-code-map.mjs');
128
+ if (codeMapScript) {
129
+ runStep('code-map', 'node', [codeMapScript], 180_000);
130
+ } else {
131
+ log('SKIP code-map (script not found)');
132
+ }
133
+ } else {
134
+ log('SKIP code-map (disabled in moflo.yaml)');
135
+ }
136
+
137
+ // 3. Test indexer
138
+ if (isIndexEnabled('tests')) {
139
+ const testScript = resolveBin('flo-testmap', 'index-tests.mjs');
140
+ if (testScript) {
141
+ runStep('test-index', 'node', [testScript]);
142
+ } else {
143
+ log('SKIP test-index (script not found)');
144
+ }
145
+ } else {
146
+ log('SKIP test-index (disabled in moflo.yaml)');
147
+ }
148
+
149
+ // 4. Patterns indexer
150
+ if (isIndexEnabled('patterns')) {
151
+ const patternsScript = resolveBin('flo-patterns', 'index-patterns.mjs');
152
+ if (patternsScript) {
153
+ runStep('patterns-index', 'node', [patternsScript]);
154
+ } else {
155
+ log('SKIP patterns-index (script not found)');
156
+ }
157
+ } else {
158
+ log('SKIP patterns-index (disabled in moflo.yaml)');
159
+ }
160
+
161
+ // 5. Pretrain (extracts patterns from repository)
162
+ const localCli = getLocalCliPath();
163
+ if (localCli) {
164
+ runStep('pretrain', 'node', [localCli, 'hooks', 'pretrain']);
165
+ } else {
166
+ log('SKIP pretrain (CLI not found)');
167
+ }
168
+
169
+ // 6. HNSW rebuild — MUST run last, after all writes are committed (#81)
170
+ if (localCli) {
171
+ runStep('hnsw-rebuild', 'node', [localCli, 'memory', 'rebuild', '--force']);
172
+ } else {
173
+ log('SKIP hnsw-rebuild (CLI not found)');
174
+ }
175
+
176
+ const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1);
177
+ log(`Sequential indexing chain complete (${totalElapsed}s)`);
178
+ }
179
+
180
+ main().catch(err => {
181
+ log(`FATAL: ${err.message}`);
182
+ process.exit(1);
183
+ });
@@ -819,8 +819,17 @@ if (!specificFile) {
819
819
  }
820
820
  }
821
821
 
822
- // Write changes back to disk and close
822
+ // Write changes back to disk
823
823
  if (docsIndexed > 0 || chunksIndexed > 0 || staleRemoved > 0) saveDb(db);
824
+
825
+ // Check for entries missing embeddings (e.g. prior background run failed)
826
+ let missingEmbeddings = 0;
827
+ {
828
+ const stmt = db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE namespace = ? AND (embedding IS NULL OR embedding = '')`);
829
+ stmt.bind([NAMESPACE]);
830
+ if (stmt.step()) missingEmbeddings = stmt.getAsObject().cnt;
831
+ stmt.free();
832
+ }
824
833
  db.close();
825
834
 
826
835
  console.log('');
@@ -840,9 +849,13 @@ log(` • Hierarchical links (h2 -> h3 parent/children)`);
840
849
  log(` • Context overlap: ${overlapPercent}% (contextBefore/contextAfter)`);
841
850
  log('═══════════════════════════════════════════════════════════');
842
851
 
843
- // Generate embeddings for new entries (unless skipped or nothing changed)
852
+ // Generate embeddings for new entries or backfill missing ones
844
853
  // Runs in BACKGROUND to avoid blocking startup
845
- if (!skipEmbeddings && (docsIndexed > 0 || chunksIndexed > 0)) {
854
+ const needsEmbeddings = (docsIndexed > 0 || chunksIndexed > 0 || missingEmbeddings > 0);
855
+ if (!skipEmbeddings && needsEmbeddings) {
856
+ if (missingEmbeddings > 0 && docsIndexed === 0 && chunksIndexed === 0) {
857
+ log(`${missingEmbeddings} entries missing embeddings — backfilling...`);
858
+ }
846
859
  console.log('');
847
860
  log('Spawning embedding generation in background...');
848
861
 
@@ -153,6 +153,15 @@ function countNamespace(db) {
153
153
  return count;
154
154
  }
155
155
 
156
+ function countMissingEmbeddings(db) {
157
+ const stmt = db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE namespace = ? AND (embedding IS NULL OR embedding = '')`);
158
+ stmt.bind([NAMESPACE]);
159
+ let count = 0;
160
+ if (stmt.step()) count = stmt.getAsObject().cnt;
161
+ stmt.free();
162
+ return count;
163
+ }
164
+
156
165
  // ---------------------------------------------------------------------------
157
166
  // Test directory discovery
158
167
  // ---------------------------------------------------------------------------
@@ -618,9 +627,15 @@ async function main() {
618
627
  if (isUnchanged(currentHash)) {
619
628
  const db = await getDb();
620
629
  const count = countNamespace(db);
630
+ const missing = countMissingEmbeddings(db);
621
631
  db.close();
622
632
  if (count > 0) {
623
- log(`Skipping — file list unchanged (${count} chunks in DB, hash ${currentHash.slice(0, 12)}...)`);
633
+ if (missing > 0 && !skipEmbeddings) {
634
+ log(`File list unchanged but ${missing}/${count} entries missing embeddings — generating...`);
635
+ await runEmbeddings();
636
+ } else {
637
+ log(`Skipping — file list unchanged (${count} chunks in DB, hash ${currentHash.slice(0, 12)}...)`);
638
+ }
624
639
  return;
625
640
  }
626
641
  log('File list unchanged but no chunks in DB — forcing regeneration');
@@ -683,24 +698,28 @@ async function main() {
683
698
 
684
699
  // 7. Generate embeddings (inline, like code-map)
685
700
  if (!skipEmbeddings && allChunks.length > 0) {
686
- const embedCandidates = [
687
- resolve(dirname(fileURLToPath(import.meta.url)), 'build-embeddings.mjs'),
688
- resolve(projectRoot, '.claude/scripts/build-embeddings.mjs'),
689
- ];
690
- const embedScript = embedCandidates.find(p => existsSync(p));
691
- if (embedScript) {
692
- log('Generating embeddings for tests...');
693
- try {
694
- execSync(`node "${embedScript}" --namespace tests`, {
695
- cwd: projectRoot,
696
- stdio: 'inherit',
697
- timeout: 120000,
698
- windowsHide: true,
699
- });
700
- } catch (err) {
701
- log(`Warning: embedding generation failed: ${err.message?.split('\n')[0]}`);
702
- }
703
- }
701
+ await runEmbeddings();
702
+ }
703
+ }
704
+
705
+ async function runEmbeddings() {
706
+ const embedCandidates = [
707
+ resolve(dirname(fileURLToPath(import.meta.url)), 'build-embeddings.mjs'),
708
+ resolve(projectRoot, '.claude/scripts/build-embeddings.mjs'),
709
+ ];
710
+ const embedScript = embedCandidates.find(p => existsSync(p));
711
+ if (!embedScript) return;
712
+
713
+ log('Generating embeddings for tests...');
714
+ try {
715
+ execSync(`node "${embedScript}" --namespace tests`, {
716
+ cwd: projectRoot,
717
+ stdio: 'inherit',
718
+ timeout: 120000,
719
+ windowsHide: true,
720
+ });
721
+ } catch (err) {
722
+ log(`Warning: embedding generation failed: ${err.message?.split('\n')[0]}`);
704
723
  }
705
724
  }
706
725
 
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared dependency resolver for moflo bin scripts.
3
+ * Resolves packages from moflo's own node_modules (not the consuming project's).
4
+ * On Windows, converts native paths to file:// URLs required by ESM import().
5
+ */
6
+
7
+ import { createRequire } from 'module';
8
+ import { fileURLToPath, pathToFileURL } from 'url';
9
+
10
+ const __require = createRequire(fileURLToPath(import.meta.url));
11
+
12
+ export function mofloResolveURL(specifier) {
13
+ return pathToFileURL(__require.resolve(specifier)).href;
14
+ }